commit cc5b16673c5a99a3f356c8ea2f473ccb6ec587ea Author: root <13910913995@163.com> Date: Wed Mar 25 10:02:19 2026 +0800 chore: init repo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..66e8377 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +.DS_Store +Thumbs.db + +.idea/ +*.iml + +.classpath +.project +.settings/ + +*.log +logs/ + +**/target/ + +.m2repo/ +backend/.m2repo/ + +backend/tmp-libscan/ + +node_modules/ +**/node_modules/ + +dist/ +**/dist/ +dist-ssr/ +**/.vite/ + +.pnpm-store/ +**/.pnpm-store/ + +coverage/ +**/coverage/ + +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +npminstall-debug.log + +.env.local +.env.*.local diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..caa3cd9 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,32 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "type": "java", + "name": "Backend (dev)", + "request": "launch", + "mainClass": "com.yfd.platform.PlatformApplication", + "cwd": "${workspaceFolder}/backend", + "preLaunchTask": "backend:prepare-classpath", + "classPaths": [ + "${workspaceFolder}/backend/target/classes", + "${workspaceFolder}/backend/target/dependency/*" + ], + "args": [ + "--spring.profiles.active=dev" + ], + "vmArgs": "-Dfile.encoding=UTF-8" + }, + { + "type": "java", + "name": "Attach (5005)", + "request": "attach", + "hostName": "localhost", + "port": 5005 + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..a5dd269 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,24 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "backend:prepare-classpath", + "type": "shell", + "command": "mvn", + "args": [ + "-f", + "${workspaceFolder}/backend/pom.xml", + "-DskipTests", + "compile", + "dependency:copy-dependencies", + "-DincludeScope=runtime", + "-DoutputDirectory=target/dependency" + ], + "options": { + "cwd": "${workspaceFolder}/backend" + }, + "problemMatcher": "$javac", + "group": "build" + } + ] +} diff --git a/backend/.mvn/maven.config b/backend/.mvn/maven.config new file mode 100644 index 0000000..140535a --- /dev/null +++ b/backend/.mvn/maven.config @@ -0,0 +1 @@ +-Dmaven.repo.local=.m2repo diff --git a/backend/db-init/README.md b/backend/db-init/README.md new file mode 100644 index 0000000..86977d9 --- /dev/null +++ b/backend/db-init/README.md @@ -0,0 +1,8 @@ +# db-init 目录说明 + +- `sql/base-schema.sql`:数据库结构(表、索引、约束),由 mysqldump 导出或手写维护。 +- `sql/base-data.sql`:初始化数据(字典、角色、菜单、管理员等),由 mysqldump 导出或手写维护。 +- `scripts/export.ps1`:Windows 导出脚本;将当前库结构与指定白名单数据导出到 sql 目录。 +- `scripts/import.ps1`:Windows 导入脚本;将 sql 中的结构与数据导入到目标库。 + +请参考 `../docs/数据库初始方案.md` 获取完整流程与约束。 \ No newline at end of file diff --git a/backend/db-init/scripts/export.ps1 b/backend/db-init/scripts/export.ps1 new file mode 100644 index 0000000..4fae615 --- /dev/null +++ b/backend/db-init/scripts/export.ps1 @@ -0,0 +1,100 @@ +param( + [string]$DB_HOST = '43.138.168.68', + [int]$DB_PORT = 3306, + [string]$DB_NAME = 'frameworkdb2023', + [string]$DB_USER = 'root', + [string]$DB_PASSWORD = 'ylfw20230626@' +) + +# 确保输出目录存在 +$outputDir = Join-Path $PSScriptRoot '..\sql' +if (!(Test-Path $outputDir)) { + New-Item -ItemType Directory -Path $outputDir | Out-Null +} + +# 构建输出文件路径 +$schemaFile = Join-Path $outputDir "base-schema.sql" +$dataFile = Join-Path $outputDir "base-data.sql" + +# 构建 mysqldump 命令参数 +$commonArgs = @( + "--host=$hostName", + "--port=$port", + "--user=$user", + "--routines", + "--triggers", + "--single-transaction", + "--set-charset", + "--default-character-set=utf8mb4" +) + +if ($password) { + $commonArgs += "--password=$password" +} + +Write-Host "开始导出数据库结构和数据..." +Write-Host "数据库: $database" +Write-Host "主机: $hostName:$port" +Write-Host "用户: $user" +Write-Host "输出目录: $outputDir" +Write-Host "" + +# 导出数据库结构(仅结构,不包含数据) +Write-Host "正在导出数据库结构到: $schemaFile" +try { + $schemaArgs = $commonArgs + @( + "--no-data", + "--result-file=$schemaFile", + $database + ) + + & mysqldump @schemaArgs + + if ($LASTEXITCODE -eq 0) { + Write-Host "✓ 数据库结构导出成功" -ForegroundColor Green + } else { + throw "mysqldump 返回错误代码: $LASTEXITCODE" + } +} catch { + Write-Error "导出数据库结构失败: $_" + exit 1 +} + +# 导出数据(仅数据,不包含结构) +Write-Host "" +Write-Host "正在导出数据到: $dataFile" +try { + $dataArgs = $commonArgs + @( + "--no-create-info", + "--skip-triggers", + "--result-file=$dataFile", + $database + ) + + & mysqldump @dataArgs + + if ($LASTEXITCODE -eq 0) { + Write-Host "✓ 数据导出成功" -ForegroundColor Green + } else { + throw "mysqldump 返回错误代码: $LASTEXITCODE" + } +} catch { + Write-Error "导出数据失败: $_" + exit 1 +} + +Write-Host "" +Write-Host "数据库导出完成!" -ForegroundColor Green +Write-Host "结构文件: $schemaFile" +Write-Host "数据文件: $dataFile" + +# 显示文件大小 +if (Test-Path $schemaFile) { + $schemaSize = (Get-Item $schemaFile).Length + Write-Host "结构文件大小: $([math]::Round($schemaSize/1KB, 2)) KB" +} + +if (Test-Path $dataFile) { + $dataSize = (Get-Item $dataFile).Length + Write-Host "数据文件大小: $([math]::Round($dataSize/1KB, 2)) KB" +} \ No newline at end of file diff --git a/backend/db-init/scripts/import.ps1 b/backend/db-init/scripts/import.ps1 new file mode 100644 index 0000000..131efcd --- /dev/null +++ b/backend/db-init/scripts/import.ps1 @@ -0,0 +1,46 @@ +param( + [string]$DB_HOST = '43.138.168.68', + [int]$DB_PORT = 3306, + [string]$DB_NAME = 'frameworkdb2025', + [string]$DB_USER = 'root', + [string]$DB_PASSWORD = 'ylfw20230626@' +) + +$ErrorActionPreference = 'Stop' +$env:MYSQL_PWD = $DB_PASSWORD + +$schemaPath = Join-Path $PSScriptRoot '..\sql\base-schema.sql' +$dataPath = Join-Path $PSScriptRoot '..\sql\base-data.sql' + +function Invoke-MySqlFile([string]$filePath) { + Write-Host ("Processing SQL file: {0}" -f $filePath) + $sql = Get-Content -Raw $filePath + # Strip UTF-8 BOM if present + $bom = [char]0xFEFF + if ($sql.StartsWith($bom)) { $sql = $sql.Substring(1) } + # Sanitize dump headers and environment-specific statements + $lines = $sql -split "`r?`n" + $cleanLines = $lines | Where-Object { + $t = $_.Trim() + if ($t -eq '') { return $false } + if ($t -match '^(--|/\*|\*/|/\*!|[-]+$)') { return $false } + if ($t -match '^USE\s+') { return $false } + if ($t -match '^CREATE\s+DATABASE') { return $false } + return $true + } + $cleanSql = ($cleanLines -join "`n") + if ([string]::IsNullOrWhiteSpace($cleanSql)) { + Write-Warning ("Sanitized script is empty, skipping: {0}" -f $filePath) + return + } + Write-Host ("Importing via mysql from: {0}" -f $filePath) + $cleanSql | & mysql -h $DB_HOST -P $DB_PORT -u $DB_USER $DB_NAME + if ($LASTEXITCODE -ne 0) { + throw ("Import failed for {0} with exit code {1}" -f $filePath, $LASTEXITCODE) + } +} + +Invoke-MySqlFile $schemaPath +Invoke-MySqlFile $dataPath + +Write-Host 'Import completed' \ No newline at end of file diff --git a/backend/docs/Java框架项目技术文档.md b/backend/docs/Java框架项目技术文档.md new file mode 100644 index 0000000..c3c35ce --- /dev/null +++ b/backend/docs/Java框架项目技术文档.md @@ -0,0 +1,141 @@ +# 项目技术文档(framework 模块) + +> 运行环境:Windows,Java 21(JDK 21),Spring Boot 4.0.3 + +## 概述 +- 平台型后端服务,采用 `Spring Boot 4.0.3`,区分 `dev` / `server` 两种运行配置。 +- 数据访问:`MyBatis-Plus 3.5.16` + `MyBatis 3.5.16`;连接池:`Druid 1.2.20`。 +- 任务调度:`Quartz 2.3.2`;API 文档:`springdoc-openapi`(Swagger UI)。 +- 缓存与基础设施:`Redis`、`WebSocket`、`Actuator`、验证码(`easy-captcha`)、加密(`jasypt`)、`ip2region`、`hutool` 等。 +- 打包方式:可执行 `JAR`(已启用 `spring-boot-maven-plugin` 的 `repackage`)。默认开发端口 `8093`(`server` 可使用 `8090`)。 + +## 目录结构 +- 仓库根(当前工作目录):`D:\Trae_space\WholeProcessPlatform` +- 主要结构: +``` +WholeProcessPlatform/ +├── frontend/ # 前端源码(Vite + TypeScript + Tailwind) +└── backend/ # 后端服务(Spring Boot 4.0.3) + ├── pom.xml # Maven 构建管理 + └── src/ + ├── main/ + │ ├── java/ # 业务代码(入口类在 com.yfd.platform.*) + │ └── resources/ # 配置与静态资源(static、配置 YAML、日志) + └── test/ + └── java/ # 测试代码 +``` + +## 快速开始 +- 前置要求: + - 安装 `JDK 21`、`Maven 3.6.3+`(建议 `3.9+`)、`Git`、`Node.js 18+`(前端构建)。 + - Windows 终端建议执行 `chcp 65001`,确保 UTF-8 编码输出。 +- 构建后端: + - `cd backend && mvn clean package -DskipTests` +- 本地运行(dev): + - `cd backend && java -jar target/platform-1.0.jar --spring.profiles.active=dev` +- 运行(server): + - `cd backend && java -jar target/platform-1.0.jar --spring.profiles.active=server` +- API 文档: + - 访问 `http://localhost:8093/swagger-ui.html`(以实际配置为准) + +## 前端集成 +- 自动构建:Maven 在 `generate-resources` 阶段调用 `npm install` 与 `npm run build:mvn`,产物输出到 `frontend/dist`。 +- 静态资源复制:可通过 `maven-resources-plugin` 将 `frontend/dist` 复制至 `src/main/resources/static`,如需开启请将 POM 中该插件的 `` 设置为 `false` 或按需配置 Profile。 +- 单独构建:在 `framework/frontend` 目录手动执行 `npm install && npm run build`,然后复制 `dist/` 到后端静态目录。 + +## 配置说明 +- Profile 切换:通过 `--spring.profiles.active=` 激活环境。 +- 关键属性: + - `file-space.system`:文件根路径,需在激活的 profile 中配置。 + - `spring.datasource.druid.*`:数据库连接与池化参数。 + - `server.port`:端口(`dev` 默认 8093,`server` 可使用 8090)。 +- 编码与 JVM 参数: + - 统一 JVM 编码:`-Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8`(POM 已配置)。 + - PowerShell 输出建议设为 UTF-8:`chcp 65001`。 + - Maven 本地仓库: + - 为避免某些 IDE/沙箱环境写入受限,后端已配置 Maven 本地仓库为项目内目录(`backend/.m2repo`),配置文件:`backend/.mvn/maven.config`。 + +## 依赖与版本 +- Spring:`spring-boot-starter-web`、`security`、`cache`、`websocket`、`actuator`(Spring Boot 4.0.3 / Spring Framework 7)。 +- 数据访问:`mybatis-spring-boot-starter 4.0.1`、`mybatis-plus-spring-boot4-starter 3.5.16`、`druid 1.2.20`、`mysql-connector-j`、`sqlite-jdbc`。 +- 工具与增强:`hutool-all`、`poi` / `poi-ooxml`、`fastjson`、`freemarker`、`jsoup`、`jasypt`、`ip2region`、`easy-captcha`、`UserAgentUtils`、`nashorn-core`。 +- 文档:`springdoc-openapi-starter-webmvc-ui 3.0.2`(Swagger UI)。 +- 构建管控:`maven-enforcer-plugin`(JDK≥17、Maven≥3.6.3)。 + +## 数据库配置示例 +- MySQL 连接: +```yaml +spring: + datasource: + druid: + master: + url: jdbc:mysql://:3306/?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&useSSL=false&allowPublicKeyRetrieval=true + username: + password: +``` +- 授权建议: +```sql +CREATE USER 'appuser'@'%' IDENTIFIED BY 'StrongPassword!'; +GRANT ALL PRIVILEGES ON .* TO 'appuser'@'%'; +FLUSH PRIVILEGES; +``` +- Druid 健壮性: +```yaml +spring: + datasource: + druid: + initial-size: 0 + test-on-borrow: false + test-while-idle: true + validation-query: SELECT 1 +``` + +## 日志与监控 +- 日志:Logback(UTF-8 输出),推荐格式:`%d [%thread] %-5level %logger{50} - %msg%n`。 +- 监控:启用 Actuator,健康检查路径:`/actuator/health`。 +- 建议接入集中日志与指标采集(ELK / Vector),监控数据库与线程池(Druid / Quartz)。 + +## 定时任务 +- 默认 `RAMJobStore`(非集群、内存存储)。 +- 如需持久化与集群,改用 `JdbcJobStore` 并配置数据源与表结构。 + +## 安全与鉴权 +- 采用 JWT 进行鉴权(如 `jwtAuthenticationTokenFilter`,按实际代码启用)。 +- 敏感配置(`jwt.secret`、数据库密码)通过环境变量或外部密钥管理。 +- 生产环境建议启用 HTTPS 并使用强密钥。 + +## Docker 部署 +- 端口:默认暴露 `8093`(dev);可在 `server` profile 使用 `8090`。 +- 基本流程: + - 构建镜像:`docker build -t projectframework-app:latest .` + - 运行(开发环境):`docker run -d --name platform-app -p 8093:8093 -e SPRING_PROFILES_ACTIVE=dev projectframework-app:latest` + - 运行(服务器环境):`docker run -d --name platform-app -p 8090:8090 -e SPRING_PROFILES_ACTIVE=server projectframework-app:latest` +- 文件空间挂载示例:`-v D:/data/file-space:/data/file-space -e FILE_SPACE_SYSTEM=/data/file-space` + +## Git 使用(框架分支) +- 远程仓库:根据实际仓库地址配置(示例:`http://121.37.111.42:3000/ThbTech/JavaProjectRepo.git`)。 +- 分支建议:`main-framework`(稳定分支)、`develop-framework`(开发分支)。 +- 常用命令:`git pull`、`git add .`、`git commit -m ""`、`git push`。 +- 提交署名:`git config user.name ""`,`git config user.email ""`。 + +## CI/CD 建议 +- CI 阶段:`mvn -B -DskipTests clean package`,配合单元测试与安全扫描(依赖检查、代码质量)。 +- CD 阶段:推送镜像到私有仓库,使用环境变量注入敏感信息。 + +## 常见问题 +- 中文乱码:执行 `chcp 65001`,确保 `logback-spring.xml` 使用 UTF-8。 +- `NoSuchMethodError`(MyBatis):升级到 `MyBatis 3.5.16+` 并与 MP 版本匹配。 +- 数据库 `Access denied`:校验账户密码与远程授权;必要时新建业务账户并放开 `3306`。 +- 端口占用/启动失败:检查 `server.port` 与冲突端口;查看应用日志定位根因。 +- 前端构建问题:如遇 npm 依赖警告或复制冲突,可先独立构建并手动复制 `dist/`。 + +## 变更日志(模板) +- `feat:` 新增功能说明 +- `fix:` 缺陷修复说明 +- `docs:` 文档更新说明 +- `refactor:` 重构说明 +- `perf:` 性能优化说明 + +--- + +如需扩展专题文档(接口规范、部署拓扑、参数字典等),请在 `docs/` 目录继续维护并与版本管理同步。 diff --git a/backend/docs/数据库初始方案.md b/backend/docs/数据库初始方案.md new file mode 100644 index 0000000..4720576 --- /dev/null +++ b/backend/docs/数据库初始方案.md @@ -0,0 +1,134 @@ +# 数据库初始方案(framework) + +## 目标 +- 在 `framework` 目录下建立 `db-init` 初始化目录,保存基础结构与基础数据。 +- 支持从 MySQL 导出一份“初始化用”的基线数据,并在应用启动时自动检测是否已初始化;若未初始化,自动导入。 +- 过程可重复、可审计、可在 `dev` 与 `server` 环境下安全运行。 + +## 目录结构规划 +- `framework/db-init/` + - `sql/` + - `base-schema.sql`:基础库结构(表、索引、约束);可由 `mysqldump --no-data` 导出。 + - `base-data.sql`:初始化数据(字典、默认角色、管理员等);可由 `mysqldump --no-create-info` 导出或手写。 + - `scripts/` + - `export.ps1`:Windows 导出脚本(PowerShell)。 + - `import.ps1`:Windows 导入脚本(PowerShell)。 + - `export.sh`:Linux/Mac 导出脚本(可选)。 + - `import.sh`:Linux/Mac 导入脚本(可选)。 + - `README.md`:说明如何导出/导入、注意事项与环境变量。 + +## MySQL 导出策略 +- 推荐使用 `mysqldump` 分离结构与数据,以便初始化时灵活控制: + - 结构:`mysqldump -h$DB_HOST -P$DB_PORT -u$DB_USER -p$DB_PASSWORD --no-data --databases $DB_NAME > sql/base-schema.sql` + - 数据:`mysqldump -h$DB_HOST -P$DB_PORT -u$DB_USER -p$DB_PASSWORD --no-create-info --databases $DB_NAME --tables <白名单表> > sql/base-data.sql` +- 数据白名单建议包含: + - 字典/配置表(如 `sys_dict`, `sys_config`) + - 权限/角色/菜单(如 `sys_user`(仅管理员)、`sys_role`, `sys_menu`, `sys_user_role`, `sys_role_menu`) + - 地区或静态映射表(如有) +- 机密信息与动态数据不导出: + - 排除审计日志、任务运行记录、业务动态表等。 + +## 导入策略 +- 应用启动时进行“初始化检测”: + - 建议引入标记表:`app_init`(单行),字段 `initialized boolean`, `version varchar`,或在 `sys_config` 中维护 `init_done` 键。 + - 检测逻辑: + - 若标记不存在或值为 `false`,视为未初始化。 + - 若存在且为 `true`,跳过初始化。 +- 导入步骤(未初始化时): + - 执行 `base-schema.sql`(若目标库为空或需校正结构时)。 + - 执行 `base-data.sql`(插入/更新缺失的基线数据,注意幂等性)。 + - 更新标记为已初始化,并记录版本与时间戳。 +- 幂等性建议: + - 数据插入使用“存在则跳过/更新”策略(`INSERT ... ON DUPLICATE KEY UPDATE` 或先查询再写入)。 + - 避免覆盖已有业务数据;仅在缺失时补充。 + +## Spring Boot 集成方案 +- 方案 A(轻量):在后端增加初始化 Runner + - 新增一个 `@Component` 实现 `ApplicationRunner` 或 `CommandLineRunner` 的 `DataInitializer`。 + - 启动时:检查标记 → 未初始化则依次执行 `sql/base-schema.sql` 与 `sql/base-data.sql`。 + - SQL 执行方式: + - 直接通过 JDBC 读取并执行 SQL 文件(分号切分、事务包裹、失败回滚)。 + - 或调用外部 `mysql` 命令(需服务器已安装 `mysql` 客户端,适合运维环境)。 + - Profile 控制:默认仅在 `server` 或 `dev` 首次运行时启用;可通过配置 `app.init.enabled=true/false` 控制。 +- 方案 B(推荐中长期):引入数据库版本管理工具 + - `Flyway` 或 `Liquibase` 管理结构与数据变更;将初始化作为 `V1__base.sql` 脚本。 + - 优点:变更有版本与校验,部署一致性更强;缺点:需要改造现有 SQL 并纳入流水线。 + +## 配置示例 +- `application-server.yml` 中添加: +``` +app: + init: + enabled: true + schema: classpath:db-init/sql/base-schema.sql + data: classpath:db-init/sql/base-data.sql + marker-table: app_init + marker-version: v1.0.0 +``` +- 路径策略: + - 若以资源方式打包,建议将 `db-init/sql` 复制到 `src/main/resources/db-init/sql`。 + - 或保留在项目根并通过绝对/相对路径访问(需考虑部署路径与权限)。 + +## 脚本示例(Windows PowerShell) +- `framework/db-init/scripts/export.ps1` +``` +param( + [string]$DB_HOST = "43.138.168.68", + [int]$DB_PORT = 3306, + [string]$DB_NAME = "frameworkdb2025", + [string]$DB_USER = "root", + [string]$DB_PASSWORD = "ylfw20230626@" +) + +$env:MYSQL_PWD = $DB_PASSWORD + +# 导出结构 +mysqldump -h $DB_HOST -P $DB_PORT -u $DB_USER --no-data --databases $DB_NAME > ../sql/base-schema.sql + +# 按需导出数据(示例表名可调整) +mysqldump -h $DB_HOST -P $DB_PORT -u $DB_USER --no-create-info $DB_NAME sys_dict sys_config sys_role sys_menu sys_user sys_user_role sys_role_menu > ../sql/base-data.sql + +Write-Host "Export completed: base-schema.sql & base-data.sql" +``` +- `framework/db-init/scripts/import.ps1` +``` +param( + [string]$DB_HOST = "127.0.0.1", + [int]$DB_PORT = 3306, + [string]$DB_NAME = "platform", + [string]$DB_USER = "root", + [string]$DB_PASSWORD = "root" +) + +$env:MYSQL_PWD = $DB_PASSWORD + +mysql -h $DB_HOST -P $DB_PORT -u $DB_USER $DB_NAME < ../sql/base-schema.sql +mysql -h $DB_HOST -P $DB_PORT -u $DB_USER $DB_NAME < ../sql/base-data.sql + +Write-Host "Import completed" +``` + +## 启动时自动初始化的实现建议(代码思路) +- 创建 `DataInitializer`: + - 读取 `app.init.enabled`;若为 `false` 则直接返回。 + - 使用 `JdbcTemplate` 或 `EntityManager` 查询 `marker-table` 状态。 + - 若未初始化: + - 读取并执行 `schema`、`data` 指定的 SQL 文件;分批执行并开启事务。 + - 写入标记表 `initialized=true, version=marker-version, ts=now()`。 +- 并发保护: + - 使用数据库锁或单例启动(例如基于 `SELECT ... FOR UPDATE` 或分布式锁)避免多实例重复初始化。 +- 失败回滚与告警: + - 执行失败时回滚事务并记录日志,提示人工介入;避免部分写入。 + +## 运维与审计 +- 对 `sql/base-data.sql` 的修改应评审并走 PR,避免带入敏感数据。 +- CI 提示: + - 在 `server` 构建流水线中,可增加“初始化脚本语法检查”(如用 `mysql --dry-run` 或解析器)。 +- 版本管理: + - 在 `db-init/README.md` 中记录每次更新目的与影响;或使用 `Flyway` 版本号管理。 + +## 下一步落地 +- 我可以为你: + - 搭建 `framework/db-init` 目录与示例脚本、README。 + - 在后端新增 `DataInitializer` 组件与配置项,支持 `dev/server` 下自动初始化。 + - 可选集成 `Flyway`,将初始化脚本迁移为 `V1__base.sql` 并接入 CI。 \ No newline at end of file diff --git a/backend/pom.xml b/backend/pom.xml new file mode 100644 index 0000000..cbe7af1 --- /dev/null +++ b/backend/pom.xml @@ -0,0 +1,422 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 4.0.3 + + + com.yfd + platform + 1.0 + jar + platform + springboot 项目基础框架4.0.3 + + 21 + UTF-8 + 4.0.3 + 21 + 21 + 21 + 3.5.16 + 3.5.16 + + 30.0-jre + 4.0.1 + 1.2.20 + 1.2.20 + 3.32.3.2 + 5.8.8 + 4.1.2 + 4.1.2 + 1.2.70 + 3.0.2 + 2.3.28 + 1.11.3 + 1.16 + 1.7.2 + 1.6.2 + 1.21 + + + + + + + + org.mybatis + mybatis + ${mybatis.version} + + + org.mybatis + mybatis-spring + 3.0.3 + + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-security + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.springframework.boot + spring-boot-starter-websocket + + + + + org.springframework.boot + spring-boot-starter-cache + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + com.google.guava + guava + ${guava.version} + + + + + + + org.springframework.boot + spring-boot-starter-quartz + + + + + + + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis.spring.boot.starter.version} + + + + + org.mybatis + mybatis + ${mybatis.version} + + + + + com.alibaba + druid + ${druid.version} + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + + com.mysql + mysql-connector-j + runtime + + + + + org.xerial + sqlite-jdbc + ${sqlite-jdbc.version} + + + + + com.baomidou + mybatis-plus-spring-boot4-starter + ${mybatis-plus.version} + + + + com.baomidou + mybatis-plus-jsqlparser + ${mybatis-plus.version} + + + + com.baomidou + mybatis-plus-generator + ${mybatis-plus.version} + + + + + org.apache.commons + commons-lang3 + + + + + org.projectlombok + lombok + true + + + + + org.springframework.boot + spring-boot-starter-aspectj + + + + + cn.hutool + hutool-all + ${hutool.version} + + + + + org.apache.poi + poi + ${poi.version} + + + org.apache.poi + poi-ooxml + ${poi.ooxml.version} + + + + + com.alibaba + fastjson + ${fastjson.version} + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc.version} + + + + io.micrometer + micrometer-registry-prometheus + + + + org.freemarker + freemarker + ${freemarker.version} + compile + + + org.jsoup + jsoup + ${jsoup.version} + + + + + com.github.ulisesbocchio + jasypt-spring-boot-starter + ${jasypt.version} + + + + org.lionsoul + ip2region + ${ip2region.version} + + + + + com.github.whvcse + easy-captcha + ${easy.captcha.version} + + + + + eu.bitwalker + UserAgentUtils + ${useragentutils.version} + + + + org.openjdk.nashorn + nashorn-core + 15.4 + + + + + + + src/main/resources + + **/*.* + + false + + + src/main/java + + **/*.* + + + **/*.java + + false + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + ${java.version} + ${java.version} + + -parameters + + + + org.projectlombok + lombok + 1.18.42 + + + + + + + org.asciidoctor + asciidoctor-maven-plugin + 1.5.8 + + + generate-docs + prepare-package + + process-asciidoc + + + true + html + book + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + -Dfile.encoding=UTF-8 + + + + + org.projectlombok + lombok + + + + + + + repackage + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.4.1 + + + enforce-rules + + enforce + + + + + + [3.6.3,) + + + [21,) + + + + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + attach-jar + + jar + + + plain + + + + + + + + diff --git a/backend/src/main/java/com/yfd/platform/PlatformApplication.java b/backend/src/main/java/com/yfd/platform/PlatformApplication.java new file mode 100644 index 0000000..03ca484 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/PlatformApplication.java @@ -0,0 +1,46 @@ +package com.yfd.platform; + +import com.yfd.platform.annotation.rest.AnonymousGetMapping; +import com.yfd.platform.datasource.DynamicDataSourceConfig; +import com.yfd.platform.utils.SpringContextHolder; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.web.server.servlet.context.ServletComponentScan; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.web.bind.annotation.RestController; + +//@SpringBootApplication +@RestController +@EnableTransactionManagement +@ServletComponentScan("com.yfd.platform.config") +@MapperScan(basePackages = "com.yfd.platform.*.mapper") +@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class, DataRedisAutoConfiguration.class}) +@Import({DynamicDataSourceConfig.class}) +@EnableCaching +public class PlatformApplication { + + public static void main(String[] args) { + SpringApplication.run(PlatformApplication.class, args); + } + + @Bean + public SpringContextHolder springContextHolder() { + return new SpringContextHolder(); + } + + /** + * 访问首页提示 + * + * @return / + */ + @AnonymousGetMapping("/") + public String index() { + return "Backend service started successfully"; + } +} diff --git a/backend/src/main/java/com/yfd/platform/ServletInitializer.java b/backend/src/main/java/com/yfd/platform/ServletInitializer.java new file mode 100644 index 0000000..0234b48 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/ServletInitializer.java @@ -0,0 +1,13 @@ +package com.yfd.platform; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +public class ServletInitializer extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(PlatformApplication.class); + } + +} diff --git a/backend/src/main/java/com/yfd/platform/annotation/AnonymousAccess.java b/backend/src/main/java/com/yfd/platform/annotation/AnonymousAccess.java new file mode 100644 index 0000000..2fbd4c0 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/annotation/AnonymousAccess.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.annotation; + +import java.lang.annotation.*; + +/** + * @author jacky + * 用于标记匿名访问方法 + */ +@Inherited +@Documented +@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AnonymousAccess { + +} diff --git a/backend/src/main/java/com/yfd/platform/annotation/Log.java b/backend/src/main/java/com/yfd/platform/annotation/Log.java new file mode 100644 index 0000000..6739494 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/annotation/Log.java @@ -0,0 +1,20 @@ +package com.yfd.platform.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author TangWei + * @date 2018-11-24 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Log { + + String value() default ""; + + String module() default ""; +} + diff --git a/backend/src/main/java/com/yfd/platform/annotation/rest/AnonymousGetMapping.java b/backend/src/main/java/com/yfd/platform/annotation/rest/AnonymousGetMapping.java new file mode 100644 index 0000000..01fcc32 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/annotation/rest/AnonymousGetMapping.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yfd.platform.annotation.rest; + +import com.yfd.platform.annotation.AnonymousAccess; +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.lang.annotation.*; + +/** + * Annotation for mapping HTTP {@code GET} requests onto specific handler + * methods. + *

+ * 支持匿名访问 GetMapping + * + * @author liaojinlong + * @see RequestMapping + */ +@AnonymousAccess +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@RequestMapping(method = RequestMethod.GET) +public @interface AnonymousGetMapping { + + /** + * Alias for {@link RequestMapping#name}. + */ + @AliasFor(annotation = RequestMapping.class) + String name() default ""; + + /** + * Alias for {@link RequestMapping#value}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] value() default {}; + + /** + * Alias for {@link RequestMapping#path}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] path() default {}; + + /** + * Alias for {@link RequestMapping#params}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] params() default {}; + + /** + * Alias for {@link RequestMapping#headers}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] headers() default {}; + + /** + * Alias for {@link RequestMapping#consumes}. + * + * @since 4.3.5 + */ + @AliasFor(annotation = RequestMapping.class) + String[] consumes() default {}; + + /** + * Alias for {@link RequestMapping#produces}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] produces() default {}; + +} diff --git a/backend/src/main/java/com/yfd/platform/aspect/LogAspect.java b/backend/src/main/java/com/yfd/platform/aspect/LogAspect.java new file mode 100644 index 0000000..0c52a21 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/aspect/LogAspect.java @@ -0,0 +1,93 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.aspect; + +import com.yfd.platform.system.domain.SysLog; +import com.yfd.platform.system.mapper.SysUserMapper; +import com.yfd.platform.system.service.ISysLogService; +import com.yfd.platform.system.service.IUserService; +import com.yfd.platform.utils.RequestHolder; +import com.yfd.platform.utils.SecurityUtils; +import com.yfd.platform.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * @author + * @date 2018-11-24 + */ +@Component +@Aspect +@Slf4j +public class LogAspect { + + @Resource + private final ISysLogService sysLogService; + + @Resource + private IUserService userService; + + ThreadLocal currentTime = new ThreadLocal<>(); + + public LogAspect(ISysLogService sysLogService) { + this.sysLogService = sysLogService; + } + + /** + * 配置切入点 + */ + @Pointcut("@annotation(com.yfd.platform.annotation.Log)") + public void logPointcut() { + // 该方法无方法体,主要为了让同类中其他方法使用此切入点 + } + + /** + * 配置环绕通知,使用在方法logPointcut()上注册的切入点 + * + * @param joinPoint join point for advice + */ + @Around("logPointcut()") + public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { + Object result; + currentTime.set(System.currentTimeMillis()); + result = joinPoint.proceed(); + SysLog log = new SysLog("INFO"); + currentTime.remove(); + HttpServletRequest request = RequestHolder.getHttpServletRequest(); + Map nameInfo = userService.getNameInfo(); + String nickname = nameInfo.get("nickname"); + String username = nameInfo.get("username"); + sysLogService.save(nickname, username, StringUtils.getBrowser(request), + StringUtils.getIp(request), joinPoint, log); + return result; + } + + public String getUsername() { + try { + return SecurityUtils.getCurrentUsername(); + } catch (Exception e) { + return ""; + } + } +} diff --git a/backend/src/main/java/com/yfd/platform/component/ServerSendEventServer.java b/backend/src/main/java/com/yfd/platform/component/ServerSendEventServer.java new file mode 100644 index 0000000..ca1b1fe --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/component/ServerSendEventServer.java @@ -0,0 +1,147 @@ +package com.yfd.platform.component; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +/** + * @author Huhailong + * SSE Server send Event 服务器推送服务 + */ +@Slf4j +public class ServerSendEventServer { + + /** + * 当前连接数 + */ + private static AtomicInteger count = new AtomicInteger(0); + + private static Map sseEmitterMap = + new ConcurrentHashMap<>(); + + public static SseEmitter connect(String userId) { + //设置超时时间,0表示不过期,默认是30秒,超过时间未完成会抛出异常 + SseEmitter sseEmitter = new SseEmitter(0L); + //SseEmitter sseEmitter = new SseEmitter(); + //注册回调 + sseEmitter.onCompletion(completionCallBack(userId)); + sseEmitter.onError(errorCallBack(userId)); + sseEmitter.onTimeout(timeOutCallBack(userId)); + sseEmitterMap.put(userId, sseEmitter); + //数量+1 + count.getAndIncrement(); + log.info("create new sse connect ,current user:{}", userId); + return sseEmitter; + } + + /** + * 给指定用户发消息 + */ + public static void sendMessage(String userId, String message) { + if (sseEmitterMap.containsKey(userId)) { + try { + sseEmitterMap.get(userId).send(message); + } catch (IOException e) { + log.error("user id:{}, send message error:{}", userId, + e.getMessage()); + e.printStackTrace(); + } + } + } + + /** + * 给所有用户发消息 + */ + public static void sendMessage(String message) { + if (sseEmitterMap != null && !sseEmitterMap.isEmpty()) { + sseEmitterMap.forEach((k, v) -> { + // 发送消息 + sendMessage(k, message); + + }); + } + } + + /** + * 想多人发送消息,组播 + */ + public static void groupSendMessage(String groupId, String message) { + if (sseEmitterMap != null && !sseEmitterMap.isEmpty()) { + sseEmitterMap.forEach((k, v) -> { + try { + if (k.startsWith(groupId)) { + v.send(message, MediaType.APPLICATION_JSON); + } + } catch (IOException e) { + log.error("user id:{}, send message error:{}", groupId, + message); + removeUser(k); + } + }); + } + } + + public static void batchSendMessage(String message) { + sseEmitterMap.forEach((k, v) -> { + try { + v.send(message, MediaType.APPLICATION_JSON); + } catch (IOException e) { + log.error("user id:{}, send message error:{}", k, + e.getMessage()); + removeUser(k); + } + }); + } + + /** + * 群发消息 + */ + public static void batchSendMessage(String message, Set userIds) { + userIds.forEach(userId -> sendMessage(userId, message)); + } + + public static void removeUser(String userId) { + sseEmitterMap.remove(userId); + //数量-1 + count.getAndDecrement(); + log.info("remove user id:{}", userId); + } + + public static List getIds() { + return new ArrayList<>(sseEmitterMap.keySet()); + } + + public static int getUserCount() { + return count.intValue(); + } + + private static Runnable completionCallBack(String userId) { + return () -> { + log.info("结束连接,{}", userId); + removeUser(userId); + }; + } + + private static Runnable timeOutCallBack(String userId) { + return () -> { + log.info("连接超时,{}", userId); + removeUser(userId); + }; + } + + private static Consumer errorCallBack(String userId) { + return throwable -> { + log.error("连接异常,{}", userId); + removeUser(userId); + }; + } +} diff --git a/backend/src/main/java/com/yfd/platform/component/WebSocketServer.java b/backend/src/main/java/com/yfd/platform/component/WebSocketServer.java new file mode 100644 index 0000000..1d0b913 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/component/WebSocketServer.java @@ -0,0 +1,106 @@ +package com.yfd.platform.component; + +import org.springframework.stereotype.Component; + +import jakarta.websocket.*; +import jakarta.websocket.server.PathParam; +import jakarta.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.concurrent.CopyOnWriteArrayList; + +@ServerEndpoint("/websocket/{token}") +@Component +public class WebSocketServer { + private static int onlineCount=0;//在线人数 + private static CopyOnWriteArrayList webSocketSet=new CopyOnWriteArrayList();//在线用户集合 + private Session session;//与某个客户端的连接会话 + private String currentUser; + + @OnOpen + public void onOpen(@PathParam("token") String token, Session session){ + this.currentUser = token; + this.session=session; + webSocketSet.add(this);//加入set中 + addOnlineCount(); + System.out.println("有新连接加入!当前在线人数为"+getOnlineCount()); + allCurrentOnline(); + } + + @OnClose + public void onClose(){ + webSocketSet.remove(this); + subOnlineCount(); + System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount()); + allCurrentOnline(); + } + + @OnMessage + public void onMessage(String message, Session session){ + System.out.println("来自客户端的消息:"+message); + for (WebSocketServer item:webSocketSet){ + try { + item.sendMessage(message); + } catch (IOException e) { + e.printStackTrace(); + continue; + } + } + } + + @OnError + public void onError(Session session, Throwable throwable){ + System.out.println("发生错误!"); + throwable.printStackTrace(); + } + + public void sendMessage(String message) throws IOException { + this.session.getBasicRemote().sendText(message); + } + + /** + * 获取当前所有在线用户名 + */ + public static void allCurrentOnline(){ + for (WebSocketServer item : webSocketSet) { + System.out.println(item.currentUser); + } + } + + /** + * 发送给指定用户 + */ + public static void sendMessageTo(String message,String token) throws IOException { + for (WebSocketServer item : webSocketSet) { + if(item.currentUser.equals(token)){ + item.session.getBasicRemote().sendText(message); + } + } + } + + /** + * 群发自定义消息 + */ + public static void sendInfo(String message) throws IOException { + System.out.println(message); + for (WebSocketServer item : webSocketSet) { + try { + item.sendMessage(message); + } catch (IOException e) { + continue; + } + } + } + + public static synchronized int getOnlineCount(){ + return onlineCount; + } + public static synchronized void addOnlineCount(){ + WebSocketServer.onlineCount++; + } + public static synchronized void subOnlineCount(){ + WebSocketServer.onlineCount--; + } + +} + + diff --git a/backend/src/main/java/com/yfd/platform/config/AppInitProperties.java b/backend/src/main/java/com/yfd/platform/config/AppInitProperties.java new file mode 100644 index 0000000..4ee304f --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/AppInitProperties.java @@ -0,0 +1,17 @@ +package com.yfd.platform.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@ConfigurationProperties(prefix = "app.init") +public class AppInitProperties { + private boolean enabled = false; + private String schema; + private String data; + // 用于判断是否已初始化:默认检查是否存在核心表 + private String markerTable = "sys_user"; + private String markerVersion = "v1.0.0"; +} \ No newline at end of file diff --git a/backend/src/main/java/com/yfd/platform/config/DataInitializer.java b/backend/src/main/java/com/yfd/platform/config/DataInitializer.java new file mode 100644 index 0000000..b5dab5a --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/DataInitializer.java @@ -0,0 +1,129 @@ +package com.yfd.platform.config; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.context.annotation.Profile; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.EncodedResource; +import org.springframework.jdbc.datasource.init.ScriptUtils; +import org.springframework.jdbc.datasource.init.ScriptException; +import org.springframework.jdbc.datasource.init.ScriptStatementFailedException; +import org.springframework.stereotype.Component; + +import javax.sql.DataSource; +import java.nio.charset.StandardCharsets; +import java.util.stream.Collectors; +import org.springframework.util.StreamUtils; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; + +@Slf4j +@Component +@Profile({"dev","server"}) +@RequiredArgsConstructor +@Order(Ordered.HIGHEST_PRECEDENCE) +public class DataInitializer implements ApplicationRunner { + + private final DataSource dataSource; + private final AppInitProperties properties; + private final ResourceLoader resourceLoader; + + @Override + public void run(ApplicationArguments args) throws Exception { + if (!properties.isEnabled()) { + log.info("[DataInit] 自动初始化已关闭 app.init.enabled=false"); + return; + } + + try (Connection conn = dataSource.getConnection()) { + boolean initialized = tableExists(conn, properties.getMarkerTable()); + if (initialized) { + log.info("[DataInit] 检测到标记表已存在: {},跳过初始化", properties.getMarkerTable()); + return; + } + + log.info("[DataInit] 未检测到标记表: {},开始导入 schema 与 data", properties.getMarkerTable()); + executeIfPresent(conn, properties.getSchema()); + executeIfPresent(conn, properties.getData()); + log.info("[DataInit] 导入完成。marker={} version={}", properties.getMarkerTable(), properties.getMarkerVersion()); + } catch (Exception ex) { + log.error("[DataInit] 初始化失败: {}", ex.getMessage(), ex); + throw ex; + } + } + + private boolean tableExists(Connection conn, String tableName) { + if (tableName == null || tableName.isEmpty()) return false; + try { + DatabaseMetaData meta = conn.getMetaData(); + try (ResultSet rs = meta.getTables(conn.getCatalog(), null, tableName, null)) { + return rs.next(); + } + } catch (Exception e) { + log.warn("[DataInit] 检查表存在异常: {}", e.getMessage()); + return false; + } + } + + private void executeIfPresent(Connection conn, String location) throws Exception { + if (location == null || location.isEmpty()) return; + Resource resource = resourceLoader.getResource(location); + if (!resource.exists()) { + log.warn("[DataInit] 资源不存在: {}", location); + return; + } + log.info("[DataInit] 执行脚本: {}", location); + // 读取并清理脚本首部的 BOM 和危险语句(CREATE DATABASE / USE schema) + String sql = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8); + if (sql != null && !sql.isEmpty()) { + // 移除 UTF-8 BOM(\uFEFF) + if (sql.charAt(0) == '\uFEFF') { + sql = sql.substring(1); + } + // 移除 "USE xxx;" 和 "CREATE DATABASE" 等与连接无关的语句 + sql = sql.lines() + .filter(line -> { + String raw = line; + String t = raw.trim(); + String u = t.toUpperCase(); + // 过滤与连接无关或可能引发解析问题的语句/注释 + if (u.startsWith("USE ")) return false; + if (u.startsWith("CREATE DATABASE")) return false; + if (t.startsWith("--")) return false; // 单行注释 + if (t.startsWith("/*") || t.startsWith("*/")) return false; // 多行注释行 + if (t.startsWith("/*!")) return false; // MySQL 版本注释 + if (t.matches("^-+$")) return false; // 分隔线 + return true; + }) + .collect(Collectors.joining("\n")); + } + if (sql != null && !sql.trim().isEmpty()) { + try { + ScriptUtils.executeSqlScript(conn, new EncodedResource(new ByteArrayResource(sql.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8)); + } catch (ScriptStatementFailedException e) { + String preview = sql.lines() + .filter(s -> !s.trim().isEmpty()) + .limit(10) + .collect(Collectors.joining("\n")); + log.error("[DataInit] SQL语句执行失败: {}\n前10行预览:\n{}", e.getMessage(), preview); + throw e; + } catch (ScriptException e) { + String preview = sql.lines() + .filter(s -> !s.trim().isEmpty()) + .limit(10) + .collect(Collectors.joining("\n")); + log.error("[DataInit] 脚本执行失败: {}\n前10行预览:\n{}", e.getMessage(), preview); + throw e; + } + } else { + log.warn("[DataInit] 脚本在清理后为空,跳过执行: {}", location); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/yfd/platform/config/FileProperties.java b/backend/src/main/java/com/yfd/platform/config/FileProperties.java new file mode 100644 index 0000000..c179499 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/FileProperties.java @@ -0,0 +1,59 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @author + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "file") +public class FileProperties { + + /** 文件大小限制 */ + private Long maxSize; + + /** 头像大小限制 */ + private Long avatarMaxSize; + + private ElPath mac; + + private ElPath linux; + + private ElPath windows; + + public ElPath getPath(){ + String os = System.getProperty("os.name"); + if(os.toLowerCase().startsWith("win")) { + return windows; + } else if(os.toLowerCase().startsWith("mac")){ + return mac; + } + return linux; + } + + @Data + public static class ElPath{ + + private String path; + + private String avatar; + } +} diff --git a/backend/src/main/java/com/yfd/platform/config/FileSpaceProperties.java b/backend/src/main/java/com/yfd/platform/config/FileSpaceProperties.java new file mode 100644 index 0000000..74aab7d --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/FileSpaceProperties.java @@ -0,0 +1,17 @@ +package com.yfd.platform.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * 文件空间相关配置,替换 @Value("${file-space.system}") 用法 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "file-space") +public class FileSpaceProperties { + + /** 基础目录,例如 D:/data/platform/ */ + private String system; +} \ No newline at end of file diff --git a/backend/src/main/java/com/yfd/platform/config/GlobalExceptionHandler.java b/backend/src/main/java/com/yfd/platform/config/GlobalExceptionHandler.java new file mode 100644 index 0000000..d9c7d7a --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/GlobalExceptionHandler.java @@ -0,0 +1,24 @@ +package com.yfd.platform.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * @author TangWei + * @Date: 2023/3/27 18:07 + * @Description: + */ +@Slf4j +@ControllerAdvice +public class GlobalExceptionHandler { + + @ResponseBody + @ExceptionHandler(value = Throwable.class) + public ResponseResult handleException(Throwable e) { + log.error("message:{}", e.getMessage()); + return ResponseResult.error(e.getMessage()); + } + +} diff --git a/backend/src/main/java/com/yfd/platform/config/JobRunner.java b/backend/src/main/java/com/yfd/platform/config/JobRunner.java new file mode 100644 index 0000000..74bf8db --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/JobRunner.java @@ -0,0 +1,59 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.config; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.yfd.platform.system.domain.QuartzJob; +import com.yfd.platform.system.mapper.QuartzJobMapper; +import com.yfd.platform.utils.QuartzManage; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * @author + * @date 2019-01-07 + */ +@Component +@RequiredArgsConstructor +@Order(Ordered.LOWEST_PRECEDENCE) +public class JobRunner implements ApplicationRunner { + + private static final Logger log = LoggerFactory.getLogger(JobRunner.class); + private final QuartzJobMapper quartzJobMapper; + private final QuartzManage quartzManage; + + /** + * 项目启动时重新激活启用的定时任务 + * + * @param applicationArguments / + */ + @Override + public void run(ApplicationArguments applicationArguments) { + log.info("--------------------注入定时任务---------------------"); + List quartzJobs = + quartzJobMapper.selectList(new LambdaQueryWrapper().eq(QuartzJob::getStatus, "1")); + quartzJobs.forEach(quartzManage::addJob); + log.info("--------------------定时任务注入完成---------------------"); + } +} diff --git a/backend/src/main/java/com/yfd/platform/config/JwtAuthenticationTokenFilter.java b/backend/src/main/java/com/yfd/platform/config/JwtAuthenticationTokenFilter.java new file mode 100644 index 0000000..bcb79c0 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/JwtAuthenticationTokenFilter.java @@ -0,0 +1,83 @@ +package com.yfd.platform.config; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.jwt.JWT; +import cn.hutool.jwt.JWTUtil; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.yfd.platform.component.ServerSendEventServer; +import com.yfd.platform.constant.Constant; +import com.yfd.platform.system.domain.LoginUser; +import com.yfd.platform.system.domain.Message; +import com.yfd.platform.system.service.IMessageService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import jakarta.annotation.Resource; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { + + @Autowired + private WebConfig webConfig; + + @Override + protected void doFilterInternal(HttpServletRequest httpServletRequest, + HttpServletResponse httpServletResponse, + FilterChain filterChain) throws ServletException, IOException { + //获取token + String uri = httpServletRequest.getRequestURI(); + String token = httpServletRequest.getHeader("token"); + if (StrUtil.isEmpty(token) || "/user/login".equals(uri)) { + filterChain.doFilter(httpServletRequest, httpServletResponse); + return; + } + //解析token + boolean isok = JWTUtil.verify(token, "12345678".getBytes()); + String userid = ""; + if (isok) { + final JWT jwt = JWTUtil.parseToken(token); + userid = jwt.getPayload("userid").toString(); + //从cachekey中获取用户信息失效时间 + String cachekey = "expire_time:" + userid; + if(StrUtil.isNotEmpty(webConfig.loginuserCache().get(cachekey))){ + long expire_time =Long.parseLong(webConfig.loginuserCache().get(cachekey)); + if (System.currentTimeMillis() > expire_time) { + httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Token超过期限!"); + return; + } + } + } + + //从cachekey中获取用户信息 + String cachekey = "login:" + userid; + String jsonstr = webConfig.loginuserCache().get(cachekey); + LoginUser loginUser = JSON.parseObject(jsonstr, LoginUser.class); + if (ObjectUtil.isEmpty(loginUser)) { + httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, + "登录用户已失效!"); + return; + } + //存入SecurityContextHolder + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken(loginUser, null, + loginUser.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + webConfig.loginuserCache().put(Constant.TOKEN + userid, token); + //更新了超期时间 + long expireTime =System.currentTimeMillis() + ( 30L * 60L * 1000L); + webConfig.loginuserCache().put("expire_time:" + userid, String.valueOf(expireTime)); + //放行过滤器 + filterChain.doFilter(httpServletRequest, httpServletResponse); + } + +} diff --git a/backend/src/main/java/com/yfd/platform/config/MessageConfig.java b/backend/src/main/java/com/yfd/platform/config/MessageConfig.java new file mode 100644 index 0000000..e079d02 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/MessageConfig.java @@ -0,0 +1,50 @@ +package com.yfd.platform.config; + +import cn.hutool.cache.Cache; +import cn.hutool.cache.impl.CacheObj; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.yfd.platform.component.ServerSendEventServer; +import com.yfd.platform.constant.Constant; +import com.yfd.platform.system.domain.Message; +import com.yfd.platform.system.domain.SysUser; +import com.yfd.platform.system.service.IMessageService; +import com.yfd.platform.system.service.IUserService; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; +import java.util.Iterator; + +/** + * @author TangWei + * @Date: 2023/3/24 15:56 + * @Description: + */ +@Component +public class MessageConfig { + + @Resource + private IMessageService messageService; + + @Resource + private IUserService userService; + + @Resource + private WebConfig webConfig; + + public void sendMessage() { + long count = + messageService.count(new LambdaQueryWrapper().eq(Message::getStatus, "1")); + String userId = userService.getUserInfo().getId(); + String token = webConfig.loginuserCache().get(Constant.TOKEN + userId); + ServerSendEventServer.sendMessage(token, count + ""); + } + + public void addMessage(Message message) { + messageService.save(message); + long count = + messageService.count(new LambdaQueryWrapper().eq(Message::getStatus, "1")); + ServerSendEventServer.sendMessage(count + ""); + } +} diff --git a/backend/src/main/java/com/yfd/platform/config/MybitsPlusConfig.java b/backend/src/main/java/com/yfd/platform/config/MybitsPlusConfig.java new file mode 100644 index 0000000..de009d7 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/MybitsPlusConfig.java @@ -0,0 +1,24 @@ +package com.yfd.platform.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/****************************** + * 用途说明: + * 作者姓名: pcj + * 创建时间: 2022/10/24 10:50 + ******************************/ +@Configuration +public class MybitsPlusConfig { + + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); + mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return mybatisPlusInterceptor; + } + +} diff --git a/backend/src/main/java/com/yfd/platform/config/ProdApiPrefixFilter.java b/backend/src/main/java/com/yfd/platform/config/ProdApiPrefixFilter.java new file mode 100644 index 0000000..4c1680a --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/ProdApiPrefixFilter.java @@ -0,0 +1,44 @@ +package com.yfd.platform.config; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.annotation.WebFilter; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * 将以 /prod-api/ 开头的请求转发到去掉前缀的真实后端接口路径。 + * 例如:/prod-api/user/code -> /user/code + * 这样可以兼容前端生产环境仍使用 /prod-api 作为网关前缀的情况。 + */ +@WebFilter(urlPatterns = "/prod-api/*", filterName = "prodApiPrefixFilter") +public class ProdApiPrefixFilter implements Filter { + + private static final String PREFIX = "/prod-api"; + + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { + if (!(req instanceof HttpServletRequest) || !(res instanceof HttpServletResponse)) { + chain.doFilter(req, res); + return; + } + + HttpServletRequest request = (HttpServletRequest) req; + String uri = request.getRequestURI(); + + // 仅拦截 /prod-api/* 的接口请求并进行内部 forward + if (uri.startsWith(PREFIX + "/")) { + String forwardUri = uri.substring(PREFIX.length()); + RequestDispatcher dispatcher = request.getRequestDispatcher(forwardUri); + dispatcher.forward(req, res); + return; + } + + chain.doFilter(req, res); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/yfd/platform/config/QuartzConfig.java b/backend/src/main/java/com/yfd/platform/config/QuartzConfig.java new file mode 100644 index 0000000..41a36db --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/QuartzConfig.java @@ -0,0 +1,77 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.config; + +import org.quartz.Scheduler; +import org.quartz.spi.TriggerFiredBundle; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.quartz.AdaptableJobFactory; +import org.springframework.scheduling.quartz.SchedulerFactoryBean; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * 定时任务配置 + * + * @author / + * @date 2019-01-07 + */ +@Configuration +public class QuartzConfig { + + /** + * 解决Job中注入Spring Bean为null的问题 + */ + @Component("quartzJobFactory") + public static class QuartzJobFactory extends AdaptableJobFactory { + + private final AutowireCapableBeanFactory capableBeanFactory; + + public QuartzJobFactory(AutowireCapableBeanFactory capableBeanFactory) { + this.capableBeanFactory = capableBeanFactory; + } + + @Override + protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { + + //调用父类的方法 + Object jobInstance = super.createJobInstance(bundle); + capableBeanFactory.autowireBean(jobInstance); + return jobInstance; + } + } + + /** + * 注入scheduler到spring + * + * @param quartzJobFactory / + * @return Scheduler + * @throws Exception / + */ + @Bean(name = "scheduler") + public Scheduler scheduler(QuartzJobFactory quartzJobFactory) throws Exception { + SchedulerFactoryBean factoryBean = new SchedulerFactoryBean(); + factoryBean.setJobFactory(quartzJobFactory); + factoryBean.afterPropertiesSet(); + Scheduler scheduler = factoryBean.getScheduler(); + scheduler.start(); + return scheduler; + } + +} diff --git a/backend/src/main/java/com/yfd/platform/config/ResponseResult.java b/backend/src/main/java/com/yfd/platform/config/ResponseResult.java new file mode 100644 index 0000000..9638fcf --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/ResponseResult.java @@ -0,0 +1,57 @@ +package com.yfd.platform.config; + +import java.util.HashMap; + +public class ResponseResult extends HashMap { + private static final long serialVersionUID = 1L; + + public ResponseResult() { + } + + public static ResponseResult unlogin() { + return message("401", "未登录"); + } + + public static ResponseResult error() { + return error("操作失败"); + } + + public static ResponseResult success() { + return success("操作成功"); + } + + public static ResponseResult error(String msg) { + ResponseResult json = new ResponseResult(); + json.put((String)"code", "1");//错误 + json.put((String)"msg", msg); + return json; + } + + public static ResponseResult message(String code, String msg) { + ResponseResult json = new ResponseResult(); + json.put((String)"code", code); + json.put((String)"msg", msg); + return json; + } + + public static ResponseResult success(String msg) { + ResponseResult json = new ResponseResult(); + json.put((String)"code", "0");//正常 + json.put((String)"msg", msg); + return json; + } + + public static ResponseResult successData(Object obj) { + ResponseResult json = new ResponseResult(); + json.put((String)"code", "0");//正常 + json.put((String)"msg", "操作成功"); + json.put("data", obj); + return json; + } + + + public ResponseResult put(String key, Object value) { + super.put(key, value); + return this; + } +} diff --git a/backend/src/main/java/com/yfd/platform/config/SecurityConfig.java b/backend/src/main/java/com/yfd/platform/config/SecurityConfig.java new file mode 100644 index 0000000..37aa470 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/SecurityConfig.java @@ -0,0 +1,90 @@ +package com.yfd.platform.config; + +import com.yfd.platform.config.bean.LoginProperties; +import com.yfd.platform.exception.AccessDeniedHandExcetion; +import com.yfd.platform.exception.AuthenticationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +public class SecurityConfig { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + @ConfigurationProperties(prefix = "login", ignoreUnknownFields = true) + public LoginProperties loginProperties() { + return new LoginProperties(); + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } + + @Autowired + private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; + + @Autowired + private AuthenticationException authenticationException; + + @Autowired + private AccessDeniedHandExcetion accessDeniedHandExcetion; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) + .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/user/login").anonymous() + .requestMatchers("/user/code").permitAll() + .requestMatchers(HttpMethod.GET, "/").permitAll() + .requestMatchers(HttpMethod.GET, + "/*.html", + "/webSocket/**", + "/assets/**", + "/icon/**").permitAll() + .requestMatchers( + "/swagger-ui.html", + "/swagger-ui/**", + "/v3/api-docs/**", + "/v3/api-docs.yaml", + "/swagger-resources/**", + "/webjars/**", + "/*/api-docs").permitAll() + .requestMatchers( + "/report/**", + "/images/**", + "/pageimage/**", + "/avatar/**", + "/systemurl/**", + "/api/imageserver/upload").permitAll() + .anyRequest().authenticated() + ) + .cors(cors -> {}); + + http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); + + http.exceptionHandling(ex -> ex + .authenticationEntryPoint(authenticationException) + .accessDeniedHandler(accessDeniedHandExcetion) + ); + + return http.build(); + } +} diff --git a/backend/src/main/java/com/yfd/platform/config/SwaggerConfig.java b/backend/src/main/java/com/yfd/platform/config/SwaggerConfig.java new file mode 100644 index 0000000..bdfe72a --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/SwaggerConfig.java @@ -0,0 +1,49 @@ +package com.yfd.platform.config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springdoc.core.models.GroupedOpenApi; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.Contact; + +/** + * Springdoc OpenAPI 配置 + */ +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI projectOpenAPI() { + return new OpenAPI() + .info(new Info() + .title("项目API 接口文档") + .version("3.0") + .description("") + .contact(new Contact().name("郑顺利").email("13910913995@163.com")) + ); + } + + @Bean + public GroupedOpenApi groupWebsiteApi() { + return GroupedOpenApi.builder() + .group("1. 平台模块") + .packagesToScan("com.yfd.platform.modules.platformdb.controller") + .build(); + } + + @Bean + public GroupedOpenApi groupQuartzApi() { + return GroupedOpenApi.builder() + .group("2. 定时任务") + .packagesToScan("com.yfd.platform.modules.quartz.controller") + .build(); + } + + @Bean + public GroupedOpenApi groupSystemApi() { + return GroupedOpenApi.builder() + .group("3. 系统管理") + .packagesToScan("com.yfd.platform.system.controller") + .build(); + } +} diff --git a/backend/src/main/java/com/yfd/platform/config/WebConfig.java b/backend/src/main/java/com/yfd/platform/config/WebConfig.java new file mode 100644 index 0000000..45b051a --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/WebConfig.java @@ -0,0 +1,61 @@ +package com.yfd.platform.config; + +import cn.hutool.cache.Cache; +import cn.hutool.cache.CacheUtil; +import lombok.SneakyThrows; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + @Resource + private FileSpaceProperties fileSpaceProperties; + + + + @Bean + public Cache loginuserCache() { + return CacheUtil.newLRUCache(200);//用户登录缓存数 缺省200 + } + + @Bean + public CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = + new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedOriginPattern("*"); + config.addAllowedHeader("*"); + config.addAllowedMethod("*"); + config.setMaxAge(3600L); + source.registerCorsConfiguration("/**", config); + return new CorsFilter(source); + } + + @SneakyThrows + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/icon/**") + .addResourceLocations("classpath:/static/icon/") + .setCachePeriod(0); + + registry.addResourceHandler("/assets/**") + .addResourceLocations("classpath:/static/assets/") + .setCachePeriod(0); + + registry.addResourceHandler("swagger-ui.html").addResourceLocations( + "classpath:/META-INF/resources/"); + + String systemUrl = "file:" + fileSpaceProperties.getSystem().replace("\\", "/")+"user\\"; + registry.addResourceHandler("/avatar/**").addResourceLocations(systemUrl).setCachePeriod(0); + + + } + +} diff --git a/backend/src/main/java/com/yfd/platform/config/WebSocketConfig.java b/backend/src/main/java/com/yfd/platform/config/WebSocketConfig.java new file mode 100644 index 0000000..349ead0 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/WebSocketConfig.java @@ -0,0 +1,16 @@ +package com.yfd.platform.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +@Configuration +public class WebSocketConfig { + + @Bean + public ServerEndpointExporter serverEndpointExporter() { + + return new ServerEndpointExporter(); + } +} + diff --git a/backend/src/main/java/com/yfd/platform/config/bean/LoginCode.java b/backend/src/main/java/com/yfd/platform/config/bean/LoginCode.java new file mode 100644 index 0000000..2a7586b --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/bean/LoginCode.java @@ -0,0 +1,61 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.config.bean; + +import lombok.Data; + +/** + * 登录验证码配置信息 + * + * @author: liaojinlong + * @date: 2020/6/10 18:53 + */ +@Data +public class LoginCode { + + /** + * 验证码配置 + */ + private LoginCodeEnum codeType; + /** + * 验证码有效期 分钟 + */ + private Long expiration = 2L; + /** + * 验证码内容长度 + */ + private int length = 2; + /** + * 验证码宽度 + */ + private int width = 111; + /** + * 验证码高度 + */ + private int height = 36; + /** + * 验证码字体 + */ + private String fontName; + /** + * 字体大小 + */ + private int fontSize = 25; + + public LoginCodeEnum getCodeType() { + return codeType; + } +} diff --git a/backend/src/main/java/com/yfd/platform/config/bean/LoginCodeEnum.java b/backend/src/main/java/com/yfd/platform/config/bean/LoginCodeEnum.java new file mode 100644 index 0000000..d9ade21 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/bean/LoginCodeEnum.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.config.bean; + +/** + * 验证码配置枚举 + * + * @author: liaojinlong + * @date: 2020/6/10 17:40 + */ + +public enum LoginCodeEnum { + /** + * 算数 + */ + arithmetic, + /** + * 中文 + */ + chinese, + /** + * 中文闪图 + */ + chinese_gif, + /** + * 闪图 + */ + gif, + spec +} diff --git a/backend/src/main/java/com/yfd/platform/config/bean/LoginProperties.java b/backend/src/main/java/com/yfd/platform/config/bean/LoginProperties.java new file mode 100644 index 0000000..b16644d --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/bean/LoginProperties.java @@ -0,0 +1,110 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * Licensed under the Apache License, Version loginCode.length.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-loginCode.length.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.config.bean; + +import cn.hutool.core.util.StrUtil; +import com.wf.captcha.*; +import com.wf.captcha.base.Captcha; +import com.yfd.platform.exception.BadConfigurationException; +import lombok.Data; +import java.awt.*; +import java.util.Objects; + +/** + * 配置文件读取 + * + * @author liaojinlong + * @date loginCode.length0loginCode.length0/6/10 17:loginCode.length6 + */ +@Data +public class LoginProperties { + + /** + * 账号单用户 登录 + */ + private boolean singleLogin = false; + + private LoginCode loginCode; + /** + * 用户登录信息缓存 + */ + private boolean cacheEnable; + + public boolean isSingleLogin() { + return singleLogin; + } + + public boolean isCacheEnable() { + return cacheEnable; + } + + /** + * 获取验证码生产类 + * + * @return / + */ + public Captcha getCaptcha() { + if (Objects.isNull(loginCode)) { + loginCode = new LoginCode(); + if (Objects.isNull(loginCode.getCodeType())) { + loginCode.setCodeType(LoginCodeEnum.arithmetic); + } + } + return switchCaptcha(loginCode); + } + + /** + * 依据配置信息生产验证码 + * + * @param loginCode 验证码配置信息 + * @return / + */ + private Captcha switchCaptcha(LoginCode loginCode) { + Captcha captcha; + synchronized (this) { + switch (loginCode.getCodeType()) { + case arithmetic: + // 算术类型 https://gitee.com/whvse/EasyCaptcha + captcha = new ArithmeticCaptcha(loginCode.getWidth(), loginCode.getHeight()); + // 几位数运算,默认是两位 + captcha.setLen(loginCode.getLength()); + break; + case chinese: + captcha = new ChineseCaptcha(loginCode.getWidth(), loginCode.getHeight()); + captcha.setLen(loginCode.getLength()); + break; + case chinese_gif: + captcha = new ChineseGifCaptcha(loginCode.getWidth(), loginCode.getHeight()); + captcha.setLen(loginCode.getLength()); + break; + case gif: + captcha = new GifCaptcha(loginCode.getWidth(), loginCode.getHeight()); + captcha.setLen(loginCode.getLength()); + break; + case spec: + captcha = new SpecCaptcha(loginCode.getWidth(), loginCode.getHeight()); + captcha.setLen(loginCode.getLength()); + break; + default: + throw new BadConfigurationException("验证码配置信息错误!正确配置查看 LoginCodeEnum "); + } + } + if(StrUtil.isNotBlank(loginCode.getFontName())){ + captcha.setFont(new Font(loginCode.getFontName(), Font.PLAIN, loginCode.getFontSize())); + } + return captcha; + } +} diff --git a/backend/src/main/java/com/yfd/platform/config/thread/AsyncTaskExecutePool.java b/backend/src/main/java/com/yfd/platform/config/thread/AsyncTaskExecutePool.java new file mode 100644 index 0000000..ff10654 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/thread/AsyncTaskExecutePool.java @@ -0,0 +1,70 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.config.thread; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 异步任务线程池装配类 + * @author https://juejin.im/entry/5abb8f6951882555677e9da2 + * @date 2019年10月31日15:06:18 + */ +@Slf4j +@Configuration +public class AsyncTaskExecutePool implements AsyncConfigurer { + + /** 注入配置类 */ + private final AsyncTaskProperties config; + + public AsyncTaskExecutePool(AsyncTaskProperties config) { + this.config = config; + } + + @Override + public Executor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + //核心线程池大小 + executor.setCorePoolSize(config.getCorePoolSize()); + //最大线程数 + executor.setMaxPoolSize(config.getMaxPoolSize()); + //队列容量 + executor.setQueueCapacity(config.getQueueCapacity()); + //活跃时间 + executor.setKeepAliveSeconds(config.getKeepAliveSeconds()); + //线程名字前缀 + executor.setThreadNamePrefix("el-async-"); + // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务 + // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + return executor; + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return (throwable, method, objects) -> { + log.error("===="+throwable.getMessage()+"====", throwable); + log.error("exception method:"+method.getName()); + }; + } +} diff --git a/backend/src/main/java/com/yfd/platform/config/thread/AsyncTaskProperties.java b/backend/src/main/java/com/yfd/platform/config/thread/AsyncTaskProperties.java new file mode 100644 index 0000000..a5bc7d2 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/thread/AsyncTaskProperties.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.config.thread; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 线程池配置属性类 + * @author https://juejin.im/entry/5abb8f6951882555677e9da2 + * @date 2019年10月31日14:58:18 + */ +@Data +@Component +@ConfigurationProperties(prefix = "task.pool") +public class AsyncTaskProperties { + + private int corePoolSize; + + private int maxPoolSize; + + private int keepAliveSeconds; + + private int queueCapacity; +} diff --git a/backend/src/main/java/com/yfd/platform/config/thread/TheadFactoryName.java b/backend/src/main/java/com/yfd/platform/config/thread/TheadFactoryName.java new file mode 100644 index 0000000..118faba --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/thread/TheadFactoryName.java @@ -0,0 +1,62 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.config.thread; + +import org.springframework.stereotype.Component; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 自定义线程名称 + * @author + * @date 2019年10月31日17:49:55 + */ +@Component +public class TheadFactoryName implements ThreadFactory { + + private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1); + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + + public TheadFactoryName() { + this("el-pool"); + } + + private TheadFactoryName(String name){ + // 使用当前线程的线程组,避免依赖已弃用的 SecurityManager + group = Thread.currentThread().getThreadGroup(); + //此时namePrefix就是 name + 第几个用这个工厂创建线程池的 + this.namePrefix = name + + POOL_NUMBER.getAndIncrement(); + } + + @Override + public Thread newThread(Runnable r) { + //此时线程的名字 就是 namePrefix + -thread- + 这个线程池中第几个执行的线程 + Thread t = new Thread(group, r, + namePrefix + "-thread-"+threadNumber.getAndIncrement(), + 0); + if (t.isDaemon()) { + t.setDaemon(false); + } + if (t.getPriority() != Thread.NORM_PRIORITY) { + t.setPriority(Thread.NORM_PRIORITY); + } + return t; + } +} diff --git a/backend/src/main/java/com/yfd/platform/config/thread/ThreadPoolExecutorUtil.java b/backend/src/main/java/com/yfd/platform/config/thread/ThreadPoolExecutorUtil.java new file mode 100644 index 0000000..cb84cc4 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/config/thread/ThreadPoolExecutorUtil.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.config.thread; + + + +import com.yfd.platform.utils.SpringContextHolder; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * 用于获取自定义线程池 + * @author + * @date 2019年10月31日18:16:47 + */ +public class ThreadPoolExecutorUtil { + + public static ThreadPoolExecutor getPoll(){ + AsyncTaskProperties properties = SpringContextHolder.getBean(AsyncTaskProperties.class); + return new ThreadPoolExecutor( + properties.getCorePoolSize(), + properties.getMaxPoolSize(), + properties.getKeepAliveSeconds(), + TimeUnit.SECONDS, + new ArrayBlockingQueue<>(properties.getQueueCapacity()), + new TheadFactoryName() + ); + } +} diff --git a/backend/src/main/java/com/yfd/platform/constant/Constant.java b/backend/src/main/java/com/yfd/platform/constant/Constant.java new file mode 100644 index 0000000..c460999 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/constant/Constant.java @@ -0,0 +1,42 @@ +package com.yfd.platform.constant; + +/** + * @author TangWei + * @Date: 2023/3/3 17:40 + * @Description: 常量类 + */ +public class Constant { + + public static final String LOGIN = "login:"; + public static final String TOKEN = "token:"; + public static final String USER_ID = "userid"; + public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; + + public static final String CODE_KEY = "code-key-"; + public static final long CODE_EXPIRATION_TIME = 1000 * 60; + /** + * 用于IP定位转换 + */ + public static final String REGION = "内网IP|内网IP"; + /** + * win 系统 + */ + public static final String WIN = "win"; + + /** + * mac 系统 + */ + public static final String MAC = "mac"; + + /** + * 常用接口 + */ + public static class Url { + + // IP归属地查询 + // public static final String IP_URL = "http://whois.pconline.com + // .cn/ipJson.jsp?ip=%s&json=true"; + public static final String IP_URL = "http://whois.pconline.com" + + ".cn/ipJson.jsp?ip=%s&json=true"; + } +} diff --git a/backend/src/main/java/com/yfd/platform/datasource/DataSource.java b/backend/src/main/java/com/yfd/platform/datasource/DataSource.java new file mode 100644 index 0000000..7c6d795 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/datasource/DataSource.java @@ -0,0 +1,17 @@ +package com.yfd.platform.datasource; + +import java.lang.annotation.*; + +/****************************** + * 用途说明: + * 作者姓名: wxy + * 创建时间: 2022/9/23 17:48 + ******************************/ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataSource { + + String name() default ""; + +} diff --git a/backend/src/main/java/com/yfd/platform/datasource/DataSourceAspect.java b/backend/src/main/java/com/yfd/platform/datasource/DataSourceAspect.java new file mode 100644 index 0000000..f20c0f8 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/datasource/DataSourceAspect.java @@ -0,0 +1,55 @@ +package com.yfd.platform.datasource; + +import cn.hutool.core.util.StrUtil; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; + +/****************************** + * 用途说明: + * 作者姓名: wxy + * 创建时间: 2022/9/23 17:50 + ******************************/ +@Aspect +@Component +public class DataSourceAspect { + + @Pointcut("@annotation(com.yfd.platform.datasource.DataSource)") + public void dataSourcePointCut() { + + } + + private String DataBaseName; + + @Around("dataSourcePointCut()") + public Object around(ProceedingJoinPoint point) throws Throwable { + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + if (StrUtil.isNotBlank(DataBaseName)){ + DynamicDataSource.setDataSource(DataBaseName); + }else { + DynamicDataSource.setDataSource("master"); + } + + try { + return point.proceed(); + } finally { + DynamicDataSource.clearDataSource(); + } + } + + public String getDataBase(Integer type){ + if (type == 1){ + DataBaseName="master"; + }else { + DataBaseName="slave"; + } + return DataBaseName; + } + +} diff --git a/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSource.java b/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSource.java new file mode 100644 index 0000000..8b52521 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSource.java @@ -0,0 +1,40 @@ +package com.yfd.platform.datasource; + +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +import javax.sql.DataSource; +import java.util.Map; + +/****************************** + * 用途说明: + * 作者姓名: wxy + * 创建时间: 2022/9/23 17:47 + ******************************/ +public class DynamicDataSource extends AbstractRoutingDataSource { + private static final ThreadLocal contextHolder = new ThreadLocal<>(); + + public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) { + super.setDefaultTargetDataSource(defaultTargetDataSource); + super.setTargetDataSources(targetDataSources); + super.afterPropertiesSet(); + } + + @Override + protected Object determineCurrentLookupKey() { + return getDataSource(); + } + + public static void setDataSource(String dataSource) { + contextHolder.set(dataSource); + } + + public static String getDataSource() { + return contextHolder.get(); + } + + public static void clearDataSource() { + contextHolder.remove(); + } + + +} diff --git a/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSourceConfig.java b/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSourceConfig.java new file mode 100644 index 0000000..f559443 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSourceConfig.java @@ -0,0 +1,51 @@ +package com.yfd.platform.datasource; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; +import com.alibaba.druid.pool.DruidDataSource; + +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.Map; + +/****************************** + * 用途说明: + * 作者姓名: wxy + * 创建时间: 2022/9/23 17:45 + ******************************/ +@Configuration +@Component +public class DynamicDataSourceConfig { + + @Bean + @ConfigurationProperties("spring.datasource.druid.master") + public DataSource wglMasterDataSource(){ + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setBreakAfterAcquireFailure(true); + dataSource.setConnectionErrorRetryAttempts(0); + return dataSource; + } + + @Bean + @ConfigurationProperties("spring.datasource.druid.slave") + public DataSource wglSlaveDataSource(){ + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setBreakAfterAcquireFailure(true); + dataSource.setConnectionErrorRetryAttempts(0); + return dataSource; + } + + @Bean + @Primary + public DynamicDataSource dataSource(DataSource wglMasterDataSource, DataSource wglSlaveDataSource) { + Map targetDataSources = new HashMap<>(); + targetDataSources.put("master",wglMasterDataSource); + targetDataSources.put("slave",wglSlaveDataSource); + return new DynamicDataSource(wglMasterDataSource, targetDataSources); + } + + +} diff --git a/backend/src/main/java/com/yfd/platform/exception/AccessDeniedHandExcetion.java b/backend/src/main/java/com/yfd/platform/exception/AccessDeniedHandExcetion.java new file mode 100644 index 0000000..0bb0ed3 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/exception/AccessDeniedHandExcetion.java @@ -0,0 +1,27 @@ +package com.yfd.platform.exception; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +@Component +public class AccessDeniedHandExcetion implements AccessDeniedHandler { + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { + JSONObject jobj=new JSONObject(); + jobj.putOnce("status","403"); + jobj.putOnce("msg","用户权限不足,不能访问"); + response.setStatus(200); + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + response.getWriter().println(JSONUtil.toJsonStr(jobj)); + } +} diff --git a/backend/src/main/java/com/yfd/platform/exception/AuthenticationException.java b/backend/src/main/java/com/yfd/platform/exception/AuthenticationException.java new file mode 100644 index 0000000..15e8e97 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/exception/AuthenticationException.java @@ -0,0 +1,32 @@ +package com.yfd.platform.exception; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.yfd.platform.config.ResponseResult; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +@Component +public class AuthenticationException implements AuthenticationEntryPoint { + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, org.springframework.security.core.AuthenticationException authException) throws IOException, ServletException { + JSONObject jobj=new JSONObject(); + if(authException.getMessage().equals("用户账号不存在!")){ + jobj.putOnce("code","401"); + jobj.putOnce("msg","用户账号不存在/密码错误,登录失败!"); + }else{ + jobj.putOnce("code","401"); + jobj.putOnce("msg","用户Token失效,请重新登录!"); + } + response.setStatus(200); + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + response.getWriter().println(JSONUtil.toJsonStr(jobj)); + } +} diff --git a/backend/src/main/java/com/yfd/platform/exception/BadConfigurationException.java b/backend/src/main/java/com/yfd/platform/exception/BadConfigurationException.java new file mode 100644 index 0000000..92ffac0 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/exception/BadConfigurationException.java @@ -0,0 +1,98 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.exception; + +/** + * 统一关于错误配置信息 异常 + * + * @author: liaojinlong + * @date: 2020/6/10 18:06 + */ +public class BadConfigurationException extends RuntimeException { + /** + * Constructs a new runtime exception with {@code null} as its + * detail message. The cause is not initialized, and may subsequently be + * initialized by a call to {@link #initCause}. + */ + public BadConfigurationException() { + super(); + } + + /** + * Constructs a new runtime exception with the specified detail message. + * The cause is not initialized, and may subsequently be initialized by a + * call to {@link #initCause}. + * + * @param message the detail message. The detail message is saved for + * later retrieval by the {@link #getMessage()} method. + */ + public BadConfigurationException(String message) { + super(message); + } + + /** + * Constructs a new runtime exception with the specified detail message and + * cause.

Note that the detail message associated with + * {@code cause} is not automatically incorporated in + * this runtime exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A {@code null} value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public BadConfigurationException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new runtime exception with the specified cause and a + * detail message of {@code (cause==null ? null : cause.toString())} + * (which typically contains the class and detail message of + * {@code cause}). This constructor is useful for runtime exceptions + * that are little more than wrappers for other throwables. + * + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A {@code null} value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public BadConfigurationException(Throwable cause) { + super(cause); + } + + /** + * Constructs a new runtime exception with the specified detail + * message, cause, suppression enabled or disabled, and writable + * stack trace enabled or disabled. + * + * @param message the detail message. + * @param cause the cause. (A {@code null} value is permitted, + * and indicates that the cause is nonexistent or unknown.) + * @param enableSuppression whether or not suppression is enabled + * or disabled + * @param writableStackTrace whether or not the stack trace should + * be writable + * @since 1.7 + */ + protected BadConfigurationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/backend/src/main/java/com/yfd/platform/exception/BadRequestException.java b/backend/src/main/java/com/yfd/platform/exception/BadRequestException.java new file mode 100644 index 0000000..f2202ec --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/exception/BadRequestException.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +/** + * @author + * @date 2018-11-23 + * 统一异常处理 + */ +@Getter +public class BadRequestException extends RuntimeException{ + + private Integer status = BAD_REQUEST.value(); + + public BadRequestException(String msg){ + super(msg); + } + + public BadRequestException(HttpStatus status, String msg){ + super(msg); + this.status = status.value(); + } +} diff --git a/backend/src/main/java/com/yfd/platform/exception/ChildrenExistException.java b/backend/src/main/java/com/yfd/platform/exception/ChildrenExistException.java new file mode 100644 index 0000000..18eca34 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/exception/ChildrenExistException.java @@ -0,0 +1,20 @@ +package com.yfd.platform.exception; + +import org.springframework.util.StringUtils; + +/** + * @Author pcj + * @Date 2021/1/26 9:07 + * @Version 1.0 + */ +public class ChildrenExistException extends RuntimeException{ + + public ChildrenExistException(Class clazz, String field, String val) { + super(ChildrenExistException.generateMessage(clazz.getSimpleName(), field, val)); + } + + private static String generateMessage(String entity, String field, String val) { + return StringUtils.capitalize(entity) + + " with " + field + " "+ val + " Children Exist"; + } +} diff --git a/backend/src/main/java/com/yfd/platform/exception/EntityExistException.java b/backend/src/main/java/com/yfd/platform/exception/EntityExistException.java new file mode 100644 index 0000000..028aeed --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/exception/EntityExistException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.exception; + +import org.springframework.util.StringUtils; + +/** + * @author + * @date 2018-11-23 + */ +public class EntityExistException extends RuntimeException { + + public EntityExistException(Class clazz, String field, String val) { + super(EntityExistException.generateMessage(clazz.getSimpleName(), field, val)); + } + + private static String generateMessage(String entity, String field, String val) { + return StringUtils.capitalize(entity) + + " with " + field + " "+ val + " existed"; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/yfd/platform/exception/EntityNotFoundException.java b/backend/src/main/java/com/yfd/platform/exception/EntityNotFoundException.java new file mode 100644 index 0000000..8f5e1c5 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/exception/EntityNotFoundException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.exception; + +import org.springframework.util.StringUtils; + +/** + * @author + * @date 2018-11-23 + */ +public class EntityNotFoundException extends RuntimeException { + + public EntityNotFoundException(Class clazz, String field, String val) { + super(EntityNotFoundException.generateMessage(clazz.getSimpleName(), field, val)); + } + + private static String generateMessage(String entity, String field, String val) { + return StringUtils.capitalize(entity) + + " with " + field + " "+ val + " does not exist"; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/yfd/platform/system/controller/DataSourceController.java b/backend/src/main/java/com/yfd/platform/system/controller/DataSourceController.java new file mode 100644 index 0000000..b34b37c --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/controller/DataSourceController.java @@ -0,0 +1,42 @@ +package com.yfd.platform.system.controller; + +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.datasource.DataSource; +import com.yfd.platform.datasource.DataSourceAspect; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; + +/** + * @author zhengsl + * @since 2022-09-20 + */ +@RestController +@RequestMapping("/system") +@Tag(name = "切换数据库") +public class DataSourceController { + + @Resource + DataSourceAspect dataSourceAspect; + + /** + * 切换数据库 + * + * @DataSource(name="master") 可以通过注解方式切换数据库 + */ + @GetMapping("/changeDataSource") + @Operation(summary = "切换数据库") + public ResponseResult changeDataSource(Integer type) { + if (type == null) { + return ResponseResult.error("参数为空"); + } + String dataBase = dataSourceAspect.getDataBase(type); + String mess = "已切换为" + dataBase + "数据库"; + return ResponseResult.success(mess); + } + +} diff --git a/backend/src/main/java/com/yfd/platform/system/controller/LoginController.java b/backend/src/main/java/com/yfd/platform/system/controller/LoginController.java new file mode 100644 index 0000000..ac08378 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/controller/LoginController.java @@ -0,0 +1,238 @@ +package com.yfd.platform.system.controller; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import cn.hutool.jwt.JWTUtil; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.wf.captcha.base.Captcha; +import com.yfd.platform.annotation.Log; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.config.WebConfig; +import com.yfd.platform.config.bean.LoginCodeEnum; +import com.yfd.platform.config.bean.LoginProperties; +import com.yfd.platform.constant.Constant; +import com.yfd.platform.system.domain.LoginUser; +import com.yfd.platform.system.domain.SysLog; +import com.yfd.platform.system.domain.SysUser; +import com.yfd.platform.system.service.ISysLogService; +import com.yfd.platform.system.service.IUserService; +import com.yfd.platform.utils.RequestHolder; +import com.yfd.platform.utils.RsaUtils; +import com.yfd.platform.utils.StringUtils; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import java.sql.Timestamp; +import java.util.HashMap; +import java.util.Map; + +/** + * @author TangWei + */ +@RestController +@RequestMapping("/user") +@Tag(name = "用户登录") +public class LoginController { + + @Autowired + private AuthenticationManager authenticationManager; + + @Autowired + private WebConfig webConfig; + + @Resource + private IUserService userService; + + @Value("${rsa.private_key}") + private String privateKey; + + @Resource + private ISysLogService sysLogService; + + @Resource + private LoginProperties loginProperties; + + @PostMapping("/login") + @Operation(summary = "登录用户") + @ResponseBody + public ResponseResult login(SysUser user) throws Exception { + // 密码解密 + String password = RsaUtils.decryptByPrivateKey(privateKey, + user.getPassword()); + + // 是否需要验证码不需要改成false + boolean hascode = true; + if (hascode) { + // 查询验证码 + String code = webConfig.loginuserCache().get(user.getUuid()); + // 清除验证码 + webConfig.loginuserCache().remove(user.getUuid()); + if (StrUtil.isBlank(code)) { + return ResponseResult.error("验证码不存在或已过期"); + } + if (StrUtil.isBlank(user.getCode()) || !user.getCode().equalsIgnoreCase(code)) { + return ResponseResult.error("验证码错误"); + } + } + //如果认证通过了,使用userId生成token token存入ResponseResult返回 + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken(user.getUsername(), + password); + Authentication authenticate = + authenticationManager.authenticate(authenticationToken); + if (ObjectUtil.isNull(authenticate)) { + return ResponseResult.unlogin(); + } + LoginUser loginUser = (LoginUser) authenticate.getPrincipal(); + Integer status = loginUser.getUser().getStatus(); + if ("0".equals(status.toString())) { + return ResponseResult.error("账号已停用"); + } + HttpServletRequest request = RequestHolder.getHttpServletRequest(); + SysLog sysLog = new SysLog(); + sysLog.setUsercode(user.getUsername()); + sysLog.setUsername(loginUser.getUser().getNickname()); + sysLog.setRequestip(StringUtils.getIp(request)); + sysLog.setBrowser(StringUtils.getBrowser(request)); + sysLog.setOpttype("登录(login)"); + sysLog.setModule("用户登录"); + String className = this.getClass().getName(); + String method = + Thread.currentThread().getStackTrace()[1].getMethodName(); + sysLog.setMethod(className + "." + method + "()"); + //sysLog.setParams(user.toString()); + sysLog.setDescription(loginUser.getUser().getNickname() + "登录系统!"); + sysLog.setLogtime(new Timestamp(System.currentTimeMillis())); + sysLogService.save(sysLog); + String userId = loginUser.getUser().getId(); + Map map = new HashMap(10) { + private static final long serialVersionUID = 1L; + + { + put("userid", userId); + put("username", loginUser.getUsername()); + long expireTime = + System.currentTimeMillis() + (long) (30L * 24L * 60L * 60L * 1000L); + put("expire_time", expireTime);//个月过期 + } + }; + + String token = JWTUtil.createToken(map, "12345678".getBytes()); + map.put("token", token); + //把完整的用户信息存入到HuTool缓存中,userId作为key + String jsonStr = JSONUtil.toJsonStr(loginUser); + webConfig.loginuserCache().put("login:" + userId, jsonStr); + webConfig.loginuserCache().put("expire_time:" + userId, map.get("expire_time").toString()); + return ResponseResult.successData(map); + } + + @Operation(summary = "获取验证码") + @GetMapping(value = "/code") + public ResponseResult getCode() { + // 获取运算的结果 + Captcha captcha = loginProperties.getCaptcha(); + String uuid = Constant.CODE_KEY + IdUtil.simpleUUID(); + //当验证码类型为 arithmetic时且长度 >= 2 时,captcha.text()的结果有几率为浮点型 + String captchaValue = captcha.text(); + if (captcha.getCharType() - 1 == LoginCodeEnum.arithmetic.ordinal() && captchaValue.contains(".")) { + captchaValue = captchaValue.split("\\.")[0]; + } + // 保存 + //redisUtils.set(uuid, captchaValue, loginProperties.getLoginCode() + // .getExpiration(), TimeUnit.MINUTES); + // 将验证码放入缓存,设置失效时间为60秒 + webConfig.loginuserCache().put(uuid, captchaValue, + Constant.CODE_EXPIRATION_TIME); + // 验证码信息 + Map imgResult = new HashMap(2) {{ + put("img", captcha.toBase64()); + put("uuid", uuid); + }}; + return ResponseResult.successData(imgResult); + } + + @PostMapping("/logout") + @Operation(summary = "退出登录") + @ResponseBody + public ResponseResult logout() { + //获取SecurityContextHolder中的用户id + UsernamePasswordAuthenticationToken authentication = + (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); + LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + String userId = loginuser.getUser().getId(); + //删除redis中的登陆用户信息 + webConfig.loginuserCache().remove("login:" + userId); + //记录退出日志 + HttpServletRequest request = RequestHolder.getHttpServletRequest(); + SysLog sysLog = new SysLog(); + sysLog.setUsercode(loginuser.getUsername()); + sysLog.setUsername(loginuser.getUser().getNickname()); + sysLog.setRequestip(StringUtils.getIp(request)); + sysLog.setBrowser(StringUtils.getBrowser(request)); + sysLog.setOpttype("其他(other)"); + sysLog.setModule("注销退出"); + sysLog.setDescription("注销退出系统!"); + sysLog.setLogtime(new Timestamp(System.currentTimeMillis())); + sysLogService.save(sysLog); + return ResponseResult.success(); + } + + @Log(module = "用户登录", value = "更改用户密码") + @GetMapping("/updatePassword") + @Operation(summary = "更改用户密码") + @ResponseBody + public ResponseResult updatePassword(@RequestBody SysUser user) throws Exception { + // 密码解密 + String password = RsaUtils.decryptByPrivateKey(privateKey, + user.getPassword()); + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + String cryptPassword = passwordEncoder.encode(password); + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.set("password", cryptPassword); + updateWrapper.eq("id", user.getId()); + userService.update(updateWrapper); + return ResponseResult.success(); + } + + @GetMapping("/me") + @Operation(summary = "查询当前用户信息") + @ResponseBody + public ResponseResult getUserInfo() { + ResponseResult responseResult = userService.getLoginUserInfo(); + return ResponseResult.successData(responseResult); + } + + @Log(module = "用户登录", value = "修改个人信息") + @PostMapping("/updatePersonalInfo") + @Operation(summary = "修改个人信息") + @ResponseBody + public ResponseResult updateUser(@org.springframework.web.bind.annotation.RequestBody SysUser user) { + if (StrUtil.isEmpty(user.getId())) { + return ResponseResult.error("没有用户ID"); + } + //填写 当前用户名称 + user.setLastmodifier(userService.getUsername()); + //填写 当前日期 + user.setLastmodifydate(new Timestamp(System.currentTimeMillis())); + boolean ok = userService.updateById(user); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + + } + +} diff --git a/backend/src/main/java/com/yfd/platform/system/controller/MessageController.java b/backend/src/main/java/com/yfd/platform/system/controller/MessageController.java new file mode 100644 index 0000000..58f61ba --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/controller/MessageController.java @@ -0,0 +1,150 @@ +package com.yfd.platform.system.controller; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.annotation.Log; +import com.yfd.platform.config.MessageConfig; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.config.WebConfig; +import com.yfd.platform.system.domain.Message; +import com.yfd.platform.system.service.IMessageService; +import com.yfd.platform.system.service.IUserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.sql.Timestamp; +import java.util.*; + +/** + *

+ * 消息通知 前端控制器 + *

+ * + * @author TangWei + * @since 2023-03-19 + */ +@RestController +@RequestMapping("/system/message") +@Tag(name = "消息通知") +public class MessageController { + + @Resource + private IMessageService messageService; + + @Resource + private MessageConfig messageConfig; + + @Operation(summary = "查询消息") + @GetMapping("/getMessageList") + public ResponseResult getMessageList(Page page, + String status, String title, + String type, String startDate, + String endDate) { + if (StrUtil.isBlank(status)) { + return ResponseResult.error("参数为空"); + } + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if ("0".equals(status)) { + queryWrapper.eq(Message::getStatus, "1"); + } else { + List statusList = new ArrayList<>(); + statusList.add("2"); + statusList.add("9"); + queryWrapper.in(Message::getStatus, statusList); + + if (StrUtil.isNotBlank(title)) { + queryWrapper.like(Message::getTitle, title); + } + + if (StrUtil.isNotBlank(type)) { + queryWrapper.eq(Message::getType, type); + } + + DateTime parseStartDate = DateUtil.parse(startDate); + DateTime parseEndDate = DateUtil.parse(endDate); + DateTime dateTime = DateUtil.offsetDay(parseEndDate, 1); + + if (parseStartDate != null && parseEndDate != null) { + queryWrapper.ge(Message::getCreatetime, parseStartDate).lt(Message::getCreatetime, dateTime); + } + } + queryWrapper.orderByDesc(Message::getCreatetime); + Page pageList = messageService.page(page, queryWrapper); + return ResponseResult.successData(pageList); + } + + @Operation(summary = "根据ID查询消息") + @GetMapping("/getMessageById") + public ResponseResult getMessageById(String id) { + if (StrUtil.isBlank(id)) { + return ResponseResult.error("参数为空"); + } + Message message = messageService.getById(id); + Map map = new HashMap<>(); + map.put("title", message.getTitle()); + map.put("content", message.getContent()); + map.put("createtime", message.getCreatetime()); + return ResponseResult.successData(map); + } + + @Log(module = "消息通知",value = "根据ID删除消息") + @Operation(summary = "根据ID删除消息") + @PostMapping("/deleteMessageById") + public ResponseResult deleteMessageById(@RequestParam String id) { + if (StrUtil.isBlank(id)) { + return ResponseResult.error("参数为空"); + } + String[] split = id.split(","); + List idList = Arrays.asList(split); + boolean ok = messageService.removeByIds(idList); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error("删除失败"); + } + + } + + @Log(module = "消息通知", value = "将消息标记为已阅状态") + @Operation(summary = "标记已阅") + @PostMapping("/setMessageStatus") + public ResponseResult setMessageStatus(@RequestParam String id) { + if (StrUtil.isBlank(id)) { + return ResponseResult.error("参数为空"); + } + String[] split = id.split(","); + long time = System.currentTimeMillis(); + for (String mid : split) { + Message message = messageService.getById(mid); + if ("9".equals(message.getStatus())) { + continue; + } + message.setStatus("2"); + message.setReadtime(new Timestamp(time)); + messageService.updateById(message); + } + messageConfig.sendMessage(); + return ResponseResult.success(); + } + + @Operation(summary = "全部已阅") + @PostMapping("/setAllMessageStatus") + public ResponseResult setAllMessageStatus() { + long time = System.currentTimeMillis(); + List list = + messageService.list(new LambdaQueryWrapper().eq(Message::getStatus, "1")); + for (Message message : list) { + message.setStatus("2"); + message.setReadtime(new Timestamp(time)); + messageService.updateById(message); + } + messageConfig.sendMessage(); + return ResponseResult.success(); + } + +} diff --git a/backend/src/main/java/com/yfd/platform/system/controller/QuartzJobController.java b/backend/src/main/java/com/yfd/platform/system/controller/QuartzJobController.java new file mode 100644 index 0000000..9da6c89 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/controller/QuartzJobController.java @@ -0,0 +1,183 @@ +package com.yfd.platform.system.controller; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.annotation.Log; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.system.domain.QuartzJob; +import com.yfd.platform.system.service.IQuartzJobService; +import com.yfd.platform.system.service.impl.UserServiceImpl; +import com.yfd.platform.utils.QuartzManage; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.quartz.CronExpression; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.sql.Timestamp; +import java.time.LocalDateTime; + +/** + *

+ * 定时任务 前端控制器 + *

+ * + * @author TangWei + * @since 2023-03-19 + */ +@RestController +@RequestMapping("/system/quartzjob") +@Tag(name = "定时任务") +@Transactional +public class QuartzJobController { + + @Resource + private IQuartzJobService quartzJobService; + + @Resource + private UserServiceImpl currentUser; + + @Resource + private QuartzManage quartzManage; + + @Operation(summary = "查询定时任务") + @GetMapping("/getQuartzJobList") + public ResponseResult getQuartzJobList(Page page, + String jobName) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (StrUtil.isNotBlank(jobName)) { + queryWrapper.like(QuartzJob::getJobName, jobName); + } + queryWrapper.orderByAsc(QuartzJob::getOrderno); + Page pageList = quartzJobService.page(page, queryWrapper); + return ResponseResult.successData(pageList); + } + + @Log(module = "定时任务管理", value = "新增定时任务") + @Operation(summary = "新增定时任务") + @PostMapping("/addQuartzJob") + public ResponseResult addQuartzJob(@RequestBody QuartzJob quartzJob) { + if (quartzJob == null) { + return ResponseResult.error("参数为空"); + } + // 添加最近修改人 + quartzJob.setLastmodifier(currentUser.getUsername()); + // 添加最近修改时间 + quartzJob.setLastmodifydate(new Timestamp(System.currentTimeMillis())); + if (StrUtil.isBlank(quartzJob.getJobCron()) || !CronExpression.isValidExpression(quartzJob.getJobCron())) { + return ResponseResult.error("cron表达式格式错误"); + } + quartzJob.setStatus("0"); + boolean ok = quartzJobService.addQuartzJob(quartzJob); + quartzManage.addJob(quartzJob); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error("新增失败"); + } + } + + @Log(module = "定时任务管理", value = "设置定时任务是否有效") + @Operation(summary = "设置定时任务是否有效") + @PostMapping("/setQuartzStatus") + public ResponseResult setQuartzStatus(@RequestParam String id, + @RequestParam String status) { + if (StrUtil.isBlank(id) || StrUtil.isBlank(status)) { + return ResponseResult.error("参数为空"); + } + LambdaUpdateWrapper updateWrapper = + new LambdaUpdateWrapper<>(); + //根据id 更新状态,最近修改人,最近修改时间 + updateWrapper.eq(QuartzJob::getId, id).set(QuartzJob::getStatus, + status).set( + QuartzJob::getLastmodifier, currentUser.getUsername()).set(QuartzJob::getLastmodifydate, + LocalDateTime.now()); + boolean ok = quartzJobService.update(updateWrapper); + QuartzJob quartzJob = quartzJobService.getById(id); + if ("0".equals(quartzJob.getStatus())) { + quartzManage.pauseJob(quartzJob); + } else { + quartzManage.resumeJob(quartzJob); + } + + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + @Operation(summary = "根据ID查询定时任务") + @GetMapping("/getQuartzJobById") + public ResponseResult getQuartzJobById(String id) { + QuartzJob quartzJob = quartzJobService.getById(id); + return ResponseResult.successData(quartzJob); + } + + @Log(module = "定时任务管理", value = "修改定时任务") + @Operation(summary = "修改定时任务") + @PostMapping("/updateQuartzJob") + @Transactional(rollbackFor = Exception.class) + public ResponseResult updateQuartzJob(@RequestBody QuartzJob quartzJob) { + // 添加最近修改人 + quartzJob.setLastmodifier(currentUser.getUsername()); + // 添加最近修改时间 + quartzJob.setLastmodifydate(new Timestamp(System.currentTimeMillis())); + if (StrUtil.isBlank(quartzJob.getJobCron()) || !CronExpression.isValidExpression(quartzJob.getJobCron())) { + return ResponseResult.error("cron表达式格式错误"); + } + boolean ok = quartzJobService.updateById(quartzJob); + quartzManage.updateJobCron(quartzJob); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error("修改失败"); + } + } + + @Log(module = "定时任务管理", value = "删除定时任务") + @Operation(summary = "删除定时任务") + @PostMapping("/deleteQuartzJob") + public ResponseResult deleteQuartzJob(@RequestParam String id) { + if (StrUtil.isBlank(id)) { + return ResponseResult.error("参数为空"); + } + boolean ok = quartzJobService.deleteQuartzJob(id); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error("删除失败"); + } + } + + @Log(module = "定时任务管理", value = "执行定时任务") + @Operation(summary = "执行定时任务") + @PostMapping("/execution") + public ResponseResult execution(@RequestParam String id) { + quartzJobService.execution(quartzJobService.getById(id)); + return ResponseResult.success(); + } + + /********************************** + * 用途说明: 拖动修改定时顺序 + * 参数说明 fromID 当前ID toID 到达ID + * 返回值说明: com.yfd.platform.config.ResponseResult 成功或者失败 + ***********************************/ + @Log(module = "定时任务管理", value = "拖动定时任务") + @PostMapping("/changeDictOrder") + @Operation(summary = "拖动修改定时任务顺序") + public ResponseResult changeQuartzOrder(@RequestParam String fromID, + @RequestParam String toID) { + + boolean ok = quartzJobService.changeDictOrder(fromID, toID); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + + } +} diff --git a/backend/src/main/java/com/yfd/platform/system/controller/SSEController.java b/backend/src/main/java/com/yfd/platform/system/controller/SSEController.java new file mode 100644 index 0000000..41adfbb --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/controller/SSEController.java @@ -0,0 +1,59 @@ +package com.yfd.platform.system.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.yfd.platform.component.ServerSendEventServer; +import com.yfd.platform.config.WebConfig; +import com.yfd.platform.constant.Constant; +import com.yfd.platform.system.domain.Message; +import com.yfd.platform.system.service.IMessageService; +import com.yfd.platform.system.service.IUserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import jakarta.annotation.Resource; + +/** + * @author Huhailong + */ +@Slf4j +@RestController +@CrossOrigin +@RequestMapping("/sse") +@Tag(name = "SSE推送服务") +public class SSEController { + + @Resource + private IMessageService messageService; + + @GetMapping("/connect/{token}") + @Operation(summary = "建立连接") + public SseEmitter connect(@PathVariable String token) { + SseEmitter connect = ServerSendEventServer.connect(token); + long count = + messageService.count(new LambdaQueryWrapper().eq(Message::getStatus, "1")); + ServerSendEventServer.sendMessage(token, count + ""); + return connect; + } + + @GetMapping("/sendmsg") + @Operation(summary = "发送消息") + public void sendMessage(String token, String message) throws InterruptedException { + + ServerSendEventServer.sendMessage(token, message); + } + + @GetMapping("/sendgroupmsg") + @Operation(summary = "多人发送消息") + public void sendgroupmsg(String groupid, String message) throws InterruptedException { + ServerSendEventServer.groupSendMessage(groupid, message); + } + + @GetMapping("/disconnect/{token}") + @Operation(summary = "关闭连接") + public void disconnect(@PathVariable String token) throws InterruptedException { + ServerSendEventServer.removeUser(token); + } +} diff --git a/backend/src/main/java/com/yfd/platform/system/controller/SysConfigController.java b/backend/src/main/java/com/yfd/platform/system/controller/SysConfigController.java new file mode 100644 index 0000000..3cfa7cc --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/controller/SysConfigController.java @@ -0,0 +1,68 @@ +package com.yfd.platform.system.controller; + + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.system.domain.SysConfig; +import com.yfd.platform.system.service.ISysConfigService; +import com.yfd.platform.system.service.IUserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import javax.sound.sampled.UnsupportedAudioFileException; +import java.io.IOException; +import java.sql.Timestamp; + + +/** + *

+ * 系统全局配置 前端控制器 + *

+ * + * @author zhengsl + * @since 2022-01-19 + */ +@RestController + @RequestMapping("/system/config") +@Tag(name = "系统全局配置") +public class SysConfigController { + @Resource + private ISysConfigService configService; + + @Resource + private IUserService userService; + + @PostMapping("/getOneById") + @Operation(summary = "根据id查询全局配置详情记录") + @ResponseBody + public SysConfig getOneById(String id){ + return configService.getById(id); + } + + @PostMapping("/addConfig") + @Operation(summary = "根据id查询全局配置详情记录") + @ResponseBody + public ResponseResult addConfig(@RequestBody SysConfig config ) throws IOException, UnsupportedAudioFileException { + if (StrUtil.isEmpty(config.getId())){ + config.setId(IdUtil.fastSimpleUUID()); } + config.setLastmodifier(userService.getUsername()); + config.setLastmodifydate(new Timestamp(System.currentTimeMillis())); + boolean ok=configService.save(config); + return ResponseResult.success(); + } + + @PostMapping("/updateById") + @Operation(summary = "根据id修改全局配置记录") + @ResponseBody + public ResponseResult updateById(@RequestBody SysConfig config) throws IOException, UnsupportedAudioFileException { + config.setLastmodifier(userService.getUsername()); + config.setLastmodifydate(new Timestamp(System.currentTimeMillis())); + boolean ok=configService.updateById(config); + return ResponseResult.success(); + } + + +} diff --git a/backend/src/main/java/com/yfd/platform/system/controller/SysDictionaryController.java b/backend/src/main/java/com/yfd/platform/system/controller/SysDictionaryController.java new file mode 100644 index 0000000..70a5fdf --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/controller/SysDictionaryController.java @@ -0,0 +1,142 @@ +package com.yfd.platform.system.controller; + +import cn.hutool.core.util.StrUtil; +import com.yfd.platform.annotation.Log; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.system.domain.SysDictionary; +import com.yfd.platform.system.mapper.SysDictionaryItemsMapper; +import com.yfd.platform.system.service.ISysDictionaryService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.util.List; +import java.util.Objects; + +/** + *

+ * 数据字典表 前端控制器 + *

+ * + * @author TangWei + * @since 2023-03-08 + */ +@RestController +@RequestMapping("/system/dictionary") +@Tag(name = "数据字典") +public class SysDictionaryController { + + @Resource + private ISysDictionaryService sysDictionaryService; + + /********************************** + * 用途说明: 获取数据字典列表 + * 参数说明 dictType 字典类型 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + @GetMapping("/dictList") + @Operation(summary = "获取数据字典列表") + public ResponseResult getDictList(String dictType) { + if (StrUtil.isBlank(dictType)) { + return ResponseResult.error("参数为空"); + } + List sysDictionaries = + sysDictionaryService.getDictList(dictType); + return ResponseResult.successData(sysDictionaries); + } + + /********************************** + * 用途说明: 根据ID删除字典 + * 参数说明 id 字典ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除结果成功或者失败 + ***********************************/ + @Log(module = "数据字典", value = "根据ID删除字典") + @PostMapping("/deleteById") + @Operation(summary = "根据ID删除字典") + public ResponseResult deleteDictById(@RequestParam String id) { + boolean ok = sysDictionaryService.deleteDictById(id); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /********************************** + * 用途说明: 新增字典 + * 参数说明 sysDictionary 字典对象 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回增加成功或者失败 + ***********************************/ + @Log(module = "数据字典", value = "新增数据字典") + @PostMapping("/addDict") + @Operation(summary = "新增字典") + public ResponseResult addDict(@RequestBody SysDictionary sysDictionary) { + if (sysDictionary == null) { + return ResponseResult.error("参数为空"); + } + boolean ok = sysDictionaryService.addDict(sysDictionary); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /********************************** + * 用途说明: 修改字典 + * 参数说明 sysDictionary 字典对象 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + @Log(module = "数据字典", value = "修改数据字典") + @PostMapping("/updateDict") + @Operation(summary = "修改字典") + public ResponseResult updateDict(@RequestBody SysDictionary sysDictionary) { + if (sysDictionary == null) { + return ResponseResult.error("参数为空"); + } + boolean ok = sysDictionaryService.updateById(sysDictionary); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /********************************** + * 用途说明: 根据ID查询字典 + * 参数说明 sysDictionary 字典对象 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回查询结果 + ***********************************/ + @PostMapping("/getDictById") + @Operation(summary = "根据ID查询字典") + public ResponseResult getDictById(String id) { + if (StrUtil.isBlank(id)) { + return ResponseResult.error("参数为空"); + } + SysDictionary sysDictionary = sysDictionaryService.getById(id); + return ResponseResult.successData(sysDictionary); + } + + /********************************** + * 用途说明: 拖动修改字典顺序 + * 参数说明 fromID 当前ID toID 到达ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回拖动成功或者失败 + ***********************************/ + @Log(module = "数据字典", value = "拖动修改字典顺序") + @PostMapping("/changeDictOrder") + @Operation(summary = "拖动修改字典顺序") + public ResponseResult changeDictOrder(@RequestParam String fromID, + @RequestParam String toID) { + + boolean ok = sysDictionaryService.changeDictOrder(fromID, toID); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + + } + +} diff --git a/backend/src/main/java/com/yfd/platform/system/controller/SysDictionaryItemsController.java b/backend/src/main/java/com/yfd/platform/system/controller/SysDictionaryItemsController.java new file mode 100644 index 0000000..bf85d30 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/controller/SysDictionaryItemsController.java @@ -0,0 +1,201 @@ +package com.yfd.platform.system.controller; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.annotation.Log; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.system.domain.SysDictionaryItems; +import com.yfd.platform.system.mapper.SysDictionaryItemsMapper; +import com.yfd.platform.system.service.ISysDictionaryItemsService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import java.util.Arrays; +import java.util.List; + +/** + *

+ * 数据字典明细 前端控制器 + *

+ * + * @author TangWei + * @since 2023-03-08 + */ +@RestController +@RequestMapping("/system/dictionaryItems") +@Tag(name = "数据字典项") +public class SysDictionaryItemsController { + + @Resource + private ISysDictionaryItemsService sysDictionaryItemsService; + + /********************************** + * 用途说明: 分页查询字典项信息 + * 参数说明 dictID 字典ID ItemName 字典项名称 pageNum 当前页 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + @GetMapping("/page") + @Operation(summary = "分页查询字典项信息") + public ResponseResult getDictItemPage(String dictId, String dictName, + Page page) { + + LambdaQueryWrapper queryWrapper = + new LambdaQueryWrapper<>(); + queryWrapper.eq(SysDictionaryItems::getDictId, dictId).orderByAsc(SysDictionaryItems::getOrderNo); + + // 查询前将序号初始化 + List list = + sysDictionaryItemsService.list(queryWrapper); + for (int i = 0; i < list.size(); i++) { + SysDictionaryItems sysDictionaryItems = list.get(i); + sysDictionaryItems.setOrderNo(i + 1); + sysDictionaryItemsService.updateById(sysDictionaryItems); + } + Page sysDictionaryItemsPage = + sysDictionaryItemsService.getDictItemPage(dictId, dictName, + page); + + return ResponseResult.successData(sysDictionaryItemsPage); + } + + /********************************** + * 用途说明: 增加字典项 + * 参数说明 sysDictionaryItems 字典项信息 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回增加成功或者失败 + ***********************************/ + @Log(module = "数据字典项", value = "增加字典项") + @PostMapping("/addDictionaryItem") + @Operation(summary = "增加字典项") + public ResponseResult addDictionaryItem(@RequestBody SysDictionaryItems sysDictionaryItems) { + if (sysDictionaryItems == null) { + return ResponseResult.error("参数为空"); + } + boolean ok = + sysDictionaryItemsService.addDictionaryItem(sysDictionaryItems); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /********************************** + * 用途说明: 修改字典项 + * 参数说明 sysDictionaryItems 字典项信息 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + @Log(module = "数据字典项", value = "修改字典项") + @PostMapping("/updateDictionaryItem") + @Operation(summary = "修改字典项") + public ResponseResult updateDictionaryItem(@RequestBody SysDictionaryItems sysDictionaryItems) { + if (sysDictionaryItems == null) { + return ResponseResult.error("参数为空"); + } + boolean ok = + sysDictionaryItemsService.updateById(sysDictionaryItems); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /********************************** + * 用途说明: 根据ID查询字典项 + * 参数说明 id 字典项ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回字典项信息 + ***********************************/ + @GetMapping("/getDictItemById") + @Operation(summary = "根据ID查询字典项") + public ResponseResult getDictItemById(String id) { + if (StrUtil.isBlank(id)) { + return ResponseResult.error("参数为空"); + } + SysDictionaryItems sysDictionaryItems = + sysDictionaryItemsService.getById(id); + return ResponseResult.successData(sysDictionaryItems); + } + + /********************************** + * 用途说明: 根据ID删除字典项 + * 参数说明 id 字典项ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 + ***********************************/ + @Log(module = "数据字典项", value = "根据ID删除字典项") + @PostMapping("/deleteDictItemById") + @Operation(summary = "根据ID删除字典项") + public ResponseResult deleteDictItemById(@RequestParam String id) { + if (StrUtil.isBlank(id)) { + return ResponseResult.error("参数为空"); + } + boolean ok = sysDictionaryItemsService.removeById(id); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /********************************** + * 用途说明: 批量删除字典项 + * 参数说明 ids 字典项id数组 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回批量删除成功或失败 + ***********************************/ + @Log(module = "数据字典项", value = "批量删除字典项") + @PostMapping("/deleteDictItemByIds") + @Operation(summary = "批量删除字典项") + public ResponseResult deleteDictItemByIds(@RequestParam String id) { + if (StrUtil.isBlank(id)) { + return ResponseResult.error("参数为空"); + } + String[] splitIds = id.split(","); + // 数组转集合 + List ids = Arrays.asList(splitIds); + boolean ok = sysDictionaryItemsService.removeByIds(ids); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /********************************** + * 用途说明: 拖动修改字典项顺序 + * 参数说明 fromID 当前ID toID 到达ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回拖动成功或者失败 + ***********************************/ + @Log(module = "数据字典项", value = "拖动修改字典项顺序") + @PostMapping("/changeItemOrder") + @Operation(summary = "拖动修改字典项顺序") + public ResponseResult changeItemOrder(@RequestParam String fromID, + @RequestParam String toID) { + boolean ok = sysDictionaryItemsService.changeItemOrder(fromID, toID); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /********************************** + * 用途说明: 导出数据字典项数据 + * 参数说明 sysDictionaryItemsList 所需导出的字典项集合 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回导出成功或失败 + ***********************************/ + @Log(module = "数据字典项", value = "导出字典数据到Excel") + @GetMapping("/exportExcel") + @Operation(summary = "导出数据字典项数据") + public void exportExcel(String dictID, String itemName, + Page page, + HttpServletResponse response) { + Page sysDictionaryItemsPage = + sysDictionaryItemsService.getDictItemPage(dictID, itemName, + page); + sysDictionaryItemsService.exportExcel(sysDictionaryItemsPage.getRecords(), response); + } + +} diff --git a/backend/src/main/java/com/yfd/platform/system/controller/SysLogController.java b/backend/src/main/java/com/yfd/platform/system/controller/SysLogController.java new file mode 100644 index 0000000..9f2081a --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/controller/SysLogController.java @@ -0,0 +1,74 @@ +package com.yfd.platform.system.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.annotation.Log; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.system.domain.SysLog; +import com.yfd.platform.system.service.ISysLogService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

+ * 系统操作日志 前端控制器 + *

+ * + * @author TangWei + * @since 2023-03-08 + */ +@RestController +@RequestMapping("/system/log") +@Tag(name = "系统日志") +public class SysLogController { + + @Resource + private ISysLogService sysLogService; + + /********************************** + * 用途说明: 分页查询日志信息 + * 参数说明 page分页对象、username(用户名)、(optType) + * 操作类型、startDate(开始日期)、endDate(结束日期) + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + @PostMapping("/getLogList") + @Operation(summary = "分页查询日志信息") + public ResponseResult getLogList(String username, String optType, + String startDate, + String endDate, Page page) { + + Page sysLogPage = sysLogService.getLogList(username, optType, + startDate, endDate, page); + Map map = new HashMap<>(); + map.put("list", sysLogPage.getRecords()); + map.put("total", sysLogPage.getTotal()); + map.put("size", sysLogPage.getSize()); + map.put("current", sysLogPage.getCurrent()); + return ResponseResult.successData(map); + } + + /********************************** + * 用途说明: 导出日志数据 + * 参数说明 sysLogs 所需导出的字典项集合 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回导出成功或者失败 + ***********************************/ + @Log(module = "系统日志", value = "导出系统日志到Excel") + @GetMapping("/exportExcel") + @Operation(summary = "导出日志数据") + public void exportExcel(String username, String optType, + String startDate, + String endDate, Page page, + HttpServletResponse response) throws IOException { + + Page sysLogPage = sysLogService.getLogList(username, optType, + startDate, endDate, page); + sysLogService.exportExcel(sysLogPage.getRecords(), response); + } +} diff --git a/backend/src/main/java/com/yfd/platform/system/controller/SysMenuController.java b/backend/src/main/java/com/yfd/platform/system/controller/SysMenuController.java new file mode 100644 index 0000000..70a16a0 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/controller/SysMenuController.java @@ -0,0 +1,308 @@ +package com.yfd.platform.system.controller; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.yfd.platform.annotation.Log; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.system.domain.SysMenu; +import com.yfd.platform.system.domain.SysUser; +import com.yfd.platform.system.service.ISysMenuService; +import com.yfd.platform.system.service.IUserService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.apache.catalina.User; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.annotation.Resource; +import java.io.File; +import java.io.FileNotFoundException; +import java.sql.Timestamp; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + *

+ * 菜单及按钮 前端控制器 + *

+ * + * @author zhengsl + * @since 2021-12-15 + */ +@RestController +@RequestMapping("/system/menu") +@Tag(name = "菜单及按钮") +public class SysMenuController { + + @Resource + private ISysMenuService sysMenuService; + + @Resource + private IUserService userService; + + // 菜单图片路径通过服务层配置获取,无需在控制器注入 + + /*********************************** + * 用途说明:获取菜单结构树(含按钮) + * 参数说明 + * systemcode 系统 + * name 名称 + * isdisplay 是否显示 + * 返回值说明: 菜单结构树集合 + ***********************************/ + @PostMapping("/getMenuButtonTree") + @Operation(summary = "获取菜单结构树(含按钮)") + @ResponseBody + public List> getMenuButtonTree(String systemcode, + String name, + String isdisplay) { + return sysMenuService.getMenuButtonTree(systemcode, name, isdisplay); + } + + /*********************************** + * 用途说明:获取菜单结构树(不含按钮) + * 参数说明 + * systemcode 系统 + * name 名称 + * isdisplay 是否显示 + * 返回值说明: 菜单结构树集合 + ***********************************/ + @PostMapping("/getMenuTree") + @Operation(summary = "获取菜单结构树(不含按钮)") + @ResponseBody + public List> getMenuTree(String systemcode, + String name, + String isdisplay) { + return sysMenuService.getMenuTree(systemcode, name, isdisplay); + } + + /*********************************** + * 用途说明:权限分配 + * 参数说明 + * systemcode 系统 + * name 名称 + * isdisplay 是否显示 + * 返回值说明: 菜单结构树集合 + ***********************************/ + @PostMapping("/permissionAssignment") + @Operation(summary = "获取分配权限(不含按钮)") + @ResponseBody + public List> permissionAssignment(String roleId) { + + return sysMenuService.permissionAssignment(roleId); + } + + /********************************** + * 用途说明: 获取当前用户菜单结构树 + * 参数说明 + * 返回值说明: java.util.List + ***********************************/ + @GetMapping("/treeRoutes") + @Operation(summary = "获取当前用户菜单结构树") + @ResponseBody + public List> getMenuTreeByUser() { + SysUser userInfo = userService.getUserInfo(); + String id = ""; + if (0 != userInfo.getUsertype()) { + id = userInfo.getId(); + } + return sysMenuService.getMenuTree(id); + } + + /*********************************** + * 用途说明:根据id查询菜单或按钮详情 + * 参数说明 + * id 菜单或按钮表id + * 返回值说明: 菜单或按钮表对象 + ***********************************/ + @PostMapping("/getOneById") + @Operation(summary = "根据id查询菜单或按钮详情") + @ResponseBody + public ResponseResult getOneById(String id) { + SysMenu sysMenu = sysMenuService.getById(id); + return ResponseResult.successData(sysMenu); + } + + /*********************************** + * 用途说明:新增菜单及按钮 + * 参数说明 + * sysMenu 菜单或按钮表对象 + * 返回值说明: 是否添加成功提示 + ***********************************/ + @Log(module = "菜单及按钮", value = "新增菜单及按钮!") + @PostMapping("/addMenu") + @Operation(summary = "新增菜单及按钮") + @ResponseBody + public ResponseResult addMenu(@RequestBody SysMenu sysMenu) { + boolean isOk = sysMenuService.addMenu(sysMenu); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:修改菜单及按钮 + * 参数说明 + * sysMenu 菜单或按钮表对象 + * 返回值说明: 是否修改成功提示 + ***********************************/ + @Log(module = "菜单及按钮", value = "修改菜单及按钮") + @PostMapping("/updateById") + @Operation(summary = "修改菜单及按钮") + @ResponseBody + public ResponseResult updateById(@RequestBody SysMenu sysMenu) { + sysMenu.setLastmodifier(userService.getUsername()); + sysMenu.setLastmodifydate(new Timestamp(System.currentTimeMillis())); + boolean isOk = sysMenuService.updateById(sysMenu); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:根据id删除单个图标 + * 参数说明 + * id 删除图标id + * icon 图标名称 + * 返回值说明: 是否删除成功 + ***********************************/ + @Log(module = "菜单及按钮", value = "根据id删除单个图标!") + @PostMapping("/deleteIcon") + @Operation(summary = "根据id删除单个图标") + @ResponseBody + public ResponseResult deleteIcon(@RequestParam String id) { + boolean ok = sysMenuService.deleteIcon(id); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:更新菜单及按钮是否有效 + * 参数说明 + * id 菜单及按钮表id + * isdisplay 是否有效字段 + * 返回值说明: 是否更新成功 + ***********************************/ + @Log(module = "菜单及按钮", value = "更新菜单及按钮是否有效!") + @PostMapping("/setIsDisplay") + @Operation(summary = "更新菜单及按钮是否有效") + @ResponseBody + public ResponseResult setIsDisplay(String id, String isdisplay) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + //根据id 修改是否显示 ,最近修改人,最近修改时间 + updateWrapper.eq("id", id).set("isdisplay", isdisplay).set( + "lastmodifier", userService.getUsername()).set( + "lastmodifydate", + new Timestamp(System.currentTimeMillis())); + boolean ok = sysMenuService.update(updateWrapper); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:菜单及按钮序号排序 + * 参数说明 + * parentid 上级id + * orderMap map<菜单及按钮表id,排列序号> + * 返回值说明: 是否更新成功 + ***********************************/ + @Log(module = "菜单及按钮", value = "菜单及按钮序号排序!") + @PostMapping("/moveOrderno") + @Operation(summary = "菜单及按钮序号排序") + @ResponseBody + public ResponseResult moveOrderno(@RequestParam String parentid, + @RequestParam String id, + @RequestParam int orderno) { + boolean ok = sysMenuService.moveOrderno(parentid, id, orderno); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:根据id删除菜单或按钮 + * 参数说明 + * id 删除列的id + * 返回值说明: 是否删除成功 + ***********************************/ + @Log(module = "菜单及按钮", value = "根据id删除菜单或按钮!") + @PostMapping("/deleteById") + @Operation(summary = "根据id删除菜单或按钮") + @ResponseBody + public ResponseResult deleteById(@RequestParam String id) { + boolean ok = sysMenuService.deleteById(id); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /********************************** + * 用途说明: 菜单或者按钮拖动 + * 参数说明 id + * 参数说明 id1 + * 返回值说明: com.yfd.platform.config.ResponseResult + ***********************************/ + @Log(module = "菜单及按钮", value = "拖动修改菜单或按钮同级顺序!") + @PostMapping("/changeMenuOrder") + @Operation(summary = "菜单或按钮切换") + @ResponseBody + public ResponseResult changeMenuOrder(@RequestParam String fromId, + @RequestParam String toId) { + if (StrUtil.isBlank(fromId) || StrUtil.isBlank(toId)) { + return ResponseResult.error("参数为空!"); + } + if (fromId.equals(toId)) { + return ResponseResult.error("切换失败!"); + } + boolean ok = sysMenuService.changeOderNoById(fromId, toId); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:上传单个图标 + * 参数说明 + * icon 图标 + * 返回值说明: 是否上传成功 + ***********************************/ + @PostMapping("/uploadIcon") + @Operation(summary = "上传单个图标") + @ResponseBody + public ResponseResult uploadIcon(MultipartFile icon, String menuId) throws FileNotFoundException { + if (StrUtil.isNotBlank(menuId)) { + SysMenu sysMenu = sysMenuService.getById(menuId); + //图片路径 + String iconname = + System.getProperty("user.dir") + "\\src\\main" + + "\\resources\\static\\icon" + File.separator + sysMenu.getIcon(); + //删除图标 + new File(iconname).delete(); + } + String filename = sysMenuService.uploadIcon(icon); + SysMenu sysMenu = new SysMenu(); + sysMenu.setId(menuId); + sysMenu.setIcon(filename); + sysMenuService.updateById(sysMenu); + return ResponseResult.successData(filename); + } +} diff --git a/backend/src/main/java/com/yfd/platform/system/controller/SysOrganizationController.java b/backend/src/main/java/com/yfd/platform/system/controller/SysOrganizationController.java new file mode 100644 index 0000000..9f51b71 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/controller/SysOrganizationController.java @@ -0,0 +1,210 @@ +package com.yfd.platform.system.controller; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.yfd.platform.annotation.Log; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.system.domain.SysOrganization; +import com.yfd.platform.system.domain.SysRole; +import com.yfd.platform.system.domain.SysUser; +import com.yfd.platform.system.mapper.SysRoleMapper; +import com.yfd.platform.system.service.ISysOrganizationService; +import com.yfd.platform.system.service.IUserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.sql.Timestamp; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + *

+ * 系统组织框架 前端控制器 + *

+ * + * @author zhengsl + * @since 2021-12-15 + */ +@RestController +@RequestMapping("/system/organization") +@Tag(name = "系统组织框架") +public class SysOrganizationController { + + @Resource + private ISysOrganizationService organizationService; + + @Resource + private IUserService userService; + + /*********************************** + * 用途说明:获取组织范围树结构 + * 参数说明 + *parentid 上级id + * params 名称(根据名称查询二级) + * 返回值说明: 组织树集合 + ***********************************/ + @PostMapping("/getOrgScopeTree") + @Operation(summary = "获取组织范围树结构") + @ResponseBody + public List> getOrgScopeTree(String roleId) { + return organizationService.getOrgScopeTree(roleId); + } + + /*********************************** + * 用途说明:获取组织范围 + * 参数说明 + * 返回值说明: 组织范围集合 + ***********************************/ + @PostMapping("/getOrgTree") + @Operation(summary = "获取组织结构树") + @ResponseBody + public List> getOrgTree(String parentid, + String params) { + return organizationService.getOrgTree(parentid, params); + } + + /*********************************** + * 用途说明:根据企业ID查询组织详情 + * 参数说明 + * id 企业id + * 返回值说明: 系统组织框架对象 + ***********************************/ + @PostMapping("/getOrganizationById") + @Operation(summary = "根据企业ID查询组织信息") + @ResponseBody + public ResponseResult getOrganizationById(String id, String orgName) { + if (StrUtil.isBlank(id)) { + return ResponseResult.error("查询失败!"); + } + List sysOrganizations = + organizationService.getOrganizationById(id, orgName); + return ResponseResult.successData(sysOrganizations); + } + + /*********************************** + * 用途说明:根据ID查询组织详情 + * 参数说明 + * id 系统组织id + * 返回值说明: 系统组织框架对象 + ***********************************/ + @PostMapping("/getOneById") + @Operation(summary = "根据ID查询组织详情") + @ResponseBody + public ResponseResult getOneById(String id) { + SysOrganization sysOrganization = organizationService.getById(id); + return ResponseResult.successData(sysOrganization); + } + + /*********************************** + * 用途说明:新增系统组织框架 + * 参数说明 + * sysOrganization 系统组织框架对象 + * 返回值说明: 是否新增成功 + ***********************************/ + @Log(module = "系统组织框架", value = "新增企业或者部门!") + @PostMapping("/addOrg") + @Operation(summary = "新增系统组织框架") + @ResponseBody + public ResponseResult addOrg(@RequestBody SysOrganization sysOrganization) { + //判断是否是否填写 有效 否则默认为 1 + if (StrUtil.isEmpty(sysOrganization.getIsvaild())) { + sysOrganization.setIsvaild("1"); + } + //填写 当前用户名称 + sysOrganization.setLastmodifier(userService.getUsername()); + //填写 当前日期 + sysOrganization.setLastmodifydate(new Timestamp(System.currentTimeMillis())); + //新增 系统组织R + boolean isOk = organizationService.addOrg(sysOrganization); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:修改系统组织框架 + * 参数说明 + * sysOrganization 系统组织框架对象 + * 返回值说明: 是否修改成功 + ***********************************/ + @Log(module = "系统组织框架", value = "修改企业或者部门信息!") + @PostMapping("/updateById") + @Operation(summary = "修改系统组织框架") + @ResponseBody + public ResponseResult updateById(@RequestBody SysOrganization sysOrganization) { + //填写 当前用户名称 + sysOrganization.setLastmodifier(userService.getUsername()); + //填写 当前日期 + sysOrganization.setLastmodifydate(new Timestamp(System.currentTimeMillis())); + //根据id 修改系统组织 + boolean isOk = organizationService.updateById(sysOrganization); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:修改系统组织框架 + * 参数说明 + * sysOrganization 系统组织框架对象 + * 返回值说明: 是否修改成功 + ***********************************/ + @Log(module = "系统组织框架", value = "设置企业/部门是否有效!") + @PostMapping("/setIsValid") + @Operation(summary = "设置组织是否有效") + @ResponseBody + public ResponseResult setIsValid(@RequestParam String id, + @RequestParam String isvaild) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + //根据id 修改是否有效,最近修改人,最近修改时间 + updateWrapper.eq("id", id).set("isvaild", isvaild).set("lastmodifier" + , userService.getUsername()).set("lastmodifydate", + new Timestamp(System.currentTimeMillis())); + boolean isOk = organizationService.update(updateWrapper); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:根据id删除系统组织框架 + * 参数说明 + * id 系统组织框架id + * 返回值说明: 是否删除成功 + ***********************************/ + @Log(module = "系统组织框架", value = "根据ID删除企业或者部门!") + @PostMapping("/deleteById") + @Operation(summary = "根据id删除系统组织框架") + @ResponseBody + public ResponseResult deleteById(@RequestParam String id) { + String[] orgIds = id.split(","); + for (String orgId : orgIds) { + LambdaQueryWrapper queryWrapper = + new LambdaQueryWrapper<>(); + List list = + organizationService.list(queryWrapper.eq(SysOrganization::getParentid, orgId)); + List ids = + list.stream().map(SysOrganization::getId).collect(Collectors.toList()); + boolean isOk = organizationService.removeById(orgId); + if (!isOk) { + continue; + } + for (String oid : ids) { + organizationService.removeById(oid); + } + } + return ResponseResult.success(); + } + +} diff --git a/backend/src/main/java/com/yfd/platform/system/controller/SysRoleController.java b/backend/src/main/java/com/yfd/platform/system/controller/SysRoleController.java new file mode 100644 index 0000000..ea2fa0f --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/controller/SysRoleController.java @@ -0,0 +1,324 @@ +package com.yfd.platform.system.controller; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.yfd.platform.annotation.Log; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.system.domain.SysRole; +import com.yfd.platform.system.service.ISysRoleService; +import com.yfd.platform.system.service.IUserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + *

+ * 系统角色 前端控制器 + *

+ * + * @author zhengsl + * @since 2021-12-15 + */ +@RestController +@RequestMapping("/system/role") +@Tag(name = "系统角色") +public class SysRoleController { + + @Resource + private ISysRoleService roleService; + + @Resource + private IUserService userService; + + /*********************************** + * 用途说明:查询所有角色 + * 参数说明 + * roleName 角色名称 + * 返回值说明: 查询都有角色 + ***********************************/ + @PostMapping("/list") + @Operation(summary = "查询所有角色") + @ResponseBody + public List list(String rolename) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (StrUtil.isNotEmpty(rolename)) { + //根据角色名称模糊查询 + queryWrapper.like("rolename", rolename); + } + //根据角色级别,角色编号 正序排序 + queryWrapper.ne("level", "1").orderByAsc("level", "lastmodifydate"); + return roleService.list(queryWrapper); + } + + /*********************************** + * 用途说明:根据Id获取当个角色 + * 参数说明 + * id 角色表id + * 返回值说明: 根据id查询到角色详情 + ***********************************/ + @PostMapping("/getOneById") + @Operation(summary = "根据Id获取当个角色") + @ResponseBody + public ResponseResult getOneById(String id) { + SysRole sysRole = roleService.getById(id); + return ResponseResult.successData(sysRole); + } + + /*********************************** + * 用途说明:新增角色 + * 参数说明 + * sysRole 新增角色信息 + * 返回值说明: 是否新增成功 + ***********************************/ + @Log(module = "系统角色", value = "新增角色") + @PostMapping("/addRole") + @Operation(summary = "新增角色") + @ResponseBody + public ResponseResult addRole(@RequestBody SysRole sysRole) { + boolean isOk = roleService.addRole(sysRole); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:分配操作权限 + * 参数说明 + * id 角色id + * optscope 分配的权限 + * 返回值说明: 是否新增成功 + ***********************************/ + @Log(module = "系统角色", value = "分配操作权限") + @PostMapping("/setOptScope") + @Operation(summary = "分配操作权限") + @ResponseBody + public ResponseResult setOptScope(@RequestParam String id, + @RequestParam String optscope) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + //根据id 更新权限,最近修改人,最近修改时间 + updateWrapper.eq("id", id).set("optscope", optscope).set( + "lastmodifier", userService.getUsername()).set( + "lastmodifydate", LocalDateTime.now()); + boolean ok = roleService.update(updateWrapper); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:角色菜单权限 + * 参数说明 + * id 角色id + * menuIds 权限id字符串 + * 返回值说明: 是否分配成功 + ***********************************/ + @Log(module = "系统角色", value = "角色菜单权限") + @PostMapping("/setMenuById") + @Operation(summary = "角色菜单权限") + @ResponseBody + public ResponseResult setMenuById(String id, String menuIds) { + if (StrUtil.isBlank(id)) { + return ResponseResult.error("参数为空"); + } + if (StrUtil.isBlank(menuIds)) { + return ResponseResult.success(); + } + boolean ok = roleService.setMenuById(id, menuIds); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + + } + + /*********************************** + * 用途说明:设置组织范围 + * 参数说明 + * id 角色id + * orgscope 组织范围 + * 返回值说明: 是否新增成功 + ***********************************/ + @Log(module = "系统角色", value = "设置组织范围") + @PostMapping("/setOrgscope") + @Operation(summary = "设置组织范围") + @ResponseBody + public ResponseResult setOrgscope(@RequestParam String id, + @RequestParam String orgscope) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + //根据id 更新组织范围,最近修改人,最近修改时间 + updateWrapper.eq("id", id).set("orgscope", orgscope).set( + "lastmodifier", userService.getUsername()).set( + "lastmodifydate", LocalDateTime.now()); + boolean ok = roleService.update(updateWrapper); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:设置业务范围 + * 参数说明 + * id 角色id + * busscope 业务范围 + * 返回值说明: 是否新增成功 + ***********************************/ + @Log(module = "系统角色", value = "设置业务范围") + @PostMapping("/setBusscope") + @Operation(summary = "设置业务范围") + @ResponseBody + public ResponseResult setBusscope(@RequestParam String id, + @RequestParam String busscope) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + //根据id 更新业务范围,最近修改人,最近修改时间 + updateWrapper.eq("id", id).set("busscope", busscope).set( + "lastmodifier", userService.getUsername()).set( + "lastmodifydate", LocalDateTime.now()); + boolean ok = roleService.update(updateWrapper); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:角色添加用户 + * 参数说明 + * roleid 角色id + * userids 用户id组 + * 返回值说明: 是否新增成功 + ***********************************/ + @Log(module = "系统角色", value = "角色添加用户") + @PostMapping("/setRoleUsers") + @Operation(summary = "角色添加用户") + @ResponseBody + public ResponseResult setRoleUsers(String roleid, String userids) { + boolean isOk = true; + String[] temp = userids.split(","); + for (String userid : temp) { + isOk = isOk && userService.addUserRoles(roleid, userid); + } + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:删除角色用户 + * 参数说明 + * roleid 角色id + * 返回值说明: 是否新增成功 + ***********************************/ + @PostMapping("/deleteRoleUser") + @Operation(summary = "删除角色用户") + @ResponseBody + public ResponseResult deleteRoleUsers(@RequestParam String roleid, + @RequestParam String userids) { + //根据角色id、用户id删除 + boolean ok = roleService.deleteRoleUsers(roleid, userids); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:设置角色是否有效 + * 参数说明 + * id 角色id + *isvaild 是否有效(1 是 0 否 ) + * 返回值说明: 是否新增成功 + ***********************************/ + @PostMapping("/setIsvaild") + @Operation(summary = "设置角色是否有效") + @ResponseBody + public ResponseResult setIsvaild(String id, String isvaild) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + //根据id 更新业务范围,最近修改人,最近修改时间 + updateWrapper.eq("id", id).set("isvaild", isvaild).set("lastmodifier" + , userService.getUsername()).set("lastmodifydate", + LocalDateTime.now()); + boolean ok = roleService.update(updateWrapper); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:更新角色信息 + * 参数说明 + *sysRole 角色对象 + * 返回值说明: 是否修改成功 + ***********************************/ + @PostMapping("/updateById") + @Operation(summary = "更新角色信息") + @ResponseBody + public ResponseResult updateById(@RequestBody SysRole sysRole) { + //更新最近修改人 + sysRole.setLastmodifier(userService.getUsername()); + //更新最近修改时间 + sysRole.setLastmodifydate(new Timestamp(System.currentTimeMillis())); + //根据id更新角色信息 + boolean ok = roleService.updateById(sysRole); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:根据id删除角色 + * 参数说明 + *id 角色id + * 返回值说明: 是否删除成功 + ***********************************/ + @PostMapping("/deleteById") + @Operation(summary = "根据id删除角色") + @ResponseBody + public ResponseResult deleteById(@RequestParam String id) { + roleService.deleteById(id); + return ResponseResult.success(); + } + + /*********************************** + * 用途说明:查询已分配的用户 + * 参数说明 + *orgid 所属组织 + *username 用户名称 + *status 状态 + *level 角色级别 + * rolename 角色名称 + * isvaild 角色是否有效 + * 返回值说明: 系统用户角色数据集合 + ***********************************/ + @PostMapping("/listRoleUsers") + @Operation(summary = "查询已分配的用户") + @ResponseBody + public List listRoleUsers(String orgid, String username, + String status, String level, + String rolename, String isvaild) { + return roleService.listRoleUsers(orgid, username, status, level, + rolename, isvaild); + } + +} diff --git a/backend/src/main/java/com/yfd/platform/system/controller/UserController.java b/backend/src/main/java/com/yfd/platform/system/controller/UserController.java new file mode 100644 index 0000000..c90e345 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/controller/UserController.java @@ -0,0 +1,188 @@ +package com.yfd.platform.system.controller; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.annotation.Log; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.datasource.DataSource; +import com.yfd.platform.system.domain.SysUser; +import com.yfd.platform.system.service.IUserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.annotation.Resource; +import java.sql.Timestamp; +import java.util.Map; + +/** + *

+ * 用户信息 前端控制器 + *

+ * + * @author zhengsl + * @since 2022-09-20 + */ +@RestController +@RequestMapping("/system/user") +@Tag(name = "系统用户") +public class UserController { + + @Resource + private IUserService userService; + + @Log(module = "系统用户", value = "新增系统用户") + @PostMapping("/addUser") + @Operation(summary = "新增系统用户") + @ResponseBody + public ResponseResult addUser(@RequestBody SysUser user, String roleids) { + Map reslut = userService.addUser(user, roleids); + return ResponseResult.successData(reslut); + } + + @Log(module = "系统用户", value = "修改用户信息") + @PostMapping("/updateUser") + @Operation(summary = "修改用户信息") + @ResponseBody + public ResponseResult updateUser(@RequestBody SysUser user, + String roleids) { + if (StrUtil.isEmpty(user.getId())) { + return ResponseResult.error("没有用户ID"); + } + //填写 当前用户名称 + user.setLastmodifier(userService.getUsername()); + //填写 当前日期 + user.setLastmodifydate(new Timestamp(System.currentTimeMillis())); + Map reslut = userService.updateById(user, roleids); + return ResponseResult.successData(reslut); + } + + @GetMapping("/queryUsers") + @Operation(summary = "查询用户信息") + @ResponseBody + public ResponseResult queryUsers(String orgid, + String username, Page page) { + + Page> mapPage = userService.queryUsers(orgid, + username, page); + return ResponseResult.successData(mapPage); + } + + /*********************************** + * 用途说明:用户分配角色 + * 参数说明 + *idMap 用户id与角色id + * 返回值说明: 判断是否添加成功 + ************************************/ + @Log(module = "系统用户", value = "用户分配角色") + @PostMapping("/setUserRoles") + @Operation(summary = "用户分配角色") + @ResponseBody + public ResponseResult setUserRoles(String roleid, String userids) { + boolean ok = userService.setUserRoles(roleid, userids); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:根据id删除用户 + * 参数说明 + *id 用户id + * 返回值说明: 判断是否删除成功 + ************************************/ + @Log(module = "系统用户", value = "根据ID删除用户") + @PostMapping("/deleteById") + @Operation(summary = "根据ID删除用户") + @ResponseBody + public ResponseResult deleteById(String id) { + userService.deleteById(id); + return ResponseResult.success(); + } + + /*********************************** + * 用途说明:根据ID批量删除用户 + * 参数说明 + *ids 用户id集合 + * 返回值说明: 判断是否删除成功 + ************************************/ + @Log(module = "系统用户", value = "根据ID批量删除用户") + @PostMapping("/deleteUserByIds") + @Operation(summary = "根据ID批量删除用户") + @ResponseBody + public ResponseResult deleteUserByIds(String id) { + if (StrUtil.isBlank(id)) { + return ResponseResult.error("参数为空"); + } + boolean ok = userService.deleteUserByIds(id); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:重置用户密码(管理员) + * 参数说明 + *id 重置密码的 用户id + * 返回值说明: 判断是否重置成功 + ************************************/ + @Log(module = "系统用户", value = "重置用户密码") + @PostMapping("/resetPassword") + @Operation(summary = "重置用户密码") + @ResponseBody + @DataSource + public ResponseResult resetPassword(String id) throws Exception { + if (StrUtil.isBlank(id)) { + ResponseResult.error("参数为空"); + } + boolean ok = userService.resetPassword(id); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:设置账号状态(管理员) + * 参数说明 + *id 用户id + * status 设置状态 + * 返回值说明: 判断是否设置成功 + ************************************/ + @Log(module = "系统用户", value = "设置账号状态") + @PostMapping("/setStatus") + @Operation(summary = "设置账号状态") + @ResponseBody + public ResponseResult setStatus(@RequestParam String id, + @RequestParam String status) { + boolean ok = userService.setStatus(id, status); + if (ok) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:修改头像(管理员) + * 参数说明 + * multipartFile 文件对象 + * status 设置状态 + * 返回值说明: 文件名 + ************************************/ + @Operation(summary = "修改头像") + @PostMapping(value = "/updateAvatar") + public ResponseResult updateAvatar(String id, MultipartFile multipartFile) { + if (multipartFile == null) { + ResponseResult.error("参数为空"); + } + boolean ok = userService.uploadAvatar(id, multipartFile); + return ResponseResult.success(); + } +} diff --git a/backend/src/main/java/com/yfd/platform/system/domain/Dictionary.java b/backend/src/main/java/com/yfd/platform/system/domain/Dictionary.java new file mode 100644 index 0000000..df234e3 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/domain/Dictionary.java @@ -0,0 +1,78 @@ +package com.yfd.platform.system.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +/** + *

+ * 数据字典表 + *

+ * + * @author zhengsl + * @since 2021-10-27 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("rca_dictionary") +public class Dictionary implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + /** + * 类型 + */ + private String type; + + /** + * 类型名称 + */ + private String typename; + + /** + * 代码 + */ + private String code; + + /** + * 名称 + */ + private String name; + + /** + * 顺序号 + */ + private String orderno; + + /** + * 上级代码 + */ + private String parentcode; + + /** + * 备用1 + */ + private String custom1; + + /** + * 备用2 + */ + private String custom2; + + /** + * 备用3 + */ + private String custom3; + + +} diff --git a/backend/src/main/java/com/yfd/platform/system/domain/LoginUser.java b/backend/src/main/java/com/yfd/platform/system/domain/LoginUser.java new file mode 100644 index 0000000..44cfb35 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/domain/LoginUser.java @@ -0,0 +1,76 @@ +package com.yfd.platform.system.domain; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LoginUser implements UserDetails { + + private SysUser user; + + private List permissions; + + public LoginUser(SysUser user, List permissions) { + this.user = user; + this.permissions = permissions; + } + + @JSONField(serialize = false) + private List authorities; + + @Override + public Collection getAuthorities() { + // 将权限信息放入集合 + authorities = permissions.stream() + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + return authorities; + } + + @Override + public String getPassword() { + return user.getPassword(); + } + + @Override + public String getUsername() { + return user.getUsername(); + } + + //获取用户昵称 + public String geNickname() { + return user.getNickname(); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/backend/src/main/java/com/yfd/platform/system/domain/Message.java b/backend/src/main/java/com/yfd/platform/system/domain/Message.java new file mode 100644 index 0000000..54578e5 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/domain/Message.java @@ -0,0 +1,117 @@ +package com.yfd.platform.system.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 消息通知 + *

+ * + * @author TangWei + * @since 2023-03-19 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("sys_message") +public class Message implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @TableId(type = IdType.ASSIGN_UUID) + @Schema(description = "ID") + private String id; + + /** + * 创建时间:排序 + */ + @Schema(description = "创建时间:排序") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Timestamp createtime; + + /** + * 消息类型:1-定时任务 2-工作流触发 3-人工触发 + */ + @Schema(description = "消息类型:1-定时任务 2-工作流触发 3-人工触发") + private String type; + + /** + * 消息标题 + */ + @Schema(description = "消息标题") + private String title; + + /** + * 消息内容 + */ + @Schema(description = "消息内容") + private String content; + + /** + * 发送者名称,定时器,人员 + */ + @Schema(description = "发送者名称,定时器,人员") + private String senderName; + + /** + * 接收者代码 人员账号列表 + */ + @Schema(description = "接收者代码 人员账号列表 ") + private String receiverCodes; + + /** + * 接收者名称:为空 即为所有人,人员名称列表 + */ + @Schema(description = "接收者名称:为空 即为所有人,人员名称列表") + private String receiverNames; + + /** + * 状态:1、初始创建 2-消息已阅 9-消息过期 + */ + @Schema(description = "状态:1、初始创建 2-消息已阅 9-消息过期") + private String status; + + /** + * 有效期:小时 + */ + @Schema(description = "有效期:小时") + private Integer validperiod; + + /** + * 已阅时间 + */ + @Schema(description = "已阅时间") + private Timestamp readtime; + + /** + * 备用1 + */ + @Schema(description = "备用1") + private String custom1; + + /** + * 备用2 + */ + @Schema(description = "备用2") + private String custom2; + + /** + * 备用3 + */ + @Schema(description = "备用3") + private String custom3; + +} diff --git a/backend/src/main/java/com/yfd/platform/system/domain/QuartzJob.java b/backend/src/main/java/com/yfd/platform/system/domain/QuartzJob.java new file mode 100644 index 0000000..e3504ce --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/domain/QuartzJob.java @@ -0,0 +1,118 @@ +package com.yfd.platform.system.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 定时任务 + *

+ * + * @author TangWei + * @since 2023-03-19 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("sys_quartz_job") +public class QuartzJob implements Serializable { + + public static final String JOB_KEY = "JOB_KEY"; + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Schema(description = "ID") + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + /** + * 排序号 + */ + @Schema(description = "排序号") + private Integer orderno; + + /** + * 任务名称 + */ + @Schema(description = "任务名称") + private String jobName; + + /** + * 执行类名称 + */ + @Schema(description = "执行类名称") + private String jobClass; + + /** + * 执行方法名称 + */ + @Schema(description = "执行方法名称") + private String jobMethod; + + /** + * 时间周期表达式 + */ + @Schema(description = "时间周期表达式") + private String jobCron; + + /** + * 方法参数 + */ + @Schema(description = "方法参数") + private String jobParams; + + /** + * 任务描述 + */ + @Schema(description = "任务描述") + private String description; + + /** + * 状态:0-暂停、1-启用 + */ + @Schema(description = "状态:0-暂停、1-启用") + private String status; + + /** + * 最近修改者 + */ + @Schema(description = "最近修改者") + private String lastmodifier; + + /** + * 最近修改日期 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @Schema(description = "最近修改日期") + private Timestamp lastmodifydate; + + /** + * 备用1 + */ + @Schema(description = "备用1") + private String custom1; + + /** + * 备用2 + */ + @Schema(description = "备用2") + private String custom2; + + /** + * 备用3 + */ + @Schema(description = "备用3") + private String custom3; + +} diff --git a/backend/src/main/java/com/yfd/platform/system/domain/SysConfig.java b/backend/src/main/java/com/yfd/platform/system/domain/SysConfig.java new file mode 100644 index 0000000..3bef030 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/domain/SysConfig.java @@ -0,0 +1,78 @@ +package com.yfd.platform.system.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.sql.Timestamp; + +/** + *

+ * 系统全局配置 + *

+ * + * @author zhengsl + * @since 2022-01-19 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("sys_config") +public class SysConfig implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + /** + * 欢迎词 + */ + private String welcome; + + /** + * 系统功能介绍 + */ + private String funcation; + + /** + * 系统版本信息 + */ + private String versioninfo; + + /** + * 备注 + */ + private String remark; + + /** + * 最近修改者 + */ + private String lastmodifier; + + /** + * 最近修改日期 + */ + @TableField(fill = FieldFill.UPDATE) + private Timestamp lastmodifydate; + + /** + * 备用1 + */ + private String custom1; + + /** + * 备用2 + */ + private String custom2; + + /** + * 备用3 + */ + private String custom3; + + +} diff --git a/backend/src/main/java/com/yfd/platform/system/domain/SysDictionary.java b/backend/src/main/java/com/yfd/platform/system/domain/SysDictionary.java new file mode 100644 index 0000000..ca1fb9c --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/domain/SysDictionary.java @@ -0,0 +1,71 @@ +package com.yfd.platform.system.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +/** + *

+ * 数据字典表 + *

+ * + * @author TangWei + * @since 2023-03-08 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class SysDictionary implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + /** + * 字典类型 00-系统内置 01-用户配置 + */ + @TableField("dicttype") + private String dictType; + + /** + * 顺序号 + */ + @TableField("orderno") + private Integer orderNo; + + /** + * 字典编码 + */ + @TableField("dictcode") + private String dictCode; + + /** + * 字典名称 + */ + @TableField("dictname") + private String dictName; + + /** + * 备用1 + */ + private String custom1; + + /** + * 备用2 + */ + private String custom2; + + /** + * 备用3 + */ + private String custom3; + + +} diff --git a/backend/src/main/java/com/yfd/platform/system/domain/SysDictionaryItems.java b/backend/src/main/java/com/yfd/platform/system/domain/SysDictionaryItems.java new file mode 100644 index 0000000..431bfda --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/domain/SysDictionaryItems.java @@ -0,0 +1,77 @@ +package com.yfd.platform.system.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +/** + *

+ * 数据字典明细 + *

+ * + * @author TangWei + * @since 2023-03-08 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class SysDictionaryItems implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + /** + * 对应字典ID + */ + @TableField("dictid") + private String dictId; + + /** + * 顺序号 + */ + @TableField("orderno") + private Integer orderNo; + + /** + * 项编码 + */ + @TableField("itemcode") + private String itemCode; + + /** + * 项名称 + */ + @TableField("dictname") + private String dictName; + + /** + * 父项编码 + */ + @TableField("parentcode") + private String parentCode; + + /** + * 备用1 + */ + private String custom1; + + /** + * 备用2 + */ + private String custom2; + + /** + * 备用3 + */ + private String custom3; + + +} diff --git a/backend/src/main/java/com/yfd/platform/system/domain/SysLog.java b/backend/src/main/java/com/yfd/platform/system/domain/SysLog.java new file mode 100644 index 0000000..dfe091d --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/domain/SysLog.java @@ -0,0 +1,92 @@ +package com.yfd.platform.system.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.sql.Timestamp; +import java.time.LocalDateTime; + +/** + *

+ * 系统操作日志 + *

+ * + * @author TangWei + * @since 2023-03-08 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class SysLog implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 用户账号 + */ + @TableField("usercode") + private String usercode; + + /** + * 用户名称 + */ + private String username; + + /** + * 操作类型 00-登录 01-新增 02-修改 03-删除 06-查询 09其他 + */ + @TableField("opttype") + private String opttype; + + /** + * 模块名称 + */ + private String module; + + /** + * 日志描述 + */ + private String description; + + /** + * 操作方法 + */ + private String method; + + /** + * 方法参数 + */ + private String params; + + /** + * 创建时间 + */ + @TableField("logtime") + private Timestamp logtime; + + /** + * 请求IP + */ + @TableField("requestip") + private String requestip; + + /** + * 浏览器类型 + */ + private String browser; + + public SysLog(String opttype) { + this.opttype = opttype; + } +} diff --git a/backend/src/main/java/com/yfd/platform/system/domain/SysMenu.java b/backend/src/main/java/com/yfd/platform/system/domain/SysMenu.java new file mode 100644 index 0000000..18802d7 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/domain/SysMenu.java @@ -0,0 +1,114 @@ +package com.yfd.platform.system.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +/** + *

+ * 菜单及按钮 + *

+ * + * @author zhengsl + * @since 2021-12-15 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("sys_menu") +public class SysMenu implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + /** + * 1-web 2-pad 3-mobile + */ + private String systemcode; + + /** + * 1-菜单 2-按钮 + */ + private String type; + + /** + * 在系统内自动生成 + */ + private String code; + + /** + * 名称 + */ + private String name; + + /** + * 图标地址 + */ + private String icon; + + /** + * 是否外链 + */ + private String islink; + + /** + * 内部模块路径或者外链地址 + */ + private String opturl; + + /** + * 权限控制标识 + */ + private String permission; + + /** + * 顶级为0 + */ + private String parentid; + + /** + * 排序号 + */ + private Integer orderno; + + /** + * 0-不显示 1-显示 + */ + private String isdisplay; + + /** + * 最近修改者 + */ + private String lastmodifier; + + /** + * 最近修改日期 + */ + @TableField(fill = FieldFill.UPDATE) + private Timestamp lastmodifydate; + + /** + * 备用1 + */ + private String custom1; + + /** + * 备用2 + */ + private String custom2; + + /** + * 备用3 + */ + private String custom3; + +} diff --git a/backend/src/main/java/com/yfd/platform/system/domain/SysOrganization.java b/backend/src/main/java/com/yfd/platform/system/domain/SysOrganization.java new file mode 100644 index 0000000..cc7f711 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/domain/SysOrganization.java @@ -0,0 +1,93 @@ +package com.yfd.platform.system.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.sql.Timestamp; + +/** + *

+ * 系统组织框架 + *

+ * + * @author zhengsl + * @since 2021-12-15 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("sys_organization") +public class SysOrganization implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + /** + * 1-公司 -2-部门 + */ + private String orgtype; + + /** + * 两位一级 + */ + private String orgcode; + + /** + * 组织名称 + */ + private String orgname; + + /** + * 上级id + */ + private String parentid; + + /** + * 组织负责人 + */ + private String manager; + + /** + * 1-是 0-否 + */ + private String isvaild; + + /** + * 描述 + */ + private String description; + + /** + * 最近修改者 + */ + private String lastmodifier; + + /** + * 最近修改日期 + */ + @TableField(fill = FieldFill.UPDATE) + private Timestamp lastmodifydate; + + /** + * 备用1 + */ + private String custom1; + + /** + * 备用2 + */ + private String custom2; + + /** + * 备用3 + */ + private String custom3; + + +} diff --git a/backend/src/main/java/com/yfd/platform/system/domain/SysRole.java b/backend/src/main/java/com/yfd/platform/system/domain/SysRole.java new file mode 100644 index 0000000..1b21e76 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/domain/SysRole.java @@ -0,0 +1,98 @@ +package com.yfd.platform.system.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.sql.Timestamp; + +/** + *

+ * 系统角色 + *

+ * + * @author zhengsl + * @since 2021-12-15 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("sys_role") +public class SysRole implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + /** + * 系统生成,三位编号 + */ + private String rolecode; + + /** + * 角色名称 + */ + private String rolename; + + /** + * 1-超级管理员 2-单位管理员 3-普通用户 + */ + private String level; + + /** + * 描述 + */ + private String description; + + /** + * org1,org2 + */ + private String orgscope; + + /** + * 多个操作代码(菜单、按钮) + */ + private String optscope; + + /** + * json格式自定义业务范围 + */ + private String busscope; + + /** + * 1-是 0-否 + */ + private String isvaild; + + /** + * 最近修改者 + */ + private String lastmodifier; + + /** + * 最近修改日期 + */ + @TableField(fill = FieldFill.UPDATE) + private Timestamp lastmodifydate; + + /** + * 备用1 + */ + private String custom1; + + /** + * 备用2 + */ + private String custom2; + + /** + * 备用3 + */ + private String custom3; + + +} diff --git a/backend/src/main/java/com/yfd/platform/system/domain/SysUser.java b/backend/src/main/java/com/yfd/platform/system/domain/SysUser.java new file mode 100644 index 0000000..0e01f4a --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/domain/SysUser.java @@ -0,0 +1,116 @@ +package com.yfd.platform.system.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.sql.Timestamp; + +/** + *

+ * 系统用户 + *

+ * + * @author zhengsl + * @since 2021-10-27 + */ + +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("sys_user") +public class SysUser implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id 主键 + */ + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + /** + * 用户类型 0-管理员 1-普通用户 + */ + private Integer usertype; + + /** + * 用户名(账号) + */ + private String username; + /** + * 用户昵称 + */ + private String nickname; + + /** + * 登录密码(加密存储) + */ + private String password; + + /** + * 性别(0-男 1-女 ) + */ + private String sex; + + /** + * 邮箱 + */ + private String email; + /** + * 手机号 + */ + private String phone; + + /** + * 头像(预留) + */ + private String avatar; + + /** + * 账号状态(1-正常 0-停用) + */ + private Integer status; + + /** + * 部门ID + */ + private String orgid; + + /** + * 密码重置时间 + */ + private String pwdresettime; + + /** + * 最近修改者 + */ + private String lastmodifier; + + /** + * 最近修改日期 + */ + @TableField(fill = FieldFill.UPDATE) + private Timestamp lastmodifydate; + + @TableField(exist = false) + private String uuid; + + @TableField(exist = false) + private String code; + + /** + * 备用1 + */ + private String custom1; + + /** + * 备用2 + */ + private String custom2; + + /** + * 备用3 + */ + private String custom3; +} diff --git a/backend/src/main/java/com/yfd/platform/system/mapper/MessageMapper.java b/backend/src/main/java/com/yfd/platform/system/mapper/MessageMapper.java new file mode 100644 index 0000000..d08fa95 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/mapper/MessageMapper.java @@ -0,0 +1,16 @@ +package com.yfd.platform.system.mapper; + +import com.yfd.platform.system.domain.Message; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 消息通知 Mapper 接口 + *

+ * + * @author TangWei + * @since 2023-03-19 + */ +public interface MessageMapper extends BaseMapper { + +} diff --git a/backend/src/main/java/com/yfd/platform/system/mapper/QuartzJobMapper.java b/backend/src/main/java/com/yfd/platform/system/mapper/QuartzJobMapper.java new file mode 100644 index 0000000..ad46f1b --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/mapper/QuartzJobMapper.java @@ -0,0 +1,16 @@ +package com.yfd.platform.system.mapper; + +import com.yfd.platform.system.domain.QuartzJob; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 定时任务 Mapper 接口 + *

+ * + * @author TangWei + * @since 2023-03-19 + */ +public interface QuartzJobMapper extends BaseMapper { + +} diff --git a/backend/src/main/java/com/yfd/platform/system/mapper/SysConfigMapper.java b/backend/src/main/java/com/yfd/platform/system/mapper/SysConfigMapper.java new file mode 100644 index 0000000..5bae048 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/mapper/SysConfigMapper.java @@ -0,0 +1,17 @@ +package com.yfd.platform.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yfd.platform.system.domain.SysConfig; + + +/** + *

+ * 系统全局配置 Mapper 接口 + *

+ * + * @author zhengsl + * @since 2022-01-19 + */ +public interface SysConfigMapper extends BaseMapper { + +} diff --git a/backend/src/main/java/com/yfd/platform/system/mapper/SysDictionaryItemsMapper.java b/backend/src/main/java/com/yfd/platform/system/mapper/SysDictionaryItemsMapper.java new file mode 100644 index 0000000..de7deaa --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/mapper/SysDictionaryItemsMapper.java @@ -0,0 +1,17 @@ +package com.yfd.platform.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.system.domain.SysDictionaryItems; + +/** + *

+ * 数据字典明细 Mapper 接口 + *

+ * + * @author TangWei + * @since 2023-03-08 + */ +public interface SysDictionaryItemsMapper extends BaseMapper { + +} diff --git a/backend/src/main/java/com/yfd/platform/system/mapper/SysDictionaryMapper.java b/backend/src/main/java/com/yfd/platform/system/mapper/SysDictionaryMapper.java new file mode 100644 index 0000000..7d03ca9 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/mapper/SysDictionaryMapper.java @@ -0,0 +1,22 @@ +package com.yfd.platform.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yfd.platform.system.domain.SysDictionary; + +/** + *

+ * 数据字典表 Mapper 接口 + *

+ * + * @author TangWei + * @since 2023-03-08 + */ +public interface SysDictionaryMapper extends BaseMapper { + + /********************************** + * 用途说明: 根据字典类型获取字典最大序号 + * 参数说明 sysDictionary 字典对象 + * 返回值说明: 返回增加成功或者失败 + ***********************************/ + Integer selectMaxNo(String dictType); +} diff --git a/backend/src/main/java/com/yfd/platform/system/mapper/SysLogMapper.java b/backend/src/main/java/com/yfd/platform/system/mapper/SysLogMapper.java new file mode 100644 index 0000000..7b18518 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/mapper/SysLogMapper.java @@ -0,0 +1,16 @@ +package com.yfd.platform.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yfd.platform.system.domain.SysLog; + +/** + *

+ * 系统操作日志 Mapper 接口 + *

+ * + * @author TangWei + * @since 2023-03-08 + */ +public interface SysLogMapper extends BaseMapper { + +} diff --git a/backend/src/main/java/com/yfd/platform/system/mapper/SysMenuMapper.java b/backend/src/main/java/com/yfd/platform/system/mapper/SysMenuMapper.java new file mode 100644 index 0000000..f57b35e --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/mapper/SysMenuMapper.java @@ -0,0 +1,59 @@ +package com.yfd.platform.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yfd.platform.system.domain.SysMenu; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +/** + *

+ * 菜单及按钮 Mapper 接口 + *

+ * + * @author zhengsl + * @since 2021-12-15 + */ +public interface SysMenuMapper extends BaseMapper { + + /*********************************** + * 用途说明:菜单及按钮序号向上移动 + * 参数说明 + * parentid 上级id + *Orderno 小于序号(原序号) + *upOrderno 大于等于序号(更改的序号加一) + * 返回值说明: 是否更新成功 + ***********************************/ + boolean upMoveOrderno(@Param("parentid") String parentid, @Param("Orderno") int Orderno, @Param("upOrderno") int upOrderno); + + /*********************************** + * 用途说明:菜单及按钮序号向下移动 + * 参数说明 + * parentid 上级id + *Orderno 大于序号(原序号) + *downOrderno 小于等于序号(更改的序号减一) + * 返回值说明: 是否更新成功 + ***********************************/ + boolean downMoveOrderno(@Param("parentid") String parentid, @Param("Orderno") int Orderno, @Param("downOrderno") int downOrderno); + + + List selectPermsByUserId(String userId); + + //List selectMenuByUserId(String userId); + List> selectMenuByUserId(String userId); + + /*********************************** + * 用途说明:根据权限id查找系统类型 + * 参数说明 id 权限id + * 返回值说明: 返回系统类型 + ***********************************/ + String getSystemCodeById(String id); + + /*********************************** + * 用途说明:根据角色Id查找权限 + * 参数说明 id 权限id + * 返回值说明: 返回权限集合 + ***********************************/ + List selectMenuByRoleId(String id); +} diff --git a/backend/src/main/java/com/yfd/platform/system/mapper/SysOrganizationMapper.java b/backend/src/main/java/com/yfd/platform/system/mapper/SysOrganizationMapper.java new file mode 100644 index 0000000..e856aae --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/mapper/SysOrganizationMapper.java @@ -0,0 +1,33 @@ +package com.yfd.platform.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yfd.platform.system.domain.SysOrganization; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * 系统组织框架 Mapper 接口 + *

+ * + * @author zhengsl + * @since 2021-12-15 + */ +public interface SysOrganizationMapper extends BaseMapper { + + /*********************************** + * 用途说明:去重查询组织分类 + * 返回值说明: 所有组织分类 + ***********************************/ + List queryOrgtype(); + + /*********************************** + * 用途说明:根据组织分类查询上级id + * 参数说明 + * orgtype 组织分类 + * 返回值说明: 上级id + ***********************************/ + List queryParentid(@Param("orgtype") String orgtype); + +} diff --git a/backend/src/main/java/com/yfd/platform/system/mapper/SysRoleMapper.java b/backend/src/main/java/com/yfd/platform/system/mapper/SysRoleMapper.java new file mode 100644 index 0000000..acb7a81 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/mapper/SysRoleMapper.java @@ -0,0 +1,89 @@ +package com.yfd.platform.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yfd.platform.system.domain.SysRole; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +/** + *

+ * 系统角色 Mapper 接口 + *

+ * + * @author zhengsl + * @since 2021-12-15 + */ +public interface SysRoleMapper extends BaseMapper { + + /*********************************** + * 用途说明:根据角色id查询是否存在用户 + * 参数说明 + * roleid 角色id + * 返回值说明: 该角色下是否存在用户 + ************************************/ + List> isRoleUsersByroleid(String roleid); + + /*********************************** + * 用途说明:根据角色id查询是否存在权限 + * 参数说明 + * roleid 角色id + * 返回值说明: 该角色下是否存在权限 + ************************************/ + List> isRoleMenuByRoleId(String roleId); + + /*********************************** + * 用途说明:查询已分配的用户 + * 参数说明 + *orgid 所属组织 + *username 用户名称 + *status 状态 + *level 角色级别 '1-超级管理员 2-单位管理员 3-普通用户' + * rolename 角色名称 + * isvaild 角色是否有效 + * 返回值说明: 系统用户角色数据集合 + ***********************************/ + List listRoleUsers(String orgid, String username, String status, + String level, String rolename, String isvaild); + + /*********************************** + * 用途说明:根据 角色id和用户id 删除 (admin除外) + * 参数说明 + *roleid 角色id + * urserid 用户id + * 返回值说明: 是否删除成功 + ***********************************/ + boolean deleteRoleUsers(String roleid, String urserid); + + /********************************** + * 用途说明: 根据用户id获取角色信息 + * 参数说明 id 角色id + * 返回值说明: void + ***********************************/ + List getRoleByUserId(String id); + + /********************************** + * 用途说明: 根据角色ID删除菜单与角色关联信息 + * 参数说明 id 角色id + * 返回值说明: void + ***********************************/ + boolean deleteRoleMenus(String id); + + /********************************** + * 用途说明: 根据角色ID删除用户与角色关联信息 + * 参数说明 id 角色id + * 返回值说明: void + ***********************************/ + boolean deleteRoleUser(String id); + + /********************************** + * 用途说明: 根据角色id获取用户id + * 参数说明 id 角色id + * 返回值说明: 用户id + ***********************************/ + List getUserIdById(String id); + + void addRoleMenu(@Param("id") String id, @Param("roleid") String roleid, + @Param("menuid") String menuid); +} diff --git a/backend/src/main/java/com/yfd/platform/system/mapper/SysUserMapper.java b/backend/src/main/java/com/yfd/platform/system/mapper/SysUserMapper.java new file mode 100644 index 0000000..ea57c78 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/mapper/SysUserMapper.java @@ -0,0 +1,96 @@ +package com.yfd.platform.system.mapper; + + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.system.domain.SysUser; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +/** + *

+ * 系统用户表 Mapper 接口 + *

+ * + * @author zhengsl + * @since 2021-10-27 + */ +public interface SysUserMapper extends BaseMapper { + List list(@Param("total")String total, @Param("size")String size, @Param("orgid")String orgid, @Param("username")String username, @Param("mobile")String mobile , @Param("status")String status); + + /*********************************** + * 用途说明:新增系统角色用户对照表 对用户分配角色 + * 参数说明 + * id 生成的id + * roleid 角色id + * userid 用户id + * 返回值说明: + ************************************/ + boolean addUserRoles(@Param("id")String id,@Param("roleid") String roleid,@Param("userid") String userid); + + /*********************************** + * 用途说明:根据用户id 和角色id 查询 系统角色用户对照表 + * 参数说明 + * userid 用户id + * roleid 角色id + * 返回值说明: + ************************************/ + List getRoleUsersByid(@Param("roleid") String roleid,@Param("userid") String userid); + + /*********************************** + * 用途说明:根据用户表id查询角色表所有角色 + * 参数说明 + * userid 用户id + * 返回值说明: + ************************************/ + List getLevel(@Param("userid") String userid); + + /*********************************** + * 用途说明:根据用户表id查询角色表所有角色id + * 参数说明 + * userid 用户id + * 返回值说明: + ************************************/ + List getRoleid(@Param("userid") String userid); + + /*********************************** + * 用途说明:根据用户表id查询角色表级别 + * 参数说明 + * userid 用户id + * 返回值说明: + ************************************/ + String getMaxLevel(@Param("userid") String userid); + + /*********************************** + * 用途说明:根据用户id删除所分配的角色 + * 参数说明 + * userid 用户id + * 返回值说明: + ************************************/ + boolean delRoleUsersByUserid(@Param("userid") String userid); + + /*********************************** + * 用途说明:根据用户id删除所分配的不包含角色 + * 参数说明 + * userid 用户id + * roleids 多个角色id + * 返回值说明: + ************************************/ + boolean delInRoleUsersByUserid(@Param("userid") String userid,@Param("roleids")String[] roleids); + + Page> queryUsers(String orgid, + String username, + Page page); + + Map getOrganizationByid(String id); + + /********************************** + * 用途说明: 根据ID删除用户与角色的关联信息 + * 参数说明 ids 用户id集合 + * 返回值说明: void + ***********************************/ + void delRoleUsersByUserIds(List ids); +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/IMessageService.java b/backend/src/main/java/com/yfd/platform/system/service/IMessageService.java new file mode 100644 index 0000000..2b4c645 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/IMessageService.java @@ -0,0 +1,16 @@ +package com.yfd.platform.system.service; + +import com.yfd.platform.system.domain.Message; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 消息通知 服务类 + *

+ * + * @author TangWei + * @since 2023-03-19 + */ +public interface IMessageService extends IService { + +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/IQuartzJobService.java b/backend/src/main/java/com/yfd/platform/system/service/IQuartzJobService.java new file mode 100644 index 0000000..8b71868 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/IQuartzJobService.java @@ -0,0 +1,43 @@ +package com.yfd.platform.system.service; + +import com.yfd.platform.system.domain.QuartzJob; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 定时任务 服务类 + *

+ * + * @author TangWei + * @since 2023-03-19 + */ +public interface IQuartzJobService extends IService { + + /********************************** + * 用途说明: 新增定时任务 + * 参数说明 quartzJob 定时对象 + * 返回值说明: boolean 是否成功 + ***********************************/ + boolean addQuartzJob(QuartzJob quartzJob); + + /********************************** + * 用途说明: 删除定时任务 + * 参数说明 id id + * 返回值说明: boolean 是否成功 + ***********************************/ + boolean deleteQuartzJob(String id); + + /********************************** + * 用途说明: 拖动修改定时任务顺序 + * 参数说明 fromID 当前ID toID 到达ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回拖动成功或者失败 + ***********************************/ + boolean changeDictOrder(String fromID, String toID); + + /********************************** + * 用途说明: 执行定时任务 + * 参数说明 id id + * 返回值说明: void + ***********************************/ + void execution(QuartzJob byId); +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/ISysConfigService.java b/backend/src/main/java/com/yfd/platform/system/service/ISysConfigService.java new file mode 100644 index 0000000..ef3a399 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/ISysConfigService.java @@ -0,0 +1,22 @@ +package com.yfd.platform.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yfd.platform.system.domain.SysConfig; + +import javax.sound.sampled.UnsupportedAudioFileException; +import java.io.IOException; +import java.util.Map; + +/** + *

+ * 系统全局配置 服务类 + *

+ * + * @author zhengsl + * @since 2022-01-19 + */ +public interface ISysConfigService extends IService { + + + +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/ISysDictionaryItemsService.java b/backend/src/main/java/com/yfd/platform/system/service/ISysDictionaryItemsService.java new file mode 100644 index 0000000..32f9b8f --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/ISysDictionaryItemsService.java @@ -0,0 +1,47 @@ +package com.yfd.platform.system.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yfd.platform.system.domain.SysDictionaryItems; + +import jakarta.servlet.http.HttpServletResponse; +import java.util.List; + +/** + *

+ * 数据字典明细 服务类 + *

+ * + * @author TangWei + * @since 2023-03-08 + */ +public interface ISysDictionaryItemsService extends IService { + + /********************************** + * 用途说明: 分页查询字典项信息 + * 参数说明 dictID 字典ID ItemName 字典项名称 pageNum 当前页 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + Page getDictItemPage(String dictId, String itemName, Page page); + + /********************************** + * 用途说明: 增加字典项 + * 参数说明 sysDictionaryItems 字典项信息 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回增加成功或者失败 + ***********************************/ + boolean addDictionaryItem(SysDictionaryItems sysDictionaryItems); + + /********************************** + * 用途说明: 拖动修改字典项顺序 + * 参数说明 fromID 当前ID toID 到达ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回拖动成功或者失败 + ***********************************/ + boolean changeItemOrder(String fromID, String toID); + + /********************************** + * 用途说明: 导出数据字典项数据 + * 参数说明 sysDictionaryItemsList 所需导出的字典项集合 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回导出成功或失败 + ***********************************/ + void exportExcel(List records, HttpServletResponse response); +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/ISysDictionaryService.java b/backend/src/main/java/com/yfd/platform/system/service/ISysDictionaryService.java new file mode 100644 index 0000000..f3a4990 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/ISysDictionaryService.java @@ -0,0 +1,45 @@ +package com.yfd.platform.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yfd.platform.system.domain.SysDictionary; + +import java.util.List; + +/** + *

+ * 数据字典表 服务类 + *

+ * + * @author TangWei + * @since 2023-03-08 + */ +public interface ISysDictionaryService extends IService { + + /********************************** + * 用途说明: 获取数据字典列表 + * 参数说明 dictType 字典类型 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + List getDictList(String dictType); + + /********************************** + * 用途说明: 新增字典 + * 参数说明 sysDictionary 字典对象 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回增加成功或者失败 + ***********************************/ + boolean addDict(SysDictionary sysDictionary); + + /********************************** + * 用途说明: 根据ID删除字典 + * 参数说明 id 字典ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除结果成功或者失败 + ***********************************/ + boolean deleteDictById(String id); + + /********************************** + * 用途说明: 拖动修改字典顺序 + * 参数说明 fromID 当前ID toID 到达ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回拖动成功或者失败 + ***********************************/ + boolean changeDictOrder(String fromID, String toID); +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/ISysLogService.java b/backend/src/main/java/com/yfd/platform/system/service/ISysLogService.java new file mode 100644 index 0000000..85fdffa --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/ISysLogService.java @@ -0,0 +1,52 @@ +package com.yfd.platform.system.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yfd.platform.system.domain.SysLog; +import org.aspectj.lang.ProceedingJoinPoint; + +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + *

+ * 系统操作日志 服务类 + *

+ * + * @author TangWei + * @since 2023-03-08 + */ +public interface ISysLogService extends IService { + + /********************************** + * 用途说明: 分页查询日志信息 + * 参数说明 pageNum(页码数)、pageSize(页大小,如果是固定页大小可不传)、username(用户名)、(optType) + * 操作类型、startDate(开始日期)、endDate(结束日期) + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + Page getLogList(String username, String optType, + String startDate, + String endDate, Page page); + + + /********************************** + * 用途说明: 导出日志数据 + * 参数说明 sysLogs 所需导出的字典项集合 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回导出成功或者失败 + ***********************************/ + void exportExcel(List sysLogs, HttpServletResponse response) throws IOException; + + /********************************** + * 用途说明: 新增日志 + * 参数说明 nickname 用户名 + * 参数说明 username 用户账号 + * 参数说明 browser 浏览器 + * 参数说明 ip 本机Ip地址 + * 参数说明 joinPoint 连接点 + * 参数说明 log 日志信息 + * 返回值说明: void + ***********************************/ + void save(String nickname,String username, String browser, String ip, ProceedingJoinPoint joinPoint, SysLog log); +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/ISysMenuService.java b/backend/src/main/java/com/yfd/platform/system/service/ISysMenuService.java new file mode 100644 index 0000000..7093e3b --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/ISysMenuService.java @@ -0,0 +1,101 @@ +package com.yfd.platform.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; + +import com.yfd.platform.system.domain.SysMenu; +import org.springframework.web.multipart.MultipartFile; + +import java.io.FileNotFoundException; +import java.util.List; +import java.util.Map; + +/** + *

+ * 菜单及按钮 服务类 + *

+ * + * @author zhengsl + * @since 2021-12-15 + */ +public interface ISysMenuService extends IService { + + /*********************************** + * 用途说明:获取菜单结构树(含按钮) + * 参数说明 + * systemcode 系统 + * name 名称 + * isdisplay 是否显示 + * 返回值说明: 菜单结构树集合 + ***********************************/ + List> getMenuButtonTree(String systemcode, String name, String isdisplay); + + /*********************************** + * 用途说明:获取菜单结构树(不含按钮) + * 参数说明 + * systemcode 系统 + * name 名称 + * isdisplay 是否显示 + * 返回值说明: 菜单结构树集合 + ***********************************/ + List> getMenuTree(String systemcode, String name, String isdisplay); + + + /*********************************** + * 用途说明:新增菜单及按钮 + * 参数说明 + * sysMenu 菜单或按钮表对象 + * 返回值说明: 是否添加成功提示 + ***********************************/ + boolean addMenu(SysMenu sysMenu); + + /*********************************** + * 用途说明:上传单个图标 + * 参数说明 + * id 上传图标id + * icon 图标 + * 返回值说明: 是否上传成功 + ***********************************/ + boolean uploadIcon(String id, MultipartFile icon); + + /*********************************** + * 用途说明:根据id删除单个图标 + * 参数说明 + * id 删除图标id + * icon 图标名称 + * 返回值说明: 是否删除成功 + ***********************************/ + boolean deleteIcon(String id); + + /*********************************** + * 用途说明:菜单及按钮序号排序 + * 参数说明 + * parentid 上级id + * orderMap map<菜单及按钮表id,排列序号> + * 返回值说明: 是否更新成功 + ***********************************/ + boolean moveOrderno(String parentid, String id, int orderno); + + /*********************************** + * 用途说明:根据id删除菜单或按钮 + * 参数说明 + * id 删除列的id + * 返回值说明: 是否删除成功 + ***********************************/ + boolean deleteById(String id); + + boolean changeOderNoById(String fromId, String toId); + + List> getMenuTree(String id); + + /*********************************** + * 用途说明:权限分配 + * 参数说明 + * systemcode 系统 + * name 名称 + * isdisplay 是否显示 + * 返回值说明: 菜单结构树集合 + ***********************************/ + List> permissionAssignment(String roleId); + + String uploadIcon(MultipartFile icon) throws FileNotFoundException; +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/ISysOrganizationService.java b/backend/src/main/java/com/yfd/platform/system/service/ISysOrganizationService.java new file mode 100644 index 0000000..5b03119 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/ISysOrganizationService.java @@ -0,0 +1,60 @@ +package com.yfd.platform.system.service; + + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yfd.platform.system.domain.SysOrganization; + +import java.util.List; +import java.util.Map; + +/** + *

+ * 系统组织框架 服务类 + *

+ * + * @author zhengsl + * @since 2021-12-15 + */ +public interface ISysOrganizationService extends IService { + + /*********************************** + * 用途说明:获取组织结构树 + * 参数说明 + *parentid 上级id + * params 名称(根据名称查询二级) + * 返回值说明: 组织树集合 + ***********************************/ + List> getOrgTree(String parentid, String params); + + /*********************************** + * 用途说明:新增系统组织框架 + * 参数说明 + * sysOrganization 系统组织框架对象 + * 返回值说明: 是否新增成功 + ***********************************/ + boolean addOrg(SysOrganization sysOrganization); + + /*********************************** + * 用途说明:根据企业ID查询组织详情 + * 参数说明 + * id 企业id + * 返回值说明: 系统组织框架对象 + ***********************************/ + List getOrganizationById(String id,String orgName); + + /*********************************** + * 用途说明:获取组织范围树结构 + * 参数说明 + *roleId 角色id + * 返回值说明: 组织树集合 + ***********************************/ + List> getOrgScopeTree(String roleId); + + /********************************** + * 用途说明: 修改角色组织范围 + * 参数说明 roleId 角色id + * 参数说明 orgscope 组织id集合 + * 返回值说明: boolean 是否修改成功 + ***********************************/ + boolean updateOrgScopeByRoleId(String roleId, String orgscope); +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/ISysRoleService.java b/backend/src/main/java/com/yfd/platform/system/service/ISysRoleService.java new file mode 100644 index 0000000..7dcdb5c --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/ISysRoleService.java @@ -0,0 +1,66 @@ +package com.yfd.platform.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yfd.platform.system.domain.SysRole; + +import java.util.List; +import java.util.Map; + +/** + *

+ * 系统角色 服务类 + *

+ * + * @author zhengsl + * @since 2021-12-15 + */ +public interface ISysRoleService extends IService { + + /*********************************** + * 用途说明:新增角色 + * 参数说明 + * sysRole 新增角色信息 + * 返回值说明: 是否新增成功 + ***********************************/ + boolean addRole(SysRole sysRole); + + /*********************************** + * 用途说明:删除角色用户 + * 参数说明 + * id 系统角色用户对照表id + * 返回值说明: 是否新增成功 + ***********************************/ + + boolean deleteRoleUsers(String roleid, String urserids); + + /*********************************** + * 用途说明:根据id删除角色 + * 参数说明 + *id 角色id + * 返回值说明: 是否删除成功 + ***********************************/ + void deleteById(String id); + + /*********************************** + * 用途说明:查询已分配的用户 + * 参数说明 + *orgid 所属组织 + *username 用户名称 + *status 状态 + *level 角色级别 + * rolename 角色名称 + * isvaild 角色是否有效 + * 返回值说明: 系统用户角色数据集合 + ***********************************/ + List listRoleUsers(String orgid, String username, String status, String level, String rolename, String isvaild); + + + /*********************************** + * 用途说明:角色分配权限 + * 参数说明 + * id 角色id + * menuIds 权限id字符串 + * 返回值说明: 是否分配成功 + ***********************************/ + boolean setMenuById(String id, String menuIds); +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/IUserService.java b/backend/src/main/java/com/yfd/platform/system/service/IUserService.java new file mode 100644 index 0000000..b141e53 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/IUserService.java @@ -0,0 +1,143 @@ +package com.yfd.platform.system.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.system.domain.LoginUser; +import com.yfd.platform.system.domain.SysUser; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.util.List; +import java.util.Map; + +/** + *

+ * 系统用户 + *

+ * + * @author zhengsl + * @since 2021-10-27 + */ +public interface IUserService extends IService { + + //获取当前用户账号及名称 + String getUsername(); + + //获取当前用户信息 + SysUser getUserInfo(); + /*********************************** + * 用途说明:获取当前用户账号与姓名 + * 返回值说明: 当前用户账号与姓名 + ************************************/ + Map getNameInfo(); + //获取当前用户信息带权限 + ResponseResult getLoginUserInfo(); + + /*********************************** + * 用途说明:新增用户 + * 参数说明 + *sysUser 新增用户对象 + * id 创建者id + * roleId 角色id + * 返回值说明: 提示字符串 + ************************************/ + Map addUser(SysUser sysUser, String roleids); + + /*********************************** + * 用途说明:查询系统用户 + * 参数说明 + *page 分页集合参数 + *orgid 所属组织 + *username 用户名称 + * mobile 手机号 + * status 状态 + * 返回值说明: 用户分页集合 + ************************************/ + List list(String total, String size, String orgid, String username, + String mobile, String status); + + /*********************************** + * 用途说明:根据ID查询用户详情 + * 参数说明 + *id 用户id + * 返回值说明: 用户表对象 + ************************************/ + Map getOneById(String id); + + /*********************************** + * 用途说明:根据ID修改用户 + * 参数说明 + *sysUser 用户对象 + *roleids 角色id + * 返回值说明: 是否更新成功 + ************************************/ + Map updateById(SysUser sysUser, String roleids); + + /*********************************** + * 用途说明:用户分配角色(多个) + * 参数说明 + *roleid 角色id + * userids 用户id数组 + * 返回值说明: 判断是否添加成功 + ************************************/ + boolean setUserRoles(String roleid, String userids); + + /*********************************** + * 用途说明:根据id删除用户 + * 参数说明 + *id 用户id + * 返回值说明: 判断是否删除成功 + ************************************/ + boolean deleteById(String id); + + /*********************************** + * 用途说明:重置用户密码(管理员) + * 参数说明 + *id 重置密码的 用户id + * 返回值说明: 判断是否重置成功 + ************************************/ + boolean resetPassword(String id) throws Exception; + + /*********************************** + * 用途说明:设置账号状态(管理员) + * 参数说明 + *id 用户id + * status 设置状态 + * 返回值说明: 判断是否设置成功 + ************************************/ + boolean setStatus(String id, String status); + + /*********************************** + * 用途说明:上传用户头像 + * 参数说明 + * id 用户id + * img 账号头像 + * 返回值说明: 判断是否上传 + ***********************************/ + boolean uploadAvatar(String id, MultipartFile img); + + /*********************************** + * 用途说明:新增系统角色用户对照表 对用户分配角色(单个) + * 参数说明 + * id 生成的id + * userid 用户id + * roleid 角色id + * 返回值说明: + ************************************/ + boolean addUserRoles(String roleid, String userid); + + //Page queryUsers(String orgid, String username, Page page); + Page> queryUsers(String orgid, String username, Page page); + + /*********************************** + * 用途说明:根据ID批量删除用户 + * 参数说明 + *ids 用户id集合 + * 返回值说明: 判断是否删除成功 + ************************************/ + boolean deleteUserByIds(String ids); + + + +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/impl/MessageServiceImpl.java b/backend/src/main/java/com/yfd/platform/system/service/impl/MessageServiceImpl.java new file mode 100644 index 0000000..0d3875f --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/impl/MessageServiceImpl.java @@ -0,0 +1,20 @@ +package com.yfd.platform.system.service.impl; + +import com.yfd.platform.system.domain.Message; +import com.yfd.platform.system.mapper.MessageMapper; +import com.yfd.platform.system.service.IMessageService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 消息通知 服务实现类 + *

+ * + * @author TangWei + * @since 2023-03-19 + */ +@Service +public class MessageServiceImpl extends ServiceImpl implements IMessageService { + +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/impl/QuartzJobServiceImpl.java b/backend/src/main/java/com/yfd/platform/system/service/impl/QuartzJobServiceImpl.java new file mode 100644 index 0000000..d2a9030 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/impl/QuartzJobServiceImpl.java @@ -0,0 +1,114 @@ +package com.yfd.platform.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.yfd.platform.system.domain.QuartzJob; +import com.yfd.platform.system.domain.SysDictionary; +import com.yfd.platform.system.mapper.QuartzJobMapper; +import com.yfd.platform.system.service.IQuartzJobService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yfd.platform.utils.QuartzManage; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + *

+ * 定时任务 服务实现类 + *

+ * + * @author TangWei + * @since 2023-03-19 + */ +@Service +public class QuartzJobServiceImpl extends ServiceImpl implements IQuartzJobService { + + @Resource + private QuartzJobMapper quartzJobMapper; + + @Resource + private QuartzManage quartzManage; + + /********************************** + * 用途说明: 新增定时任务 + * 参数说明 quartzJob 定时对象 + * 返回值说明: boolean 是否成功 + ***********************************/ + @Override + public boolean addQuartzJob(QuartzJob quartzJob) { + // 生成序号 + long orderNo = this.count() + 1L; + quartzJob.setOrderno((int) orderNo); + return this.save(quartzJob); + } + + /********************************** + * 用途说明: 删除定时任务 + * 参数说明 id id + * 返回值说明: boolean 是否成功 + ***********************************/ + @Override + public boolean deleteQuartzJob(String id) { + String[] split = id.split(","); + Set ids = Arrays.stream(split).collect(Collectors.toSet()); + for (String s : ids) { + QuartzJob quartzJob = this.getById(s); + quartzManage.deleteJob(quartzJob); + this.removeById(s); + } + + // 查询所有定时任务 + List list = + this.list(new LambdaQueryWrapper().orderByAsc(QuartzJob::getOrderno)); + // 更新序号 + for (int i = 0; i < list.size(); i++) { + QuartzJob quartzJob = list.get(i); + quartzJob.setOrderno(i + 1); + this.updateById(quartzJob); + } + return true; + } + + /********************************** + * 用途说明: 拖动修改定时任务顺序 + * 参数说明 fromID 当前ID toID 到达ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回拖动成功或者失败 + ***********************************/ + @Override + public boolean changeDictOrder(String fromID, String toID) { + QuartzJob fromQuartzJob = + quartzJobMapper.selectById(fromID); + QuartzJob toQuartzJob = quartzJobMapper.selectById(toID); + // 如果数据字典不存在拖动失败 + if (fromQuartzJob == null || toQuartzJob == null) { + return false; + } + Integer fromOrderNo = fromQuartzJob.getOrderno(); + Integer toOrderNo = toQuartzJob.getOrderno(); + // 如果数据字典的顺序号不存在拖动失败 + if (fromOrderNo == null || toOrderNo == null) { + return false; + } + // 将顺序号放入字典对象中 + fromQuartzJob.setOrderno(toOrderNo); + toQuartzJob.setOrderno(fromOrderNo); + // 更改顺序号 + boolean fromBool = this.updateById(fromQuartzJob); + boolean toBool = this.updateById(toQuartzJob); + return fromBool && toBool; + } + + /********************************** + * 用途说明: 执行定时任务 + * 参数说明 id id + * 返回值说明: void + ***********************************/ + @Override + public void execution(QuartzJob quartzJob) { + quartzManage.runJobNow(quartzJob); + } +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/impl/SysConfigServiceImpl.java b/backend/src/main/java/com/yfd/platform/system/service/impl/SysConfigServiceImpl.java new file mode 100644 index 0000000..da15188 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/impl/SysConfigServiceImpl.java @@ -0,0 +1,34 @@ +package com.yfd.platform.system.service.impl; + + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yfd.platform.system.domain.SysConfig; +import com.yfd.platform.system.mapper.SysConfigMapper; +import com.yfd.platform.system.service.ISysConfigService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import javax.sound.sampled.UnsupportedAudioFileException; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + *

+ * 系统全局配置 服务实现类 + *

+ * + * @author zhengsl + * @since 2022-01-19 + */ +@Service +public class SysConfigServiceImpl extends ServiceImpl implements ISysConfigService { + @Resource + private UserServiceImpl currentUser; + + + + +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/impl/SysDictionaryItemsServiceImpl.java b/backend/src/main/java/com/yfd/platform/system/service/impl/SysDictionaryItemsServiceImpl.java new file mode 100644 index 0000000..26a30ab --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/impl/SysDictionaryItemsServiceImpl.java @@ -0,0 +1,123 @@ +package com.yfd.platform.system.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yfd.platform.system.domain.SysDictionaryItems; +import com.yfd.platform.system.mapper.SysDictionaryItemsMapper; +import com.yfd.platform.system.service.ISysDictionaryItemsService; +import com.yfd.platform.utils.FileUtil; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + *

+ * 数据字典明细 服务实现类 + *

+ * + * @author TangWei + * @since 2023-03-08 + */ +@Service +public class SysDictionaryItemsServiceImpl extends ServiceImpl implements ISysDictionaryItemsService { + + @Resource + private SysDictionaryItemsMapper sysDictionaryItemsMapper; + + /********************************** + * 用途说明: 分页查询字典项信息 + * 参数说明 dictID 字典ID ItemName 字典项名称 pageNum 当前页 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + @Override + public Page getDictItemPage(String dictId, + String itemName, + Page page) { + LambdaQueryWrapper queryWrapper = + new LambdaQueryWrapper<>(); + if (StrUtil.isNotBlank(itemName)) { + queryWrapper.like(SysDictionaryItems::getDictName, itemName); + } + queryWrapper.eq(SysDictionaryItems::getDictId, dictId).orderByAsc(SysDictionaryItems::getOrderNo); + return sysDictionaryItemsMapper.selectPage(page, queryWrapper); + } + + /********************************** + * 用途说明: 增加字典项 + * 参数说明 sysDictionaryItems 字典项信息 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回增加成功或者失败 + ***********************************/ + @Override + public boolean addDictionaryItem(SysDictionaryItems sysDictionaryItems) { + LambdaQueryWrapper queryWrapper = + new LambdaQueryWrapper<>(); + queryWrapper.eq(SysDictionaryItems::getDictId,sysDictionaryItems.getDictId()); + long orderNo = this.count(queryWrapper) + 1L; + // 添加顺序号 + sysDictionaryItems.setOrderNo((int) orderNo); + return this.save(sysDictionaryItems); + } + + /********************************** + * 用途说明: 拖动修改字典项顺序 + * 参数说明 fromID 当前ID toID 到达ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回拖动成功或者失败 + ***********************************/ + @Override + public boolean changeItemOrder(String fromID, String toID) { + SysDictionaryItems fromSysDictionaryItems = + sysDictionaryItemsMapper.selectById(fromID); + SysDictionaryItems toSysDictionaryItems = + sysDictionaryItemsMapper.selectById(toID); + // 如果数据字典项不存在拖动失败 + if (fromSysDictionaryItems == null || toSysDictionaryItems == null) { + return false; + } + Integer fromOrderNo = fromSysDictionaryItems.getOrderNo(); + Integer toOrderNo = toSysDictionaryItems.getOrderNo(); + // 如果数据字典的顺序号不存在拖动失败 + if (fromOrderNo == null || toOrderNo == null) { + return false; + } + // 将顺序号放入字典对象中 + fromSysDictionaryItems.setOrderNo(toOrderNo); + toSysDictionaryItems.setOrderNo(fromOrderNo); + // 更改顺序号 + boolean fromBool = this.updateById(fromSysDictionaryItems); + boolean toBool = this.updateById(toSysDictionaryItems); + return fromBool && toBool; + + } + + /********************************** + * 用途说明: 导出数据字典项数据 + * 参数说明 sysDictionaryItemsList 所需导出的字典项集合 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回导出成功或失败 + ***********************************/ + @Override + public void exportExcel(List sysDictionaryItems, + HttpServletResponse response) { + try { + List> list = new LinkedList<>(); + for (SysDictionaryItems sysDictionaryItem : sysDictionaryItems) { + Map map = new LinkedHashMap<>(); + map.put("项编号", sysDictionaryItem.getItemCode()); + map.put("项名称", sysDictionaryItem.getDictName()); + map.put("父编码", sysDictionaryItem.getParentCode()); + map.put("备注", sysDictionaryItem.getCustom1()); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/impl/SysDictionaryServiceImpl.java b/backend/src/main/java/com/yfd/platform/system/service/impl/SysDictionaryServiceImpl.java new file mode 100644 index 0000000..58508b3 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/impl/SysDictionaryServiceImpl.java @@ -0,0 +1,114 @@ +package com.yfd.platform.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yfd.platform.system.domain.SysDictionary; +import com.yfd.platform.system.domain.SysDictionaryItems; +import com.yfd.platform.system.mapper.SysDictionaryItemsMapper; +import com.yfd.platform.system.mapper.SysDictionaryMapper; +import com.yfd.platform.system.service.ISysDictionaryService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + *

+ * 数据字典表 服务实现类 + *

+ * + * @author TangWei + * @since 2023-03-08 + */ +@Service +public class SysDictionaryServiceImpl extends ServiceImpl implements ISysDictionaryService { + + @Resource + private SysDictionaryMapper sysDictionaryMapper; + + @Resource + private SysDictionaryItemsMapper sysDictionaryItemsMapper; + + /********************************** + * 用途说明: 获取数据字典列表 + * 参数说明 dictType 字典类型 + * 返回值说明: 返回字典列表集合 + ***********************************/ + @Override + public List getDictList(String dictType) { + LambdaQueryWrapper queryWrapper = + new LambdaQueryWrapper<>(); + queryWrapper.eq(SysDictionary::getDictType, dictType).orderByAsc(SysDictionary::getOrderNo); + return sysDictionaryMapper.selectList(queryWrapper); + } + + /********************************** + * 用途说明: 新增字典 + * 参数说明 sysDictionary 字典对象 + * 返回值说明: 返回增加成功或者失败 + ***********************************/ + @Override + public boolean addDict(SysDictionary sysDictionary) { + //int orderNo = this.count() + 1; + Integer maxNo = + sysDictionaryMapper.selectMaxNo(sysDictionary.getDictType()); + if (maxNo == null) { + maxNo = 0; + } + // 添加顺序号 + sysDictionary.setOrderNo(maxNo + 1); + return this.save(sysDictionary); + } + + /********************************** + * 用途说明: 根据ID删除字典 + * 参数说明 id 字典ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除结果成功或者失败 + ***********************************/ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean deleteDictById(String id) { + // 根据字典编码查询字典项中是否关联 + boolean isok=true; + QueryWrapper Wrapper = new QueryWrapper<>(); + Wrapper.eq("dictid", id); + if(sysDictionaryItemsMapper.delete(Wrapper)>0) { + isok=true; + } + return isok&&this.removeById(id); + } + + /********************************** + * 用途说明: 拖动修改字典顺序 + * 参数说明 fromID 当前ID toID 到达ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回拖动成功或者失败 + ***********************************/ + @Override + public boolean changeDictOrder(String fromID, String toID) { + SysDictionary fromSysDictionary = + sysDictionaryMapper.selectById(fromID); + SysDictionary toSysDictionary = sysDictionaryMapper.selectById(toID); + // 如果数据字典不存在拖动失败 + if (fromSysDictionary == null || toSysDictionary == null) { + return false; + } + Integer fromOrderNo = fromSysDictionary.getOrderNo(); + Integer toOrderNo = toSysDictionary.getOrderNo(); + // 如果数据字典的顺序号不存在拖动失败 + if (fromOrderNo == null || toOrderNo == null) { + return false; + } + // 将顺序号放入字典对象中 + fromSysDictionary.setOrderNo(toOrderNo); + toSysDictionary.setOrderNo(fromOrderNo); + // 更改顺序号 + boolean fromBool = this.updateById(fromSysDictionary); + boolean toBool = this.updateById(toSysDictionary); + return fromBool && toBool; + } + +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/impl/SysLogServiceImpl.java b/backend/src/main/java/com/yfd/platform/system/service/impl/SysLogServiceImpl.java new file mode 100644 index 0000000..ee671bf --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/impl/SysLogServiceImpl.java @@ -0,0 +1,219 @@ +package com.yfd.platform.system.service.impl; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yfd.platform.annotation.Log; +import com.yfd.platform.system.domain.SysLog; +import com.yfd.platform.system.mapper.SysLogMapper; +import com.yfd.platform.system.mapper.SysUserMapper; +import com.yfd.platform.system.service.ISysLogService; +import com.yfd.platform.utils.FileUtil; +import com.yfd.platform.utils.StringUtils; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +/** + *

+ * 系统操作日志 服务实现类 + *

+ * + * @author TangWei + * @since 2023-03-08 + */ +@Service +public class SysLogServiceImpl extends ServiceImpl implements ISysLogService { + + @Resource + private SysLogMapper sysLogMapper; + + /********************************** + * 用途说明: 分页查询日志信息 + * 参数说明 pageNum(页码数)、pageSize(页大小,如果是固定页大小可不传)、username(用户名)、(optType) + * 操作类型、startDate(开始日期)、endDate(结束日期) + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + @Override + public Page getLogList(String username, String optType, + String startDate, + String endDate, Page page) { + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + // 没有传username就不按此条件查询 + if (StrUtil.isNotBlank(username)) { + queryWrapper.like(SysLog::getUsername, username); + } + // 没有传optType就不按此条件查询 + if (StrUtil.isNotBlank(optType)) { + queryWrapper.eq(SysLog::getOpttype, optType); + } + DateTime parseStartDate = DateUtil.parse(startDate); + DateTime parseEndDate = DateUtil.parse(endDate); + DateTime dateTime = DateUtil.offsetDay(parseEndDate, 1); + if (parseStartDate != null && parseEndDate != null) { + queryWrapper.ge(SysLog::getLogtime, parseStartDate).lt(SysLog::getLogtime, dateTime); + } + + queryWrapper.orderByDesc(SysLog::getLogtime); + return sysLogMapper.selectPage(page, queryWrapper); + } + + /********************************** + * 用途说明: 导出日志数据 + * 参数说明 sysLogs 所需导出的字典项集合 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回导出成功或者失败 + ***********************************/ + @Override + public void exportExcel(List sysLogs, + HttpServletResponse response) { + try { + List> list = new LinkedList<>(); + for (SysLog sysLog : sysLogs) { + Map map = new LinkedHashMap<>(); + map.put("操作账号", sysLog.getUsercode()); + map.put("用户姓名", sysLog.getUsername()); + map.put("IP地址", sysLog.getRequestip()); + map.put("浏览器", sysLog.getBrowser()); + map.put("日志类型", sysLog.getOpttype()); + map.put("模块名称", sysLog.getModule()); + map.put("日志描述", sysLog.getDescription()); + Timestamp logTime = sysLog.getLogtime(); + String dateTime = ""; + if (logTime != null) { + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd " + + "HH:mm:ss"); + dateTime = df.format(logTime); + } + + /*String dateTime = ""; + if (logTime != null) { + dateTime = logTime.format(DateTimeFormatter.ofPattern( + "yyyy-MM-dd HH:mm:ss")); + }*/ + + map.put("操作日期", dateTime); + list.add(map); + } + FileUtil.downloadExcel(list, response); + } catch (Exception e) { + e.printStackTrace(); + } + + } + + /********************************** + * 用途说明: 新增日志 + * 参数说明 nickname 用户名 + * 参数说明 username 用户账号 + * 参数说明 browser 浏览器 + * 参数说明 ip 本机Ip地址 + * 参数说明 joinPoint 连接点 + * 参数说明 log 日志信息 + * 返回值说明: void + ***********************************/ + @Override + public void save(String nickname, String username, String browser, + String ip, + ProceedingJoinPoint joinPoint, SysLog log) { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + Log aopLog = method.getAnnotation(Log.class); + // 方法路径 + String methodName = + joinPoint.getTarget().getClass().getName() + "." + signature.getName() + "()"; + // 描述 + if (log != null) { + log.setDescription(aopLog.value()); + log.setModule(aopLog.module()); + } + assert log != null; + log.setUsercode(username); + log.setRequestip(ip); + log.setMethod(methodName); + log.setUsername(nickname); + log.setParams(getParameter(method, joinPoint.getArgs())); + log.setBrowser(browser); + String operationtype = getOperationtype(signature.getName()); + log.setOpttype(operationtype); + log.setLogtime(new Timestamp(System.currentTimeMillis())); + sysLogMapper.insert(log); + } + + /** + * 根据方法和传入的参数获取请求参数 + */ + private String getParameter(Method method, Object[] args) { + List argList = new ArrayList<>(); + Parameter[] parameters = method.getParameters(); + for (int i = 0; i < parameters.length; i++) { + //将RequestBody注解修饰的参数作为请求参数 + AnnotatedType type = parameters[i].getAnnotatedType(); + + RequestBody requestBody = + parameters[i].getAnnotation(RequestBody.class); + if (requestBody != null) { + argList.add(args[i]); + } + + //将RequestParam注解修饰的参数作为请求参数 + RequestParam requestParam = + parameters[i].getAnnotation(RequestParam.class); + if (requestParam != null) { + Map map = new HashMap<>(); + String key = parameters[i].getName(); + if (!StringUtils.isEmpty(requestParam.value())) { + key = requestParam.value(); + } + map.put(key, args[i]); + argList.add(map); + } + } + if (argList.size() == 0) { + return ""; + } + return argList.size() == 1 ? JSONUtil.toJsonStr(argList.get(0)) : + JSONUtil.toJsonStr(argList); + } + + public static String getOperationtype(String value) { + String type = ""; + if (value.contains("get") || value.contains("select")) { + type = "查询(select)"; + } else if (value.contains("add") || value.contains("insert")) { + type = "添加(insert)"; + } else if (value.contains("update") || value.contains("upd") || value.contains("change") || value.contains("set")) { + type = "修改(update)"; + } else if (value.contains("delete") || value.contains("del")) { + type = "删除(delete)"; + } else if (value.contains("dowload")) { + type = "下载(dowload)"; + } else if (value.contains("import")) { + type = "导入(import)"; + } else if (value.contains("word")) { + type = "word转pdf(wordToPdf)"; + } else { + type = "其他(other)"; + } + return type; + } + +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/impl/SysMenuServiceImpl.java b/backend/src/main/java/com/yfd/platform/system/service/impl/SysMenuServiceImpl.java new file mode 100644 index 0000000..b53b789 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/impl/SysMenuServiceImpl.java @@ -0,0 +1,674 @@ +package com.yfd.platform.system.service.impl; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yfd.platform.system.domain.SysMenu; +import com.yfd.platform.system.domain.SysRole; +import com.yfd.platform.system.mapper.SysMenuMapper; +import com.yfd.platform.system.mapper.SysRoleMapper; +import com.yfd.platform.system.service.ISysMenuService; +import com.yfd.platform.utils.FileUtil; +import com.yfd.platform.config.FileSpaceProperties; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.ResourceUtils; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.annotation.Resource; +import java.io.File; +import java.io.FileNotFoundException; +import java.sql.Timestamp; +import java.text.DecimalFormat; +import java.util.*; +import java.util.stream.Collectors; + +/** + *

+ * 菜单及按钮 服务实现类 + *

+ * + * @author zhengsl + * @since 2021-12-15 + */ +@Service +@Transactional +public class SysMenuServiceImpl extends ServiceImpl implements ISysMenuService { + + @Resource + private SysMenuMapper sysMenuMapper; + + @Resource + private UserServiceImpl currentUser; + + @Resource + private SysRoleMapper sysRoleMapper; + + // 菜单图片路径配置 + @Resource + private FileSpaceProperties fileSpaceProperties; + + /*********************************** + * 用途说明:查询菜单及按钮树状图 + * 参数说明 + * systemcode 系统 + *isdisplay 是否显示 + * 返回值说明: 菜单结构树集合 + ***********************************/ + @Override + public List> getMenuButtonTree(String systemcode, + String name, + String isdisplay) { + List> listMap=null; + if(StrUtil.isEmpty(name)){//不带名称查询,返回树结构 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("parentid", "0").eq("systemcode", systemcode).orderByAsc("orderno"); + listMap = this.listMaps(queryWrapper); + + for (int i = 0; i < listMap.size(); i++) { + //查询下一子集 + List> childList = child(listMap.get(i).get( + "id").toString(), systemcode, name, null, null); + listMap.get(i).put("children", childList); //添加新列 子集 + } + }else{ //根据菜单名称查询,直接返回类别 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.like("name", name).eq("systemcode", systemcode).orderByAsc("name"); + listMap = this.listMaps(queryWrapper); + } + + + + return listMap; + } + + /*********************************** + * 用途说明:获取菜单结构树(不含按钮) + * 参数说明 + * systemcode 系统 + * isdisplay 是否显示 + * 返回值说明: 菜单结构树集合 + ***********************************/ + @Override + public List> getMenuTree(String systemcode, + String name, + String isdisplay) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (StrUtil.isNotEmpty(isdisplay)) { + queryWrapper.eq("isdisplay", isdisplay); + } else { + queryWrapper.eq("isdisplay", 1); + } + + //根据系统 ,类型不为2 显示,序号 正序排序 + queryWrapper.eq("parentid", "0").eq("systemcode", systemcode).ne( + "type", "2").orderByAsc("orderno"); + List> listMap = this.listMaps(queryWrapper); + for (int i = 0; i < listMap.size(); i++) { + List> childList = child(listMap.get(i).get( + "id").toString(), systemcode, name, isdisplay, "2");//查询下一子集 + listMap.get(i).put("children", childList); //添加新列 子集 + } + return listMap; + } + + /*********************************** + * 用途说明:查询菜单及按钮树状图 + * 参数说明 + * parentid 上级id + *systemcode 系统 + * isdisplay 是否显示 + * type 按钮 + * 返回值说明: 菜单结构树集合 + ***********************************/ + private List> child(String parentid, + String systemcode, String name, + String isdisplay, String type) { + List> listMap = new ArrayList<>(); + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("parentid", parentid).eq("systemcode", systemcode); + //根据上级id 系统 查询 + if (StrUtil.isNotEmpty(type)) { + queryWrapper.ne("type", type); + } + + if (StrUtil.isNotEmpty(name)) { //根据菜单名称查询 + queryWrapper.like("name", name); + } + listMap = this.listMaps(queryWrapper.orderByAsc("orderno")); + if (listMap.size() > 0) { //判断是否存在子集 + for (int i = 0; i < listMap.size(); i++) { //遍历表数据 + List> childList = + child(listMap.get(i).get("id").toString(), systemcode + , name, isdisplay, type); //循环获取下一子集 + listMap.get(i).put("children", childList); //添加新列 子集 + } + } + + return listMap; + } + + /*********************************** + * 用途说明:新增菜单及按钮 + * 参数说明 + * sysMenu 菜单或按钮表对象 + * 返回值说明: 是否添加成功提示 + ***********************************/ + @Override + public boolean addMenu(SysMenu sysMenu) { + String parentId = sysMenu.getParentid(); + QueryWrapper queryWrapper = new QueryWrapper<>(); + //根据上级id 查询到总数 并累加 + long orderno = this.count(queryWrapper.eq("parentid", + parentId)) + 1L; + // 生成排序号 + sysMenu.setOrderno((int) orderno); + // 生成编号 + QueryWrapper queryMaxCode = new QueryWrapper<>(); + queryMaxCode.eq("parentid", + parentId); + // 查询最大的编号 + List maxList = this.listObjs(queryMaxCode.select("max(code) " + + "code").eq("systemcode", sysMenu.getSystemcode())); + SysMenu parentMenu = sysMenuMapper.selectById(parentId); + // 最大编号转换成int类型 + String maxCode = maxList.size() > 0 ? maxList.get(0).toString() : "0"; + int max = ObjectUtil.isEmpty(maxList) ? 0 : + Integer.parseInt(maxCode); + DecimalFormat df; + if ("0".equals(sysMenu.getParentid())) { + df = new DecimalFormat("00"); + } else if (parentMenu.getCode().length() == 2) { + df = new DecimalFormat("0000"); + } else { + df = new DecimalFormat("000000"); + } + //DecimalFormat df = new DecimalFormat("00"); + //int parentCode = Integer.parseInt(parentMenu.getCode()); + String parentCode = ""; + if (parentMenu != null) { + parentCode = parentMenu.getCode(); + } + // 生成的新编号 年月日+4位编号 + String code; + if (max > 0) { + code = df.format(max + 1); + } else { + max = max + 1; + if (StrUtil.isBlank(parentCode)) { + parentCode = "0" + max; + } else { + int i = Integer.parseInt(parentCode); + parentCode = i + "0" + max; + } + + int format = Integer.parseInt(parentCode); + code = df.format(format); + } + + // 判断是否显示字段 是否填写 为空 + if (StrUtil.isEmpty(sysMenu.getIsdisplay())) { + // 默认设置成 1 显示 + sysMenu.setIsdisplay("1"); + } + // 判断是否填写父级id 为空 默认设置成 0 + if (StrUtil.isEmpty(sysMenu.getParentid())) { + sysMenu.setParentid("0"); + } + // 添加编号 + sysMenu.setCode(code); + // 添加排序号 + sysMenu.setOrderno((int) orderno); + // 添加最近修改人 + sysMenu.setLastmodifier(currentUser.getUsername()); + // 添加最近修改时间 + sysMenu.setLastmodifydate(new Timestamp(System.currentTimeMillis())); + return this.save(sysMenu); + } + + /*********************************** + * 用途说明:上传单个图标 + * 参数说明 + * id 上传图标id + * icon 图标 + * 返回值说明: 是否上传成功 + ***********************************/ + @Override + public boolean uploadIcon(String id, MultipartFile icon) { + //根据id查询 + SysMenu sysMenu = this.getById(id); + //图片路径 + String iconPath = fileSpaceProperties.getSystem() + "menu" + File.separator; + String iconname = + IdUtil.fastSimpleUUID() + "." + FileUtil.getExtensionName(icon.getOriginalFilename()); + //上传图标并获取图标名称 (图片改为png格式) + String filename = + FileUtil.upload(icon, iconPath, iconname).getName(); + //更新图标名称 + sysMenu.setIcon(filename); + //添加最近修改人 + sysMenu.setLastmodifier(currentUser.getUsername()); + //添加最近修改时间 + sysMenu.setLastmodifydate(new Timestamp(System.currentTimeMillis())); + //更新数据 + boolean isOk = this.updateById(sysMenu); + return isOk; + } + + /*********************************** + * 用途说明:根据id删除单个图标 + * 参数说明 + * id 删除图标id + * icon 图标名称 + * 返回值说明: 是否删除成功 + ***********************************/ + @Override + public boolean deleteIcon(String id) { + //根据id查询 + SysMenu sysMenu = this.getById(id); + //图片路径 + String iconname = + System.getProperty("user.dir") + "\\src\\main" + + "\\resources\\static\\icon" + File.separator + sysMenu.getIcon(); + //更新图标名称 + sysMenu.setIcon(""); + //添加最近修改人 + sysMenu.setLastmodifier(currentUser.getUsername()); + //添加最近修改时间 + sysMenu.setLastmodifydate(new Timestamp(System.currentTimeMillis())); + //更新数据 + boolean isOk = this.updateById(sysMenu); + //更新成功 删除图片 + if (isOk == true) { + FileUtil.del(iconname); + } + return isOk; + } + + /*********************************** + * 用途说明:菜单及按钮序号排序 + * 参数说明 + * parentid 上级id + * id + * orderno 更改后序号 + * 返回值说明: 是否更新成功 + ***********************************/ + @Override + public boolean moveOrderno(String parentid, String id, int orderno) { + boolean ok = true; + SysMenu sysMenu = this.getById(id); //根据id查询原顺序号 + if (sysMenu.getOrderno() > orderno) { + ok = sysMenuMapper.upMoveOrderno(parentid, sysMenu.getOrderno(), + orderno); //根据 父级id 小于原序号 大于等于更改序号 + } else { + ok = sysMenuMapper.downMoveOrderno(parentid, sysMenu.getOrderno() + , orderno); //根据 父级id 大于原序号 小于等于更改序号 + } + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + ok = ok && this.update(updateWrapper.eq("id", id).set("orderno", + orderno)); //根据 id修改序号 + return ok; + } + + /*********************************** + * 用途说明:根据id删除菜单或按钮 + * 参数说明 + * id 删除列的id + * 返回值说明: 是否删除成功 + ***********************************/ + @Override + public boolean deleteById(String id) { + //根据id查询 + SysMenu sysMenu = this.getById(id); + //图片路径 + String iconname = + fileSpaceProperties.getSystem() + "menu" + File.separator + sysMenu.getIcon(); + //删除图标 + new File(iconname).delete(); + //根据id删除 + boolean isOk = this.removeById(id); + //删除成功同步更新表数据 + if (isOk) { + //1 创建list集合,用于封装所有删除目录或菜单id值 + List idList = new ArrayList<>(); + this.selectPermissionChildById(id, idList); + if (idList.size() > 0) { + sysMenuMapper.deleteBatchIds(idList); + } + QueryWrapper queryWrapper = new QueryWrapper<>(); + //根据上级id 查询 根据 orderno 正序排序 + queryWrapper.eq("parentid", sysMenu.getParentid()).orderByAsc( + "orderno"); + List list = this.list(queryWrapper); + for (int i = 0; i < list.size(); i++) { + SysMenu menu = list.get(i); + //更新序列号 + menu.setOrderno(i + 1); + } + //更新表数据 + this.updateBatchById(list); + } + return isOk; + } + + //2 根据当前菜单id,查询菜单里面子菜单id,封装到list集合 + private void selectPermissionChildById(String id, List idList) { + //查询菜单里面子菜单id + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("parentid", id); + wrapper.select("id"); + List childIdList = baseMapper.selectList(wrapper); + //把childIdList里面菜单id值获取出来,封装idList里面,做递归查询 + childIdList.stream().forEach(item -> { + //封装idList里面 + idList.add(item.getId()); + //递归查询 + this.selectPermissionChildById(item.getId(), idList); + }); + } + + /********************************** + * 用途说明: 调换菜单或按钮的位置 + * 参数说明 upperId 选中的菜单Id + * 参数说明 belowId 切换的菜单Id + * 返回值说明: boolean + ***********************************/ + @Override + @Transactional + public boolean changeOderNoById(String fromId, String toId) { + SysMenu fromSysMenu = this.getById(fromId); + SysMenu toSysMenu = this.getById(toId); + // 如果菜单或按钮不存在拖动失败 + if (fromSysMenu == null || toSysMenu == null) { + return false; + } + Integer fromOrderNo = fromSysMenu.getOrderno(); + Integer toOrderNo = toSysMenu.getOrderno(); + // 如果菜单或按钮的顺序号不存在拖动失败 + if (fromOrderNo == null || toOrderNo == null) { + return false; + } + fromSysMenu.setOrderno(toOrderNo); + toSysMenu.setOrderno(fromOrderNo); + boolean fromBool = this.updateById(fromSysMenu); + boolean toBool = this.updateById(toSysMenu); + return fromBool && toBool; + } + + /********************************** + * 用途说明: 根据用户id获取菜单树 + * 参数说明 id 用户id + * 返回值说明: 返回菜单树 + ***********************************/ + @Override + public List> getMenuTree(String id) { + // 根据id获取菜单 + //List sysMenus = sysMenuMapper.selectMenuByUserId(id); + List> list; + if (StrUtil.isBlank(id)) { + LambdaQueryWrapper queryWrapper = + new LambdaQueryWrapper<>(); + list = this.listMaps(queryWrapper.eq(SysMenu::getIsdisplay, "1").ne(SysMenu::getType, "2").eq(SysMenu::getSystemcode, "1").orderByAsc(SysMenu::getOrderno)); + } else { + list = sysMenuMapper.selectMenuByUserId(id); + } + // 将菜单转换成树 + List> sysMenus = buildTreeLeft(list); + return sysMenus; + } + + /*********************************** + * 用途说明:权限分配 + * 参数说明 + * systemcode 系统 + * name 名称 + * isdisplay 是否显示 + * 返回值说明: 菜单结构树集合 + ***********************************/ + @Override + public List> permissionAssignment(String roleId) { + + String code = sysMenuMapper.getSystemCodeById(roleId); + if (code == null) { + code = "1"; + } + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysMenu::getSystemcode, code).select(SysMenu::getId, + SysMenu::getParentid, SysMenu::getName).orderByAsc + (SysMenu::getOrderno); + List> listAll = + sysMenuMapper.selectMaps(queryWrapper); + List listRole = + sysMenuMapper.selectMenuByRoleId(roleId); + for (Map map : listAll) { + String id = (String) map.get("id"); + if (listRole.contains(id)) { + map.put("checkinfo", true); + } else { + map.put("checkinfo", false); + } + + } + List> listTree = buildTrees(listAll); + return listTree; + } + + // 另一种方法 + /*public List> permissionAssignment(String roleId) { + + String code = sysMenuMapper.getSystemCodeById(roleId); + if (code == null) { + code = "1"; + } + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysMenu::getSystemcode, code).select(SysMenu::getId, + SysMenu::getParentid, SysMenu::getName).orderByAsc + (SysMenu::getOrderno); + List> listAll = + sysMenuMapper.selectMaps(queryWrapper); + *//*List listRole = + sysMenuMapper.selectMenuByRoleId(roleId);*//* + SysRole sysRole = sysRoleMapper.selectById(roleId); + String optscope = sysRole.getOptscope(); + // 将当前角色所对应权限id拆分 + String[] split = optscope.split(","); + List listRole = Arrays.asList(split); + for (Map map : listAll) { + String id = (String) map.get("id"); + if (listRole.contains(id)) { + map.put("checkinfo", true); + } else { + map.put("checkinfo", false); + } + + } + List> listTree = buildTrees(listAll); + return listTree; + }*/ + + /*********************************** + * 用途说明:上传单个图标 + * 参数说明 + * icon 图标 + * 返回值说明: 是否上传成功 + ***********************************/ + @Override + public String uploadIcon(MultipartFile icon) throws FileNotFoundException { + + String path = System.getProperty("user.dir") + "\\src\\main" + + "\\resources\\"; + //图片路径 + String iconPath = path + "static\\icon" + File.separator; + String iconname = + IdUtil.fastSimpleUUID() + "." + FileUtil.getExtensionName(icon.getOriginalFilename()); + //上传图标并获取图标名称 (图片改为png格式) + String filename = + FileUtil.upload(icon, iconPath, iconname).getName(); + return filename; + } + + /** + * 菜单集合递归生成树状菜单(List) + * + * @param sysMenus 菜单对象 + * @return + */ + /* public List buildTree(List sysMenus) { + List resultMenuList = new ArrayList<>(); + for (SysMenu sysMenu : sysMenus) { + + for (SysMenu menu : sysMenus) { + if (menu.getParentid().equals(sysMenu.getId())) { + sysMenu.getChildren().add(menu); + } + } + if ("0".equals(sysMenu.getParentid())) { + resultMenuList.add(sysMenu); + } + } + return resultMenuList; + }*/ + + /** + * 菜单集合递归生成树状菜单(Map)(暂不使用该方法) + * + * @param sysMenus 菜单对象 + * @return + */ + public List> buildTree(List> sysMenus) { + List> resultMenuList = new ArrayList<>(); + for (Map sysMenu : sysMenus) { + + List> childrenList = new ArrayList<>(); + for (Map menu : sysMenus) { + if (menu.get("parentid").equals(sysMenu.get("id"))) { + childrenList.add(menu); + } + } + if ("0".equals(sysMenu.get("parentid"))) { + if (childrenList.size() > 0) { + sysMenu.put("children", childrenList); + } + resultMenuList.add(sysMenu); + } + } + return resultMenuList; + } + + /********************************** + * 用途说明: 左侧菜单树构建 + * 参数说明 sysMenus + * 返回值说明: java.util.List> + ***********************************/ + public List> buildTreeLeft(List> sysMenus) { + List> resultMenuList = new ArrayList<>(); + for (Map sysMenu : sysMenus) { + if ("0".equals(sysMenu.get("parentid"))) { + resultMenuList.add(sysMenu); + } + } + for (Map sysMenu : resultMenuList) { + List> menus = iterateMenusLeft(sysMenus, + (String) sysMenu.get("id")); + if (menus.size() > 0) { + sysMenu.put("children", menus); + } + } + return resultMenuList; + } + + /** + * 左侧多级菜单查询方法 + * + * @param menuVoList 不包含最高层次菜单的菜单集合 + * @param pid 父类id + * @return + */ + public List> iterateMenusLeft(List> menuVoList, String pid) { + List> result = new ArrayList<>(); + for (Map menu : menuVoList) { + //获取菜单的id + String menuid = (String) menu.get("id"); + //获取菜单的父id + String parentid = (String) menu.get("parentid"); + if (StrUtil.isNotBlank(parentid)) { + if (parentid.equals(pid)) { + //递归查询当前子菜单的子菜单 + List> iterateMenu = + iterateMenus(menuVoList, menuid); + if (iterateMenu.size() > 0) { + menu.put("children", iterateMenu); + } + result.add(menu); + } + } + } + return result; + } + + /********************************** + * 用途说明: 生成权菜单权限树 + * 参数说明 sysMenus + * 返回值说明: java.util.List> + ***********************************/ + public List> buildTrees(List> sysMenus) { + List> resultMenuList = new ArrayList<>(); + for (Map sysMenu : sysMenus) { + if ("0".equals(sysMenu.get("parentid"))) { + resultMenuList.add(sysMenu); + } + } + for (Map sysMenu : resultMenuList) { + List> menus = iterateMenus(sysMenus, + (String) sysMenu.get("id")); + for (Map menu : menus) { + if (!(boolean) menu.get("checkinfo")) { + sysMenu.put("checkinfo", false); + break; + } + } + sysMenu.put("children", menus); + } + return resultMenuList; + } + + /** + * 多级菜单查询方法 + * + * @param menuVoList 不包含最高层次菜单的菜单集合 + * @param pid 父类id + * @return + */ + public List> iterateMenus(List> menuVoList, String pid) { + List> result = new ArrayList<>(); + for (Map menu : menuVoList) { + //获取菜单的id + String menuid = (String) menu.get("id"); + //获取菜单的父id + String parentid = (String) menu.get("parentid"); + if (StrUtil.isNotBlank(parentid)) { + if (parentid.equals(pid)) { + //递归查询当前子菜单的子菜单 + List> iterateMenu = + iterateMenus(menuVoList, menuid); + for (Map map : iterateMenu) { + boolean checkinfo = (boolean) map.get("checkinfo"); + if (!checkinfo) { + menu.put("checkinfo", false); + } + } + menu.put("children", iterateMenu); + result.add(menu); + } + } + } + return result; + } + +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/impl/SysOrganizationServiceImpl.java b/backend/src/main/java/com/yfd/platform/system/service/impl/SysOrganizationServiceImpl.java new file mode 100644 index 0000000..01ea014 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/impl/SysOrganizationServiceImpl.java @@ -0,0 +1,338 @@ +package com.yfd.platform.system.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.system.domain.SysOrganization; +import com.yfd.platform.system.domain.SysRole; +import com.yfd.platform.system.domain.SysUser; +import com.yfd.platform.system.mapper.SysOrganizationMapper; +import com.yfd.platform.system.mapper.SysRoleMapper; +import com.yfd.platform.system.service.ISysOrganizationService; +import com.yfd.platform.system.service.IUserService; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.sql.Timestamp; +import java.text.DecimalFormat; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + *

+ * 系统组织框架 服务实现类 + *

+ * + * @author zhengsl + * @since 2021-12-15 + */ +@Service +public class SysOrganizationServiceImpl extends ServiceImpl implements ISysOrganizationService { + + @Resource + private UserServiceImpl currentUser; + + @Resource + private IUserService userService; + + @Resource + private SysRoleMapper sysRoleMapper; + + @Resource + private SysOrganizationMapper sysOrganizationMapper; + + /*********************************** + * 用途说明:获取组织结构树 + * 参数说明 + *parentid 上级id + * params 名称(根据名称查询二级) + * 返回值说明: 组织树集合 + ***********************************/ + @Override + public List> getOrgTree(String parentid, + String params) { + List> listMap = new ArrayList<>(); + QueryWrapper queryWrapper = new QueryWrapper<>(); + //根据父级id查询 + queryWrapper.eq("parentid", parentid); + if (StrUtil.isNotEmpty(params)) { + queryWrapper.like("orgname", params); // 根据 部门名称 + } + SysUser userInfo = userService.getUserInfo(); + if (userInfo.getUsertype() != 0) { + List roleByUserId = + sysRoleMapper.getRoleByUserId(userInfo.getId()); + List ids = new ArrayList<>(); + // 循环当前角色 + for (SysRole sysRole : roleByUserId) { + // 获取角色的组织Id + String orgscope = sysRole.getOrgscope(); + if (StrUtil.isBlank(orgscope)) { + continue; + } + // 拆分组织Id + String[] split = orgscope.split(","); + List stringList = Arrays.asList(split); + Set set = new HashSet<>(); + if (stringList.size() > 0) { + List list = + sysOrganizationMapper.selectList(new LambdaQueryWrapper().in(SysOrganization::getId, stringList)); + list.forEach(l -> set.add(l.getParentid())); + } + ids.addAll(stringList); + ids.addAll(set); + } + queryWrapper.in("id", ids); + } + listMap = this.listMaps(queryWrapper.orderByAsc("orgcode")); + for (int i = 0; i < listMap.size(); i++) { + List> childList = child(listMap.get(i).get( + "id").toString());//查询下一子集 + listMap.get(i).put("childList", childList); //添加新列 子集 + } + return listMap; + } + + /*********************************** + * 用途说明:获取组织结构树 + * 参数说明 + *parentid 上级id + * params (根据参数查询 组织名称 负责人 描述) + * 返回值说明: 组织树集合 + ***********************************/ + private List> child(String parentid) { + List> listMap = new ArrayList<>(); + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("parentid", parentid); //根据上级id 查询 + listMap = this.listMaps(queryWrapper.orderByAsc("orgcode")); + if (listMap.size() > 0) { //判断是否存在子集 + for (int i = 0; i < listMap.size(); i++) { //遍历表数据 + List> childList = + child(listMap.get(i).get("id").toString()); //循环获取下一子集 + listMap.get(i).put("childList", childList); //添加新列 子集 + } + } + return listMap; + } + + /*********************************** + * 用途说明:新增系统组织框架 + * 参数说明 + * sysOrganization 系统组织框架对象 + * 返回值说明: 是否新增成功 + ***********************************/ + @Override + public boolean addOrg(SysOrganization sysOrganization) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + SysOrganization parent = null; + int codeMax = 0; + //查询最大的编号 判断是否存在父级id 有值 根据父级id查询 否则 根据父级id为0 查询 + queryWrapper.select("max(orgcode)"); + if (StrUtil.isNotEmpty(sysOrganization.getParentid())) { + //根据父级id查询父级信息 + parent = this.getById(sysOrganization.getParentid()); + queryWrapper.eq("parentid", sysOrganization.getParentid()); + } else { + //默认 填写父级id为0 + sysOrganization.setParentid("0"); + queryWrapper.eq("parentid", "0"); + } + List max = this.listObjs(queryWrapper); + //判断查询是否存在 存在转换成int类型并给codeMax替换值 + if (max.size() > 0) { + codeMax = + Integer.parseInt(max.get(0).toString().substring(max.get(0).toString().length() - 2)); + } + //2位数字编号 + DecimalFormat df = new DecimalFormat("00"); + //编号 + String code = df.format(codeMax + 1); + //查询到父级不为空 重新赋值 父级编号+新的序号 + if (parent != null) { + code = parent.getOrgcode() + df.format(codeMax + 1); + } + //判断是否是否填写 有效 否则默认为 1 + if (StrUtil.isEmpty(sysOrganization.getIsvaild())) { + sysOrganization.setIsvaild("1"); + } + //填写 编号 + sysOrganization.setOrgcode(code); + //填写 当前用户名称 + sysOrganization.setLastmodifier(currentUser.getUsername()); + //填写 当前日期 + sysOrganization.setLastmodifydate(new Timestamp(System.currentTimeMillis())); + return this.save(sysOrganization); + } + + /*********************************** + * 用途说明:根据企业ID查询组织详情 + * 参数说明 + * id 企业id + * 返回值说明: 系统组织框架对象 + ***********************************/ + @Override + public List getOrganizationById(String id, + String orgName) { + + LambdaQueryWrapper queryWrapper = + new LambdaQueryWrapper<>(); + SysUser userInfo = userService.getUserInfo(); + if (userInfo.getUsertype() != 0) { + List roleByUserId = + sysRoleMapper.getRoleByUserId(userInfo.getId()); + List ids = new ArrayList<>(); + // 循环当前角色 + for (SysRole sysRole : roleByUserId) { + // 获取角色的组织Id + String orgscope = sysRole.getOrgscope(); + if (StrUtil.isBlank(orgscope)) { + continue; + } + // 拆分组织Id + String[] split = orgscope.split(","); + List stringList = Arrays.asList(split); + ids.addAll(stringList); + } + if (ObjectUtil.isNotEmpty(ids)) { + queryWrapper.in(SysOrganization::getId, ids); + } + + } + if (StrUtil.isNotBlank(orgName)) { + queryWrapper.like(SysOrganization::getOrgname, orgName); + } + queryWrapper.eq(SysOrganization::getParentid, id).orderByDesc(SysOrganization::getOrgcode); + return this.list(queryWrapper); + } + + /*********************************** + * 用途说明:获取组织范围树结构 + * 参数说明 + *parentid 上级id + * params 名称(根据名称查询二级) + * 返回值说明: 组织树集合 + ***********************************/ + @Override + public List> getOrgScopeTree(String roleId) { + LambdaQueryWrapper queryWrapper = + new LambdaQueryWrapper<>(); + queryWrapper.eq(SysOrganization::getIsvaild, '1'); + queryWrapper.orderByAsc(SysOrganization::getOrgcode); + List> listMaps = this.listMaps(queryWrapper); + // 获取当前角色 + SysRole sysRole = sysRoleMapper.selectById(roleId); + String orgscope = sysRole.getOrgscope(); + List ids = new ArrayList<>(); + if (StrUtil.isNotBlank(orgscope)) { + String[] split = orgscope.split(","); + ids = Arrays.asList(split); + } + + for (Map map : listMaps) { + String id = (String) map.get("id"); + if (ids.contains(id)) { + map.put("checkinfo", true); + } else { + map.put("checkinfo", false); + } + map.put("bool", true); + } + // 生成组织树 + List> listMap = buildTrees(listMaps); + return listMap; + } + + /********************************** + * 用途说明: 修改角色组织范围 + * 参数说明 roleId 角色id + * 参数说明 orgscope 组织id集合 + * 返回值说明: boolean 是否修改成功 + ***********************************/ + @Override + public boolean updateOrgScopeByRoleId(String roleId, String orgscope) { + SysRole sysRole = new SysRole(); + sysRole.setId(roleId); + sysRole.setOrgscope(orgscope); + int i = sysRoleMapper.updateById(sysRole); + if (i > 0) { + return true; + } else { + return false; + } + + } + + /********************************** + * 用途说明: 生成组织范围树 + * 参数说明 sysMenus + * 返回值说明: java.util.List> + ***********************************/ + public List> buildTrees(List> sysMenus) { + List> resultMenuList = new ArrayList<>(); + // 获取父节点 + for (Map sysMenu : sysMenus) { + if ("0".equals(sysMenu.get("parentid"))) { + resultMenuList.add(sysMenu); + } + } + // 寻找子节点 + for (Map sysMenu : resultMenuList) { + sysMenu.put("checkinfo", true); + List> children = new ArrayList<>(); + List array = new ArrayList<>(); + for (Map menu : sysMenus) { + String id = (String) sysMenu.get("id"); + String parentid = (String) menu.get("parentid"); + if (id.equals(parentid)) { + // 如果存在一个子节点没有被选中,父节点则不是全选状态 + if (!(boolean) menu.get("checkinfo")) { + sysMenu.put("checkinfo", false); + } else { + // 将处于选中状态的子节点加入到数组中 + array.add((String) menu.get("orgname")); + } + children.add(menu); + } + } + // 所有子节点加入父节点 + sysMenu.put("children", children); + sysMenu.put("array", array); + } + return resultMenuList; + } + + /** + * 组织集合递归生成树状菜单(Map) + * + * @param sysOrgList 组织集合 + * @return + */ + public List> buildTree(List> sysOrgList) { + List> resultOrgList = new ArrayList<>(); + for (Map sysOrg : sysOrgList) { + List> childrenList = new ArrayList<>(); + List array = new ArrayList<>(); + for (Map org : sysOrgList) { + if (org.get("parentid").equals(sysOrg.get("id"))) { + if (!(boolean) org.get("checkinfo")) { + sysOrg.put("checkinfo", false); + } + array.add((String) org.get("orgname")); + childrenList.add(org); + } + } + if ("0".equals(sysOrg.get("parentid"))) { + if (childrenList.size() > 0) { + sysOrg.put("children", childrenList); + } + resultOrgList.add(sysOrg); + } + sysOrg.put("array", array); + } + return resultOrgList; + } +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/impl/SysRoleServiceImpl.java b/backend/src/main/java/com/yfd/platform/system/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..63d5c36 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,163 @@ +package com.yfd.platform.system.service.impl; + +import cn.hutool.core.util.IdUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.system.domain.SysRole; +import com.yfd.platform.system.mapper.SysRoleMapper; +import com.yfd.platform.system.service.ISysRoleService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.Resource; +import java.sql.Timestamp; +import java.text.DecimalFormat; +import java.util.List; +import java.util.Map; + +/** + *

+ * 系统角色 服务实现类 + *

+ * + * @author zhengsl + * @since 2021-12-15 + */ +@Service +@Transactional +public class SysRoleServiceImpl extends ServiceImpl implements ISysRoleService { + + @Resource + private SysRoleMapper roleMapper; + + @Resource + private UserServiceImpl currentuser; + + /*********************************** + * 用途说明:新增角色 + * 参数说明 + * sysRole 新增角色信息 + * 返回值说明: 是否新增成功 + ***********************************/ + @Override + public boolean addRole(SysRole sysRole) { + //生成用户编号 + int codeMax = 0; + DecimalFormat df = new DecimalFormat("000");//四位数字编号 + QueryWrapper queryWrapper = new QueryWrapper<>(); + List max = this.listObjs(queryWrapper.select("MAX(rolecode) " + + "rolecode"));// 查询最大的编号 + if (max.size() > 0) { + codeMax = Integer.parseInt(max.get(0).toString());//判断查询是否存在 + } + // 存在转换成int类型并给codeMax替换值 + String code = df.format(codeMax + 1); // 最大编号累加 + + sysRole.setRolecode(code); //添加角色编号 + if (StringUtils.isEmpty(sysRole.getIsvaild())) { + sysRole.setIsvaild("1"); //判断是否填写有效性 默认为 1 是 + } + sysRole.setLastmodifier(currentuser.getUsername()); //添加最近修改者 + sysRole.setLastmodifydate(new Timestamp(System.currentTimeMillis())); //添加最近修改时间 + return this.save(sysRole); + } + + /*********************************** + * 用途说明:删除角色用户(admin除外) + * 参数说明 + * id 系统角色用户对照表id + * 返回值说明: 是否新增成功 + ***********************************/ + @Override + public boolean deleteRoleUsers(String roleid, String urserids) { + boolean ok = true; + //得到单个用户id + String[] temp = urserids.split(","); + for (String userid : temp) { + //根据角色id、用户id删除 (登录账号admin除外) + ok = ok && roleMapper.deleteRoleUsers(roleid, userid); + + } + return ok; + } + + /*********************************** + * 用途说明:根据id删除角色 //待修改 + * 参数说明 + *id 角色id + * 返回值说明: 是否删除成功 + ***********************************/ + @Override + public void deleteById(String id) { + String[] ids = id.split(","); + for (String roleId : ids) { + //根据id删除 角色 + boolean isOk = this.removeById(roleId); + if (!isOk) { + continue; + } + roleMapper.deleteRoleMenus(roleId); + roleMapper.deleteRoleUser(roleId); + } + } + /* 原逻辑 + @Override + public boolean deleteById(String id) { + //根据角色id查询 所关联的用户 + List> isRoleUsersByroleid = + roleMapper.isRoleUsersByroleid(id); + //判断是否关联用户 + if (isRoleUsersByroleid.size() > 0) { + return false; + } + //根据id删除 角色 + boolean isOk = this.removeById(id); + if (isOk) { + return true; + } + return false; + }*/ + + /*********************************** + * 用途说明:查询已分配的用户 + * 参数说明 + *orgid 所属组织 + *username 用户名称 + *status 状态 + *level 角色级别 + * rolename 角色名称 + * isvaild 角色是否有效 + * 返回值说明: 系统用户角色数据集合 + ***********************************/ + @Override + public List listRoleUsers(String orgid, String username, + String status, String level, + String rolename, String isvaild) { + return roleMapper.listRoleUsers(orgid, username, status, level, + rolename, isvaild); + } + + /*********************************** + * 用途说明:角色分配权限 + * 参数说明 + * id 角色id + * menuIds 权限id字符串 + * 返回值说明: 是否分配成功 + ***********************************/ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean setMenuById(String id, String menuIds) { + // 删除角色所对应的权限 + roleMapper.deleteRoleMenus(id); + // 重新赋予权限 + String[] ids = menuIds.split(","); + for (String menuId : ids) { + String uuid = IdUtil.fastSimpleUUID(); + roleMapper.addRoleMenu(uuid, id, menuId); + } + return true; + } + +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/impl/UserDetailsServiceImpl.java b/backend/src/main/java/com/yfd/platform/system/service/impl/UserDetailsServiceImpl.java new file mode 100644 index 0000000..dc28501 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/impl/UserDetailsServiceImpl.java @@ -0,0 +1,52 @@ +package com.yfd.platform.system.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.yfd.platform.datasource.DataSource; +import com.yfd.platform.system.domain.LoginUser; +import com.yfd.platform.system.domain.SysUser; +import com.yfd.platform.system.mapper.SysMenuMapper; +import com.yfd.platform.system.service.IUserService; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + *

+ * 用户服务实现类 继承UserDetailsService 实现接口 + *

+ * + * @author zhengsl + * @since 2021-10-27 + */ +@Service +public class UserDetailsServiceImpl implements UserDetailsService { + + @Resource + private IUserService userService; + + @Resource + private SysMenuMapper sysMenuMapper; + + @Override + @DataSource(name = "master") + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + //根据用户名称查询用户信息 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("username", username); + SysUser user = userService.getOne(queryWrapper); + if (ObjectUtil.isEmpty(user)) { + throw new RuntimeException("用户账号不存在!"); + } + //Todo 根据用户查询权限信息 添加到LoginUser中 + List permissions = + sysMenuMapper.selectPermsByUserId(user.getId()); + + //封装成UserDetails对象返回 + return new LoginUser(user,permissions); + } +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java b/backend/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..566d608 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java @@ -0,0 +1,550 @@ +package com.yfd.platform.system.service.impl; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yfd.platform.config.FileProperties; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.system.domain.LoginUser; +import com.yfd.platform.system.domain.SysOrganization; +import com.yfd.platform.system.domain.SysRole; +import com.yfd.platform.system.domain.SysUser; +import com.yfd.platform.system.mapper.SysOrganizationMapper; +import com.yfd.platform.system.mapper.SysRoleMapper; +import com.yfd.platform.system.mapper.SysUserMapper; +import com.yfd.platform.system.service.IUserService; +import com.yfd.platform.utils.FileUtil; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import com.yfd.platform.config.FileSpaceProperties; +import jakarta.annotation.Resource; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.annotation.Resource; +import jakarta.validation.constraints.NotBlank; +import java.io.File; +import java.sql.Timestamp; +import java.util.*; +import java.util.stream.Collectors; + +/** + *

+ * 用户服务实现类 + *

+ * + * @author zhengsl + * @since 2021-10-27 + */ +@Service +@RequiredArgsConstructor +public class UserServiceImpl extends ServiceImpl implements IUserService { + + @Resource + private SysUserMapper sysUserMapper; + + @Resource + private SysRoleMapper sysRoleMapper; + + @Resource + private PasswordEncoder passwordEncoder; + /** + * 文件空间配置 + */ + @Resource + private FileSpaceProperties fileSpaceProperties; + + /********************************** + * 用途说明:获取当前用户账号及名称 + * 参数说明 + * 返回值说明: 系统管理员[admin] + ***********************************/ + @Override + public String getUsername() { + UsernamePasswordAuthenticationToken authentication = + (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); + LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + String acountname = + loginuser.getUser().getNickname(); + return acountname; + //return "admin"; + } + + @Override + public Map getNameInfo() { + UsernamePasswordAuthenticationToken authentication = + (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); + LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + String nickname = loginuser.getUser().getNickname(); + String username = loginuser.getUser().getUsername(); + Map map = new HashMap<>(); + map.put("nickname", nickname); + map.put("username", username); + return map; + } + + @Override + public SysUser getUserInfo() { + UsernamePasswordAuthenticationToken authentication = + (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); + LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + return loginuser.getUser(); + } + + @Override + public ResponseResult getLoginUserInfo() { + UsernamePasswordAuthenticationToken authentication = + (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); + LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + SysUser user = loginuser.getUser(); + //根据用户ID获取组织 + Map userInfo = + sysUserMapper.getOrganizationByid(user.getId()); + List roles = + sysRoleMapper.selectList(new QueryWrapper().inSql( + "id ", "SELECT roleid FROM sys_role_users ru WHERE ru" + + ".userid = '" + user.getId() + "'")); + List collect = + roles.stream().map(SysRole::getRolename).collect(Collectors.toList()); + ResponseResult responseResult = new ResponseResult(); + responseResult.put("userInfo", userInfo); + responseResult.put("roles", collect); + responseResult.put("permissions", loginuser.getPermissions()); + return responseResult; + } + + /*********************************** + * 用途说明:新增用户 + * 参数说明 + *sysUser 新增用户对象 + * id 创建者id + * roleId 角色id + * 返回值说明: 提示字符串 + ************************************/ + @Override + public Map addUser(SysUser sysUser, String roleids) { + //返回信息 + Map result = new HashMap<>(); + sysUser.setId(IdUtil.fastSimpleUUID()); + //普通用户 + sysUser.setUsertype(1); + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + //设置缺省密码 + String cryptPassword = passwordEncoder.encode("123456"); + sysUser.setPassword(cryptPassword); + sysUser.setLastmodifydate(new Timestamp(System.currentTimeMillis())); + sysUser.setLastmodifier(getUsername()); + //账号有效 + sysUser.setStatus(1); + //判断注册的登录账号是否存在 + if (isExistAccount(sysUser.getUsername())) { + //新增用户 + boolean ok = this.save(sysUser); + //新增用户分配权限 + if (StrUtil.isNotEmpty(roleids)) { + String[] roles = roleids.split(","); + for (String roleid : roles) { + //系统生成id + String id = IdUtil.fastSimpleUUID(); + //新增sys_role_users表数据 + ok = ok && sysUserMapper.addUserRoles(id, roleid, + sysUser.getId()); + } + } + //判断新增是否成功 消息提示 + if (ok) { + result.put("status", "sucess"); + result.put("msg", "新增用户成功!"); + + } else { + result.put("status", "error"); + result.put("msg", "新增用户失败!"); + } + } else { + result.put("status", "error"); + result.put("msg", "用户账号已存在,不能重复添加!"); + } + return result; + } + + /*********************************** + * 用途说明:查询系统用户 + * 参数说明 + *page 分页集合参数 + *orgid 所属组织 + *username 用户名称 + * mobile 手机号 + * status 状态 + * 返回值说明: 用户分页集合 + ************************************/ + @Override + public List list(String total, String size, String orgid, + String username, String mobile, String status) { + List list = sysUserMapper.list(total, size, orgid, username, + mobile, status); + for (Map map : list) { + List mapList = + sysUserMapper.getLevel(map.get("id").toString()); + String roleid = ""; + String level = ""; + String rolename = ""; + for (Map map1 : mapList) { + roleid += map1.get("id") + ","; + level += map1.get("level") + ","; + rolename += map1.get("rolename") + ","; + } + if (roleid.endsWith(",")) { + roleid = roleid.substring(0, roleid.length() - 1); + } + if (level.endsWith(",")) { + level = level.substring(0, level.length() - 1); + } + if (rolename.endsWith(",")) { + rolename = rolename.substring(0, rolename.length() - 1); + } + + map.put("roleid", roleid); + map.put("level", level); + map.put("rolename", rolename); + } + return list; //返回分页集合 + } + + /*********************************** + * 用途说明:根据ID修改用户 + * 参数说明 + *sysUser 用户对象 + *roleids 角色id + * 返回值说明: 是否更新成功 + ************************************/ + @Override + public Map updateById(SysUser sysUser, String roleids) { + //返回信息 + Map result = new HashMap<>(); + //获取当前用户 最近修改者替换 + sysUser.setLastmodifier(getUsername()); + //获取当前时间 最近修改日期替换 + sysUser.setLastmodifydate(new Timestamp(System.currentTimeMillis())); + //根据修改 + boolean ok = this.updateById(sysUser); + if (ok) { + if (StrUtil.isNotEmpty(roleids)) { + String[] roles = roleids.split(","); + List list = sysUserMapper.getRoleid(sysUser.getId()); + for (String role : roles) { + if (!list.contains(role)) { + //系统生成id + String id = IdUtil.fastSimpleUUID(); + //新增sys_role_users表数据 + ok = ok && sysUserMapper.addUserRoles(id, role, + sysUser.getId()); + } + } + //删除不包含的角色 + sysUserMapper.delInRoleUsersByUserid(sysUser.getId(), roles); + + } else { + //根据用户id 删除该用户角色关联 + ok = ok && sysUserMapper.delRoleUsersByUserid(sysUser.getId()); + } + result.put("status", "sucess"); + result.put("msg", "用户信息修改成功!"); + } else { + result.put("status", "error"); + result.put("msg", "用户信息修改失败!"); + } + + return result; + } + + @Override + public Map getOneById(String id) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + Map map = this.getMap(queryWrapper.eq("id", id)); + List mapList = sysUserMapper.getLevel(id); + String roleid = ""; + String level = ""; + String rolename = ""; + for (Map map1 : mapList) { + roleid += map1.get("id") + ","; + level += map1.get("level") + ","; + rolename += map1.get("rolename") + ","; + } + if (roleid.endsWith(",")) { + roleid = roleid.substring(0, roleid.length() - 1); + } + if (level.endsWith(",")) { + level = level.substring(0, level.length() - 1); + } + if (rolename.endsWith(",")) { + rolename = rolename.substring(0, rolename.length() - 1); + } + + map.put("roleid", roleid); + map.put("level", level); + map.put("rolename", rolename); + return map; + } + + /*********************************** + * 用途说明:用户分配角色 + * 参数说明 + *listId 用户id与角色id + * 返回值说明: 判断是否添加成功 + ************************************/ + @Override + public boolean setUserRoles(String roleid, String userids) { + boolean isOk = true; + //拆分userid 数组 + String[] temp = userids.split(","); + //遍历userid + for (String userid : temp) { + //根据角色id与用户id查询 + List list = sysUserMapper.getRoleUsersByid(roleid, userid); + //判断是否用户已分配此权限 + if (list.size() == 0) { + //系统生成id + String id = IdUtil.fastSimpleUUID(); + //新增sys_role_users表数据 + isOk = isOk && sysUserMapper.addUserRoles(id, roleid, userid); + } + } + return isOk; + } + + /*********************************** + * 用途说明:根据id删除用户 + * 参数说明 + *id 用户id + * 返回值说明: 判断是否删除成功 + ************************************/ + @Override + public boolean deleteById(String id) { + //根据id查询 + SysUser sysUser = this.getById(id); + //账号头像存储地址 + String imgName = + fileSpaceProperties.getSystem() + File.separator + "user" + File.separator + sysUser.getAvatar(); + if ("admin".equals(sysUser.getUsername())) { + return false; + } else { + boolean isOk = this.removeById(id); + //判断是否删除成功 + if (isOk) { + //根据用户id 删除该用户角色关联 + sysUserMapper.delRoleUsersByUserid(id); + //判断是否存在 账号头像 存在删除 + if (StrUtil.isNotEmpty(sysUser.getAvatar())) { + FileUtil.del(imgName); + } + return false; + } else { + return false; + } + } + } + + /*********************************** + * 用途说明:重置用户密码(管理员) + * 参数说明 + *id 重置密码的 用户id + * 返回值说明: 判断是重置成功 + ************************************/ + @Override + public boolean resetPassword(String id) throws Exception { + boolean isOk = false; + //根据当前用户id 查询角色表的级别 currentUser.getUser() 获取当前用户id + String level = sysUserMapper.getMaxLevel(id); + //判断是否获取级别 + if (StrUtil.isNotEmpty(level)) { + //判断当前用户级别 管理员及以上权限 + if (Integer.parseInt(level) <= 2) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + //根据id 修改密码,密码修改时间,最近修改者,最近修改日期 将密码修改为 123456 + String cryptPassword = passwordEncoder.encode("123456"); + updateWrapper.eq("id", id).set("password", cryptPassword).set( + "pwdresettime", + new Timestamp(System.currentTimeMillis())).set( + "lastmodifydate", + new Timestamp(System.currentTimeMillis())).set( + "lastmodifier", getUsername()); + //是否修改成功 + isOk = this.update(updateWrapper); + } + } + return isOk; + } + + /*********************************** + * 用途说明:设置账号状态(管理员) + * 参数说明 + *id 用户id + * status 设置状态 + * 返回值说明: 判断是否设置成功 + ************************************/ + @Override + public boolean setStatus(String id, String status) { + boolean isOk = false; + //根据当前用户id 查询角色表的级别 currentUser.getUser() 获取当前用户id + String level = sysUserMapper.getMaxLevel(id); + //判断当前用户级别 管理员及以上权限 + if (Integer.parseInt(level) <= 2) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + //根据id修改用户状态,最近修改人,最近修改时间 + updateWrapper.eq("id", id).set("status", status).set( + "lastmodifydate", + new Timestamp(System.currentTimeMillis())).set( + "lastmodifier", getUsername()); + //是否修改成功 + isOk = this.update(updateWrapper); + } + return isOk; + } + + /*********************************** + * 用途说明:上传用户头像 + * 参数说明 + * id 用户id + * img 账号头像 + * 返回值说明: 判断是否上传 + ***********************************/ + @Override + public boolean uploadAvatar(String id, MultipartFile img) { + //根据id查询 + SysUser sysUser = this.getById(id); + //账号头像存储地址 + String imgPath = fileSpaceProperties.getSystem() + "user"; + String avatar = sysUser.getAvatar(); + if (StrUtil.isNotBlank(avatar)) { + String imgName = imgPath + File.separator + avatar; + FileUtil.del(imgName); + } + //上传图片 并获取图片名称 (图片格式修改成png) + String imgName = FileUtil.upload(img, imgPath, + IdUtil.fastSimpleUUID() + "." + FileUtil.getExtensionName(img.getOriginalFilename())).getName(); + //修改 账户头像 + sysUser.setAvatar(imgName); + //修改 最近修改者 + sysUser.setLastmodifier(getUsername()); + //修改 最近修改日期 + sysUser.setLastmodifydate(new Timestamp(System.currentTimeMillis())); + //更新用户表 + boolean isOk = this.updateById(sysUser); + return isOk; + } + + /*********************************** + * 用途说明:新增系统角色用户对照表 对用户分配角色(单个) + * 参数说明 + * id 生成的id + * roleid 角色id + * userid 用户id + * 返回值说明: + ************************************/ + @Override + public boolean addUserRoles(String roleid, String userid) { + boolean isOk = true; + //根据角色id与用户id查询 + List list = sysUserMapper.getRoleUsersByid(roleid, userid); + //判断是否用户已分配此权限 + if (list.size() == 0) { + //系统生成id + String id = IdUtil.fastSimpleUUID(); + //新增sys_role_users表数据 + isOk = sysUserMapper.addUserRoles(id, roleid, userid); + } + return isOk; + } + + /* @Override + public Page queryUsers(String orgid, + String username, Page page) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + // 分页查询中的条件查询 + if (StrUtil.isNotBlank(username)) { + queryWrapper.like(SysUser::getUsername, username); + } + queryWrapper.eq(SysUser::getOrgid, orgid); + return sysUserMapper.selectPage(page, queryWrapper); + }*/ + + @Override + public Page> queryUsers(String orgid, + String username, + Page page) { + Page> mapPage = sysUserMapper.queryUsers(orgid, + username, page); + List> list = new ArrayList<>(); + List> records = mapPage.getRecords(); + for (Map record : records) { + String id = (String) record.get("id"); + List sysRoles = sysRoleMapper.getRoleByUserId(id); + record.put("roles", sysRoles); + list.add(record); + } + mapPage.setRecords(list); + return mapPage; + } + + /*********************************** + * 用途说明:根据ID批量删除用户 + * 参数说明 + *ids 用户id集合 + * 返回值说明: 判断是否删除成功 + ************************************/ + @Override + public boolean deleteUserByIds(String id) { + String[] splitId = id.split(","); + List ids = Arrays.asList(splitId); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysUser::getId, ids); + List sysUsers = sysUserMapper.selectList(queryWrapper); + List names = + sysUsers.stream().map(SysUser::getUsername).collect(Collectors.toList()); + if (names.contains("admin")) { + return false; + } else { + int result = sysUserMapper.deleteBatchIds(ids); + if (result <= 0) { + return false; + } + // 根据ID删除用户与角色的关联信息 + sysUserMapper.delRoleUsersByUserIds(ids); + List avatars = + sysUsers.stream().map(SysUser::getAvatar).collect(Collectors.toList()); + if (avatars.size() > 0) { + for (String avatar : avatars) { + //账号头像存储地址 + String imgName = + fileSpaceProperties.getSystem() + File.separator + "user" + File.separator + avatar; + FileUtil.del(imgName); + } + } + return true; + } + } + + /*********************************** + * 用途说明:比较登录名称是否有重复 + * 参数说明 + * account 登录名称 + * 返回值说明: 重复返回 false 否则返回 true + ************************************/ + private boolean isExistAccount(String username) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (this.list(queryWrapper.eq("username", username)).size() > 0) { + //判断 查询登录账号 结果集是否为null 重复返回 false 否则返回 tree + return false; + } else { + return true; + } + } +} diff --git a/backend/src/main/java/com/yfd/platform/task/TaskMessage.java b/backend/src/main/java/com/yfd/platform/task/TaskMessage.java new file mode 100644 index 0000000..4a43d15 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/task/TaskMessage.java @@ -0,0 +1,57 @@ +package com.yfd.platform.task; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.yfd.platform.component.ServerSendEventServer; +import com.yfd.platform.config.MessageConfig; +import com.yfd.platform.config.WebConfig; +import com.yfd.platform.system.domain.Message; +import com.yfd.platform.system.service.IMessageService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; +import java.sql.Timestamp; +import java.util.List; + +/** + * @author TangWei + * @Date: 2023/3/22 15:39 + * @Description: + */ +@Component +public class TaskMessage { + + @Resource + private IMessageService messageService; + + /********************************** + * 用途说明: 定时监控消息是否过期 + * 参数说明 + * 返回值说明: void + ***********************************/ + public void examineMessage() { + List list = + messageService.list(new LambdaQueryWrapper().eq(Message::getStatus, "1")); + for (Message message : list) { + Timestamp createtime = message.getCreatetime(); + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + long create = createtime.getTime(); + long now = timestamp.getTime(); + Integer validperiod = message.getValidperiod(); + long v = validperiod * 60 * 60 * 1000; + if ((now - create) > v) { + message.setStatus("9"); + message.setReadtime(timestamp); + messageService.updateById(message); + } + + } + } + + /*public void sendMessage() { + String loginToken = webConfig.loginuserCache().get("loginToken"); + long count = + messageService.count(new LambdaQueryWrapper().eq(Message::getStatus, "1")); + ServerSendEventServer.sendMessage(loginToken, count + ""); + }*/ +} diff --git a/backend/src/main/java/com/yfd/platform/utils/CallBack.java b/backend/src/main/java/com/yfd/platform/utils/CallBack.java new file mode 100644 index 0000000..5f13d70 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/utils/CallBack.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yfd.platform.utils; + +/** + * @author: liaojinlong + * @date: 2020/6/9 17:02 + * @since: 1.0 + * @see {@link SpringContextHolder} + * 针对某些初始化方法,在SpringContextHolder 初始化前时,
+ * 可提交一个 提交回调任务。
+ * 在SpringContextHolder 初始化后,进行回调使用 + */ + +public interface CallBack { + /** + * 回调执行方法 + */ + void executor(); + + /** + * 本回调任务名称 + * @return / + */ + default String getCallBackName() { + return Thread.currentThread().getId() + ":" + this.getClass().getName(); + } +} + diff --git a/backend/src/main/java/com/yfd/platform/utils/CodeGenerator.java b/backend/src/main/java/com/yfd/platform/utils/CodeGenerator.java new file mode 100644 index 0000000..1c65429 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/utils/CodeGenerator.java @@ -0,0 +1,75 @@ +package com.yfd.platform.utils; + +import com.baomidou.mybatisplus.generator.FastAutoGenerator; +import com.baomidou.mybatisplus.generator.config.OutputFile; +import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; +import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; + +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +// 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中 +public class CodeGenerator { + + public static String scanner(String tip) { + Scanner scanner = new Scanner(System.in); + StringBuilder help = new StringBuilder(); + help.append("请输入" + tip + ":"); + System.out.println(help.toString()); + if (scanner.hasNext()) { + String ipt = scanner.next(); + if (ipt != null && !ipt.trim().isEmpty()) { + return ipt; + } + } + throw new RuntimeException("请输入正确的" + tip + "!"); + } + + public static void main(String[] args) { + String projectPath = System.getProperty("user.dir"); + String module = scanner("模块名称"); + + Map pathInfo = new HashMap<>(); + pathInfo.put(OutputFile.entity, projectPath + "/src/main/java/com/yfd/platform/" + module + "/domain"); + pathInfo.put(OutputFile.mapper, projectPath + "/src/main/java/com/yfd/platform/" + module + "/mapper"); + pathInfo.put(OutputFile.controller, projectPath + "/src/main/java/com/yfd/platform/" + module + "/controller"); + pathInfo.put(OutputFile.serviceImpl, projectPath + "/src/main/java/com/yfd/platform/" + module + "/service/impl"); + pathInfo.put(OutputFile.service, projectPath + "/src/main/java/com/yfd/platform/" + module + "/service"); + pathInfo.put(OutputFile.xml, projectPath + "/src/main/resources/mapper/" + module); + + FastAutoGenerator.create( + "jdbc:mysql://43.143.220.7:3306/frameworkdb2023?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&failOverReadOnly=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai", + "root", + "zhengg7QkXa { + builder.author("TangWei") + .disableOpenDir() + .outputDir(projectPath + "/src/main/java"); + }) + .packageConfig(builder -> { + builder.parent("com.yfd.platform") + .moduleName(module) + .pathInfo(pathInfo); + }) + .strategyConfig(builder -> { + builder.addInclude(scanner("表名,多个英文逗号分割").split(",")) + .entityBuilder() + .enableLombok() + .naming(NamingStrategy.underline_to_camel) + .columnNaming(NamingStrategy.underline_to_camel) + .controllerBuilder() + .enableRestStyle() + .mapperBuilder() + .formatMapperFileName("%sMapper") + .serviceBuilder() + .formatServiceFileName("%sService") + .formatServiceImplFileName("%sServiceImpl") + .controllerBuilder() + .formatFileName("%sController"); + }) + .templateEngine(new FreemarkerTemplateEngine()) + .execute(); + } + +} diff --git a/backend/src/main/java/com/yfd/platform/utils/EncryptConfigUtil.java b/backend/src/main/java/com/yfd/platform/utils/EncryptConfigUtil.java new file mode 100644 index 0000000..4df5527 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/utils/EncryptConfigUtil.java @@ -0,0 +1,28 @@ +package com.yfd.platform.utils; + +import org.jasypt.encryption.pbe.PooledPBEStringEncryptor; +import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig; +import org.jasypt.util.text.BasicTextEncryptor; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +public class EncryptConfigUtil { + + public static void main(String[] args) { + +// String salt = "rca20230101"; +// String password = "123456"; +// +// BasicTextEncryptor textEncryptor = new BasicTextEncryptor(); +// //加密所需的salt +// textEncryptor.setPassword(salt); +// //要加密的数据(数据库的用户名或密码) +// String encrypt = textEncryptor.encrypt(password); +// System.out.println("password:"+encrypt); + + + BCryptPasswordEncoder passwordEncoder=new BCryptPasswordEncoder(); + String cryptPassword=passwordEncoder.encode("dl_2023");//设置缺省密码 + + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/yfd/platform/utils/EncryptUtils.java b/backend/src/main/java/com/yfd/platform/utils/EncryptUtils.java new file mode 100644 index 0000000..2ae2b26 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/utils/EncryptUtils.java @@ -0,0 +1,100 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.utils; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; +import javax.crypto.spec.IvParameterSpec; +import java.nio.charset.StandardCharsets; + +/** + * 加密 + * @author + * @date 2018-11-23 + */ + +public class EncryptUtils { + + private static final String STR_PARAM = "Passw0rd"; + + private static Cipher cipher; + + private static final IvParameterSpec IV = new IvParameterSpec(STR_PARAM.getBytes(StandardCharsets.UTF_8)); + + private static DESKeySpec getDesKeySpec(String source) throws Exception { + if (source == null || source.length() == 0){ + return null; + } + cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); + String strKey = "Passw0rd"; + return new DESKeySpec(strKey.getBytes(StandardCharsets.UTF_8)); + } + + /** + * 对称加密 + */ + public static String desEncrypt(String source) throws Exception { + DESKeySpec desKeySpec = getDesKeySpec(source); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); + SecretKey secretKey = keyFactory.generateSecret(desKeySpec); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, IV); + return byte2hex( + cipher.doFinal(source.getBytes(StandardCharsets.UTF_8))).toUpperCase(); + } + + /** + * 对称解密 + */ + public static String desDecrypt(String source) throws Exception { + byte[] src = hex2byte(source.getBytes(StandardCharsets.UTF_8)); + DESKeySpec desKeySpec = getDesKeySpec(source); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); + SecretKey secretKey = keyFactory.generateSecret(desKeySpec); + cipher.init(Cipher.DECRYPT_MODE, secretKey, IV); + byte[] retByte = cipher.doFinal(src); + return new String(retByte); + } + + private static String byte2hex(byte[] inStr) { + String stmp; + StringBuilder out = new StringBuilder(inStr.length * 2); + for (byte b : inStr) { + stmp = Integer.toHexString(b & 0xFF); + if (stmp.length() == 1) { + // 如果是0至F的单位字符串,则添加0 + out.append("0").append(stmp); + } else { + out.append(stmp); + } + } + return out.toString(); + } + + private static byte[] hex2byte(byte[] b) { + int size = 2; + if ((b.length % size) != 0){ + throw new IllegalArgumentException("长度不是偶数"); + } + byte[] b2 = new byte[b.length / 2]; + for (int n = 0; n < b.length; n += size) { + String item = new String(b, n, 2); + b2[n / 2] = (byte) Integer.parseInt(item, 16); + } + return b2; + } +} diff --git a/backend/src/main/java/com/yfd/platform/utils/ExecutionJob.java b/backend/src/main/java/com/yfd/platform/utils/ExecutionJob.java new file mode 100644 index 0000000..c4a2fcf --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/utils/ExecutionJob.java @@ -0,0 +1,103 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.utils; + +import com.yfd.platform.config.MessageConfig; +import com.yfd.platform.config.thread.ThreadPoolExecutorUtil; +import com.yfd.platform.system.domain.Message; +import com.yfd.platform.system.domain.QuartzJob; +import com.yfd.platform.system.service.IMessageService; +import com.yfd.platform.system.service.IQuartzJobService; +import org.quartz.JobExecutionContext; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.quartz.QuartzJobBean; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; +import java.sql.Timestamp; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 参考人人开源,https://gitee.com/renrenio/renren-security + * + * @author / + * @date 2019-01-07 + */ +@Async +@SuppressWarnings({"unchecked", "all"}) +public class ExecutionJob extends QuartzJobBean { + + /** + * 该处仅供参考 + */ + private final static ThreadPoolExecutor EXECUTOR = + ThreadPoolExecutorUtil.getPoll(); + + @Resource + private IMessageService messageService; + + @Resource + private MessageConfig messageConfig; + + @Override + public void executeInternal(JobExecutionContext context) { + QuartzJob quartzJob = + (QuartzJob) context.getMergedJobDataMap().get(QuartzJob.JOB_KEY); + // 获取spring bean + IQuartzJobService quartzJobService = + SpringContextHolder.getBean(IQuartzJobService.class); + String uuid = quartzJob.getId(); + long startTime = System.currentTimeMillis(); + String jobName = quartzJob.getJobName(); + try { + // 执行任务 + System.out.println( + "--------------------------------------------------------------"); + System.out.println("任务开始执行,任务名称:" + jobName); + QuartzRunnable task = new QuartzRunnable(quartzJob.getJobClass(), + quartzJob.getJobMethod(), + quartzJob.getJobParams()); + Future future = EXECUTOR.submit(task); + future.get(); + long times = System.currentTimeMillis() - startTime; + Message message = new Message(); + message.setCreatetime(new Timestamp(System.currentTimeMillis())); + message.setType("1"); + message.setTitle(quartzJob.getJobName()); + message.setContent(quartzJob.getDescription()); + message.setSenderName("定时器"); + message.setReceiverCodes(quartzJob.getOrderno().toString()); + message.setReceiverNames(""); + message.setStatus("1"); + message.setValidperiod(24); + messageConfig.addMessage(message); + // 任务状态 + System.out.println("任务执行完毕,任务名称:" + jobName + ", " + + "执行时间:" + times + "毫秒"); + System.out.println( + "--------------------------------------------------------------"); + } catch (Exception e) { + System.out.println("任务执行失败,任务名称:" + jobName); + System.out.println( + "--------------------------------------------------------------"); + quartzJob.setStatus("0"); + //更新状态 + quartzJobService.updateById(quartzJob); + } + } + +} diff --git a/backend/src/main/java/com/yfd/platform/utils/FileUtil.java b/backend/src/main/java/com/yfd/platform/utils/FileUtil.java new file mode 100644 index 0000000..6940aee --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/utils/FileUtil.java @@ -0,0 +1,398 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.utils; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.poi.excel.BigExcelWriter; +import cn.hutool.poi.excel.ExcelUtil; +import com.yfd.platform.exception.BadRequestException; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.util.IOUtils; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.apache.tomcat.util.http.fileupload.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.URLDecoder; +import java.security.MessageDigest; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * File工具类,扩展 hutool 工具包 + * + * @author + * @date 2018-12-27 + */ +public class FileUtil extends cn.hutool.core.io.FileUtil { + + private static final Logger log = LoggerFactory.getLogger(FileUtil.class); + + /** + * 系统临时目录 + *
+ * windows 包含路径分割符,但Linux 不包含, + * 在windows \\==\ 前提下, + * 为安全起见 同意拼装 路径分割符, + *
+     *       java.io.tmpdir
+     *       windows : C:\Users/xxx\AppData\Local\Temp\
+     *       linux: /temp
+     * 
+ */ + public static final String SYS_TEM_DIR = System.getProperty("java.io.tmpdir") + File.separator; + /** + * 定义GB的计算常量 + */ + private static final int GB = 1024 * 1024 * 1024; + /** + * 定义MB的计算常量 + */ + private static final int MB = 1024 * 1024; + /** + * 定义KB的计算常量 + */ + private static final int KB = 1024; + + /** + * 格式化小数 + */ + private static final DecimalFormat DF = new DecimalFormat("0.00"); + + public static final String IMAGE = "image"; + public static final String TXT = "document"; + public static final String MUSIC = "music"; + public static final String VIDEO = "video"; + public static final String OTHER = "other"; + + + /** + * MultipartFile转File + */ + public static File toFile(MultipartFile multipartFile) { + // 获取文件名 + String fileName = multipartFile.getOriginalFilename(); + // 获取文件后缀 + String prefix = "." + getExtensionName(fileName); + File file = null; + try { + // 用uuid作为文件名,防止生成的临时文件重复 + file = File.createTempFile(IdUtil.simpleUUID(), prefix); + // MultipartFile to File + multipartFile.transferTo(file); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + return file; + } + + /** + * 获取文件扩展名,不带 . + */ + public static String getExtensionName(String filename) { + if ((filename != null) && (filename.length() > 0)) { + int dot = filename.lastIndexOf('.'); + if ((dot > -1) && (dot < (filename.length() - 1))) { + return filename.substring(dot + 1); + } + } + return filename; + } + + /** + * Java文件操作 获取不带扩展名的文件名 + */ + public static String getFileNameNoEx(String filename) { + if ((filename != null) && (filename.length() > 0)) { + int dot = filename.lastIndexOf('.'); + if ((dot > -1) && (dot < (filename.length()))) { + return filename.substring(0, dot); + } + } + return filename; + } + + /** + * 文件大小转换 + */ + public static String getSize(long size) { + String resultSize; + if (size / GB >= 1) { + //如果当前Byte的值大于等于1GB + resultSize = DF.format(size / (float) GB) + "GB "; + } else if (size / MB >= 1) { + //如果当前Byte的值大于等于1MB + resultSize = DF.format(size / (float) MB) + "MB "; + } else if (size / KB >= 1) { + //如果当前Byte的值大于等于1KB + resultSize = DF.format(size / (float) KB) + "KB "; + } else { + resultSize = size + "B "; + } + return resultSize; + } + + /** + * inputStream 转 File + */ + static File inputStreamToFile(InputStream ins, String name) throws Exception { + File file = new File(SYS_TEM_DIR + name); + if (file.exists()) { + return file; + } + OutputStream os = new FileOutputStream(file); + int bytesRead; + int len = 8192; + byte[] buffer = new byte[len]; + while ((bytesRead = ins.read(buffer, 0, len)) != -1) { + os.write(buffer, 0, bytesRead); + } + os.close(); + ins.close(); + return file; + } + + /** + * 将文件名解析成文件的上传路径 + */ + public static File upload(MultipartFile file, String filePath) { + Date date = new Date(); + SimpleDateFormat format = new SimpleDateFormat("yyyyMMddhhmmssS"); + String name = getFileNameNoEx(file.getOriginalFilename()); + String suffix = getExtensionName(file.getOriginalFilename()); + String nowStr = "-" + format.format(date); + try { + String fileName = name + "." + suffix; + String path = filePath +File.separator + fileName; + // getCanonicalFile 可解析正确各种路径 + File dest = new File(path).getCanonicalFile(); + // 检测是否存在目录 + if (!dest.getParentFile().exists()) { + if (!dest.getParentFile().mkdirs()) { + System.out.println("was not successful."); + } + } + // 文件写入 + file.transferTo(dest); + return dest; + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return null; + } + + /** + * 将文件名解析成文件的上传路径 + * file 上传的文件 + * filePath 存储路径 + * tofilename 保存文件名称 + + */ + public static File upload(MultipartFile file, String filePath,String tofilename) { + try { + String filename = filePath + File.separator + tofilename; + File dest = new File(filename).getCanonicalFile(); + // 检测是否存在目录 + if (!dest.getParentFile().exists()) { + if (!dest.getParentFile().mkdirs()) { + } + } + // 文件写入 + file.transferTo(dest); + return dest; + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return null; + } + + + /** + * 导出excel + */ + public static void downloadExcel(List> list, HttpServletResponse response) throws IOException { + String tempPath = SYS_TEM_DIR + IdUtil.fastSimpleUUID() + ".xlsx"; + String filename = "record"+cn.hutool.core.date.DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss"); + File file = new File(tempPath); + BigExcelWriter writer = ExcelUtil.getBigWriter(file); + // 一次性写出内容,使用默认样式,强制输出标题 + writer.write(list, true); + SXSSFSheet sheet = (SXSSFSheet)writer.getSheet(); + //上面需要强转SXSSFSheet 不然没有trackAllColumnsForAutoSizing方法 + sheet.trackAllColumnsForAutoSizing(); + //列宽自适应 + writer.autoSizeColumnAll(); + //response为HttpServletResponse对象 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8"); + //test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码 + response.setHeader("Content-Disposition", "attachment;filename="+filename+".xlsx"); + ServletOutputStream out = response.getOutputStream(); + // 终止后删除临时文件 + file.deleteOnExit(); + writer.flush(out, true); + //此处记得关闭输出Servlet流 + IoUtil.close(out); + } + + public static String getFileType(String type) { + String documents = "txt doc pdf ppt pps xlsx xls docx"; + String music = "mp3 wav wma mpa ram ra aac aif m4a"; + String video = "avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg"; + String image = "bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg"; + if (image.contains(type)) { + return IMAGE; + } else if (documents.contains(type)) { + return TXT; + } else if (music.contains(type)) { + return MUSIC; + } else if (video.contains(type)) { + return VIDEO; + } else { + return OTHER; + } + } + + public static void checkSize(long maxSize, long size) { + // 1M + int len = 1024 * 1024; + if (size > (maxSize * len)) { + throw new BadRequestException("文件超出规定大小"); + } + } + + /** + * 判断两个文件是否相同 + */ + public static boolean check(File file1, File file2) { + String img1Md5 = getMd5(file1); + String img2Md5 = getMd5(file2); + return img1Md5.equals(img2Md5); + } + + /** + * 判断两个文件是否相同 + */ + public static boolean check(String file1Md5, String file2Md5) { + return file1Md5.equals(file2Md5); + } + + private static byte[] getByte(File file) { + // 得到文件长度 + byte[] b = new byte[(int) file.length()]; + try { + InputStream in = new FileInputStream(file); + try { + System.out.println(in.read(b)); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } catch (FileNotFoundException e) { + log.error(e.getMessage(), e); + return null; + } + return b; + } + + private static String getMd5(byte[] bytes) { + // 16进制字符 + char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + try { + MessageDigest mdTemp = MessageDigest.getInstance("MD5"); + mdTemp.update(bytes); + byte[] md = mdTemp.digest(); + int j = md.length; + char[] str = new char[j * 2]; + int k = 0; + // 移位 输出字符串 + for (byte byte0 : md) { + str[k++] = hexDigits[byte0 >>> 4 & 0xf]; + str[k++] = hexDigits[byte0 & 0xf]; + } + return new String(str); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return null; + } + + /** + * 下载文件 + * + * @param request / + * @param response / + * @param file / + */ + public static void downloadFile(HttpServletRequest request, HttpServletResponse response, File file, boolean deleteOnExit) { + response.setCharacterEncoding(request.getCharacterEncoding()); + response.setContentType("application/octet-stream"); + FileInputStream fis = null; + try { + fis = new FileInputStream(file); + response.setHeader("Content-Disposition", "attachment; filename=" + file.getName()); + IOUtils.copy(fis, response.getOutputStream()); + response.flushBuffer(); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + if (fis != null) { + try { + fis.close(); + if (deleteOnExit) { + file.deleteOnExit(); + } + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } + } + } + /** + * 预览PDF文件 + * + * @param filepath / + * @param response / + */ + public static void viewPDF(String filepath, HttpServletResponse response) throws IOException { + File file=new File(filepath); + String originFileName=file.getName(); //中文编码 + response.setCharacterEncoding("UTF-8"); + String showName= StrUtil.isNotBlank(originFileName)?originFileName:file.getName(); + showName= URLDecoder.decode(showName,"UTF-8"); + response.setHeader("Content-Disposition","inline;fileName="+new String(showName.getBytes(), "ISO8859-1")+";fileName*=UTF-8''"+ new String(showName.getBytes(), "ISO8859-1")); + FileInputStream fis = new FileInputStream(file); + response.setHeader("content-type", "application/pdf"); + response.setContentType("application/pdf; charset=utf-8"); + IOUtils.copy(fis, response.getOutputStream()); + fis.close(); + } + + public static String getMd5(File file) { + return getMd5(getByte(file)); + } + +} diff --git a/backend/src/main/java/com/yfd/platform/utils/MpGenerator.java b/backend/src/main/java/com/yfd/platform/utils/MpGenerator.java new file mode 100644 index 0000000..e3aec6e --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/utils/MpGenerator.java @@ -0,0 +1,79 @@ +package com.yfd.platform.utils; + +import com.baomidou.mybatisplus.generator.FastAutoGenerator; +import com.baomidou.mybatisplus.generator.config.OutputFile; +import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; +import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; +import org.springframework.util.StringUtils; + +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +public class MpGenerator { + /** + * 读取控制台内容 + */ + public static String scanner(String tip) { + Scanner scanner = new Scanner(System.in); + StringBuilder help = new StringBuilder(); + help.append("请输入" + tip + ":"); + System.out.println(help.toString()); + if (scanner.hasNext()) { + String ipt = scanner.next(); + if (StringUtils.hasText(ipt)) { + return ipt; + } + } + throw new RuntimeException("请输入正确的" + tip + "!"); + } + + public static void main(String[] args) { + String projectPath = System.getProperty("user.dir"); + String url = PropertiesUtils.getPropertyField("spring.datasource.url"); + String username = PropertiesUtils.getPropertyField("spring.datasource.username"); + String password = PropertiesUtils.getPropertyField("spring.datasource.password"); + + String moduleName = scanner("模块名"); + String modulePath = moduleName.replace(".", "/"); + + Map pathInfo = new HashMap<>(); + pathInfo.put(OutputFile.entity, projectPath + "/src/main/java/com/yfd/platform/modules/domain" + modulePath + "/entity"); + pathInfo.put(OutputFile.mapper, projectPath + "/src/main/java/com/yfd/platform/modules/domain/" + modulePath + "/dao"); + pathInfo.put(OutputFile.controller, projectPath + "/src/main/java/com/yfd/platform/modules/domain" + modulePath + "/controller"); + pathInfo.put(OutputFile.service, projectPath + "/src/main/java/com/yfd/platform/modules/domain" + modulePath + "/service"); + pathInfo.put(OutputFile.serviceImpl, projectPath + "/src/main/java/com/yfd/platform/modules/domain" + modulePath + "/service/impl"); + pathInfo.put(OutputFile.xml, projectPath + "/src/main/resources/mapper/" + modulePath); + + FastAutoGenerator.create(url, username, password) + .globalConfig(builder -> { + builder.author("fwh") + .disableOpenDir() + .outputDir(projectPath + "/src/main/java"); + }) + .packageConfig(builder -> { + builder.parent(PropertiesUtils.getPropertyField("project.package.name")) + .moduleName(moduleName) + .pathInfo(pathInfo); + }) + .strategyConfig(builder -> { + builder.addInclude(scanner("表名,多个英文逗号分割").split(",")) + .entityBuilder() + .enableLombok() + .naming(NamingStrategy.underline_to_camel) + .columnNaming(NamingStrategy.underline_to_camel) + .controllerBuilder() + .enableRestStyle() + .mapperBuilder() + .formatMapperFileName("%sDao") + .serviceBuilder() + .formatServiceFileName("%sService") + .formatServiceImplFileName("%sServiceImpl") + .controllerBuilder() + .formatFileName("%sController"); + }) + .templateEngine(new FreemarkerTemplateEngine()) + .execute(); + } +} + diff --git a/backend/src/main/java/com/yfd/platform/utils/PropertiesUtils.java b/backend/src/main/java/com/yfd/platform/utils/PropertiesUtils.java new file mode 100644 index 0000000..93c7cfd --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/utils/PropertiesUtils.java @@ -0,0 +1,29 @@ +package com.yfd.platform.utils; + +import org.springframework.core.io.ClassPathResource; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Properties; +/****************************** + * 用途说明: + * 作者姓名: pcj + * 创建时间: 2022/9/20 14:31 + ******************************/ +public class PropertiesUtils { + public final static String RESOURCE_PATH = "application.properties"; + + public final static Properties properties = new Properties(); + + public static String getPropertyField(String parameter) { + //对应resources目录下的资源路径 + ClassPathResource resource = new ClassPathResource(RESOURCE_PATH); + try { + properties.load(new InputStreamReader(resource.getInputStream(), "gbk")); + } catch (IOException e) { + throw new RuntimeException(e); + } + return properties.getProperty(parameter); + } + +} diff --git a/backend/src/main/java/com/yfd/platform/utils/QuartzManage.java b/backend/src/main/java/com/yfd/platform/utils/QuartzManage.java new file mode 100644 index 0000000..3ff265c --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/utils/QuartzManage.java @@ -0,0 +1,187 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.utils; + +import com.yfd.platform.system.domain.QuartzJob; +import lombok.extern.slf4j.Slf4j; +import org.quartz.*; +import org.quartz.impl.triggers.CronTriggerImpl; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; +import java.util.Date; + +import static org.quartz.TriggerBuilder.newTrigger; + +/** + * @author + * @date 2019-01-07 + */ +@Slf4j +@Component +public class QuartzManage { + + private static final String JOB_NAME = "TASK_"; + + @Resource(name = "scheduler") + private Scheduler scheduler; + + public void addJob(QuartzJob quartzJob) { + try { + // 构建job信息 + JobDetail jobDetail = JobBuilder.newJob(ExecutionJob.class). + withIdentity(JOB_NAME + quartzJob.getId()).build(); + + //通过触发器名和cron 表达式创建 Trigger + Trigger cronTrigger = newTrigger() + .withIdentity(JOB_NAME + quartzJob.getId()) + .startNow() + .withSchedule(CronScheduleBuilder.cronSchedule(quartzJob.getJobCron())) + .build(); + + cronTrigger.getJobDataMap().put(QuartzJob.JOB_KEY, quartzJob); + + //重置启动时间 + ((CronTriggerImpl) cronTrigger).setStartTime(new Date()); + + //执行定时任务 + scheduler.scheduleJob(jobDetail, cronTrigger); + + // 暂停任务 + if ("0".equals(quartzJob.getStatus())) { + pauseJob(quartzJob); + } + } catch (Exception e) { + log.error("创建定时任务失败", e); + throw new RuntimeException("创建定时任务失败"); + } + } + + /** + * 更新job cron表达式 + * + * @param quartzJob / + */ + public void updateJobCron(QuartzJob quartzJob) { + try { + TriggerKey triggerKey = + TriggerKey.triggerKey(JOB_NAME + quartzJob.getId()); + CronTrigger trigger = + (CronTrigger) scheduler.getTrigger(triggerKey); + // 如果不存在则创建一个定时任务 + if (trigger == null) { + addJob(quartzJob); + trigger = (CronTrigger) scheduler.getTrigger(triggerKey); + } + CronScheduleBuilder scheduleBuilder = + CronScheduleBuilder.cronSchedule(quartzJob.getJobCron()); + trigger = + trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); + //重置启动时间 + ((CronTriggerImpl) trigger).setStartTime(new Date()); + trigger.getJobDataMap().put(QuartzJob.JOB_KEY, quartzJob); + + scheduler.rescheduleJob(triggerKey, trigger); + // 暂停任务 + if ("0".equals(quartzJob.getStatus())) { + pauseJob(quartzJob); + } + } catch (Exception e) { + log.error("更新定时任务失败", e); + throw new RuntimeException("更新定时任务失败"); + } + + } + + /** + * 删除一个job + * + * @param quartzJob / + */ + public void deleteJob(QuartzJob quartzJob) { + try { + JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId()); + scheduler.pauseJob(jobKey); + scheduler.deleteJob(jobKey); + } catch (Exception e) { + log.error("删除定时任务失败", e); + throw new RuntimeException("删除定时任务失败"); + } + } + + /** + * 恢复一个job + * + * @param quartzJob / + */ + public void resumeJob(QuartzJob quartzJob) { + try { + TriggerKey triggerKey = + TriggerKey.triggerKey(JOB_NAME + quartzJob.getId()); + CronTrigger trigger = + (CronTrigger) scheduler.getTrigger(triggerKey); + // 如果不存在则创建一个定时任务 + if (trigger == null) { + addJob(quartzJob); + } + JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId()); + scheduler.resumeJob(jobKey); + } catch (Exception e) { + log.error("恢复定时任务失败", e); + throw new RuntimeException("恢复定时任务失败"); + } + } + + /** + * 立即执行job + * + * @param quartzJob / + */ + public void runJobNow(QuartzJob quartzJob) { + try { + TriggerKey triggerKey = + TriggerKey.triggerKey(JOB_NAME + quartzJob.getId()); + CronTrigger trigger = + (CronTrigger) scheduler.getTrigger(triggerKey); + // 如果不存在则创建一个定时任务 + if (trigger == null) { + addJob(quartzJob); + } + JobDataMap dataMap = new JobDataMap(); + dataMap.put(QuartzJob.JOB_KEY, quartzJob); + JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId()); + scheduler.triggerJob(jobKey, dataMap); + } catch (Exception e) { + log.error("定时任务执行失败", e); + throw new RuntimeException("定时任务执行失败"); + } + } + + /** + * 暂停一个job + * + * @param quartzJob / + */ + public void pauseJob(QuartzJob quartzJob) { + try { + JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId()); + scheduler.pauseJob(jobKey); + } catch (Exception e) { + log.error("定时任务暂停失败", e); + throw new RuntimeException("定时任务暂停失败"); + } + } +} diff --git a/backend/src/main/java/com/yfd/platform/utils/QuartzRunnable.java b/backend/src/main/java/com/yfd/platform/utils/QuartzRunnable.java new file mode 100644 index 0000000..b1da541 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/utils/QuartzRunnable.java @@ -0,0 +1,58 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.utils; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Method; +import java.util.concurrent.Callable; + +/** + * 执行定时任务 + * @author / + */ +@Slf4j +public class QuartzRunnable implements Callable { + + private final Object target; + private final Method method; + private final String params; + + QuartzRunnable(String beanName, String methodName, String params) + throws NoSuchMethodException, SecurityException { + this.target = SpringContextHolder.getBean(beanName); + this.params = params; + + if (StringUtils.isNotBlank(params)) { + this.method = target.getClass().getDeclaredMethod(methodName, String.class); + } else { + this.method = target.getClass().getDeclaredMethod(methodName); + } + } + + @Override + public Object call() throws Exception { + ReflectionUtils.makeAccessible(method); + if (StringUtils.isNotBlank(params)) { + method.invoke(target, params); + } else { + method.invoke(target); + } + return null; + } +} diff --git a/backend/src/main/java/com/yfd/platform/utils/RequestHolder.java b/backend/src/main/java/com/yfd/platform/utils/RequestHolder.java new file mode 100644 index 0000000..3dbfa55 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/utils/RequestHolder.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.utils; + +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.Objects; + +/** + * 获取 HttpServletRequest + * @author + * @date 2018-11-24 + */ +public class RequestHolder { + + public static HttpServletRequest getHttpServletRequest() { + return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); + } +} diff --git a/backend/src/main/java/com/yfd/platform/utils/RsaUtils.java b/backend/src/main/java/com/yfd/platform/utils/RsaUtils.java new file mode 100644 index 0000000..6638ac7 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/utils/RsaUtils.java @@ -0,0 +1,181 @@ +package com.yfd.platform.utils; + +import org.apache.commons.codec.binary.Base64; + +import javax.crypto.Cipher; +import java.security.*; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +/** + * @author https://www.cnblogs.com/nihaorz/p/10690643.html + * @description Rsa 工具类,公钥私钥生成,加解密 + * @date 2020-05-18 + **/ +public class RsaUtils { + + private static final String SRC = "123456"; + + public static void main(String[] args) throws Exception { + System.out.println("\n"); + RsaKeyPair keyPair = generateKeyPair(); + System.out.println("公钥:" + keyPair.getPublicKey()); + System.out.println("私钥:" + keyPair.getPrivateKey()); + System.out.println("\n"); + test1(keyPair); + System.out.println("\n"); + test2(keyPair); + System.out.println("\n"); + } + + /** + * 公钥加密私钥解密 + */ + private static void test1(RsaKeyPair keyPair) throws Exception { + System.out.println("***************** 公钥加密私钥解密开始 *****************"); + String text1 = encryptByPublicKey(keyPair.getPublicKey(), RsaUtils.SRC); + String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1); + System.out.println("加密前:" + RsaUtils.SRC); + System.out.println("加密后:" + text1); + System.out.println("解密后:" + text2); + if (RsaUtils.SRC.equals(text2)) { + System.out.println("解密字符串和原始字符串一致,解密成功"); + } else { + System.out.println("解密字符串和原始字符串不一致,解密失败"); + } + System.out.println("***************** 公钥加密私钥解密结束 *****************"); + } + + /** + * 私钥加密公钥解密 + * @throws Exception / + */ + private static void test2(RsaKeyPair keyPair) throws Exception { + System.out.println("***************** 私钥加密公钥解密开始 *****************"); + String text1 = encryptByPrivateKey(keyPair.getPrivateKey(), RsaUtils.SRC); + String text2 = decryptByPublicKey(keyPair.getPublicKey(), text1); + System.out.println("加密前:" + RsaUtils.SRC); + System.out.println("加密后:" + text1); + System.out.println("解密后:" + text2); + if (RsaUtils.SRC.equals(text2)) { + System.out.println("解密字符串和原始字符串一致,解密成功"); + } else { + System.out.println("解密字符串和原始字符串不一致,解密失败"); + } + System.out.println("***************** 私钥加密公钥解密结束 *****************"); + } + + /** + * 公钥解密 + * + * @param publicKeyText 公钥 + * @param text 待解密的信息 + * @return / + * @throws Exception / + */ + public static String decryptByPublicKey(String publicKeyText, String text) throws Exception { + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, publicKey); + byte[] result = cipher.doFinal(Base64.decodeBase64(text)); + return new String(result); + } + + /** + * 私钥加密 + * + * @param privateKeyText 私钥 + * @param text 待加密的信息 + * @return / + * @throws Exception / + */ + public static String encryptByPrivateKey(String privateKeyText, String text) throws Exception { + PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, privateKey); + byte[] result = cipher.doFinal(text.getBytes()); + return Base64.encodeBase64String(result); + } + + /** + * 私钥解密 + * + * @param privateKeyText 私钥 + * @param text 待解密的文本 + * @return / + * @throws Exception / + */ + public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception { + PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + byte[] result = cipher.doFinal(Base64.decodeBase64(text)); + return new String(result); + } + + /** + * 公钥加密 + * + * @param publicKeyText 公钥 + * @param text 待加密的文本 + * @return / + */ + public static String encryptByPublicKey(String publicKeyText, String text) throws Exception { + X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + byte[] result = cipher.doFinal(text.getBytes()); + return Base64.encodeBase64String(result); + } + + /** + * 构建RSA密钥对 + * + * @return / + * @throws NoSuchAlgorithmException / + */ + public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(1024); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); + String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded()); + String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded()); + return new RsaKeyPair(publicKeyString, privateKeyString); + } + + + /** + * RSA密钥对对象 + */ + public static class RsaKeyPair { + + private final String publicKey; + private final String privateKey; + + public RsaKeyPair(String publicKey, String privateKey) { + this.publicKey = publicKey; + this.privateKey = privateKey; + } + + public String getPublicKey() { + return publicKey; + } + + public String getPrivateKey() { + return privateKey; + } + + } +} diff --git a/backend/src/main/java/com/yfd/platform/utils/SecurityUtils.java b/backend/src/main/java/com/yfd/platform/utils/SecurityUtils.java new file mode 100644 index 0000000..9df23cd --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/utils/SecurityUtils.java @@ -0,0 +1,84 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.utils; + +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.yfd.platform.exception.BadRequestException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; + +import java.util.List; + +/** + * 获取当前登录的用户 + * @author + * @date 2019-01-17 + */ +@Slf4j +public class SecurityUtils { + + /** + * 获取当前登录的用户 + * @return UserDetails + */ + public static UserDetails getCurrentUser() { + UserDetailsService userDetailsService = SpringContextHolder.getBean(UserDetailsService.class); + return userDetailsService.loadUserByUsername(getCurrentUsername()); + } + + /** + * 获取系统用户名称 + * + * @return 系统用户名称 + */ + public static String getCurrentUsername() { + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null) { + throw new BadRequestException(HttpStatus.UNAUTHORIZED, "当前登录状态过期"); + } + if (authentication.getPrincipal() instanceof UserDetails) { + UserDetails userDetails = (UserDetails) authentication.getPrincipal(); + return userDetails.getUsername(); + } + throw new BadRequestException(HttpStatus.UNAUTHORIZED, "找不到当前登录的信息"); + } + + /** + * 获取系统用户ID + * @return 系统用户ID + */ + public static Long getCurrentUserId() { + UserDetails userDetails = getCurrentUser(); + return new JSONObject(new JSONObject(userDetails).get("user")).get("id", Long.class); + } + + /** + * 获取当前用户的数据权限 + * @return / + */ + public static List getCurrentUserDataScope(){ + UserDetails userDetails = getCurrentUser(); + JSONArray array = JSONUtil.parseArray(new JSONObject(userDetails).get("dataScopes")); + return JSONUtil.toList(array,Long.class); + } + +} diff --git a/backend/src/main/java/com/yfd/platform/utils/SpringContextHolder.java b/backend/src/main/java/com/yfd/platform/utils/SpringContextHolder.java new file mode 100644 index 0000000..b85c770 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/utils/SpringContextHolder.java @@ -0,0 +1,145 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.utils; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.env.Environment; + +import java.util.ArrayList; +import java.util.List; +/** + * @author Jie + * @date 2019-01-07 + */ +@Slf4j +public class SpringContextHolder implements ApplicationContextAware, DisposableBean { + + private static ApplicationContext applicationContext = null; + private static final List CALL_BACKS = new ArrayList<>(); + private static boolean addCallback = true; + + /** + * 针对 某些初始化方法,在SpringContextHolder 未初始化时 提交回调方法。 + * 在SpringContextHolder 初始化后,进行回调使用 + * + * @param callBack 回调函数 + */ + public synchronized static void addCallBacks(CallBack callBack) { + if (addCallback) { + SpringContextHolder.CALL_BACKS.add(callBack); + } else { + log.warn("CallBack:{} 已无法添加!立即执行", callBack.getCallBackName()); + callBack.executor(); + } + } + + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) { + assertContextInjected(); + return (T) applicationContext.getBean(name); + } + + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + public static T getBean(Class requiredType) { + assertContextInjected(); + return applicationContext.getBean(requiredType); + } + + /** + * 获取SpringBoot 配置信息 + * + * @param property 属性key + * @param defaultValue 默认值 + * @param requiredType 返回类型 + * @return / + */ + public static T getProperties(String property, T defaultValue, Class requiredType) { + T result = defaultValue; + try { + result = getBean(Environment.class).getProperty(property, requiredType); + } catch (Exception ignored) {} + return result; + } + + /** + * 获取SpringBoot 配置信息 + * + * @param property 属性key + * @return / + */ + public static String getProperties(String property) { + return getProperties(property, null, String.class); + } + + /** + * 获取SpringBoot 配置信息 + * + * @param property 属性key + * @param requiredType 返回类型 + * @return / + */ + public static T getProperties(String property, Class requiredType) { + return getProperties(property, null, requiredType); + } + + /** + * 检查ApplicationContext不为空. + */ + private static void assertContextInjected() { + if (applicationContext == null) { + throw new IllegalStateException("applicaitonContext属性未注入, 请在applicationContext" + + ".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder."); + } + } + + /** + * 清除SpringContextHolder中的ApplicationContext为Null. + */ + private static void clearHolder() { + log.debug("清除SpringContextHolder中的ApplicationContext:" + + applicationContext); + applicationContext = null; + } + + @Override + public void destroy() { + SpringContextHolder.clearHolder(); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + if (SpringContextHolder.applicationContext != null) { + log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext); + } + SpringContextHolder.applicationContext = applicationContext; + if (addCallback) { + for (CallBack callBack : SpringContextHolder.CALL_BACKS) { + callBack.executor(); + } + CALL_BACKS.clear(); + } + SpringContextHolder.addCallback = false; + } +} diff --git a/backend/src/main/java/com/yfd/platform/utils/StringUtils.java b/backend/src/main/java/com/yfd/platform/utils/StringUtils.java new file mode 100644 index 0000000..b2a9be7 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/utils/StringUtils.java @@ -0,0 +1,305 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yfd.platform.utils; + + +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.yfd.platform.constant.Constant; +import lombok.SneakyThrows; +import org.lionsoul.ip2region.DataBlock; +import org.lionsoul.ip2region.DbConfig; +import org.lionsoul.ip2region.DbSearcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; +import eu.bitwalker.useragentutils.Browser; +import eu.bitwalker.useragentutils.UserAgent; +import jakarta.servlet.http.HttpServletRequest; +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.UnknownHostException; +import java.util.Calendar; +import java.util.Date; +import java.util.Enumeration; + +/** + * @author + * 字符串工具类, 继承org.apache.commons.lang3.StringUtils类 + */ +public class StringUtils extends org.apache.commons.lang3.StringUtils { + + private static final Logger log = LoggerFactory.getLogger(StringUtils.class); + private static boolean ipLocal = false; + private static File file ; + private static DbConfig config; + private static final char SEPARATOR = '_'; + private static final String UNKNOWN = "unknown"; + + static { + SpringContextHolder.addCallBacks(() -> { + StringUtils.ipLocal = SpringContextHolder.getProperties("ip.local-parsing", false, Boolean.class); + if (ipLocal) { + /* + * 此文件为独享 ,不必关闭 + */ + String path = "ip2region/ip2region.db"; + String name = "ip2region.db"; + try { + config = new DbConfig(); + file = FileUtil.inputStreamToFile(new ClassPathResource(path).getInputStream(), name); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + }); + } + + /** + * 驼峰命名法工具 + * + * @return toCamelCase(" hello_world ") == "helloWorld" + * toCapitalizeCamelCase("hello_world") == "HelloWorld" + * toUnderScoreCase("helloWorld") = "hello_world" + */ + public static String toCamelCase(String s) { + if (s == null) { + return null; + } + + s = s.toLowerCase(); + + StringBuilder sb = new StringBuilder(s.length()); + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + + if (c == SEPARATOR) { + upperCase = true; + } else if (upperCase) { + sb.append(Character.toUpperCase(c)); + upperCase = false; + } else { + sb.append(c); + } + } + + return sb.toString(); + } + + /** + * 驼峰命名法工具 + * + * @return toCamelCase(" hello_world ") == "helloWorld" + * toCapitalizeCamelCase("hello_world") == "HelloWorld" + * toUnderScoreCase("helloWorld") = "hello_world" + */ + public static String toCapitalizeCamelCase(String s) { + if (s == null) { + return null; + } + s = toCamelCase(s); + return s.substring(0, 1).toUpperCase() + s.substring(1); + } + + /** + * 驼峰命名法工具 + * + * @return toCamelCase(" hello_world ") == "helloWorld" + * toCapitalizeCamelCase("hello_world") == "HelloWorld" + * toUnderScoreCase("helloWorld") = "hello_world" + */ + static String toUnderScoreCase(String s) { + if (s == null) { + return null; + } + + StringBuilder sb = new StringBuilder(); + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + + boolean nextUpperCase = true; + + if (i < (s.length() - 1)) { + nextUpperCase = Character.isUpperCase(s.charAt(i + 1)); + } + + if ((i > 0) && Character.isUpperCase(c)) { + if (!upperCase || !nextUpperCase) { + sb.append(SEPARATOR); + } + upperCase = true; + } else { + upperCase = false; + } + + sb.append(Character.toLowerCase(c)); + } + + return sb.toString(); + } + + /** + * 获取ip地址 + */ + public static String getIp(HttpServletRequest request) { + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + String comma = ","; + String localhost = "127.0.0.1"; + if (ip.contains(comma)) { + ip = ip.split(",")[0]; + } + if (localhost.equals(ip)) { + // 获取本机真正的ip地址 + try { + ip = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + log.error(e.getMessage(), e); + } + } + return ip; + } + + /** + * 根据ip获取详细地址 + */ + @SneakyThrows + public static String getCityInfo(String ip) { + if (ipLocal) { + return getLocalCityInfo(ip); + } else { + return getHttpCityInfo(ip); + } + } + + /** + * 根据ip获取详细地址 + */ + public static String getHttpCityInfo(String ip) { + String host = "202.108.22.5"; + //超时应该在3钞以上 + int timeOut = 3000; + boolean status = false; + try { + status = InetAddress.getByName(host).isReachable(timeOut); + } catch (IOException e) { + e.printStackTrace(); + } + String api =""; + if (status){ + api = HttpUtil.get(String.format(Constant.Url.IP_URL, ip)); + }else { + api = "{\"ip\":\"127.0.0.1\",\"pro\":\"\",\"proCode\":\"999999\",\"city\":\"\",\"cityCode\":\"0\",\"region\":\"\",\"regionCode\":\"0\",\"addr\":\" 局域网\",\"regionNames\":\"\",\"err\":\"noprovince\"}"; + } + JSONObject object = JSONUtil.parseObj(api); + return object.get("addr", String.class); + } + + + /** + * 根据ip获取详细地址 + */ + public static String getLocalCityInfo(String ip) { + try { + DataBlock dataBlock = new DbSearcher(config, file.getPath()) + .binarySearch(ip); + String region = dataBlock.getRegion(); + String address = region.replace("0|", ""); + char symbol = '|'; + if (address.charAt(address.length() - 1) == symbol) { + address = address.substring(0, address.length() - 1); + } + return address.equals(Constant.REGION) ? "内网IP" : address; + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return ""; + } + + public static String getBrowser(HttpServletRequest request) { + UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent")); + Browser browser = userAgent.getBrowser(); + return browser.getName(); + } + + /** + * 获得当天是周几 + */ + public static String getWeekDay() { + String[] weekDays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + + int w = cal.get(Calendar.DAY_OF_WEEK) - 1; + if (w < 0) { + w = 0; + } + return weekDays[w]; + } + + /** + * 获取当前机器的IP + * + * @return / + */ + public static String getLocalIp() { + try { + InetAddress candidateAddress = null; + // 遍历所有的网络接口 + for (Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); interfaces.hasMoreElements();) { + NetworkInterface anInterface = interfaces.nextElement(); + // 在所有的接口下再遍历IP + for (Enumeration inetAddresses = anInterface.getInetAddresses(); inetAddresses.hasMoreElements();) { + InetAddress inetAddr = inetAddresses.nextElement(); + // 排除loopback类型地址 + if (!inetAddr.isLoopbackAddress()) { + if (inetAddr.isSiteLocalAddress()) { + // 如果是site-local地址,就是它了 + return inetAddr.getHostAddress(); + } else if (candidateAddress == null) { + // site-local类型的地址未被发现,先记录候选地址 + candidateAddress = inetAddr; + } + } + } + } + if (candidateAddress != null) { + return candidateAddress.getHostAddress(); + } + // 如果没有发现 non-loopback地址.只能用最次选的方案 + InetAddress jdkSuppliedAddress = InetAddress.getLocalHost(); + if (jdkSuppliedAddress == null) { + return ""; + } + return jdkSuppliedAddress.getHostAddress(); + } catch (Exception e) { + return ""; + } + } +} diff --git a/backend/src/main/resources/application-dev.yml b/backend/src/main/resources/application-dev.yml new file mode 100644 index 0000000..3576152 --- /dev/null +++ b/backend/src/main/resources/application-dev.yml @@ -0,0 +1,94 @@ +server: + port: 8093 + +spring: + #应用名称 + application: + name: Project-plateform + datasource: + type: com.alibaba.druid.pool.DruidDataSource + druid: + master: + driverClassName: com.mysql.cj.jdbc.Driver + url: "${DB_MASTER_URL:jdbc:mysql://localhost:3306/wppdb?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true}" + username: "${DB_MASTER_USERNAME:root}" + password: "${DB_MASTER_PASSWORD:}" + slave: + driverClassName: com.mysql.cj.jdbc.Driver + url: "${DB_SLAVE_URL:jdbc:mysql://localhost:3306/wppdb?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true}" + username: "${DB_SLAVE_USERNAME:root}" + password: "${DB_SLAVE_PASSWORD:}" + + mvc: + pathmatch: + matching-strategy: ant_path_matcher + servlet: + multipart: + max-file-size: 30MB + max-request-size: 100MB + +logging: + file: + name: logs/projectname.log + level: + com.genersoft.iot: debug + com.genersoft.iot.vmp.storager.dao: info + com.genersoft.iot.vmp.gb28181: info + +# 在线文档: swagger-ui(生产环境建议关闭) +swagger-ui: + enabled: true + + +# 登录相关配置 +login: + # 登录缓存 + cache-enable: true + # 是否限制单用户登录 + single-login: false + # 验证码 + login-code: + # 验证码类型配置 查看 LoginProperties 类 + code-type: arithmetic + +# 启动自动数据库初始化(仅 dev/server): +app: + init: + enabled: false + schema: classpath:db-init/sql/min-schema.sql + # data 文件可选;为避免复杂 dump 解析问题,先不导入 + # data: + marker-table: sys_user + marker-version: v1.0.0 + # 登录图形验证码有效时间/分钟 + expiration: 2 + # 验证码高度 + width: 111 + # 验证码宽度 + heigth: 36 + # 内容长度 + length: 2 + # 字体名称,为空则使用默认字体 + font-name: + # 字体大小 + font-size: 25 + +# IP 本地解析 +ip: + local-parsing: true + + +file-space: #项目文档空间 + files: D:\demoproject\files\ #单独上传的文件附件 + system: D:\demoproject\system\ #单独上传的文件 + +task: + pool: + # 核心线程池大小 + core-pool-size: 10 + # 最大线程数 + max-pool-size: 30 + # 活跃时间 + keep-alive-seconds: 60 + # 队列容量 + queue-capacity: 50 diff --git a/backend/src/main/resources/application-framework.yml b/backend/src/main/resources/application-framework.yml new file mode 100644 index 0000000..9baea68 --- /dev/null +++ b/backend/src/main/resources/application-framework.yml @@ -0,0 +1,30 @@ +jasypt: + encryptor: + password: ${JASYPT_PASSWORD:} + +# 密码加密传输,前端公钥加密,后端私钥解密(共性配置) +rsa: + private_key: ${RSA_PRIVATE_KEY:} + +# Actuator & Micrometer 默认配置(共性) +management: + endpoints: + web: + exposure: + include: health,info,metrics,prometheus,env,beans,threaddump,loggers,configprops + endpoint: + health: + show-details: always + metrics: + tags: + application: ${spring.application.name:platform} + + +# Springdoc 默认配置(共性) +springdoc: + api-docs: + enabled: true + swagger-ui: + enabled: true + path: /swagger-ui.html + packages-to-scan: com.yfd.platform diff --git a/backend/src/main/resources/application-server.yml b/backend/src/main/resources/application-server.yml new file mode 100644 index 0000000..b4e0057 --- /dev/null +++ b/backend/src/main/resources/application-server.yml @@ -0,0 +1,52 @@ +server: + port: 8090 + +spring: + #应用名称 + application: + name: Project-plateform + datasource: + type: com.alibaba.druid.pool.DruidDataSource + druid: + master: + driverClassName: com.mysql.cj.jdbc.Driver + url: "${DB_MASTER_URL:jdbc:mysql://43.138.168.68:3306/frameworkdb2025?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true}" + username: "${DB_MASTER_USERNAME:root}" + password: "${DB_MASTER_PASSWORD:}" + + mvc: + pathmatch: + matching-strategy: ant_path_matcher + servlet: + multipart: + max-file-size: 30MB + max-request-size: 100MB + +logging: + file: + name: logs/projectname.log + + level: + com.genersoft.iot: debug + com.genersoft.iot.vmp.storager.dao: info + com.genersoft.iot.vmp.gb28181: info + +# 在线文档: swagger-ui(生产环境建议关闭) +swagger-ui: + enabled: false + +file-space: #项目文档空间 + files: D:\demoproject\files\ #单独上传的文件附件 + useravatar: D:\demoproject\useravatar\ #用户头像 + system: D:\demoproject\system\ #系统文档根目录,用于头像等静态资源 + +# 启动自动数据库初始化(仅 dev/server): +app: + init: + enabled: true + schema: classpath:db-init/sql/min-schema.sql + # data 文件可选;为避免复杂 dump 解析问题,先不导入 + # data: + marker-table: sys_user + marker-version: v1.0.0 + diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml new file mode 100644 index 0000000..5ed59cb --- /dev/null +++ b/backend/src/main/resources/application.yml @@ -0,0 +1,34 @@ +spring: + profiles: + active: dev + +jasypt: + encryptor: + password: ${JASYPT_PASSWORD:} + +#密码加密传输,前端公钥加密,后端私钥解密 +rsa: + private_key: ${RSA_PRIVATE_KEY:} + +# Actuator & Micrometer 默认配置 +management: + endpoints: + web: + exposure: + include: health,info,metrics,prometheus,env,beans,threaddump,loggers,configprops + endpoint: + health: + show-details: always + metrics: + tags: + application: ${spring.application.name:platform} + + +# Springdoc 默认配置 +springdoc: + api-docs: + enabled: true + swagger-ui: + enabled: true + path: /swagger-ui.html + packages-to-scan: com.yfd.platform diff --git a/backend/src/main/resources/banner.txt b/backend/src/main/resources/banner.txt new file mode 100644 index 0000000..47e51d1 --- /dev/null +++ b/backend/src/main/resources/banner.txt @@ -0,0 +1,8 @@ + __ ____ ____ .___________. _______ ______ __ __ + | | \ \ / / | || ____| / || | | | + | | \ \/ / `---| |----`| |__ | ,----'| |__| | + .--. | | \_ _/ | | | __| | | | __ | + | `--' | | | | | | |____ | `----.| | | | + \______/ |__| |__| |_______| \______||__| |__| + + diff --git a/backend/src/main/resources/ip2region/ip2region.db b/backend/src/main/resources/ip2region/ip2region.db new file mode 100644 index 0000000..e69de29 diff --git a/backend/src/main/resources/log4jdbc.log4j2.properties b/backend/src/main/resources/log4jdbc.log4j2.properties new file mode 100644 index 0000000..302525f --- /dev/null +++ b/backend/src/main/resources/log4jdbc.log4j2.properties @@ -0,0 +1,4 @@ +# If you use SLF4J. First, you need to tell log4jdbc-log4j2 that you want to use the SLF4J logger +log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator +log4jdbc.auto.load.popular.drivers=false +log4jdbc.drivers=com.mysql.cj.jdbc.Driver \ No newline at end of file diff --git a/backend/src/main/resources/logback-spring.xml b/backend/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..31a0ed8 --- /dev/null +++ b/backend/src/main/resources/logback-spring.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + UTF-8 + ${LOG_PATTERN} + + + + + + ${LOG_PATH}/${LOG_FILE}.log + + UTF-8 + ${LOG_PATTERN} + + + ${LOG_PATH}/${LOG_FILE}.%d{yyyy-MM-dd}.log + 30 + + + + + + + + + + + + + \ No newline at end of file diff --git a/backend/src/main/resources/logback.xml b/backend/src/main/resources/logback.xml new file mode 100644 index 0000000..c949c89 --- /dev/null +++ b/backend/src/main/resources/logback.xml @@ -0,0 +1,45 @@ + + + yfAdmin + + + + + + + ${log.pattern} + ${log.charset} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/backend/src/main/resources/mapper/system/DictionaryMapper.xml b/backend/src/main/resources/mapper/system/DictionaryMapper.xml new file mode 100644 index 0000000..16edd1e --- /dev/null +++ b/backend/src/main/resources/mapper/system/DictionaryMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/backend/src/main/resources/mapper/system/MessageMapper.xml b/backend/src/main/resources/mapper/system/MessageMapper.xml new file mode 100644 index 0000000..96cbcfc --- /dev/null +++ b/backend/src/main/resources/mapper/system/MessageMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/backend/src/main/resources/mapper/system/Model3dMapper.xml b/backend/src/main/resources/mapper/system/Model3dMapper.xml new file mode 100644 index 0000000..d935f68 --- /dev/null +++ b/backend/src/main/resources/mapper/system/Model3dMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/backend/src/main/resources/mapper/system/QuartzJobMapper.xml b/backend/src/main/resources/mapper/system/QuartzJobMapper.xml new file mode 100644 index 0000000..b523b0a --- /dev/null +++ b/backend/src/main/resources/mapper/system/QuartzJobMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/backend/src/main/resources/mapper/system/SysConfigMapper.xml b/backend/src/main/resources/mapper/system/SysConfigMapper.xml new file mode 100644 index 0000000..cf22aa4 --- /dev/null +++ b/backend/src/main/resources/mapper/system/SysConfigMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/backend/src/main/resources/mapper/system/SysDictionaryItemsMapper.xml b/backend/src/main/resources/mapper/system/SysDictionaryItemsMapper.xml new file mode 100644 index 0000000..1bf0942 --- /dev/null +++ b/backend/src/main/resources/mapper/system/SysDictionaryItemsMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/backend/src/main/resources/mapper/system/SysDictionaryMapper.xml b/backend/src/main/resources/mapper/system/SysDictionaryMapper.xml new file mode 100644 index 0000000..6963e40 --- /dev/null +++ b/backend/src/main/resources/mapper/system/SysDictionaryMapper.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/backend/src/main/resources/mapper/system/SysLogMapper.xml b/backend/src/main/resources/mapper/system/SysLogMapper.xml new file mode 100644 index 0000000..1046de9 --- /dev/null +++ b/backend/src/main/resources/mapper/system/SysLogMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/backend/src/main/resources/mapper/system/SysMenuMapper.xml b/backend/src/main/resources/mapper/system/SysMenuMapper.xml new file mode 100644 index 0000000..febfab3 --- /dev/null +++ b/backend/src/main/resources/mapper/system/SysMenuMapper.xml @@ -0,0 +1,103 @@ + + + + + + update sys_menu set orderno=orderno+1 where parentid=#{parentid} and orderno < #{Orderno} and orderno >= #{upOrderno} + + + + + update sys_menu SET orderno=orderno-1 where parentid=#{parentid} and orderno > #{Orderno} and orderno <= #{downOrderno} + + + + + + + + + + diff --git a/backend/src/main/resources/mapper/system/SysMessageMapper.xml b/backend/src/main/resources/mapper/system/SysMessageMapper.xml new file mode 100644 index 0000000..cda7c6f --- /dev/null +++ b/backend/src/main/resources/mapper/system/SysMessageMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/backend/src/main/resources/mapper/system/SysOrganizationMapper.xml b/backend/src/main/resources/mapper/system/SysOrganizationMapper.xml new file mode 100644 index 0000000..053c5ae --- /dev/null +++ b/backend/src/main/resources/mapper/system/SysOrganizationMapper.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/backend/src/main/resources/mapper/system/SysQuartzJobMapper.xml b/backend/src/main/resources/mapper/system/SysQuartzJobMapper.xml new file mode 100644 index 0000000..268cb3d --- /dev/null +++ b/backend/src/main/resources/mapper/system/SysQuartzJobMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/backend/src/main/resources/mapper/system/SysRoleMapper.xml b/backend/src/main/resources/mapper/system/SysRoleMapper.xml new file mode 100644 index 0000000..4b37516 --- /dev/null +++ b/backend/src/main/resources/mapper/system/SysRoleMapper.xml @@ -0,0 +1,116 @@ + + + + + INSERT INTO sys_role_menu + + + id, + + + roleid, + + + menuid + + + + + #{id}, + + + #{roleid}, + + + #{menuid} + + + + + + + + + + + + + + + + + + + + delete from sys_role_users where userid !=(select u.id from sys_user u where u.account="admin") and roleid=#{roleid} and userid=#{urserid} + + + + + DELETE FROM sys_role_menu WHERE roleid= #{id} + + + + + DELETE FROM sys_role_users WHERE roleid= #{id} + + diff --git a/backend/src/main/resources/mapper/system/SysUserMapper.xml b/backend/src/main/resources/mapper/system/SysUserMapper.xml new file mode 100644 index 0000000..f89b6dc --- /dev/null +++ b/backend/src/main/resources/mapper/system/SysUserMapper.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + insert into sys_role_users value (#{id},#{roleid},#{userid}) + + + + + + + + + + + + + + + + + + delete from sys_role_users where userid=#{userid} + + + + delete from sys_role_users + where + userid=#{userid} + and roleid not in + + #{roleids} + + + + DELETE FROM sys_role_users WHERE userid IN + + #{id} + + + + diff --git a/backend/src/main/resources/quartz.properties b/backend/src/main/resources/quartz.properties new file mode 100644 index 0000000..03d3988 --- /dev/null +++ b/backend/src/main/resources/quartz.properties @@ -0,0 +1,21 @@ +######################################## +# Quartz 默认配置示例(RAMJobStore) +######################################## + +org.quartz.scheduler.instanceName = PlatformScheduler +org.quartz.scheduler.instanceId = AUTO + +# 线程池配置 +org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool +org.quartz.threadPool.threadCount = 10 +org.quartz.threadPool.threadPriority = 5 + +# 使用内存存储(如需持久化请改为 JobStoreTX 并配置数据源) +org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore + +# Misfire 阈值 +org.quartz.jobStore.misfireThreshold = 60000 + +# 插件(可选):关闭时清理 +org.quartz.plugin.shutdownHook.class = org.quartz.plugins.management.ShutdownHookPlugin +org.quartz.plugin.shutdownHook.cleanShutdown = true \ No newline at end of file diff --git a/backend/src/main/resources/static/assets/401-099a3a32.js b/backend/src/main/resources/static/assets/401-099a3a32.js new file mode 100644 index 0000000..314a602 --- /dev/null +++ b/backend/src/main/resources/static/assets/401-099a3a32.js @@ -0,0 +1 @@ +import{d as k,a9 as v,aa as x,c as V,g as o,w as s,a as i,Q as C,e as l,I as y,o as R,j as a,i as e,U as z,_ as B}from"./index-b25f0d08.js";const G={class:"errPage-container"},N={class:"list-unstyled"},U={class:"link-type"},j=["src"],I=["src"],P={name:"Page401"},h=k({...P,setup(E){const u=v({errGif:new URL("/assets/401-a61ddb94.gif",self.location).href,ewizardClap:"https://wpimg.wallstcn.com/007ef517-bafd-4066-aae4-6883632d9646",dialogVisible:!1}),{errGif:p,ewizardClap:_,dialogVisible:n}=x(u),f=y();function m(){f.back()}return(L,t)=>{const c=l("el-button"),b=l("router-link"),r=l("el-col"),g=l("el-row"),w=l("el-dialog");return R(),V("div",G,[o(c,{icon:"el-icon-arrow-left",class:"pan-back-btn",onClick:m},{default:s(()=>[...t[2]||(t[2]=[a(" 返回 ",-1)])]),_:1}),o(g,null,{default:s(()=>[o(r,{span:12},{default:s(()=>[t[6]||(t[6]=e("h1",{class:"text-jumbo text-ginormous"},"Oops!",-1)),t[7]||(t[7]=a(" gif来源",-1)),t[8]||(t[8]=e("a",{href:"https://zh.airbnb.com/",target:"_blank"},"airbnb",-1)),t[9]||(t[9]=a(" 页面 ",-1)),t[10]||(t[10]=e("h2",null,"你没有权限去该页面",-1)),t[11]||(t[11]=e("h6",null,"如有不满请联系你领导",-1)),e("ul",N,[t[4]||(t[4]=e("li",null,"或者你可以去:",-1)),e("li",U,[o(b,{to:"/dashboard"},{default:s(()=>[...t[3]||(t[3]=[a(" 回首页 ",-1)])]),_:1})]),t[5]||(t[5]=e("li",{class:"link-type"},[e("a",{href:"https://www.taobao.com/"},"随便看看")],-1)),e("li",null,[e("a",{href:"#",onClick:t[0]||(t[0]=z(d=>n.value=!0,["prevent"]))},"点我看图")])])]),_:1}),o(r,{span:12},{default:s(()=>[e("img",{src:i(p),width:"313",height:"428",alt:"Girl has dropped her ice cream."},null,8,j)]),_:1})]),_:1}),o(w,{modelValue:i(n),"onUpdate:modelValue":t[1]||(t[1]=d=>C(n)?n.value=d:null),title:"随便看"},{default:s(()=>[e("img",{src:i(_),class:"pan-img"},null,8,I)]),_:1},8,["modelValue"])])}}});const O=B(h,[["__scopeId","data-v-f88583c8"]]);export{O as default}; diff --git a/backend/src/main/resources/static/assets/401-485a4475.js b/backend/src/main/resources/static/assets/401-485a4475.js new file mode 100644 index 0000000..390a98a --- /dev/null +++ b/backend/src/main/resources/static/assets/401-485a4475.js @@ -0,0 +1 @@ +import{d as v,aa as x,ab as V,c as C,f as t,w as o,a as i,S as y,i as a,M as I,o as R,j as _,h as e,W as S,J as z,K as B,_ as G}from"./index-5c62e6c4.js";const s=l=>(z("data-v-25007613"),l=l(),B(),l),N={class:"errPage-container"},j=s(()=>e("h1",{class:"text-jumbo text-ginormous"},"Oops!",-1)),M=s(()=>e("a",{href:"https://zh.airbnb.com/",target:"_blank"},"airbnb",-1)),P=s(()=>e("h2",null,"你没有权限去该页面",-1)),U=s(()=>e("h6",null,"如有不满请联系你领导",-1)),E={class:"list-unstyled"},J=s(()=>e("li",null,"或者你可以去:",-1)),K={class:"link-type"},L=s(()=>e("li",{class:"link-type"},[e("a",{href:"https://www.taobao.com/"},"随便看看")],-1)),O=["src"],T=["src"],W={name:"Page401"},$=v({...W,setup(l){const u=x({errGif:new URL("/assets/401-a61ddb94.gif",self.location).href,ewizardClap:"https://wpimg.wallstcn.com/007ef517-bafd-4066-aae4-6883632d9646",dialogVisible:!1}),{errGif:p,ewizardClap:f,dialogVisible:n}=V(u),h=I();function m(){h.back()}return(q,c)=>{const b=a("el-button"),g=a("router-link"),r=a("el-col"),w=a("el-row"),k=a("el-dialog");return R(),C("div",N,[t(b,{icon:"el-icon-arrow-left",class:"pan-back-btn",onClick:m},{default:o(()=>[_(" 返回 ")]),_:1}),t(w,null,{default:o(()=>[t(r,{span:12},{default:o(()=>[j,_(" gif来源"),M,_(" 页面 "),P,U,e("ul",E,[J,e("li",K,[t(g,{to:"/dashboard"},{default:o(()=>[_(" 回首页 ")]),_:1})]),L,e("li",null,[e("a",{href:"#",onClick:c[0]||(c[0]=S(d=>n.value=!0,["prevent"]))},"点我看图")])])]),_:1}),t(r,{span:12},{default:o(()=>[e("img",{src:i(p),width:"313",height:"428",alt:"Girl has dropped her ice cream."},null,8,O)]),_:1})]),_:1}),t(k,{modelValue:i(n),"onUpdate:modelValue":c[1]||(c[1]=d=>y(n)?n.value=d:null),title:"随便看"},{default:o(()=>[e("img",{src:i(f),class:"pan-img"},null,8,T)]),_:1},8,["modelValue"])])}}});const D=G($,[["__scopeId","data-v-25007613"]]);export{D as default}; diff --git a/backend/src/main/resources/static/assets/401-a61ddb94.gif b/backend/src/main/resources/static/assets/401-a61ddb94.gif new file mode 100644 index 0000000..cd6e0d9 Binary files /dev/null and b/backend/src/main/resources/static/assets/401-a61ddb94.gif differ diff --git a/backend/src/main/resources/static/assets/401-fffd1e4b.css b/backend/src/main/resources/static/assets/401-fffd1e4b.css new file mode 100644 index 0000000..6a09eaa --- /dev/null +++ b/backend/src/main/resources/static/assets/401-fffd1e4b.css @@ -0,0 +1 @@ +.errPage-container[data-v-f88583c8]{width:800px;max-width:100%;margin:100px auto}.errPage-container .pan-back-btn[data-v-f88583c8]{background:#008489;color:#fff;border:none!important}.errPage-container .pan-gif[data-v-f88583c8]{margin:0 auto;display:block}.errPage-container .pan-img[data-v-f88583c8]{display:block;margin:0 auto;width:100%}.errPage-container .text-jumbo[data-v-f88583c8]{font-size:60px;font-weight:700;color:#484848}.errPage-container .list-unstyled[data-v-f88583c8]{font-size:14px}.errPage-container .list-unstyled li[data-v-f88583c8]{padding-bottom:5px}.errPage-container .list-unstyled a[data-v-f88583c8]{color:#008489;text-decoration:none}.errPage-container .list-unstyled a[data-v-f88583c8]:hover{text-decoration:underline} diff --git a/backend/src/main/resources/static/assets/404-51ac6f86.css b/backend/src/main/resources/static/assets/404-51ac6f86.css new file mode 100644 index 0000000..52dce10 --- /dev/null +++ b/backend/src/main/resources/static/assets/404-51ac6f86.css @@ -0,0 +1 @@ +.wscn-http404-container[data-v-578ebab8]{transform:translate(-50%,-50%);position:absolute;top:40%;left:50%}.wscn-http404[data-v-578ebab8]{position:relative;width:1200px;padding:0 50px;overflow:hidden}.wscn-http404 .pic-404[data-v-578ebab8]{position:relative;float:left;width:600px;overflow:hidden}.wscn-http404 .pic-404__parent[data-v-578ebab8]{width:100%}.wscn-http404 .pic-404__child[data-v-578ebab8]{position:absolute}.wscn-http404 .pic-404__child.left[data-v-578ebab8]{width:80px;top:17px;left:220px;opacity:0;animation-name:cloudLeft-578ebab8;animation-duration:2s;animation-timing-function:linear;animation-fill-mode:forwards;animation-delay:1s}.wscn-http404 .pic-404__child.mid[data-v-578ebab8]{width:46px;top:10px;left:420px;opacity:0;animation-name:cloudMid-578ebab8;animation-duration:2s;animation-timing-function:linear;animation-fill-mode:forwards;animation-delay:1.2s}.wscn-http404 .pic-404__child.right[data-v-578ebab8]{width:62px;top:100px;left:500px;opacity:0;animation-name:cloudRight-578ebab8;animation-duration:2s;animation-timing-function:linear;animation-fill-mode:forwards;animation-delay:1s}@keyframes cloudLeft-578ebab8{0%{top:17px;left:220px;opacity:0}20%{top:33px;left:188px;opacity:1}80%{top:81px;left:92px;opacity:1}to{top:97px;left:60px;opacity:0}}@keyframes cloudMid-578ebab8{0%{top:10px;left:420px;opacity:0}20%{top:40px;left:360px;opacity:1}70%{top:130px;left:180px;opacity:1}to{top:160px;left:120px;opacity:0}}@keyframes cloudRight-578ebab8{0%{top:100px;left:500px;opacity:0}20%{top:120px;left:460px;opacity:1}80%{top:180px;left:340px;opacity:1}to{top:200px;left:300px;opacity:0}}.wscn-http404 .bullshit[data-v-578ebab8]{position:relative;float:left;width:300px;padding:30px 0;overflow:hidden}.wscn-http404 .bullshit__oops[data-v-578ebab8]{font-size:32px;font-weight:700;line-height:40px;color:#1482f0;opacity:0;margin-bottom:20px;animation-name:slideUp-578ebab8;animation-duration:.5s;animation-fill-mode:forwards}.wscn-http404 .bullshit__headline[data-v-578ebab8]{font-size:20px;line-height:24px;color:#222;font-weight:700;opacity:0;margin-bottom:10px;animation-name:slideUp-578ebab8;animation-duration:.5s;animation-delay:.1s;animation-fill-mode:forwards}.wscn-http404 .bullshit__info[data-v-578ebab8]{font-size:13px;line-height:21px;color:gray;opacity:0;margin-bottom:30px;animation-name:slideUp-578ebab8;animation-duration:.5s;animation-delay:.2s;animation-fill-mode:forwards}.wscn-http404 .bullshit__return-home[data-v-578ebab8]{display:block;float:left;width:110px;height:36px;background:#1482f0;border-radius:100px;text-align:center;color:#fff;opacity:0;font-size:14px;line-height:36px;cursor:pointer;animation-name:slideUp-578ebab8;animation-duration:.5s;animation-delay:.3s;animation-fill-mode:forwards}@keyframes slideUp-578ebab8{0%{transform:translateY(60px);opacity:0}to{transform:translateY(0);opacity:1}} diff --git a/backend/src/main/resources/static/assets/404-538aa4d7.png b/backend/src/main/resources/static/assets/404-538aa4d7.png new file mode 100644 index 0000000..3d8e230 Binary files /dev/null and b/backend/src/main/resources/static/assets/404-538aa4d7.png differ diff --git a/backend/src/main/resources/static/assets/404-6dcbdda2.js b/backend/src/main/resources/static/assets/404-6dcbdda2.js new file mode 100644 index 0000000..7deafbf --- /dev/null +++ b/backend/src/main/resources/static/assets/404-6dcbdda2.js @@ -0,0 +1 @@ +import{d as l,c as i,i as s,af as o,j as c,t as r,o as n,_}from"./index-b25f0d08.js";const d="/assets/404-538aa4d7.png",e="/assets/404_cloud-98e7ac66.png",p={class:"wscn-http404-container"},b={name:"Page404"},u=l({...b,setup(h){function a(){return"The webmaster said that you can not enter this page..."}return(m,t)=>(n(),i("div",p,[s("div",{class:"wscn-http404"},[t[4]||(t[4]=o('
404404404404
',1)),s("div",{class:"bullshit"},[t[0]||(t[0]=s("div",{class:"bullshit__oops"},"OOPS!",-1)),t[1]||(t[1]=s("div",{class:"bullshit__info"},[c(" All rights reserved "),s("a",{style:{color:"#20a0ff"},href:"https://wallstreetcn.com",target:"_blank"},"wallstreetcn")],-1)),s("div",{class:"bullshit__headline"},r(a)),t[2]||(t[2]=s("div",{class:"bullshit__info"}," Please check that the URL you entered is correct, or click the button below to return to the homepage. ",-1)),t[3]||(t[3]=s("a",{href:"",class:"bullshit__return-home"},"Back to home",-1))])])]))}});const f=_(u,[["__scopeId","data-v-578ebab8"]]);export{f as default}; diff --git a/backend/src/main/resources/static/assets/404-ae343fa7.js b/backend/src/main/resources/static/assets/404-ae343fa7.js new file mode 100644 index 0000000..51a23cb --- /dev/null +++ b/backend/src/main/resources/static/assets/404-ae343fa7.js @@ -0,0 +1 @@ +import{d as o,c as _,h as t,t as i,ag as l,J as n,K as d,j as r,o as h,_ as p}from"./index-5c62e6c4.js";const f="/assets/404-538aa4d7.png",a="/assets/404_cloud-98e7ac66.png",e=s=>(n("data-v-ec8f1f5a"),s=s(),d(),s),u={class:"wscn-http404-container"},m=l('
404404404404
',1),v=e(()=>t("div",{class:"bullshit__oops"},"OOPS!",-1)),g=e(()=>t("div",{class:"bullshit__info"},[r(" All rights reserved "),t("a",{style:{color:"#20a0ff"},href:"https://wallstreetcn.com",target:"_blank"},"wallstreetcn")],-1)),b=e(()=>t("div",{class:"bullshit__info"}," Please check that the URL you entered is correct, or click the button below to return to the homepage. ",-1)),w=e(()=>t("a",{href:"",class:"bullshit__return-home"},"Back to home",-1)),k={name:"Page404"},y=o({...k,setup(s){function c(){return"The webmaster said that you can not enter this page..."}return(S,x)=>(h(),_("div",u,[t("div",{class:"wscn-http404"},[m,t("div",{class:"bullshit"},[v,g,t("div",{class:"bullshit__headline"},i(c)),b,w])])]))}});const I=p(y,[["__scopeId","data-v-ec8f1f5a"]]);export{I as default}; diff --git a/backend/src/main/resources/static/assets/404_cloud-98e7ac66.png b/backend/src/main/resources/static/assets/404_cloud-98e7ac66.png new file mode 100644 index 0000000..c6281d0 Binary files /dev/null and b/backend/src/main/resources/static/assets/404_cloud-98e7ac66.png differ diff --git a/backend/src/main/resources/static/assets/BarChart-a4765ae3.js b/backend/src/main/resources/static/assets/BarChart-a4765ae3.js new file mode 100644 index 0000000..95b45f0 --- /dev/null +++ b/backend/src/main/resources/static/assets/BarChart-a4765ae3.js @@ -0,0 +1 @@ +import{r as c,i as f,L as a}from"./resize-9f0962b6.js";import{d as m,I as y,aq as u,ar as x,k as h,a4 as p,o as g,c as b,X as S,p as v}from"./index-5c62e6c4.js";const w=["id"],L=m({__name:"BarChart",props:{id:{type:String,default:"barChart"},className:{type:String,default:""},width:{type:String,default:"200px",required:!0},height:{type:String,default:"200px",required:!0}},setup(e){const o=e,{mounted:i,chart:r,beforeDestroy:n,activated:l,deactivated:s}=c();function d(){const t=f(document.getElementById(o.id));t.setOption({title:{show:!0,text:"业绩总览",x:"center",padding:15,textStyle:{fontSize:18,fontStyle:"normal",fontWeight:"bold",color:"#337ecc"}},grid:{left:"2%",right:"2%",bottom:"10%",containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"cross",crossStyle:{color:"#999"}}},legend:{x:"center",y:"bottom",data:["收入","毛利润","收入增长率","利润增长率"]},xAxis:[{type:"category",data:["浙江","北京","上海","广东","深圳"],axisPointer:{type:"shadow"}}],yAxis:[{type:"value",min:0,max:1e4,interval:2e3,axisLabel:{formatter:"{value} "}},{type:"value",min:0,max:100,interval:20,axisLabel:{formatter:"{value}%"}}],series:[{name:"收入",type:"bar",data:[7e3,7100,7200,7300,7400],barWidth:20,itemStyle:{color:new a(0,0,0,1,[{offset:0,color:"#83bff6"},{offset:.5,color:"#188df0"},{offset:1,color:"#188df0"}])}},{name:"毛利润",type:"bar",data:[8e3,8200,8400,8600,8800],barWidth:20,itemStyle:{color:new a(0,0,0,1,[{offset:0,color:"#25d73c"},{offset:.5,color:"#1bc23d"},{offset:1,color:"#179e61"}])}},{name:"收入增长率",type:"line",yAxisIndex:1,data:[60,65,70,75,80],itemStyle:{color:"#67C23A"}},{name:"利润增长率",type:"line",yAxisIndex:1,data:[70,75,80,85,90],itemStyle:{color:"#409EFF"}}]}),r.value=t}return y(()=>{n()}),u(()=>{l()}),x(()=>{s()}),h(()=>{i(),p(()=>{d()})}),(t,C)=>(g(),b("div",{id:e.id,class:S(e.className),style:v({height:e.height,width:e.width})},null,14,w))}});export{L as default}; diff --git a/backend/src/main/resources/static/assets/BarChart-efd5cbe1.js b/backend/src/main/resources/static/assets/BarChart-efd5cbe1.js new file mode 100644 index 0000000..87b1a1e --- /dev/null +++ b/backend/src/main/resources/static/assets/BarChart-efd5cbe1.js @@ -0,0 +1 @@ +import{r as c,i as f,L as a}from"./resize-24879ea2.js";import{d as m,K as y,ap as u,aq as x,k as h,a3 as p,o as g,c as b,V as S,q as v}from"./index-b25f0d08.js";const w=["id"],L=m({__name:"BarChart",props:{id:{type:String,default:"barChart"},className:{type:String,default:""},width:{type:String,default:"200px",required:!0},height:{type:String,default:"200px",required:!0}},setup(e){const o=e,{mounted:i,chart:r,beforeDestroy:n,activated:l,deactivated:s}=c();function d(){const t=f(document.getElementById(o.id));t.setOption({title:{show:!0,text:"业绩总览",x:"center",padding:15,textStyle:{fontSize:18,fontStyle:"normal",fontWeight:"bold",color:"#337ecc"}},grid:{left:"2%",right:"2%",bottom:"10%",containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"cross",crossStyle:{color:"#999"}}},legend:{x:"center",y:"bottom",data:["收入","毛利润","收入增长率","利润增长率"]},xAxis:[{type:"category",data:["浙江","北京","上海","广东","深圳"],axisPointer:{type:"shadow"}}],yAxis:[{type:"value",min:0,max:1e4,interval:2e3,axisLabel:{formatter:"{value} "}},{type:"value",min:0,max:100,interval:20,axisLabel:{formatter:"{value}%"}}],series:[{name:"收入",type:"bar",data:[7e3,7100,7200,7300,7400],barWidth:20,itemStyle:{color:new a(0,0,0,1,[{offset:0,color:"#83bff6"},{offset:.5,color:"#188df0"},{offset:1,color:"#188df0"}])}},{name:"毛利润",type:"bar",data:[8e3,8200,8400,8600,8800],barWidth:20,itemStyle:{color:new a(0,0,0,1,[{offset:0,color:"#25d73c"},{offset:.5,color:"#1bc23d"},{offset:1,color:"#179e61"}])}},{name:"收入增长率",type:"line",yAxisIndex:1,data:[60,65,70,75,80],itemStyle:{color:"#67C23A"}},{name:"利润增长率",type:"line",yAxisIndex:1,data:[70,75,80,85,90],itemStyle:{color:"#409EFF"}}]}),r.value=t}return y(()=>{n()}),u(()=>{l()}),x(()=>{s()}),h(()=>{i(),p(()=>{d()})}),(t,C)=>(g(),b("div",{id:e.id,class:S(e.className),style:v({height:e.height,width:e.width})},null,14,w))}});export{L as default}; diff --git a/backend/src/main/resources/static/assets/FunnelChart-79f3d5f7.js b/backend/src/main/resources/static/assets/FunnelChart-79f3d5f7.js new file mode 100644 index 0000000..0a2fd3d --- /dev/null +++ b/backend/src/main/resources/static/assets/FunnelChart-79f3d5f7.js @@ -0,0 +1 @@ +import{r as s,i as u}from"./resize-24879ea2.js";import{d as c,K as m,ap as h,aq as f,k as p,a3 as y,o as g,c as S,V as b,q as v}from"./index-b25f0d08.js";const w=["id"],q=c({__name:"FunnelChart",props:{id:{type:String,default:"funnelChart"},className:{type:String,default:""},width:{type:String,default:"200px",required:!0},height:{type:String,default:"200px",required:!0}},setup(e){const n=e,{mounted:a,chart:i,beforeDestroy:o,activated:l,deactivated:r}=s();function d(){const t=u(document.getElementById(n.id));t.setOption({title:{show:!0,text:"订单线索转化漏斗图",x:"center",padding:15,textStyle:{fontSize:18,fontStyle:"normal",fontWeight:"bold",color:"#337ecc"}},grid:{left:"2%",right:"2%",bottom:"10%",containLabel:!0},legend:{x:"center",y:"bottom",data:["Show","Click","Visit","Inquiry","Order"]},series:[{name:"Funnel",type:"funnel",left:"20%",top:60,bottom:60,width:"60%",sort:"descending",gap:2,label:{show:!0,position:"inside"},labelLine:{length:10,lineStyle:{width:1,type:"solid"}},itemStyle:{borderColor:"#fff",borderWidth:1},emphasis:{label:{fontSize:20}},data:[{value:60,name:"Visit"},{value:40,name:"Inquiry"},{value:20,name:"Order"},{value:80,name:"Click"},{value:100,name:"Show"}]}]}),i.value=t}return m(()=>{o()}),h(()=>{l()}),f(()=>{r()}),p(()=>{a(),y(()=>{d()})}),(t,x)=>(g(),S("div",{id:e.id,class:b(e.className),style:v({height:e.height,width:e.width})},null,14,w))}});export{q as default}; diff --git a/backend/src/main/resources/static/assets/FunnelChart-8e41d306.js b/backend/src/main/resources/static/assets/FunnelChart-8e41d306.js new file mode 100644 index 0000000..b4f80e1 --- /dev/null +++ b/backend/src/main/resources/static/assets/FunnelChart-8e41d306.js @@ -0,0 +1 @@ +import{r as s,i as u}from"./resize-9f0962b6.js";import{d as c,I as m,aq as h,ar as f,k as p,a4 as y,o as g,c as S,X as b,p as v}from"./index-5c62e6c4.js";const w=["id"],q=c({__name:"FunnelChart",props:{id:{type:String,default:"funnelChart"},className:{type:String,default:""},width:{type:String,default:"200px",required:!0},height:{type:String,default:"200px",required:!0}},setup(e){const n=e,{mounted:a,chart:i,beforeDestroy:o,activated:l,deactivated:r}=s();function d(){const t=u(document.getElementById(n.id));t.setOption({title:{show:!0,text:"订单线索转化漏斗图",x:"center",padding:15,textStyle:{fontSize:18,fontStyle:"normal",fontWeight:"bold",color:"#337ecc"}},grid:{left:"2%",right:"2%",bottom:"10%",containLabel:!0},legend:{x:"center",y:"bottom",data:["Show","Click","Visit","Inquiry","Order"]},series:[{name:"Funnel",type:"funnel",left:"20%",top:60,bottom:60,width:"60%",sort:"descending",gap:2,label:{show:!0,position:"inside"},labelLine:{length:10,lineStyle:{width:1,type:"solid"}},itemStyle:{borderColor:"#fff",borderWidth:1},emphasis:{label:{fontSize:20}},data:[{value:60,name:"Visit"},{value:40,name:"Inquiry"},{value:20,name:"Order"},{value:80,name:"Click"},{value:100,name:"Show"}]}]}),i.value=t}return m(()=>{o()}),h(()=>{l()}),f(()=>{r()}),p(()=>{a(),y(()=>{d()})}),(t,x)=>(g(),S("div",{id:e.id,class:b(e.className),style:v({height:e.height,width:e.width})},null,14,w))}});export{q as default}; diff --git a/backend/src/main/resources/static/assets/PieChart-bffd7bcc.js b/backend/src/main/resources/static/assets/PieChart-bffd7bcc.js new file mode 100644 index 0000000..06edc6a --- /dev/null +++ b/backend/src/main/resources/static/assets/PieChart-bffd7bcc.js @@ -0,0 +1 @@ +import{r as c,i as u}from"./resize-24879ea2.js";import{d as m,K as h,ap as f,aq as p,k as g,a3 as y,o as C,c as v,V as x,q as S}from"./index-b25f0d08.js";const b=["id"],z=m({__name:"PieChart",props:{id:{type:String,default:"pieChart"},className:{type:String,default:""},width:{type:String,default:"200px",required:!0},height:{type:String,default:"200px",required:!0}},setup(e){const i=e,{mounted:n,chart:o,beforeDestroy:r,activated:s,deactivated:d}=c();function l(){const t=u(document.getElementById(i.id));t.setOption({title:{show:!0,text:"产品分类总览",x:"center",padding:15,textStyle:{fontSize:18,fontStyle:"normal",fontWeight:"bold",color:"#337ecc"}},grid:{left:"2%",right:"2%",bottom:"10%",containLabel:!0},legend:{top:"bottom"},series:[{name:"Nightingale Chart",type:"pie",radius:[50,130],center:["50%","50%"],roseType:"area",itemStyle:{borderRadius:1,color:function(a){return["#409EFF","#67C23A","#E6A23C","#F56C6C"][a.dataIndex]}},data:[{value:26,name:"家用电器"},{value:27,name:"户外运动"},{value:24,name:"汽车用品"},{value:23,name:"手机数码"}]}]}),o.value=t}return h(()=>{r()}),f(()=>{s()}),p(()=>{d()}),g(()=>{n(),y(()=>{l()})}),(t,a)=>(C(),v("div",{id:e.id,class:x(e.className),style:S({height:e.height,width:e.width})},null,14,b))}});export{z as default}; diff --git a/backend/src/main/resources/static/assets/PieChart-f0d9d351.js b/backend/src/main/resources/static/assets/PieChart-f0d9d351.js new file mode 100644 index 0000000..8f6ea0a --- /dev/null +++ b/backend/src/main/resources/static/assets/PieChart-f0d9d351.js @@ -0,0 +1 @@ +import{r as c,i as u}from"./resize-9f0962b6.js";import{d as m,I as h,aq as f,ar as p,k as g,a4 as y,o as C,c as v,X as x,p as S}from"./index-5c62e6c4.js";const b=["id"],B=m({__name:"PieChart",props:{id:{type:String,default:"pieChart"},className:{type:String,default:""},width:{type:String,default:"200px",required:!0},height:{type:String,default:"200px",required:!0}},setup(e){const i=e,{mounted:n,chart:o,beforeDestroy:r,activated:s,deactivated:d}=c();function l(){const t=u(document.getElementById(i.id));t.setOption({title:{show:!0,text:"产品分类总览",x:"center",padding:15,textStyle:{fontSize:18,fontStyle:"normal",fontWeight:"bold",color:"#337ecc"}},grid:{left:"2%",right:"2%",bottom:"10%",containLabel:!0},legend:{top:"bottom"},series:[{name:"Nightingale Chart",type:"pie",radius:[50,130],center:["50%","50%"],roseType:"area",itemStyle:{borderRadius:1,color:function(a){return["#409EFF","#67C23A","#E6A23C","#F56C6C"][a.dataIndex]}},data:[{value:26,name:"家用电器"},{value:27,name:"户外运动"},{value:24,name:"汽车用品"},{value:23,name:"手机数码"}]}]}),o.value=t}return h(()=>{r()}),f(()=>{s()}),p(()=>{d()}),g(()=>{n(),y(()=>{l()})}),(t,a)=>(C(),v("div",{id:e.id,class:x(e.className),style:S({height:e.height,width:e.width})},null,14,b))}});export{B as default}; diff --git a/backend/src/main/resources/static/assets/RadarChart-94b1112a.js b/backend/src/main/resources/static/assets/RadarChart-94b1112a.js new file mode 100644 index 0000000..3a44423 --- /dev/null +++ b/backend/src/main/resources/static/assets/RadarChart-94b1112a.js @@ -0,0 +1 @@ +import{r as l,i as m}from"./resize-9f0962b6.js";import{d as u,I as h,aq as f,ar as g,k as p,a4 as y,o as v,c as x,X as C,p as S}from"./index-5c62e6c4.js";const b=["id"],z=u({__name:"RadarChart",props:{id:{type:String,default:"radarChart"},className:{type:String,default:""},width:{type:String,default:"200px",required:!0},height:{type:String,default:"200px",required:!0}},setup(e){const n=e,{mounted:r,chart:i,beforeDestroy:o,activated:d,deactivated:s}=l();function c(){const t=m(document.getElementById(n.id));t.setOption({title:{show:!0,text:"订单状态统计",x:"center",padding:15,textStyle:{fontSize:18,fontStyle:"normal",fontWeight:"bold",color:"#337ecc"}},grid:{left:"2%",right:"2%",bottom:"10%",containLabel:!0},legend:{x:"center",y:"bottom",data:["预定数量","下单数量","发货数量"]},radar:{radius:"60%",indicator:[{name:"家用电器"},{name:"服装箱包"},{name:"运动户外"},{name:"手机数码"},{name:"汽车用品"},{name:"家具厨具"}]},series:[{name:"Budget vs spending",type:"radar",itemStyle:{borderRadius:6,color:function(a){return["#409EFF","#67C23A","#E6A23C","#F56C6C"][a.dataIndex]}},data:[{value:[400,400,400,400,400,400],name:"预定数量"},{value:[300,300,300,300,300,300],name:"下单数量"},{value:[200,200,200,200,200,200],name:"发货数量"}]}]}),i.value=t}return h(()=>{o()}),f(()=>{d()}),g(()=>{s()}),p(()=>{r(),y(()=>{c()})}),(t,a)=>(v(),x("div",{id:e.id,class:C(e.className),style:S({height:e.height,width:e.width})},null,14,b))}});export{z as default}; diff --git a/backend/src/main/resources/static/assets/RadarChart-e43ec971.js b/backend/src/main/resources/static/assets/RadarChart-e43ec971.js new file mode 100644 index 0000000..5f5722d --- /dev/null +++ b/backend/src/main/resources/static/assets/RadarChart-e43ec971.js @@ -0,0 +1 @@ +import{r as l,i as m}from"./resize-24879ea2.js";import{d as u,K as h,ap as f,aq as g,k as p,a3 as y,o as v,c as x,V as C,q as S}from"./index-b25f0d08.js";const b=["id"],w=u({__name:"RadarChart",props:{id:{type:String,default:"radarChart"},className:{type:String,default:""},width:{type:String,default:"200px",required:!0},height:{type:String,default:"200px",required:!0}},setup(e){const n=e,{mounted:r,chart:i,beforeDestroy:o,activated:d,deactivated:s}=l();function c(){const t=m(document.getElementById(n.id));t.setOption({title:{show:!0,text:"订单状态统计",x:"center",padding:15,textStyle:{fontSize:18,fontStyle:"normal",fontWeight:"bold",color:"#337ecc"}},grid:{left:"2%",right:"2%",bottom:"10%",containLabel:!0},legend:{x:"center",y:"bottom",data:["预定数量","下单数量","发货数量"]},radar:{radius:"60%",indicator:[{name:"家用电器"},{name:"服装箱包"},{name:"运动户外"},{name:"手机数码"},{name:"汽车用品"},{name:"家具厨具"}]},series:[{name:"Budget vs spending",type:"radar",itemStyle:{borderRadius:6,color:function(a){return["#409EFF","#67C23A","#E6A23C","#F56C6C"][a.dataIndex]}},data:[{value:[400,400,400,400,400,400],name:"预定数量"},{value:[300,300,300,300,300,300],name:"下单数量"},{value:[200,200,200,200,200,200],name:"发货数量"}]}]}),i.value=t}return h(()=>{o()}),f(()=>{d()}),g(()=>{s()}),p(()=>{r(),y(()=>{c()})}),(t,a)=>(v(),x("div",{id:e.id,class:C(e.className),style:S({height:e.height,width:e.width})},null,14,b))}});export{w as default}; diff --git a/backend/src/main/resources/static/assets/editor-501cf061.css b/backend/src/main/resources/static/assets/editor-501cf061.css new file mode 100644 index 0000000..50ac020 --- /dev/null +++ b/backend/src/main/resources/static/assets/editor-501cf061.css @@ -0,0 +1 @@ +:root,:host{--w-e-textarea-bg-color: #fff;--w-e-textarea-color: #333;--w-e-textarea-border-color: #ccc;--w-e-textarea-slight-border-color: #e8e8e8;--w-e-textarea-slight-color: #d4d4d4;--w-e-textarea-slight-bg-color: #f5f2f0;--w-e-textarea-selected-border-color: #B4D5FF;--w-e-textarea-handler-bg-color: #4290f7;--w-e-toolbar-color: #595959;--w-e-toolbar-bg-color: #fff;--w-e-toolbar-active-color: #333;--w-e-toolbar-active-bg-color: #f1f1f1;--w-e-toolbar-disabled-color: #999;--w-e-toolbar-border-color: #e8e8e8;--w-e-modal-button-bg-color: #fafafa;--w-e-modal-button-border-color: #d9d9d9}.w-e-text-container *,.w-e-toolbar *{box-sizing:border-box;margin:0;outline:none;padding:0}.w-e-text-container blockquote,.w-e-text-container li,.w-e-text-container p,.w-e-text-container td,.w-e-text-container th,.w-e-toolbar *{line-height:1.5}.w-e-text-container{background-color:var(--w-e-textarea-bg-color);color:var(--w-e-textarea-color);height:100%;position:relative}.w-e-text-container .w-e-scroll{-webkit-overflow-scrolling:touch;height:100%}.w-e-text-container [data-slate-editor]{word-wrap:break-word;border-top:1px solid transparent;min-height:100%;outline:0;padding:0 10px;white-space:pre-wrap}.w-e-text-container [data-slate-editor] p{margin:15px 0}.w-e-text-container [data-slate-editor] h1,.w-e-text-container [data-slate-editor] h2,.w-e-text-container [data-slate-editor] h3,.w-e-text-container [data-slate-editor] h4,.w-e-text-container [data-slate-editor] h5{margin:20px 0}.w-e-text-container [data-slate-editor] img{cursor:default;display:inline!important;max-width:100%;min-height:20px;min-width:20px}.w-e-text-container [data-slate-editor] span{text-indent:0}.w-e-text-container [data-slate-editor] [data-selected=true]{box-shadow:0 0 0 2px var(--w-e-textarea-selected-border-color)}.w-e-text-placeholder{font-style:italic;left:10px;top:17px;width:90%}.w-e-max-length-info,.w-e-text-placeholder{color:var(--w-e-textarea-slight-color);pointer-events:none;position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none}.w-e-max-length-info{bottom:.5em;right:1em}.w-e-bar{background-color:var(--w-e-toolbar-bg-color);color:var(--w-e-toolbar-color);font-size:14px;padding:0 5px}.w-e-bar svg{fill:var(--w-e-toolbar-color);height:14px;width:14px}.w-e-bar-show{display:flex}.w-e-bar-hidden{display:none}.w-e-hover-bar{border:1px solid var(--w-e-toolbar-border-color);border-radius:3px;box-shadow:0 2px 5px #0000001f;position:absolute}.w-e-toolbar{flex-wrap:wrap;position:relative}.w-e-bar-divider{background-color:var(--w-e-toolbar-border-color);display:inline-flex;height:40px;margin:0 5px;width:1px}.w-e-bar-item{display:flex;height:40px;padding:4px;position:relative;text-align:center}.w-e-bar-item,.w-e-bar-item button{align-items:center;justify-content:center}.w-e-bar-item button{background:transparent;border:none;color:var(--w-e-toolbar-color);cursor:pointer;display:inline-flex;height:32px;overflow:hidden;padding:0 8px;white-space:nowrap}.w-e-bar-item button:hover{background-color:var(--w-e-toolbar-active-bg-color);color:var(--w-e-toolbar-active-color)}.w-e-bar-item button .title{margin-left:5px}.w-e-bar-item .active{background-color:var(--w-e-toolbar-active-bg-color);color:var(--w-e-toolbar-active-color)}.w-e-bar-item .disabled{color:var(--w-e-toolbar-disabled-color);cursor:not-allowed}.w-e-bar-item .disabled svg{fill:var(--w-e-toolbar-disabled-color)}.w-e-bar-item .disabled:hover{background-color:var(--w-e-toolbar-bg-color);color:var(--w-e-toolbar-disabled-color)}.w-e-bar-item .disabled:hover svg{fill:var(--w-e-toolbar-disabled-color)}.w-e-menu-tooltip-v5:before{background-color:var(--w-e-toolbar-active-color);border-radius:5px;color:var(--w-e-toolbar-bg-color);content:attr(data-tooltip);font-size:.75em;opacity:0;padding:5px 10px;position:absolute;text-align:center;top:40px;transition:opacity .6s;visibility:hidden;white-space:pre;z-index:1}.w-e-menu-tooltip-v5:after{border:5px solid transparent;border-bottom:5px solid var(--w-e-toolbar-active-color);content:"";opacity:0;position:absolute;top:30px;transition:opacity .6s;visibility:hidden}.w-e-menu-tooltip-v5:hover:after,.w-e-menu-tooltip-v5:hover:before{opacity:1;visibility:visible}.w-e-menu-tooltip-v5.tooltip-right:before{left:100%;top:10px}.w-e-menu-tooltip-v5.tooltip-right:after{border-bottom-color:transparent;border-left-color:transparent;border-right-color:var(--w-e-toolbar-active-color);border-top-color:transparent;left:100%;margin-left:-10px;top:16px}.w-e-bar-item-group .w-e-bar-item-menus-container{background-color:var(--w-e-toolbar-bg-color);border:1px solid var(--w-e-toolbar-border-color);border-radius:3px;box-shadow:0 2px 10px #0000001f;display:none;left:0;margin-top:40px;position:absolute;top:0;z-index:1}.w-e-bar-item-group:hover .w-e-bar-item-menus-container{display:block}.w-e-select-list{background-color:var(--w-e-toolbar-bg-color);border:1px solid var(--w-e-toolbar-border-color);border-radius:3px;box-shadow:0 2px 10px #0000001f;left:0;margin-top:40px;max-height:350px;min-width:100px;overflow-y:auto;position:absolute;top:0;z-index:1}.w-e-select-list ul{line-height:1;list-style:none}.w-e-select-list ul .selected{background-color:var(--w-e-toolbar-active-bg-color)}.w-e-select-list ul li{cursor:pointer;padding:7px 0 7px 25px;position:relative;text-align:left;white-space:nowrap}.w-e-select-list ul li:hover{background-color:var(--w-e-toolbar-active-bg-color)}.w-e-select-list ul li svg{left:0;margin-left:5px;margin-top:-7px;position:absolute;top:50%}.w-e-bar-bottom .w-e-select-list{bottom:0;margin-bottom:40px;margin-top:0;top:inherit}.w-e-drop-panel{background-color:var(--w-e-toolbar-bg-color);border:1px solid var(--w-e-toolbar-border-color);border-radius:3px;box-shadow:0 2px 10px #0000001f;margin-top:40px;min-width:200px;padding:10px;position:absolute;top:0;z-index:1}.w-e-bar-bottom .w-e-drop-panel{bottom:0;margin-bottom:40px;margin-top:0;top:inherit}.w-e-modal{background-color:var(--w-e-toolbar-bg-color);border:1px solid var(--w-e-toolbar-border-color);border-radius:3px;box-shadow:0 2px 10px #0000001f;color:var(--w-e-toolbar-color);font-size:14px;min-height:40px;min-width:100px;padding:20px 15px 0;position:absolute;text-align:left;z-index:1}.w-e-modal .btn-close{cursor:pointer;line-height:1;padding:5px;position:absolute;right:8px;top:7px}.w-e-modal .btn-close svg{fill:var(--w-e-toolbar-color);height:10px;width:10px}.w-e-modal .babel-container{display:block;margin-bottom:15px}.w-e-modal .babel-container span{display:block;margin-bottom:10px}.w-e-modal .button-container{margin-bottom:15px}.w-e-modal button{background-color:var(--w-e-modal-button-bg-color);border:1px solid var(--w-e-modal-button-border-color);border-radius:4px;color:var(--w-e-toolbar-color);cursor:pointer;font-weight:400;height:32px;padding:4.5px 15px;text-align:center;touch-action:manipulation;transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.w-e-modal input[type=number],.w-e-modal input[type=text],.w-e-modal textarea{font-feature-settings:"tnum";background-color:var(--w-e-toolbar-bg-color);border:1px solid var(--w-e-modal-button-border-color);border-radius:4px;color:var(--w-e-toolbar-color);font-variant:tabular-nums;padding:4.5px 11px;transition:all .3s;width:100%}.w-e-modal textarea{min-height:60px}body .w-e-modal,body .w-e-modal *{box-sizing:border-box}.w-e-progress-bar{background-color:var(--w-e-textarea-handler-bg-color);height:1px;position:absolute;transition:width .3s;width:0}.w-e-full-screen-container{bottom:0!important;display:flex!important;flex-direction:column!important;height:100%!important;left:0!important;margin:0!important;padding:0!important;position:fixed;right:0!important;top:0!important;width:100%!important}.w-e-full-screen-container [data-w-e-textarea=true]{flex:1!important}.w-e-text-container [data-slate-editor] code{background-color:var(--w-e-textarea-slight-bg-color);border-radius:3px;font-family:monospace;padding:3px}.w-e-panel-content-color{list-style:none;text-align:left;width:230px}.w-e-panel-content-color li{border:1px solid var(--w-e-toolbar-bg-color);border-radius:3px;cursor:pointer;display:inline-block;padding:2px}.w-e-panel-content-color li:hover{border-color:var(--w-e-toolbar-color)}.w-e-panel-content-color li .color-block{border:1px solid var(--w-e-toolbar-border-color);border-radius:3px;height:17px;width:17px}.w-e-panel-content-color .active{border-color:var(--w-e-toolbar-color)}.w-e-panel-content-color .clear{line-height:1.5;margin-bottom:5px;width:100%}.w-e-panel-content-color .clear svg{height:16px;margin-bottom:-4px;width:16px}.w-e-text-container [data-slate-editor] blockquote{background-color:var(--w-e-textarea-slight-bg-color);border-left:8px solid var(--w-e-textarea-selected-border-color);display:block;font-size:100%;line-height:1.5;margin:10px 0;padding:10px}.w-e-panel-content-emotion{font-size:20px;list-style:none;text-align:left;width:300px}.w-e-panel-content-emotion li{border-radius:3px;cursor:pointer;display:inline-block;padding:0 5px}.w-e-panel-content-emotion li:hover{background-color:var(--w-e-textarea-slight-bg-color)}.w-e-textarea-divider{border-radius:3px;margin:20px auto;padding:20px}.w-e-textarea-divider hr{background-color:var(--w-e-textarea-border-color);border:0;display:block;height:1px}.w-e-text-container [data-slate-editor] pre>code{background-color:var(--w-e-textarea-slight-bg-color);border:1px solid var(--w-e-textarea-slight-border-color);border-radius:4px;display:block;font-size:14px;padding:10px;text-indent:0}.w-e-text-container [data-slate-editor] .w-e-image-container{display:inline-block;margin:0 3px}.w-e-text-container [data-slate-editor] .w-e-image-container:hover{box-shadow:0 0 0 2px var(--w-e-textarea-selected-border-color)}.w-e-text-container [data-slate-editor] .w-e-selected-image-container{overflow:hidden;position:relative}.w-e-text-container [data-slate-editor] .w-e-selected-image-container .w-e-image-dragger{background-color:var(--w-e-textarea-handler-bg-color);height:7px;position:absolute;width:7px}.w-e-text-container [data-slate-editor] .w-e-selected-image-container .left-top{cursor:nwse-resize;left:0;top:0}.w-e-text-container [data-slate-editor] .w-e-selected-image-container .right-top{cursor:nesw-resize;right:0;top:0}.w-e-text-container [data-slate-editor] .w-e-selected-image-container .left-bottom{bottom:0;cursor:nesw-resize;left:0}.w-e-text-container [data-slate-editor] .w-e-selected-image-container .right-bottom{bottom:0;cursor:nwse-resize;right:0}.w-e-text-container [data-slate-editor] .w-e-selected-image-container:hover,.w-e-text-container [contenteditable=false] .w-e-image-container:hover{box-shadow:none}.w-e-text-container [data-slate-editor] .table-container{border:1px dashed var(--w-e-textarea-border-color);border-radius:5px;margin-top:10px;overflow-x:auto;padding:10px;width:100%}.w-e-text-container [data-slate-editor] table{border-collapse:collapse}.w-e-text-container [data-slate-editor] table td,.w-e-text-container [data-slate-editor] table th{border:1px solid var(--w-e-textarea-border-color);line-height:1.5;min-width:30px;padding:3px 5px;text-align:left}.w-e-text-container [data-slate-editor] table th{background-color:var(--w-e-textarea-slight-bg-color);font-weight:700;text-align:center}.w-e-panel-content-table{background-color:var(--w-e-toolbar-bg-color)}.w-e-panel-content-table table{border-collapse:collapse}.w-e-panel-content-table td{border:1px solid var(--w-e-toolbar-border-color);cursor:pointer;height:15px;padding:3px 5px;width:20px}.w-e-panel-content-table td.active{background-color:var(--w-e-toolbar-active-bg-color)}.w-e-textarea-video-container{background-image:linear-gradient(45deg,#eee 25%,transparent 0,transparent 75%,#eee 0,#eee),linear-gradient(45deg,#eee 25%,#fff 0,#fff 75%,#eee 0,#eee);background-position:0 0,10px 10px;background-size:20px 20px;border:1px dashed var(--w-e-textarea-border-color);border-radius:5px;margin:10px auto 0;padding:10px 0;text-align:center}.w-e-text-container [data-slate-editor] pre>code{word-wrap:normal;font-family:Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace;-webkit-hyphens:none;hyphens:none;line-height:1.5;margin:.5em 0;overflow:auto;padding:1em;-moz-tab-size:4;-o-tab-size:4;tab-size:4;text-align:left;text-shadow:0 1px #fff;white-space:pre;word-break:normal;word-spacing:normal}.w-e-text-container [data-slate-editor] pre>code .token.cdata,.w-e-text-container [data-slate-editor] pre>code .token.comment,.w-e-text-container [data-slate-editor] pre>code .token.doctype,.w-e-text-container [data-slate-editor] pre>code .token.prolog{color:#708090}.w-e-text-container [data-slate-editor] pre>code .token.punctuation{color:#999}.w-e-text-container [data-slate-editor] pre>code .token.namespace{opacity:.7}.w-e-text-container [data-slate-editor] pre>code .token.boolean,.w-e-text-container [data-slate-editor] pre>code .token.constant,.w-e-text-container [data-slate-editor] pre>code .token.deleted,.w-e-text-container [data-slate-editor] pre>code .token.number,.w-e-text-container [data-slate-editor] pre>code .token.property,.w-e-text-container [data-slate-editor] pre>code .token.symbol,.w-e-text-container [data-slate-editor] pre>code .token.tag{color:#905}.w-e-text-container [data-slate-editor] pre>code .token.attr-name,.w-e-text-container [data-slate-editor] pre>code .token.builtin,.w-e-text-container [data-slate-editor] pre>code .token.char,.w-e-text-container [data-slate-editor] pre>code .token.inserted,.w-e-text-container [data-slate-editor] pre>code .token.selector,.w-e-text-container [data-slate-editor] pre>code .token.string{color:#690}.w-e-text-container [data-slate-editor] pre>code .language-css .token.string,.w-e-text-container [data-slate-editor] pre>code .style .token.string,.w-e-text-container [data-slate-editor] pre>code .token.entity,.w-e-text-container [data-slate-editor] pre>code .token.operator,.w-e-text-container [data-slate-editor] pre>code .token.url{color:#9a6e3a}.w-e-text-container [data-slate-editor] pre>code .token.atrule,.w-e-text-container [data-slate-editor] pre>code .token.attr-value,.w-e-text-container [data-slate-editor] pre>code .token.keyword{color:#07a}.w-e-text-container [data-slate-editor] pre>code .token.class-name,.w-e-text-container [data-slate-editor] pre>code .token.function{color:#dd4a68}.w-e-text-container [data-slate-editor] pre>code .token.important,.w-e-text-container [data-slate-editor] pre>code .token.regex,.w-e-text-container [data-slate-editor] pre>code .token.variable{color:#e90}.w-e-text-container [data-slate-editor] pre>code .token.bold,.w-e-text-container [data-slate-editor] pre>code .token.important{font-weight:700}.w-e-text-container [data-slate-editor] pre>code .token.italic{font-style:italic}.w-e-text-container [data-slate-editor] pre>code .token.entity{cursor:help} diff --git a/backend/src/main/resources/static/assets/editor-b13c93a6.js b/backend/src/main/resources/static/assets/editor-b13c93a6.js new file mode 100644 index 0000000..65ea8e0 --- /dev/null +++ b/backend/src/main/resources/static/assets/editor-b13c93a6.js @@ -0,0 +1,186 @@ +import{d as N1,r as Eg,al as lP,k as u$,a2 as s$,o as I1,c as L1,a8 as l$,am as c$,a9 as cP,aa as fP,K as f$,g as Dg,a as $r,Q as d$,ad as yx,w as p$}from"./index-b25f0d08.js";import{u as h$}from"./index-5282e30f.js";var se=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function g$(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}function us(t){var e={exports:{}};return t(e,e.exports),e.exports}var yi,$0,Fh=function(t){return t&&t.Math==Math&&t},kt=Fh(typeof globalThis=="object"&&globalThis)||Fh(typeof window=="object"&&window)||Fh(typeof self=="object"&&self)||Fh(typeof se=="object"&&se)||function(){return this}()||Function("return this")(),r5=Function.prototype,bx=r5.apply,v$=r5.bind,wx=r5.call,dP=typeof Reflect=="object"&&Reflect.apply||(v$?wx.bind(bx):function(){return wx.apply(bx,arguments)}),pP=Function.prototype,w4=pP.bind,E4=pP.call,m$=w4&&w4.bind(E4),ge=w4?function(t){return t&&m$(E4,t)}:function(t){return t&&function(){return E4.apply(t,arguments)}},sn=function(t){return typeof t=="function"},Gn=function(t){try{return!!t()}catch{return!0}},Hn=!Gn(function(){return Object.defineProperty({},1,{get:function(){return 7}})[1]!=7}),kc=Function.prototype.call,zn=kc.bind?kc.bind(kc):function(){return kc.apply(kc,arguments)},Ex={}.propertyIsEnumerable,Dx=Object.getOwnPropertyDescriptor,y$=Dx&&!Ex.call({1:2},1)?function(t){var e=Dx(this,t);return!!e&&e.enumerable}:Ex,o5={f:y$},Xr=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}},b$=ge({}.toString),w$=ge("".slice),Du=function(t){return w$(b$(t),8,-1)},qm=kt.Object,E$=ge("".split),hP=Gn(function(){return!qm("z").propertyIsEnumerable(0)})?function(t){return Du(t)=="String"?E$(t,""):qm(t)}:qm,D$=kt.TypeError,i5=function(t){if(t==null)throw D$("Can't call method on "+t);return t},jo=function(t){return hP(i5(t))},tr=function(t){return typeof t=="object"?t!==null:sn(t)},Qn={},Cx=function(t){return sn(t)?t:void 0},oc=function(t,e){return arguments.length<2?Cx(Qn[t])||Cx(kt[t]):Qn[t]&&Qn[t][e]||kt[t]&&kt[t][e]},Td=ge({}.isPrototypeOf),Km=oc("navigator","userAgent")||"",xx=kt.process,Sx=kt.Deno,Ax=xx&&xx.versions||Sx&&Sx.version,Ox=Ax&&Ax.v8;Ox&&($0=(yi=Ox.split("."))[0]>0&&yi[0]<4?1:+(yi[0]+yi[1])),!$0&&Km&&(!(yi=Km.match(/Edge\/(\d+)/))||yi[1]>=74)&&(yi=Km.match(/Chrome\/(\d+)/))&&($0=+yi[1]);var _h,Cg=$0,ho=!!Object.getOwnPropertySymbols&&!Gn(function(){var t=Symbol();return!String(t)||!(Object(t)instanceof Symbol)||!Symbol.sham&&Cg&&Cg<41}),a5=ho&&!Symbol.sham&&typeof Symbol.iterator=="symbol",C$=kt.Object,hl=a5?function(t){return typeof t=="symbol"}:function(t){var e=oc("Symbol");return sn(e)&&Td(e.prototype,C$(t))},x$=kt.String,D4=function(t){try{return x$(t)}catch{return"Object"}},S$=kt.TypeError,u5=function(t){if(sn(t))return t;throw S$(D4(t)+" is not a function")},xg=function(t,e){var n=t[e];return n==null?void 0:u5(n)},A$=kt.TypeError,O$=Object.defineProperty,ea=kt["__core-js_shared__"]||function(t,e){try{O$(kt,t,{value:e,configurable:!0,writable:!0})}catch{kt[t]=e}return e}("__core-js_shared__",{}),ss=us(function(t){(t.exports=function(e,n){return ea[e]||(ea[e]=n!==void 0?n:{})})("versions",[]).push({version:"3.19.3",mode:"pure",copyright:"© 2021 Denis Pushkarev (zloirock.ru)"})}),k$=kt.Object,Fp=function(t){return k$(i5(t))},B$=ge({}.hasOwnProperty),Vt=Object.hasOwn||function(t,e){return B$(Fp(t),e)},F$=0,_$=Math.random(),T$=ge(1 .toString),Sg=function(t){return"Symbol("+(t===void 0?"":t)+")_"+T$(++F$+_$,36)},Bc=ss("wks"),Cu=kt.Symbol,kx=Cu&&Cu.for,P$=a5?Cu:Cu&&Cu.withoutSetter||Sg,Bn=function(t){if(!Vt(Bc,t)||!ho&&typeof Bc[t]!="string"){var e="Symbol."+t;ho&&Vt(Cu,t)?Bc[t]=Cu[t]:Bc[t]=a5&&kx?kx(e):P$(e)}return Bc[t]},j$=kt.TypeError,N$=Bn("toPrimitive"),I$=function(t,e){if(!tr(t)||hl(t))return t;var n,r=xg(t,N$);if(r){if(e===void 0&&(e="default"),n=zn(r,t,e),!tr(n)||hl(n))return n;throw j$("Can't convert object to primitive value")}return e===void 0&&(e="number"),function(o,i){var a,u;if(i==="string"&&sn(a=o.toString)&&!tr(u=zn(a,o))||sn(a=o.valueOf)&&!tr(u=zn(a,o))||i!=="string"&&sn(a=o.toString)&&!tr(u=zn(a,o)))return u;throw A$("Can't convert object to primitive value")}(t,e)},ic=function(t){var e=I$(t,"string");return hl(e)?e:e+""},C4=kt.document,L$=tr(C4)&&tr(C4.createElement),gP=function(t){return L$?C4.createElement(t):{}},vP=!Hn&&!Gn(function(){return Object.defineProperty(gP("div"),"a",{get:function(){return 7}}).a!=7}),Bx=Object.getOwnPropertyDescriptor,R$=Hn?Bx:function(t,e){if(t=jo(t),e=ic(e),vP)try{return Bx(t,e)}catch{}if(Vt(t,e))return Xr(!zn(o5.f,t,e),t[e])},R1={f:R$},M$=/#|\.prototype\./,_p=function(t,e){var n=$$[z$(t)];return n==V$||n!=H$&&(sn(e)?Gn(e):!!e)},z$=_p.normalize=function(t){return String(t).replace(M$,".").toLowerCase()},$$=_p.data={},H$=_p.NATIVE="N",V$=_p.POLYFILL="P",U$=_p,Fx=ge(ge.bind),s5=function(t,e){return u5(t),e===void 0?t:Fx?Fx(t,e):function(){return t.apply(e,arguments)}},W$=kt.String,G$=kt.TypeError,ar=function(t){if(tr(t))return t;throw G$(W$(t)+" is not an object")},q$=kt.TypeError,_x=Object.defineProperty,K$=Hn?_x:function(t,e,n){if(ar(t),e=ic(e),ar(n),vP)try{return _x(t,e,n)}catch{}if("get"in n||"set"in n)throw q$("Accessors not supported");return"value"in n&&(t[e]=n.value),t},Na={f:K$},Tn=Hn?function(t,e,n){return Na.f(t,e,Xr(1,n))}:function(t,e,n){return t[e]=n,t},Y$=R1.f,X$=function(t){var e=function(n,r,o){if(this instanceof e){switch(arguments.length){case 0:return new t;case 1:return new t(n);case 2:return new t(n,r)}return new t(n,r,o)}return dP(t,this,arguments)};return e.prototype=t.prototype,e},Ko=function(t,e){var n,r,o,i,a,u,s,l,c=t.target,f=t.global,p=t.stat,d=t.proto,v=f?kt:p?kt[c]:(kt[c]||{}).prototype,g=f?Qn:Qn[c]||Tn(Qn,c,{})[c],m=g.prototype;for(o in e)n=!U$(f?o:c+(p?".":"#")+o,t.forced)&&v&&Vt(v,o),a=g[o],n&&(u=t.noTargetGet?(l=Y$(v,o))&&l.value:v[o]),i=n&&u?u:e[o],n&&typeof a==typeof i||(s=t.bind&&n?s5(i,kt):t.wrap&&n?X$(i):d&&sn(i)?ge(i):i,(t.sham||i&&i.sham||a&&a.sham)&&Tn(s,"sham",!0),Tn(g,o,s),d&&(Vt(Qn,r=c+"Prototype")||Tn(Qn,r,{}),Tn(Qn[r],o,i),t.real&&m&&!m[o]&&Tn(m,o,i)))},Tx=ss("keys"),M1=function(t){return Tx[t]||(Tx[t]=Sg(t))},Z$=!Gn(function(){function t(){}return t.prototype.constructor=null,Object.getPrototypeOf(new t)!==t.prototype}),Px=M1("IE_PROTO"),x4=kt.Object,J$=x4.prototype,Ag=Z$?x4.getPrototypeOf:function(t){var e=Fp(t);if(Vt(e,Px))return e[Px];var n=e.constructor;return sn(n)&&e instanceof n?n.prototype:e instanceof x4?J$:null},Q$=kt.String,tH=kt.TypeError,Og=Object.setPrototypeOf||("__proto__"in{}?function(){var t,e=!1,n={};try{(t=ge(Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set))(n,[]),e=n instanceof Array}catch{}return function(r,o){return ar(r),function(i){if(typeof i=="object"||sn(i))return i;throw tH("Can't set "+Q$(i)+" as a prototype")}(o),e?t(r,o):r.__proto__=o,r}}():void 0),eH=Math.ceil,nH=Math.floor,l5=function(t){var e=+t;return e!=e||e===0?0:(e>0?nH:eH)(e)},rH=Math.max,oH=Math.min,S4=function(t,e){var n=l5(t);return n<0?rH(n+e,0):oH(n,e)},iH=Math.min,Tp=function(t){return(e=t.length)>0?iH(l5(e),9007199254740991):0;var e},jx=function(t){return function(e,n,r){var o,i=jo(e),a=Tp(i),u=S4(r,a);if(t&&n!=n){for(;a>u;)if((o=i[u++])!=o)return!0}else for(;a>u;u++)if((t||u in i)&&i[u]===n)return t||u||0;return!t&&-1}},aH={includes:jx(!0),indexOf:jx(!1)},Pp={},uH=aH.indexOf,Nx=ge([].push),mP=function(t,e){var n,r=jo(t),o=0,i=[];for(n in r)!Vt(Pp,n)&&Vt(r,n)&&Nx(i,n);for(;e.length>o;)Vt(r,n=e[o++])&&(~uH(i,n)||Nx(i,n));return i},kg=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],sH=kg.concat("length","prototype"),lH=Object.getOwnPropertyNames||function(t){return mP(t,sH)},c5={f:lH},H0={f:Object.getOwnPropertySymbols},cH=ge([].concat),fH=oc("Reflect","ownKeys")||function(t){var e=c5.f(ar(t)),n=H0.f;return n?cH(e,n(t)):e},f5=Object.keys||function(t){return mP(t,kg)},dH=Hn?Object.defineProperties:function(t,e){ar(t);for(var n,r=jo(e),o=f5(e),i=o.length,a=0;i>a;)Na.f(t,n=o[a++],r[n]);return t},pH=oc("document","documentElement"),yP=M1("IE_PROTO"),Ym=function(){},bP=function(t){return" + + + +
+ + + diff --git a/backend/src/test/java/com/yfd/platform/PlatformApplicationTests.java b/backend/src/test/java/com/yfd/platform/PlatformApplicationTests.java new file mode 100644 index 0000000..c04ccc4 --- /dev/null +++ b/backend/src/test/java/com/yfd/platform/PlatformApplicationTests.java @@ -0,0 +1,49 @@ +package com.yfd.platform; + +import cn.hutool.jwt.JWTUtil; +import org.junit.jupiter.api.Test; + +import java.sql.Timestamp; +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.Map; + +class PlatformApplicationTests { + + @Test + void contextLoads() { + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + Timestamp timestamp1 = new Timestamp(System.currentTimeMillis()); + long time = timestamp.getTime(); + long time1 = timestamp1.getTime(); + System.out.println(time1 - time); + String path = System.getProperty("user.dir"); + System.out.println(path); + int max = 101; + DecimalFormat df = new DecimalFormat("0000"); + String code = df.format(max + 1); + int i = Integer.parseInt("0030"); + System.out.println(i); + } + + @Test + void myTest() throws InterruptedException { + Map map = new HashMap() { + private static final long serialVersionUID = 1L; + + { + put("userid", Integer.parseInt("123232323")); + put("expire_time", System.currentTimeMillis() + 5 * 1000); + } + }; + String token = JWTUtil.createToken(map, "1234".getBytes()); + System.out.println(token); + Thread.sleep(10 * 1000); + + //解析token + boolean isok = JWTUtil.verify(token, "1234".getBytes()); + String userid = ""; + + } + +} diff --git a/backend/src/test/java/com/yfd/platform/TestGuaVA.java b/backend/src/test/java/com/yfd/platform/TestGuaVA.java new file mode 100644 index 0000000..870023e --- /dev/null +++ b/backend/src/test/java/com/yfd/platform/TestGuaVA.java @@ -0,0 +1,73 @@ +package com.yfd.platform; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +public class TestGuaVA { + private Cache cache = CacheBuilder.newBuilder().maximumSize(2).expireAfterWrite(10, TimeUnit.MINUTES).build(); + + public Object getCache(K keyValue, final String ThreadName) { + Object value = null; + try { + System.out.println("ThreadName getCache==============" + ThreadName); + value = cache.get(keyValue, new Callable() { + @SuppressWarnings("unchecked") + public V call() { + System.out.println("ThreadName 执行业务数据并返回处理结果的数据(访问数据库等)==============" + ThreadName); + return (V) "dataValue"; + } + }); + } catch (ExecutionException e) { + e.printStackTrace(); + } + return value; + } + + public static void main(String[] args) { + final TestGuaVA TestGuaVA=new TestGuaVA(); + + + Thread t1=new Thread(new Runnable() { + @Override + public void run() { + System.out.println("T1======start========"); + Object value=TestGuaVA.getCache("key","T1"); + System.out.println("T1 value=============="+value); + System.out.println("T1======end========"); + + } + }); + + Thread t2=new Thread(new Runnable() { + @Override + public void run() { + System.out.println("T2======start========"); + Object value=TestGuaVA.getCache("key","T2"); + System.out.println("T2 value=============="+value); + System.out.println("T2======end========"); + + } + }); + + Thread t3=new Thread(new Runnable() { + @Override + public void run() { + System.out.println("T3======start========"); + Object value=TestGuaVA.getCache("key","T3"); + System.out.println("T3 value=============="+value); + System.out.println("T3======end========"); + + } + }); + + t1.start(); + t2.start(); + t3.start(); + } + +} + diff --git a/frontend/.editorconfig b/frontend/.editorconfig new file mode 100644 index 0000000..3c3960b --- /dev/null +++ b/frontend/.editorconfig @@ -0,0 +1,15 @@ +# http://editorconfig.org + +root = true + +[*] # 表示所有文件适用 +charset = utf-8 # 设置文件字符集为 utf-8 +indent_style = space # 缩进风格(tab | space) +indent_size = 2 # 缩进大小 +end_of_line = lf # 控制换行类型(lf | cr | crlf) +trim_trailing_whitespace = true # 去除行首的任意空白字符 +insert_final_newline = true # 始终在文件末尾插入一个新行 + +[*.md] # 表示仅 md 文件适用以下规则 +max_line_length = off +trim_trailing_whitespace = false \ No newline at end of file diff --git a/frontend/.env.development b/frontend/.env.development new file mode 100644 index 0000000..282c646 --- /dev/null +++ b/frontend/.env.development @@ -0,0 +1,8 @@ +## 开发环境 + +# 变量必须以 VITE_ 为前缀才能暴露给外部读取 +NODE_ENV='development' + +VITE_APP_TITLE = '公司开发平台框架' +VITE_APP_PORT = 3000 +VITE_APP_BASE_API = '/dev-api' diff --git a/frontend/.env.production b/frontend/.env.production new file mode 100644 index 0000000..728f41a --- /dev/null +++ b/frontend/.env.production @@ -0,0 +1,6 @@ +## 生产环境 +NODE_ENV='production' + +VITE_APP_TITLE = 'NewFrameWork2023-WEB' +VITE_APP_PORT = 3000 +VITE_APP_BASE_API = '/prod-api' diff --git a/frontend/.env.staging b/frontend/.env.staging new file mode 100644 index 0000000..c192ef8 --- /dev/null +++ b/frontend/.env.staging @@ -0,0 +1,6 @@ +## 模拟环境 +NODE_ENV='staging' + +VITE_APP_TITLE = 'NewFrameWork2023-WEB' +VITE_APP_PORT = 3000 +VITE_APP_BASE_API = '/prod--api' diff --git a/frontend/.eslintignore b/frontend/.eslintignore new file mode 100644 index 0000000..46d4b17 --- /dev/null +++ b/frontend/.eslintignore @@ -0,0 +1,16 @@ +*.sh +node_modules +*.md +*.woff +*.ttf +.vscode +.idea +dist +/public +/docs +.husky +.local +/bin +.eslintrc.js +prettier.config.js +src/assets \ No newline at end of file diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js new file mode 100644 index 0000000..d7878cc --- /dev/null +++ b/frontend/.eslintrc.js @@ -0,0 +1,32 @@ +module.exports = { + env: { + browser: true, + es2021: true, + node: true + }, + globals: { + defineProps: 'readonly', + defineEmits: 'readonly', + defineExpose: 'readonly', + DialogType: "readonly", + OptionType: "readonly", + }, + parser: 'vue-eslint-parser', + extends: [ + 'eslint:recommended', + 'plugin:vue/vue3-essential', + 'plugin:@typescript-eslint/recommended' + ], + parserOptions: { + ecmaVersion: 'latest', + parser: '@typescript-eslint/parser', + sourceType: 'module' + }, + plugins: ['vue', '@typescript-eslint'], + rules: { + 'vue/multi-word-component-names': 'off', + '@typescript-eslint/no-empty-function': 'off', // 关闭空方法检查 + '@typescript-eslint/no-explicit-any': 'off', // 关闭any类型的警告 + 'vue/no-v-model-argument': 'off' + } +}; diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..1da66c1 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,17 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.local + +package-lock.json +yarn.lock \ No newline at end of file diff --git a/frontend/.npmrc b/frontend/.npmrc new file mode 100644 index 0000000..b3db441 --- /dev/null +++ b/frontend/.npmrc @@ -0,0 +1,3 @@ +registry=https://registry.npmjs.org +fetch-retries=5 +strict-peer-dependencies=false \ No newline at end of file diff --git a/frontend/.prettierignore b/frontend/.prettierignore new file mode 100644 index 0000000..d251d2e --- /dev/null +++ b/frontend/.prettierignore @@ -0,0 +1,9 @@ +/dist/* +.local +.output.js +/node_modules/** + +**/*.svg +**/*.sh + +/public/* \ No newline at end of file diff --git a/frontend/.prettierrc.js b/frontend/.prettierrc.js new file mode 100644 index 0000000..7a42426 --- /dev/null +++ b/frontend/.prettierrc.js @@ -0,0 +1,36 @@ +/** + * 代码格式化配置 + */ +module.exports = { + // 指定每个缩进级别的空格数 + tabWidth: 2, + // 使用制表符而不是空格缩进行 + useTabs: false, + // 在语句末尾打印分号 + semi: true, + // 使用单引号而不是双引号 + singleQuote: true, + // 更改引用对象属性的时间 可选值"" + quoteProps: 'as-needed', + // 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"",默认none + trailingComma: 'none', + // 在对象文字中的括号之间打印空格 + bracketSpacing: true, + // 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x + arrowParens: 'avoid', + // 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码 + rangeStart: 0, + rangeEnd: Infinity, + // 指定要使用的解析器,不需要写文件开头的 @prettier + requirePragma: false, + // 不需要自动在文件开头插入 @prettier + insertPragma: false, + // 换行设置 always\never\preserve + proseWrap: 'never', + // 指定HTML文件的全局空格敏感度 css\strict\ignore + htmlWhitespaceSensitivity: 'css', + // Vue文件脚本和样式标签缩进 + vueIndentScriptAndStyle: false, + // 换行符使用 lf 结尾是 可选值"" + endOfLine: 'lf' +}; diff --git a/frontend/LICENSE b/frontend/LICENSE new file mode 100644 index 0000000..2660254 --- /dev/null +++ b/frontend/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 有来开源组织 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/frontend/commitlint.config.js b/frontend/commitlint.config.js new file mode 100644 index 0000000..efff054 --- /dev/null +++ b/frontend/commitlint.config.js @@ -0,0 +1,26 @@ +module.exports = { + // 继承的规则 + extends: ['@commitlint/config-conventional'], + // 定义规则类型 + rules: { + // type 类型定义,表示 git 提交的 type 必须在以下类型范围内 + 'type-enum': [ + 2, + 'always', + [ + 'feat', // 新功能 feature + 'fix', // 修复 bug + 'docs', // 文档注释 + 'style', // 代码格式(不影响代码运行的变动) + 'refactor', // 重构(既不增加新功能,也不是修复bug) + 'perf', // 性能优化 + 'test', // 增加测试 + 'chore', // 构建过程或辅助工具的变动 + 'revert', // 回退 + 'build' // 打包 + ] + ], + // subject 大小写不做校验 + 'subject-case': [0] + } +}; diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..c48809f --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,15 @@ + + + + + + + + + 公司开发平台框架 + + +
+ + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..7b20367 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,66 @@ +{ + "name": "NewFrameWork2023-WEB", + "version": "1.2.0", + "scripts": { + "dev": "vite serve --mode development", + "build:prod": "vue-tsc --noEmit && vite build --mode production", + "build:mvn": "vite build --mode production", + "serve": "vite preview", + "lint": "eslint src/**/*.{ts,js,vue} --fix", + "prettier": "prettier --write ." + }, + "dependencies": { + "@element-plus/icons-vue": "^2.0.10", + "@types/js-cookie": "^3.0.2", + "@vueuse/core": "^9.1.1", + "@wangeditor/editor": "^5.0.0", + "@wangeditor/editor-for-vue": "^5.1.10", + "axios": "^1.2.0", + "better-scroll": "^2.4.2", + "default-passive-events": "^2.0.0", + "echarts": "^5.2.2", + "element-plus": "^2.2.27", + "js-base64": "^3.7.5", + "js-cookie": "^3.0.1", + "jsencrypt": "^3.3.2", + "nprogress": "^0.2.0", + "path-browserify": "^1.0.1", + "path-to-regexp": "^6.2.0", + "pinia": "^2.0.12", + "screenfull": "^6.0.0", + "sortablejs": "^1.14.0", + "vue": "^3.2.40", + "vue-i18n": "^9.1.9", + "vue-router": "^4.1.6", + "vuedraggable": "^2.24.3" + }, + "devDependencies": { + "@commitlint/cli": "^16.2.3", + "@commitlint/config-conventional": "^16.2.1", + "@types/node": "^16.11.7", + "@types/nprogress": "^0.2.0", + "@types/path-browserify": "^1.0.0", + "@typescript-eslint/eslint-plugin": "^5.19.0", + "@typescript-eslint/parser": "^5.19.0", + "@vitejs/plugin-vue": "^4.0.0", + "autoprefixer": "^10.4.13", + "eslint": "^8.14.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-vue": "^8.6.0", + "fast-glob": "^3.2.11", + "husky": "^7.0.4", + "postcss": "^8.4.20", + "prettier": "^2.6.2", + "sass": "^1.53.0", + "tailwindcss": "^3.2.4", + "typescript": "^4.7.4", + "vite": "^4.0.3", + "vite-plugin-svg-icons": "^2.0.1", + "vue-tsc": "^0.35.0" + }, + "repository": "https://gitee.com/youlaiorg/vue3-element-admin.git", + "author": "有来开源组织", + "license": "MIT", + "__npminstall_done": false +} diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml new file mode 100644 index 0000000..f2b2b91 --- /dev/null +++ b/frontend/pnpm-lock.yaml @@ -0,0 +1,6776 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@element-plus/icons-vue': + specifier: ^2.0.10 + version: 2.3.2(vue@3.5.24(typescript@4.9.5)) + '@types/js-cookie': + specifier: ^3.0.2 + version: 3.0.6 + '@vueuse/core': + specifier: ^9.1.1 + version: 9.13.0(vue@3.5.24(typescript@4.9.5)) + '@wangeditor/editor': + specifier: ^5.0.0 + version: 5.1.23 + '@wangeditor/editor-for-vue': + specifier: ^5.1.10 + version: 5.1.12(@wangeditor/editor@5.1.23)(vue@3.5.24(typescript@4.9.5)) + axios: + specifier: ^1.2.0 + version: 1.13.2 + better-scroll: + specifier: ^2.4.2 + version: 2.5.1 + default-passive-events: + specifier: ^2.0.0 + version: 2.0.0 + echarts: + specifier: ^5.2.2 + version: 5.6.0 + element-plus: + specifier: ^2.2.27 + version: 2.11.7(vue@3.5.24(typescript@4.9.5)) + js-base64: + specifier: ^3.7.5 + version: 3.7.8 + js-cookie: + specifier: ^3.0.1 + version: 3.0.5 + jsencrypt: + specifier: ^3.3.2 + version: 3.5.4 + nprogress: + specifier: ^0.2.0 + version: 0.2.0 + path-browserify: + specifier: ^1.0.1 + version: 1.0.1 + path-to-regexp: + specifier: ^6.2.0 + version: 6.3.0 + pinia: + specifier: ^2.0.12 + version: 2.3.1(typescript@4.9.5)(vue@3.5.24(typescript@4.9.5)) + screenfull: + specifier: ^6.0.0 + version: 6.0.2 + sortablejs: + specifier: ^1.14.0 + version: 1.15.6 + vue: + specifier: ^3.2.40 + version: 3.5.24(typescript@4.9.5) + vue-i18n: + specifier: ^9.1.9 + version: 9.14.5(vue@3.5.24(typescript@4.9.5)) + vue-router: + specifier: ^4.1.6 + version: 4.6.3(vue@3.5.24(typescript@4.9.5)) + vuedraggable: + specifier: ^2.24.3 + version: 2.24.3 + devDependencies: + '@commitlint/cli': + specifier: ^16.2.3 + version: 16.3.0 + '@commitlint/config-conventional': + specifier: ^16.2.1 + version: 16.2.4 + '@types/node': + specifier: ^16.11.7 + version: 16.18.126 + '@types/nprogress': + specifier: ^0.2.0 + version: 0.2.3 + '@types/path-browserify': + specifier: ^1.0.0 + version: 1.0.3 + '@typescript-eslint/eslint-plugin': + specifier: ^5.19.0 + version: 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/parser': + specifier: ^5.19.0 + version: 5.62.0(eslint@8.57.1)(typescript@4.9.5) + '@vitejs/plugin-vue': + specifier: ^4.0.0 + version: 4.6.2(vite@4.5.14(@types/node@16.18.126)(sass@1.93.3))(vue@3.5.24(typescript@4.9.5)) + autoprefixer: + specifier: ^10.4.13 + version: 10.4.21(postcss@8.5.6) + eslint: + specifier: ^8.14.0 + version: 8.57.1 + eslint-config-prettier: + specifier: ^8.5.0 + version: 8.10.2(eslint@8.57.1) + eslint-plugin-prettier: + specifier: ^4.0.0 + version: 4.2.5(eslint-config-prettier@8.10.2(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8) + eslint-plugin-vue: + specifier: ^8.6.0 + version: 8.7.1(eslint@8.57.1) + fast-glob: + specifier: ^3.2.11 + version: 3.3.3 + husky: + specifier: ^7.0.4 + version: 7.0.4 + postcss: + specifier: ^8.4.20 + version: 8.5.6 + prettier: + specifier: ^2.6.2 + version: 2.8.8 + sass: + specifier: ^1.53.0 + version: 1.93.3 + tailwindcss: + specifier: ^3.2.4 + version: 3.4.18 + typescript: + specifier: ^4.7.4 + version: 4.9.5 + vite: + specifier: ^4.0.3 + version: 4.5.14(@types/node@16.18.126)(sass@1.93.3) + vite-plugin-svg-icons: + specifier: ^2.0.1 + version: 2.0.1(vite@4.5.14(@types/node@16.18.126)(sass@1.93.3)) + vue-tsc: + specifier: ^0.35.0 + version: 0.35.2(typescript@4.9.5) + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + + '@better-scroll/core@2.5.1': + resolution: {integrity: sha512-koKOuYA55dQ04FJRIVUpMGDr1hbCfWmfX0MGp1hKagkQSWSRpwblqACiwtggVauoj9aaJRJZ9hDsTM4weaavlg==} + + '@better-scroll/indicators@2.5.1': + resolution: {integrity: sha512-Hk+Y00pR6fTsu6C9HGg1yYZtsu1gAcTgcs4C9aM5h6fQANX/T2YIYrOSjZmdL+js2PTcXJWZS8VM4Xjoi1PbfQ==} + + '@better-scroll/infinity@2.5.1': + resolution: {integrity: sha512-GKHrrasIh0KlGzhASHDo5hEEBJcDFpP4XaZGPH9Ey8+QBH6/O1ykAXS2ixkVAOTkBrv+KgFXoCUr4oN1xWeM+g==} + + '@better-scroll/mouse-wheel@2.5.1': + resolution: {integrity: sha512-DGnrirRMY6zMM7xwgx09D/cA9A//3J1/uDkq8iBVEyE5p0sEr/keQpjEfFHGkBRa505BnbBwdbN6f5lugEDSPw==} + + '@better-scroll/movable@2.5.1': + resolution: {integrity: sha512-8bLPRY15bbK4K5+tjrtdaKsFFKmJx72wRdg+xz3xQGFcTD940HFkJiORSOcz8Ufue7eOJfcmreQJBw6XY+TqTw==} + + '@better-scroll/nested-scroll@2.5.1': + resolution: {integrity: sha512-3cRsARxf9tq1VWBq7YAaET0xGAmgY1ERMmnXDo2gHFrmsJoNOionlpAeHdZvKQp2jG7JrzJ1O27nGCXf40gnkw==} + + '@better-scroll/observe-dom@2.5.1': + resolution: {integrity: sha512-TCMGFLRfpXBPIwtUV/efliUmfmrhSNI7NXdSyjdWjsLOS7dh3eFkmcom5ERVWMaXVELSmujGXLqobT+dT0C/jg==} + + '@better-scroll/observe-image@2.5.1': + resolution: {integrity: sha512-0Lhfj83o8EESwOxr8bfStCzNOokTm3KB7JeyMS8u/xl+3tyTuls9889cyAukYk4Yly1cS49pCGfj2P8YOiwtUg==} + + '@better-scroll/pull-down@2.5.1': + resolution: {integrity: sha512-Y6XcGu2NlevPg3k9VBRRFvpmfoTA+rO96JGdog2qKHclIPNXnsVwsIHtZfAm9weE/f9UuC4BnB+VUFRlucfupg==} + + '@better-scroll/pull-up@2.5.1': + resolution: {integrity: sha512-1hu3xSMxdB8T391KffpNZ7g93lMwZEHjfb1F1Y4KvIkciDt8nXqkGpqrZF+YwR+EJTgYcWqUO8kgmI6XXu7Pkg==} + + '@better-scroll/scroll-bar@2.5.1': + resolution: {integrity: sha512-i6r60pWG/ztkFK2j5Gj54I0LJb2jGh5TWJNQBoW0gUkp28B+0JvBFTwZn9tF7beZCBorKR7Hvvu4O9A1TJy94Q==} + + '@better-scroll/shared-utils@2.5.1': + resolution: {integrity: sha512-AplkfSjXVYP9LZiD6JsKgmgQJ/mG4uuLmBuwLz8W5OsYc7AYTfN8kw6GqZ5OwCGoXkVhBGyd8NeC4xwYItp0aw==} + + '@better-scroll/slide@2.5.1': + resolution: {integrity: sha512-aDOrfsmjAcz6DXN7mDX3tPieAn195R43Yn9e3waI19TIEok/mQlI1a/kb5quqWOoxkiaZQ8xe3vx5ZTj9C+F6Q==} + + '@better-scroll/wheel@2.5.1': + resolution: {integrity: sha512-fYLcEvkh88Z/2L+P5/+SGMunuc+HzAjGOiORIa/x21qb/knO2RFH4A/V1Rt3OIW4QluWzuFnU6jJRPlsQVZ4fg==} + + '@better-scroll/zoom@2.5.1': + resolution: {integrity: sha512-aGvFY5ooeZWS4RcxQLD+pGLpQHQxpPy0sMZV3yadcd2QK53PK9gS4Dp+BYfRv8lZ4/P2LoNEhr6Wq1DN6+uPlA==} + + '@commitlint/cli@16.3.0': + resolution: {integrity: sha512-P+kvONlfsuTMnxSwWE1H+ZcPMY3STFaHb2kAacsqoIkNx66O0T7sTpBxpxkMrFPyhkJiLJnJWMhk4bbvYD3BMA==} + engines: {node: '>=v12'} + hasBin: true + + '@commitlint/config-conventional@16.2.4': + resolution: {integrity: sha512-av2UQJa3CuE5P0dzxj/o/B9XVALqYzEViHrMXtDrW9iuflrqCStWBAioijppj9URyz6ONpohJKAtSdgAOE0gkA==} + engines: {node: '>=v12'} + + '@commitlint/config-validator@16.2.1': + resolution: {integrity: sha512-hogSe0WGg7CKmp4IfNbdNES3Rq3UEI4XRPB8JL4EPgo/ORq5nrGTVzxJh78omibNuB8Ho4501Czb1Er1MoDWpw==} + engines: {node: '>=v12'} + + '@commitlint/ensure@16.2.1': + resolution: {integrity: sha512-/h+lBTgf1r5fhbDNHOViLuej38i3rZqTQnBTk+xEg+ehOwQDXUuissQ5GsYXXqI5uGy+261ew++sT4EA3uBJ+A==} + engines: {node: '>=v12'} + + '@commitlint/execute-rule@16.2.1': + resolution: {integrity: sha512-oSls82fmUTLM6cl5V3epdVo4gHhbmBFvCvQGHBRdQ50H/690Uq1Dyd7hXMuKITCIdcnr9umyDkr8r5C6HZDF3g==} + engines: {node: '>=v12'} + + '@commitlint/format@16.2.1': + resolution: {integrity: sha512-Yyio9bdHWmNDRlEJrxHKglamIk3d6hC0NkEUW6Ti6ipEh2g0BAhy8Od6t4vLhdZRa1I2n+gY13foy+tUgk0i1Q==} + engines: {node: '>=v12'} + + '@commitlint/is-ignored@16.2.4': + resolution: {integrity: sha512-Lxdq9aOAYCOOOjKi58ulbwK/oBiiKz+7Sq0+/SpFIEFwhHkIVugvDvWjh2VRBXmRC/x5lNcjDcYEwS/uYUvlYQ==} + engines: {node: '>=v12'} + + '@commitlint/lint@16.2.4': + resolution: {integrity: sha512-AUDuwOxb2eGqsXbTMON3imUGkc1jRdtXrbbohiLSCSk3jFVXgJLTMaEcr39pR00N8nE9uZ+V2sYaiILByZVmxQ==} + engines: {node: '>=v12'} + + '@commitlint/load@16.3.0': + resolution: {integrity: sha512-3tykjV/iwbkv2FU9DG+NZ/JqmP0Nm3b7aDwgCNQhhKV5P74JAuByULkafnhn+zsFGypG1qMtI5u+BZoa9APm0A==} + engines: {node: '>=v12'} + + '@commitlint/message@16.2.1': + resolution: {integrity: sha512-2eWX/47rftViYg7a3axYDdrgwKv32mxbycBJT6OQY/MJM7SUfYNYYvbMFOQFaA4xIVZt7t2Alyqslbl6blVwWw==} + engines: {node: '>=v12'} + + '@commitlint/parse@16.2.1': + resolution: {integrity: sha512-2NP2dDQNL378VZYioLrgGVZhWdnJO4nAxQl5LXwYb08nEcN+cgxHN1dJV8OLJ5uxlGJtDeR8UZZ1mnQ1gSAD/g==} + engines: {node: '>=v12'} + + '@commitlint/read@16.2.1': + resolution: {integrity: sha512-tViXGuaxLTrw2r7PiYMQOFA2fueZxnnt0lkOWqKyxT+n2XdEMGYcI9ID5ndJKXnfPGPppD0w/IItKsIXlZ+alw==} + engines: {node: '>=v12'} + + '@commitlint/resolve-extends@16.2.1': + resolution: {integrity: sha512-NbbCMPKTFf2J805kwfP9EO+vV+XvnaHRcBy6ud5dF35dxMsvdJqke54W3XazXF1ZAxC4a3LBy4i/GNVBAthsEg==} + engines: {node: '>=v12'} + + '@commitlint/rules@16.2.4': + resolution: {integrity: sha512-rK5rNBIN2ZQNQK+I6trRPK3dWa0MtaTN4xnwOma1qxa4d5wQMQJtScwTZjTJeallFxhOgbNOgr48AMHkdounVg==} + engines: {node: '>=v12'} + + '@commitlint/to-lines@16.2.1': + resolution: {integrity: sha512-9/VjpYj5j1QeY3eiog1zQWY6axsdWAc0AonUUfyZ7B0MVcRI0R56YsHAfzF6uK/g/WwPZaoe4Lb1QCyDVnpVaQ==} + engines: {node: '>=v12'} + + '@commitlint/top-level@16.2.1': + resolution: {integrity: sha512-lS6GSieHW9y6ePL73ied71Z9bOKyK+Ib9hTkRsB8oZFAyQZcyRwq2w6nIa6Fngir1QW51oKzzaXfJL94qwImyw==} + engines: {node: '>=v12'} + + '@commitlint/types@16.2.1': + resolution: {integrity: sha512-7/z7pA7BM0i8XvMSBynO7xsB3mVQPUZbVn6zMIlp/a091XJ3qAXRXc+HwLYhiIdzzS5fuxxNIHZMGHVD4HJxdA==} + engines: {node: '>=v12'} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@ctrl/tinycolor@3.6.1': + resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} + engines: {node: '>=10'} + + '@element-plus/icons-vue@2.3.2': + resolution: {integrity: sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==} + peerDependencies: + vue: ^3.2.0 + + '@esbuild/android-arm64@0.18.20': + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.18.20': + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.18.20': + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.18.20': + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.18.20': + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.18.20': + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.18.20': + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.18.20': + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.18.20': + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.18.20': + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.18.20': + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.18.20': + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.18.20': + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.18.20': + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.18.20': + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.18.20': + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.18.20': + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.18.20': + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.18.20': + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.18.20': + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.18.20': + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.18.20': + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@intlify/core-base@9.14.5': + resolution: {integrity: sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==} + engines: {node: '>= 16'} + + '@intlify/message-compiler@9.14.5': + resolution: {integrity: sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==} + engines: {node: '>= 16'} + + '@intlify/shared@9.14.5': + resolution: {integrity: sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==} + engines: {node: '>= 16'} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@parcel/watcher-android-arm64@2.5.1': + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.1': + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.1': + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.1': + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.1': + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm-musl@2.5.1': + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm64-musl@2.5.1': + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-x64-glibc@2.5.1': + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-x64-musl@2.5.1': + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@parcel/watcher-win32-arm64@2.5.1': + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.1': + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.1': + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.1': + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + engines: {node: '>= 10.0.0'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@sxzz/popperjs-es@2.11.7': + resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==} + + '@transloadit/prettier-bytes@0.0.7': + resolution: {integrity: sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==} + + '@trysound/sax@0.2.0': + resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} + engines: {node: '>=10.13.0'} + + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/event-emitter@0.3.5': + resolution: {integrity: sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ==} + + '@types/js-cookie@3.0.6': + resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.20': + resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==} + + '@types/minimist@1.2.5': + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + + '@types/node@16.18.126': + resolution: {integrity: sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==} + + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + + '@types/nprogress@0.2.3': + resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==} + + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + + '@types/path-browserify@1.0.3': + resolution: {integrity: sha512-ZmHivEbNCBtAfcrFeBCiTjdIc2dey0l7oCGNGpSuRTy8jP6UVND7oUowlvDujBy8r2Hoa8bfFUOCiPWfmtkfxw==} + + '@types/semver@7.7.1': + resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + + '@types/svgo@2.6.4': + resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==} + + '@types/web-bluetooth@0.0.16': + resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==} + + '@typescript-eslint/eslint-plugin@5.62.0': + resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@5.62.0': + resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@5.62.0': + resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/type-utils@5.62.0': + resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@5.62.0': + resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/typescript-estree@5.62.0': + resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@5.62.0': + resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@typescript-eslint/visitor-keys@5.62.0': + resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@uppy/companion-client@2.2.2': + resolution: {integrity: sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==} + + '@uppy/core@2.3.4': + resolution: {integrity: sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==} + + '@uppy/store-default@2.1.1': + resolution: {integrity: sha512-xnpTxvot2SeAwGwbvmJ899ASk5tYXhmZzD/aCFsXePh/v8rNvR2pKlcQUH7cF/y4baUGq3FHO/daKCok/mpKqQ==} + + '@uppy/utils@4.1.3': + resolution: {integrity: sha512-nTuMvwWYobnJcytDO3t+D6IkVq/Qs4Xv3vyoEZ+Iaf8gegZP+rEyoaFT2CK5XLRMienPyqRqNbIfRuFaOWSIFw==} + + '@uppy/xhr-upload@2.1.3': + resolution: {integrity: sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==} + peerDependencies: + '@uppy/core': ^2.3.3 + + '@vitejs/plugin-vue@4.6.2': + resolution: {integrity: sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.0.0 || ^5.0.0 + vue: ^3.2.25 + + '@volar/code-gen@0.35.2': + resolution: {integrity: sha512-MoZHuNnPfUWnCNkQUI5+U+gvLTxrU+XlCTusdNOTFYUUAa+M68MH0RxFIS9Ybj4uAUWTcZx0Ow1q5t/PZozo+Q==} + + '@volar/source-map@0.35.2': + resolution: {integrity: sha512-PFHh9wN/qMkOWYyvmB8ckvIzolrpNOvK5EBdxxdTpiPJhfYjW82rMDBnYf6RxCe7yQxrUrmve6BWVO7flxWNVQ==} + + '@volar/vue-code-gen@0.35.2': + resolution: {integrity: sha512-8H6P8EtN06eSVGjtcJhGqZzFIg6/nWoHVOlnhc5vKqC7tXwpqPbyMQae0tO7pLBd5qSb/dYU5GQcBAHsi2jgyA==} + deprecated: 'WARNING: This project has been renamed to @vue/language-core. Install using @vue/language-core instead.' + + '@volar/vue-typescript@0.35.2': + resolution: {integrity: sha512-PZI6Urb+Vr5Dvgf9xysM8X7TP09inWDy1wjDtprBoBhxS7r0Dg3V0qZuJa7sSGz7M0QMa5R/CBaZPhlxFCfJBw==} + deprecated: 'WARNING: This project has been renamed to @vue/typescript. Install using @vue/typescript instead.' + + '@vue/compiler-core@3.5.24': + resolution: {integrity: sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==} + + '@vue/compiler-dom@3.5.24': + resolution: {integrity: sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==} + + '@vue/compiler-sfc@3.5.24': + resolution: {integrity: sha512-8EG5YPRgmTB+YxYBM3VXy8zHD9SWHUJLIGPhDovo3Z8VOgvP+O7UP5vl0J4BBPWYD9vxtBabzW1EuEZ+Cqs14g==} + + '@vue/compiler-ssr@3.5.24': + resolution: {integrity: sha512-trOvMWNBMQ/odMRHW7Ae1CdfYx+7MuiQu62Jtu36gMLXcaoqKvAyh+P73sYG9ll+6jLB6QPovqoKGGZROzkFFg==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/reactivity@3.5.24': + resolution: {integrity: sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==} + + '@vue/runtime-core@3.5.24': + resolution: {integrity: sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==} + + '@vue/runtime-dom@3.5.24': + resolution: {integrity: sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==} + + '@vue/server-renderer@3.5.24': + resolution: {integrity: sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==} + peerDependencies: + vue: 3.5.24 + + '@vue/shared@3.5.24': + resolution: {integrity: sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==} + + '@vueuse/core@9.13.0': + resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==} + + '@vueuse/metadata@9.13.0': + resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==} + + '@vueuse/shared@9.13.0': + resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==} + + '@wangeditor/basic-modules@1.1.7': + resolution: {integrity: sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==} + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + lodash.throttle: ^4.1.1 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/code-highlight@1.0.3': + resolution: {integrity: sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==} + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/core@1.1.19': + resolution: {integrity: sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==} + peerDependencies: + '@uppy/core': ^2.1.1 + '@uppy/xhr-upload': ^2.0.3 + dom7: ^3.0.0 + is-hotkey: ^0.2.0 + lodash.camelcase: ^4.3.0 + lodash.clonedeep: ^4.5.0 + lodash.debounce: ^4.0.8 + lodash.foreach: ^4.5.0 + lodash.isequal: ^4.5.0 + lodash.throttle: ^4.1.1 + lodash.toarray: ^4.4.0 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/editor-for-vue@5.1.12': + resolution: {integrity: sha512-0Ds3D8I+xnpNWezAeO7HmPRgTfUxHLMd9JKcIw+QzvSmhC5xUHbpCcLU+KLmeBKTR/zffnS5GQo6qi3GhTMJWQ==} + peerDependencies: + '@wangeditor/editor': '>=5.1.0' + vue: ^3.0.5 + + '@wangeditor/editor@5.1.23': + resolution: {integrity: sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==} + + '@wangeditor/list-module@1.0.5': + resolution: {integrity: sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ==} + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/table-module@1.1.4': + resolution: {integrity: sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w==} + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + lodash.isequal: ^4.5.0 + lodash.throttle: ^4.1.1 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/upload-image-module@1.0.2': + resolution: {integrity: sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA==} + peerDependencies: + '@uppy/core': ^2.0.3 + '@uppy/xhr-upload': ^2.0.3 + '@wangeditor/basic-modules': 1.x + '@wangeditor/core': 1.x + dom7: ^3.0.0 + lodash.foreach: ^4.5.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/video-module@1.1.4': + resolution: {integrity: sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==} + peerDependencies: + '@uppy/core': ^2.1.4 + '@uppy/xhr-upload': ^2.0.7 + '@wangeditor/core': 1.x + dom7: ^3.0.0 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + JSONStream@1.3.5: + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + hasBin: true + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-regex@2.1.1: + resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} + engines: {node: '>=0.10.0'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@2.2.1: + resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} + engines: {node: '>=0.10.0'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + arr-diff@4.0.0: + resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==} + engines: {node: '>=0.10.0'} + + arr-flatten@1.1.0: + resolution: {integrity: sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==} + engines: {node: '>=0.10.0'} + + arr-union@3.1.0: + resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==} + engines: {node: '>=0.10.0'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-ify@1.0.0: + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + array-unique@0.3.2: + resolution: {integrity: sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==} + engines: {node: '>=0.10.0'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + + assign-symbols@1.0.0: + resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} + engines: {node: '>=0.10.0'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + async-validator@4.2.5: + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + atob@2.1.2: + resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} + engines: {node: '>= 4.5.0'} + hasBin: true + + autoprefixer@10.4.21: + resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axios@1.13.2: + resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base@0.11.2: + resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} + engines: {node: '>=0.10.0'} + + baseline-browser-mapping@2.8.25: + resolution: {integrity: sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==} + hasBin: true + + better-scroll@2.5.1: + resolution: {integrity: sha512-OiF3cQroRfTzf+CRQH2z1G52ZAlNHINI6lCAvDmyFu0o0nRuTaV9F+fmBGIU2BL5p5IplUQ4E7sYa1TLfZarzQ==} + + big.js@5.2.2: + resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@2.3.2: + resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==} + engines: {node: '>=0.10.0'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.27.0: + resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + cache-base@1.0.1: + resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==} + engines: {node: '>=0.10.0'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + camelcase-keys@6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001754: + resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==} + + chalk@1.1.3: + resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} + engines: {node: '>=0.10.0'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + class-utils@0.3.6: + resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==} + engines: {node: '>=0.10.0'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + + collection-visit@1.0.0: + resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==} + engines: {node: '>=0.10.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + compare-func@2.0.0: + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + + compute-scroll-into-view@1.0.20: + resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + conventional-changelog-angular@5.0.13: + resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} + engines: {node: '>=10'} + + conventional-changelog-conventionalcommits@4.6.3: + resolution: {integrity: sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==} + engines: {node: '>=10'} + + conventional-commits-parser@3.2.4: + resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==} + engines: {node: '>=10'} + hasBin: true + + copy-descriptor@0.1.1: + resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==} + engines: {node: '>=0.10.0'} + + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + + cosmiconfig-typescript-loader@2.0.2: + resolution: {integrity: sha512-KmE+bMjWMXJbkWCeY4FJX/npHuZPNr9XF9q9CIQ/bpFwi1qHfCmSiKarrCcRa0LO4fWjk93pVoeRtJAkTGcYNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@types/node': '*' + cosmiconfig: '>=7' + typescript: '>=3' + + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-select@4.3.0: + resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} + + css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csso@4.2.0: + resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} + engines: {node: '>=8.0.0'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + d@1.0.2: + resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} + engines: {node: '>=0.12'} + + dargs@7.0.0: + resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} + engines: {node: '>=8'} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize-keys@1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + decode-uri-component@0.2.2: + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + default-passive-events@2.0.0: + resolution: {integrity: sha512-eMtt76GpDVngZQ3ocgvRcNCklUMwID1PaNbCNxfpDXuiOXttSh0HzBbda1HU9SIUsDc02vb7g9+3I5tlqe/qMQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + define-property@0.2.5: + resolution: {integrity: sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==} + engines: {node: '>=0.10.0'} + + define-property@1.0.0: + resolution: {integrity: sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==} + engines: {node: '>=0.10.0'} + + define-property@2.0.2: + resolution: {integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==} + engines: {node: '>=0.10.0'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dom-serializer@0.2.2: + resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==} + + dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + + dom7@3.0.0: + resolution: {integrity: sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==} + + domelementtype@1.3.1: + resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@2.4.2: + resolution: {integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==} + + domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + + domutils@1.7.0: + resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==} + + domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + + dot-prop@5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + echarts@5.6.0: + resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==} + + electron-to-chromium@1.5.248: + resolution: {integrity: sha512-zsur2yunphlyAO4gIubdJEXCK6KOVvtpiuDfCIqbM9FjcnMYiyn0ICa3hWfPr0nc41zcLWobgy1iL7VvoOyA2Q==} + + element-plus@2.11.7: + resolution: {integrity: sha512-Bh47wuzsqaNBNDkbtlOlZER1cGcOB8GsXp/+C9b95MOrk0wvoHUV4NKKK7xMkfYNFYdYysQ752oMhnExgAL6+g==} + peerDependencies: + vue: ^3.2.0 + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + emojis-list@3.0.0: + resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} + engines: {node: '>= 4'} + + entities@1.1.2: + resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==} + + entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-abstract@1.24.0: + resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + es5-ext@0.10.64: + resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} + engines: {node: '>=0.10'} + + es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + + es6-symbol@3.1.4: + resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} + engines: {node: '>=0.12'} + + esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@8.10.2: + resolution: {integrity: sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@4.2.5: + resolution: {integrity: sha512-9Ni+xgemM2IWLq6aXEpP2+V/V30GeA/46Ar629vcMqVPodFFWC9skHu/D1phvuqtS8bJCFnNf01/qcmqYEwNfg==} + engines: {node: '>=12.0.0'} + peerDependencies: + eslint: '>=7.28.0' + eslint-config-prettier: '*' + prettier: '>=2.0.0' + peerDependenciesMeta: + eslint-config-prettier: + optional: true + + eslint-plugin-vue@8.7.1: + resolution: {integrity: sha512-28sbtm4l4cOzoO1LtzQPxfxhQABararUb1JtqusQqObJpWX2e/gmVyeYVfepizPFne0Q5cILkYGiBoV36L12Wg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-utils@3.0.0: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + + eslint-visitor-keys@2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + esniff@2.0.1: + resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} + engines: {node: '>=0.10'} + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + expand-brackets@2.1.4: + resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==} + engines: {node: '>=0.10.0'} + + ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + + extend-shallow@3.0.2: + resolution: {integrity: sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==} + engines: {node: '>=0.10.0'} + + extglob@2.0.4: + resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + fill-range@4.0.0: + resolution: {integrity: sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==} + engines: {node: '>=0.10.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + for-in@1.0.2: + resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} + engines: {node: '>=0.10.0'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + + fragment-cache@0.2.1: + resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==} + engines: {node: '>=0.10.0'} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-value@2.0.6: + resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} + engines: {node: '>=0.10.0'} + + git-raw-commits@2.0.11: + resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} + engines: {node: '>=10'} + hasBin: true + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + global-dirs@0.1.1: + resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} + engines: {node: '>=4'} + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + hard-rejection@2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + + has-ansi@2.0.0: + resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} + engines: {node: '>=0.10.0'} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@1.0.0: + resolution: {integrity: sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==} + engines: {node: '>=0.10.0'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + has-value@0.3.1: + resolution: {integrity: sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==} + engines: {node: '>=0.10.0'} + + has-value@1.0.0: + resolution: {integrity: sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==} + engines: {node: '>=0.10.0'} + + has-values@0.1.4: + resolution: {integrity: sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==} + engines: {node: '>=0.10.0'} + + has-values@1.0.0: + resolution: {integrity: sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==} + engines: {node: '>=0.10.0'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + + hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + + html-void-elements@2.0.1: + resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==} + + htmlparser2@3.10.1: + resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + husky@7.0.4: + resolution: {integrity: sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==} + engines: {node: '>=12'} + hasBin: true + + i18next@20.6.1: + resolution: {integrity: sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + image-size@0.5.5: + resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} + engines: {node: '>=0.10.0'} + hasBin: true + + immer@9.0.21: + resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} + + immutable@5.1.4: + resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + is-accessor-descriptor@1.0.1: + resolution: {integrity: sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==} + engines: {node: '>= 0.10'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-buffer@1.1.6: + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-descriptor@1.0.1: + resolution: {integrity: sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-descriptor@0.1.7: + resolution: {integrity: sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==} + engines: {node: '>= 0.4'} + + is-descriptor@1.0.3: + resolution: {integrity: sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==} + engines: {node: '>= 0.4'} + + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + + is-extendable@1.0.1: + resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==} + engines: {node: '>=0.10.0'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hotkey@0.2.0: + resolution: {integrity: sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@3.0.0: + resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + + is-plain-object@2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-text-path@1.0.1: + resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} + engines: {node: '>=0.10.0'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-url@1.2.4: + resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isobject@2.1.0: + resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==} + engines: {node: '>=0.10.0'} + + isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + js-base64@2.6.4: + resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==} + + js-base64@3.7.8: + resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} + + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsencrypt@3.5.4: + resolution: {integrity: sha512-kNjfYEMNASxrDGsmcSQh/rUTmcoRfSUkxnAz+MMywM8jtGu+fFEZ3nJjHM58zscVnwR0fYmG9sGkTDjqUdpiwA==} + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kind-of@3.2.2: + resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} + engines: {node: '>=0.10.0'} + + kind-of@4.0.0: + resolution: {integrity: sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==} + engines: {node: '>=0.10.0'} + + kind-of@5.1.0: + resolution: {integrity: sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==} + engines: {node: '>=0.10.0'} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + loader-utils@1.4.2: + resolution: {integrity: sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==} + engines: {node: '>=4.0.0'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash-unified@1.0.3: + resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==} + peerDependencies: + '@types/lodash-es': '*' + lodash: '*' + lodash-es: '*' + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.foreach@4.5.0: + resolution: {integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==} + + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + + lodash.toarray@4.4.0: + resolution: {integrity: sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + map-cache@0.2.2: + resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} + engines: {node: '>=0.10.0'} + + map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + + map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + + map-visit@1.0.0: + resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==} + engines: {node: '>=0.10.0'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + + memoize-one@6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + + meow@8.1.2: + resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} + engines: {node: '>=10'} + + merge-options@1.0.1: + resolution: {integrity: sha512-iuPV41VWKWBIOpBsjoxjDZw8/GbSfZ2mk7N1453bwMrfzdrIk7EzBd+8UVR6rkw67th7xnk9Dytl3J+lHPdxvg==} + engines: {node: '>=4'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@3.1.0: + resolution: {integrity: sha512-3StSelAE+hnRvMs8IdVW7Uhk8CVed5tp+kLLGlBP6WiRAXS21GPGu/Nat4WNPXj2Eoc24B02SaeoyozPMfj0/g==} + engines: {node: '>=0.10.0'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-match@1.0.2: + resolution: {integrity: sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist-options@4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mixin-deep@1.3.2: + resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} + engines: {node: '>=0.10.0'} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + namespace-emitter@2.0.1: + resolution: {integrity: sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanomatch@1.2.13: + resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} + engines: {node: '>=0.10.0'} + + natural-compare-lite@1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + + normalize-package-data@3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + normalize-wheel-es@1.2.0: + resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + nprogress@0.2.0: + resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-copy@0.1.0: + resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object-visit@1.0.1: + resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==} + engines: {node: '>=0.10.0'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.pick@1.3.0: + resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==} + engines: {node: '>=0.10.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + pascalcase@0.1.1: + resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==} + engines: {node: '>=0.10.0'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@0.2.0: + resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pinia@2.3.1: + resolution: {integrity: sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==} + peerDependencies: + typescript: '>=4.4.4' + vue: ^2.7.0 || ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + posix-character-classes@0.1.1: + resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==} + engines: {node: '>=0.10.0'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-prefix-selector@1.16.1: + resolution: {integrity: sha512-Umxu+FvKMwlY6TyDzGFoSUnzW+NOfMBLyC1tAkIjgX+Z/qGspJeRjVC903D7mx7TuBpJlwti2ibXtWuA7fKMeQ==} + peerDependencies: + postcss: '>4 <9' + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@5.2.18: + resolution: {integrity: sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==} + engines: {node: '>=0.12'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + posthtml-parser@0.2.1: + resolution: {integrity: sha512-nPC53YMqJnc/+1x4fRYFfm81KV2V+G9NZY+hTohpYg64Ay7NemWWcV4UWuy/SgMupqQ3kJ88M/iRfZmSnxT+pw==} + + posthtml-rename-id@1.0.12: + resolution: {integrity: sha512-UKXf9OF/no8WZo9edRzvuMenb6AD5hDLzIepJW+a4oJT+T/Lx7vfMYWT4aWlGNQh0WMhnUx1ipN9OkZ9q+ddEw==} + + posthtml-render@1.4.0: + resolution: {integrity: sha512-W1779iVHGfq0Fvh2PROhCe2QhB8mEErgqzo1wpIt36tCgChafP+hbXIhLDOM8ePJrZcFs0vkNEtdibEWVqChqw==} + engines: {node: '>=10'} + + posthtml-svg-mode@1.0.3: + resolution: {integrity: sha512-hEqw9NHZ9YgJ2/0G7CECOeuLQKZi8HjWLkBaSVtOWjygQ9ZD8P7tqeowYs7WrFdKsWEKG7o+IlsPY8jrr0CJpQ==} + + posthtml@0.9.2: + resolution: {integrity: sha512-spBB5sgC4cv2YcW03f/IAUN1pgDJWNWD8FzkyY4mArLUMJW+KlQhlmUdKAHQuPfb00Jl5xIfImeOsf6YL8QK7Q==} + engines: {node: '>=0.10.0'} + + preact@10.27.2: + resolution: {integrity: sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + q@1.5.1: + resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} + engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + deprecated: |- + You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) + + query-string@4.3.4: + resolution: {integrity: sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==} + engines: {node: '>=0.10.0'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + quick-lru@4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + read-pkg-up@7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + + read-pkg@5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regex-not@1.0.2: + resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==} + engines: {node: '>=0.10.0'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + repeat-element@1.1.4: + resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==} + engines: {node: '>=0.10.0'} + + repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-global@1.0.0: + resolution: {integrity: sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==} + engines: {node: '>=8'} + + resolve-url@0.2.1: + resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} + deprecated: https://github.com/lydell/resolve-url#deprecated + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + ret@0.1.15: + resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} + engines: {node: '>=0.12'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup@3.29.5: + resolution: {integrity: sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safe-regex@1.1.0: + resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==} + + sass@1.93.3: + resolution: {integrity: sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg==} + engines: {node: '>=14.0.0'} + hasBin: true + + screenfull@6.0.2: + resolution: {integrity: sha512-AQdy8s4WhNvUZ6P8F6PB21tSPIYKniic+Ogx0AacBMjKP1GUHN2E9URxQHtCusiwxudnCKkdy4GrHXPPJSkCCw==} + engines: {node: ^14.13.1 || >=16.0.0} + + scroll-into-view-if-needed@2.2.31: + resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@7.3.7: + resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==} + engines: {node: '>=10'} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + set-value@2.0.1: + resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} + engines: {node: '>=0.10.0'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slate-history@0.66.0: + resolution: {integrity: sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==} + peerDependencies: + slate: '>=0.65.3' + + slate@0.72.8: + resolution: {integrity: sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==} + + snabbdom@3.6.3: + resolution: {integrity: sha512-W2lHLLw2qR2Vv0DcMmcxXqcfdBaIcoN+y/86SmHv8fn4DazEQSH6KN3TjZcWvwujW56OHiiirsbHWZb4vx/0fg==} + engines: {node: '>=12.17.0'} + + snapdragon-node@2.1.1: + resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==} + engines: {node: '>=0.10.0'} + + snapdragon-util@3.0.1: + resolution: {integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==} + engines: {node: '>=0.10.0'} + + snapdragon@0.8.2: + resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==} + engines: {node: '>=0.10.0'} + + sortablejs@1.10.2: + resolution: {integrity: sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A==} + + sortablejs@1.15.6: + resolution: {integrity: sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-resolve@0.5.3: + resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} + deprecated: See https://github.com/lydell/source-map-resolve#deprecated + + source-map-url@0.4.1: + resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==} + deprecated: See https://github.com/lydell/source-map-url#deprecated + + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.22: + resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} + + split-string@3.1.0: + resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} + engines: {node: '>=0.10.0'} + + split2@3.2.2: + resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + + ssr-window@3.0.0: + resolution: {integrity: sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==} + + stable@0.1.8: + resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} + deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' + + static-extend@0.1.2: + resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==} + engines: {node: '>=0.10.0'} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + strict-uri-encode@1.1.0: + resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==} + engines: {node: '>=0.10.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@3.0.1: + resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} + engines: {node: '>=0.10.0'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@2.0.0: + resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} + engines: {node: '>=0.8.0'} + + supports-color@3.2.3: + resolution: {integrity: sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==} + engines: {node: '>=0.8.0'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + svg-baker@1.7.0: + resolution: {integrity: sha512-nibslMbkXOIkqKVrfcncwha45f97fGuAOn1G99YwnwTj8kF9YiM6XexPcUso97NxOm6GsP0SIvYVIosBis1xLg==} + + svgo@2.8.0: + resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} + engines: {node: '>=10.13.0'} + hasBin: true + + tailwindcss@3.4.18: + resolution: {integrity: sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + text-extensions@1.9.0: + resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} + engines: {node: '>=0.10'} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + through2@4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + + to-object-path@0.3.0: + resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==} + engines: {node: '>=0.10.0'} + + to-regex-range@2.1.1: + resolution: {integrity: sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==} + engines: {node: '>=0.10.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + to-regex@3.0.2: + resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==} + engines: {node: '>=0.10.0'} + + traverse@0.6.11: + resolution: {integrity: sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==} + engines: {node: '>= 0.4'} + + trim-newlines@3.0.1: + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + + tsutils@3.21.0: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.18.1: + resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} + engines: {node: '>=10'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + + type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + + type@2.7.3: + resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typedarray.prototype.slice@1.0.5: + resolution: {integrity: sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==} + engines: {node: '>= 0.4'} + + typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + union-value@1.0.1: + resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==} + engines: {node: '>=0.10.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unset-value@1.0.0: + resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==} + engines: {node: '>=0.10.0'} + + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + urix@0.1.0: + resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==} + deprecated: Please see https://github.com/lydell/urix#deprecated + + use@3.1.1: + resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==} + engines: {node: '>=0.10.0'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite-plugin-svg-icons@2.0.1: + resolution: {integrity: sha512-6ktD+DhV6Rz3VtedYvBKKVA2eXF+sAQVaKkKLDSqGUfnhqXl3bj5PPkVTl3VexfTuZy66PmINi8Q6eFnVfRUmA==} + peerDependencies: + vite: '>=2.0.0' + + vite@4.5.14: + resolution: {integrity: sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-eslint-parser@8.3.0: + resolution: {integrity: sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + vue-i18n@9.14.5: + resolution: {integrity: sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==} + engines: {node: '>= 16'} + peerDependencies: + vue: ^3.0.0 + + vue-router@4.6.3: + resolution: {integrity: sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==} + peerDependencies: + vue: ^3.5.0 + + vue-tsc@0.35.2: + resolution: {integrity: sha512-aqY16VlODHzqtKGUkqdumNpH+s5ABCkufRyvMKQlL/mua+N2DfSVnHufzSNNUMr7vmOO0YsNg27jsspBMq4iGA==} + hasBin: true + peerDependencies: + typescript: '*' + + vue@3.5.24: + resolution: {integrity: sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + vuedraggable@2.24.3: + resolution: {integrity: sha512-6/HDXi92GzB+Hcs9fC6PAAozK1RLt1ewPTLjK0anTYguXLAeySDmcnqE8IC0xa7shvSzRjQXq3/+dsZ7ETGF3g==} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wildcard@1.1.2: + resolution: {integrity: sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zrender@5.6.1: + resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/runtime@7.28.4': {} + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@better-scroll/core@2.5.1': + dependencies: + '@better-scroll/shared-utils': 2.5.1 + + '@better-scroll/indicators@2.5.1': + dependencies: + '@better-scroll/core': 2.5.1 + + '@better-scroll/infinity@2.5.1': + dependencies: + '@better-scroll/core': 2.5.1 + + '@better-scroll/mouse-wheel@2.5.1': + dependencies: + '@better-scroll/core': 2.5.1 + + '@better-scroll/movable@2.5.1': + dependencies: + '@better-scroll/core': 2.5.1 + + '@better-scroll/nested-scroll@2.5.1': + dependencies: + '@better-scroll/core': 2.5.1 + + '@better-scroll/observe-dom@2.5.1': + dependencies: + '@better-scroll/core': 2.5.1 + + '@better-scroll/observe-image@2.5.1': + dependencies: + '@better-scroll/core': 2.5.1 + + '@better-scroll/pull-down@2.5.1': + dependencies: + '@better-scroll/core': 2.5.1 + + '@better-scroll/pull-up@2.5.1': + dependencies: + '@better-scroll/core': 2.5.1 + + '@better-scroll/scroll-bar@2.5.1': + dependencies: + '@better-scroll/core': 2.5.1 + + '@better-scroll/shared-utils@2.5.1': {} + + '@better-scroll/slide@2.5.1': + dependencies: + '@better-scroll/core': 2.5.1 + + '@better-scroll/wheel@2.5.1': + dependencies: + '@better-scroll/core': 2.5.1 + + '@better-scroll/zoom@2.5.1': + dependencies: + '@better-scroll/core': 2.5.1 + + '@commitlint/cli@16.3.0': + dependencies: + '@commitlint/format': 16.2.1 + '@commitlint/lint': 16.2.4 + '@commitlint/load': 16.3.0 + '@commitlint/read': 16.2.1 + '@commitlint/types': 16.2.1 + lodash: 4.17.21 + resolve-from: 5.0.0 + resolve-global: 1.0.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + + '@commitlint/config-conventional@16.2.4': + dependencies: + conventional-changelog-conventionalcommits: 4.6.3 + + '@commitlint/config-validator@16.2.1': + dependencies: + '@commitlint/types': 16.2.1 + ajv: 6.12.6 + + '@commitlint/ensure@16.2.1': + dependencies: + '@commitlint/types': 16.2.1 + lodash: 4.17.21 + + '@commitlint/execute-rule@16.2.1': {} + + '@commitlint/format@16.2.1': + dependencies: + '@commitlint/types': 16.2.1 + chalk: 4.1.2 + + '@commitlint/is-ignored@16.2.4': + dependencies: + '@commitlint/types': 16.2.1 + semver: 7.3.7 + + '@commitlint/lint@16.2.4': + dependencies: + '@commitlint/is-ignored': 16.2.4 + '@commitlint/parse': 16.2.1 + '@commitlint/rules': 16.2.4 + '@commitlint/types': 16.2.1 + + '@commitlint/load@16.3.0': + dependencies: + '@commitlint/config-validator': 16.2.1 + '@commitlint/execute-rule': 16.2.1 + '@commitlint/resolve-extends': 16.2.1 + '@commitlint/types': 16.2.1 + '@types/node': 16.18.126 + chalk: 4.1.2 + cosmiconfig: 7.1.0 + cosmiconfig-typescript-loader: 2.0.2(@types/node@16.18.126)(cosmiconfig@7.1.0)(typescript@4.9.5) + lodash: 4.17.21 + resolve-from: 5.0.0 + typescript: 4.9.5 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + + '@commitlint/message@16.2.1': {} + + '@commitlint/parse@16.2.1': + dependencies: + '@commitlint/types': 16.2.1 + conventional-changelog-angular: 5.0.13 + conventional-commits-parser: 3.2.4 + + '@commitlint/read@16.2.1': + dependencies: + '@commitlint/top-level': 16.2.1 + '@commitlint/types': 16.2.1 + fs-extra: 10.1.0 + git-raw-commits: 2.0.11 + + '@commitlint/resolve-extends@16.2.1': + dependencies: + '@commitlint/config-validator': 16.2.1 + '@commitlint/types': 16.2.1 + import-fresh: 3.3.1 + lodash: 4.17.21 + resolve-from: 5.0.0 + resolve-global: 1.0.0 + + '@commitlint/rules@16.2.4': + dependencies: + '@commitlint/ensure': 16.2.1 + '@commitlint/message': 16.2.1 + '@commitlint/to-lines': 16.2.1 + '@commitlint/types': 16.2.1 + execa: 5.1.1 + + '@commitlint/to-lines@16.2.1': {} + + '@commitlint/top-level@16.2.1': + dependencies: + find-up: 5.0.0 + + '@commitlint/types@16.2.1': + dependencies: + chalk: 4.1.2 + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@ctrl/tinycolor@3.6.1': {} + + '@element-plus/icons-vue@2.3.2(vue@3.5.24(typescript@4.9.5))': + dependencies: + vue: 3.5.24(typescript@4.9.5) + + '@esbuild/android-arm64@0.18.20': + optional: true + + '@esbuild/android-arm@0.18.20': + optional: true + + '@esbuild/android-x64@0.18.20': + optional: true + + '@esbuild/darwin-arm64@0.18.20': + optional: true + + '@esbuild/darwin-x64@0.18.20': + optional: true + + '@esbuild/freebsd-arm64@0.18.20': + optional: true + + '@esbuild/freebsd-x64@0.18.20': + optional: true + + '@esbuild/linux-arm64@0.18.20': + optional: true + + '@esbuild/linux-arm@0.18.20': + optional: true + + '@esbuild/linux-ia32@0.18.20': + optional: true + + '@esbuild/linux-loong64@0.18.20': + optional: true + + '@esbuild/linux-mips64el@0.18.20': + optional: true + + '@esbuild/linux-ppc64@0.18.20': + optional: true + + '@esbuild/linux-riscv64@0.18.20': + optional: true + + '@esbuild/linux-s390x@0.18.20': + optional: true + + '@esbuild/linux-x64@0.18.20': + optional: true + + '@esbuild/netbsd-x64@0.18.20': + optional: true + + '@esbuild/openbsd-x64@0.18.20': + optional: true + + '@esbuild/sunos-x64@0.18.20': + optional: true + + '@esbuild/win32-arm64@0.18.20': + optional: true + + '@esbuild/win32-ia32@0.18.20': + optional: true + + '@esbuild/win32-x64@0.18.20': + optional: true + + '@eslint-community/eslint-utils@4.9.0(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/utils@0.2.10': {} + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@intlify/core-base@9.14.5': + dependencies: + '@intlify/message-compiler': 9.14.5 + '@intlify/shared': 9.14.5 + + '@intlify/message-compiler@9.14.5': + dependencies: + '@intlify/shared': 9.14.5 + source-map-js: 1.2.1 + + '@intlify/shared@9.14.5': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@parcel/watcher-android-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-x64@2.5.1': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.1': + optional: true + + '@parcel/watcher-win32-arm64@2.5.1': + optional: true + + '@parcel/watcher-win32-ia32@2.5.1': + optional: true + + '@parcel/watcher-win32-x64@2.5.1': + optional: true + + '@parcel/watcher@2.5.1': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 + optional: true + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@sxzz/popperjs-es@2.11.7': {} + + '@transloadit/prettier-bytes@0.0.7': {} + + '@trysound/sax@0.2.0': {} + + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/event-emitter@0.3.5': {} + + '@types/js-cookie@3.0.6': {} + + '@types/json-schema@7.0.15': {} + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.20 + + '@types/lodash@4.17.20': {} + + '@types/minimist@1.2.5': {} + + '@types/node@16.18.126': {} + + '@types/normalize-package-data@2.4.4': {} + + '@types/nprogress@0.2.3': {} + + '@types/parse-json@4.0.2': {} + + '@types/path-browserify@1.0.3': {} + + '@types/semver@7.7.1': {} + + '@types/svgo@2.6.4': + dependencies: + '@types/node': 16.18.126 + + '@types/web-bluetooth@0.0.16': {} + + '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5) + debug: 4.4.3 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare-lite: 1.4.0 + semver: 7.7.3 + tsutils: 3.21.0(typescript@4.9.5) + optionalDependencies: + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5)': + dependencies: + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) + debug: 4.4.3 + eslint: 8.57.1 + optionalDependencies: + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@5.62.0': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + + '@typescript-eslint/type-utils@5.62.0(eslint@8.57.1)(typescript@4.9.5)': + dependencies: + '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5) + debug: 4.4.3 + eslint: 8.57.1 + tsutils: 3.21.0(typescript@4.9.5) + optionalDependencies: + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@5.62.0': {} + + '@typescript-eslint/typescript-estree@5.62.0(typescript@4.9.5)': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + debug: 4.4.3 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.7.3 + tsutils: 3.21.0(typescript@4.9.5) + optionalDependencies: + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@4.9.5)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1) + '@types/json-schema': 7.0.15 + '@types/semver': 7.7.1 + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) + eslint: 8.57.1 + eslint-scope: 5.1.1 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@5.62.0': + dependencies: + '@typescript-eslint/types': 5.62.0 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.3.0': {} + + '@uppy/companion-client@2.2.2': + dependencies: + '@uppy/utils': 4.1.3 + namespace-emitter: 2.0.1 + + '@uppy/core@2.3.4': + dependencies: + '@transloadit/prettier-bytes': 0.0.7 + '@uppy/store-default': 2.1.1 + '@uppy/utils': 4.1.3 + lodash.throttle: 4.1.1 + mime-match: 1.0.2 + namespace-emitter: 2.0.1 + nanoid: 3.3.11 + preact: 10.27.2 + + '@uppy/store-default@2.1.1': {} + + '@uppy/utils@4.1.3': + dependencies: + lodash.throttle: 4.1.1 + + '@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4)': + dependencies: + '@uppy/companion-client': 2.2.2 + '@uppy/core': 2.3.4 + '@uppy/utils': 4.1.3 + nanoid: 3.3.11 + + '@vitejs/plugin-vue@4.6.2(vite@4.5.14(@types/node@16.18.126)(sass@1.93.3))(vue@3.5.24(typescript@4.9.5))': + dependencies: + vite: 4.5.14(@types/node@16.18.126)(sass@1.93.3) + vue: 3.5.24(typescript@4.9.5) + + '@volar/code-gen@0.35.2': + dependencies: + '@volar/source-map': 0.35.2 + + '@volar/source-map@0.35.2': {} + + '@volar/vue-code-gen@0.35.2': + dependencies: + '@volar/code-gen': 0.35.2 + '@volar/source-map': 0.35.2 + '@vue/compiler-core': 3.5.24 + '@vue/compiler-dom': 3.5.24 + '@vue/shared': 3.5.24 + + '@volar/vue-typescript@0.35.2': + dependencies: + '@volar/code-gen': 0.35.2 + '@volar/source-map': 0.35.2 + '@volar/vue-code-gen': 0.35.2 + '@vue/compiler-sfc': 3.5.24 + '@vue/reactivity': 3.5.24 + + '@vue/compiler-core@3.5.24': + dependencies: + '@babel/parser': 7.28.5 + '@vue/shared': 3.5.24 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.24': + dependencies: + '@vue/compiler-core': 3.5.24 + '@vue/shared': 3.5.24 + + '@vue/compiler-sfc@3.5.24': + dependencies: + '@babel/parser': 7.28.5 + '@vue/compiler-core': 3.5.24 + '@vue/compiler-dom': 3.5.24 + '@vue/compiler-ssr': 3.5.24 + '@vue/shared': 3.5.24 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.24': + dependencies: + '@vue/compiler-dom': 3.5.24 + '@vue/shared': 3.5.24 + + '@vue/devtools-api@6.6.4': {} + + '@vue/reactivity@3.5.24': + dependencies: + '@vue/shared': 3.5.24 + + '@vue/runtime-core@3.5.24': + dependencies: + '@vue/reactivity': 3.5.24 + '@vue/shared': 3.5.24 + + '@vue/runtime-dom@3.5.24': + dependencies: + '@vue/reactivity': 3.5.24 + '@vue/runtime-core': 3.5.24 + '@vue/shared': 3.5.24 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.24(vue@3.5.24(typescript@4.9.5))': + dependencies: + '@vue/compiler-ssr': 3.5.24 + '@vue/shared': 3.5.24 + vue: 3.5.24(typescript@4.9.5) + + '@vue/shared@3.5.24': {} + + '@vueuse/core@9.13.0(vue@3.5.24(typescript@4.9.5))': + dependencies: + '@types/web-bluetooth': 0.0.16 + '@vueuse/metadata': 9.13.0 + '@vueuse/shared': 9.13.0(vue@3.5.24(typescript@4.9.5)) + vue-demi: 0.14.10(vue@3.5.24(typescript@4.9.5)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/metadata@9.13.0': {} + + '@vueuse/shared@9.13.0(vue@3.5.24(typescript@4.9.5))': + dependencies: + vue-demi: 0.14.10(vue@3.5.24(typescript@4.9.5)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + is-url: 1.2.4 + lodash.throttle: 4.1.1 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/code-highlight@1.0.3(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + prismjs: 1.30.0 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@types/event-emitter': 0.3.5 + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + dom7: 3.0.0 + event-emitter: 0.3.5 + html-void-elements: 2.0.1 + i18next: 20.6.1 + is-hotkey: 0.2.0 + lodash.camelcase: 4.3.0 + lodash.clonedeep: 4.5.0 + lodash.debounce: 4.0.8 + lodash.foreach: 4.5.0 + lodash.isequal: 4.5.0 + lodash.throttle: 4.1.1 + lodash.toarray: 4.4.0 + nanoid: 3.3.11 + scroll-into-view-if-needed: 2.2.31 + slate: 0.72.8 + slate-history: 0.66.0(slate@0.72.8) + snabbdom: 3.6.3 + + '@wangeditor/editor-for-vue@5.1.12(@wangeditor/editor@5.1.23)(vue@3.5.24(typescript@4.9.5))': + dependencies: + '@wangeditor/editor': 5.1.23 + vue: 3.5.24(typescript@4.9.5) + + '@wangeditor/editor@5.1.23': + dependencies: + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + '@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/code-highlight': 1.0.3(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/list-module': 1.0.5(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/table-module': 1.1.4(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/upload-image-module': 1.0.2(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.foreach@4.5.0)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/video-module': 1.1.4(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + is-hotkey: 0.2.0 + lodash.camelcase: 4.3.0 + lodash.clonedeep: 4.5.0 + lodash.debounce: 4.0.8 + lodash.foreach: 4.5.0 + lodash.isequal: 4.5.0 + lodash.throttle: 4.1.1 + lodash.toarray: 4.4.0 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/list-module@1.0.5(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/table-module@1.1.4(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + lodash.isequal: 4.5.0 + lodash.throttle: 4.1.1 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/upload-image-module@1.0.2(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.foreach@4.5.0)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + '@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + lodash.foreach: 4.5.0 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/video-module@1.1.4(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.3 + + JSONStream@1.3.5: + dependencies: + jsonparse: 1.3.1 + through: 2.3.8 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@2.1.1: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@2.2.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@4.1.3: {} + + arg@5.0.2: {} + + argparse@2.0.1: {} + + arr-diff@4.0.0: {} + + arr-flatten@1.1.0: {} + + arr-union@3.1.0: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-ify@1.0.0: {} + + array-union@2.1.0: {} + + array-unique@0.3.2: {} + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + arrify@1.0.1: {} + + assign-symbols@1.0.0: {} + + async-function@1.0.0: {} + + async-validator@4.2.5: {} + + asynckit@0.4.0: {} + + atob@2.1.2: {} + + autoprefixer@10.4.21(postcss@8.5.6): + dependencies: + browserslist: 4.27.0 + caniuse-lite: 1.0.30001754 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + axios@1.13.2: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + base@0.11.2: + dependencies: + cache-base: 1.0.1 + class-utils: 0.3.6 + component-emitter: 1.3.1 + define-property: 1.0.0 + isobject: 3.0.1 + mixin-deep: 1.3.2 + pascalcase: 0.1.1 + + baseline-browser-mapping@2.8.25: {} + + better-scroll@2.5.1: + dependencies: + '@better-scroll/core': 2.5.1 + '@better-scroll/indicators': 2.5.1 + '@better-scroll/infinity': 2.5.1 + '@better-scroll/mouse-wheel': 2.5.1 + '@better-scroll/movable': 2.5.1 + '@better-scroll/nested-scroll': 2.5.1 + '@better-scroll/observe-dom': 2.5.1 + '@better-scroll/observe-image': 2.5.1 + '@better-scroll/pull-down': 2.5.1 + '@better-scroll/pull-up': 2.5.1 + '@better-scroll/scroll-bar': 2.5.1 + '@better-scroll/slide': 2.5.1 + '@better-scroll/wheel': 2.5.1 + '@better-scroll/zoom': 2.5.1 + + big.js@5.2.2: {} + + binary-extensions@2.3.0: {} + + bluebird@3.7.2: {} + + boolbase@1.0.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@2.3.2: + dependencies: + arr-flatten: 1.1.0 + array-unique: 0.3.2 + extend-shallow: 2.0.1 + fill-range: 4.0.0 + isobject: 3.0.1 + repeat-element: 1.1.4 + snapdragon: 0.8.2 + snapdragon-node: 2.1.1 + split-string: 3.1.0 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.27.0: + dependencies: + baseline-browser-mapping: 2.8.25 + caniuse-lite: 1.0.30001754 + electron-to-chromium: 1.5.248 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.27.0) + + cache-base@1.0.1: + dependencies: + collection-visit: 1.0.0 + component-emitter: 1.3.1 + get-value: 2.0.6 + has-value: 1.0.0 + isobject: 3.0.1 + set-value: 2.0.1 + to-object-path: 0.3.0 + union-value: 1.0.1 + unset-value: 1.0.0 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + camelcase-keys@6.2.2: + dependencies: + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 + + camelcase@5.3.1: {} + + caniuse-lite@1.0.30001754: {} + + chalk@1.1.3: + dependencies: + ansi-styles: 2.2.1 + escape-string-regexp: 1.0.5 + has-ansi: 2.0.0 + strip-ansi: 3.0.1 + supports-color: 2.0.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + class-utils@0.3.6: + dependencies: + arr-union: 3.1.0 + define-property: 0.2.5 + isobject: 3.0.1 + static-extend: 0.1.2 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@2.1.2: {} + + collection-visit@1.0.0: + dependencies: + map-visit: 1.0.0 + object-visit: 1.0.1 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@4.1.1: {} + + commander@7.2.0: {} + + compare-func@2.0.0: + dependencies: + array-ify: 1.0.0 + dot-prop: 5.3.0 + + component-emitter@1.3.1: {} + + compute-scroll-into-view@1.0.20: {} + + concat-map@0.0.1: {} + + conventional-changelog-angular@5.0.13: + dependencies: + compare-func: 2.0.0 + q: 1.5.1 + + conventional-changelog-conventionalcommits@4.6.3: + dependencies: + compare-func: 2.0.0 + lodash: 4.17.21 + q: 1.5.1 + + conventional-commits-parser@3.2.4: + dependencies: + JSONStream: 1.3.5 + is-text-path: 1.0.1 + lodash: 4.17.21 + meow: 8.1.2 + split2: 3.2.2 + through2: 4.0.2 + + copy-descriptor@0.1.1: {} + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cosmiconfig-typescript-loader@2.0.2(@types/node@16.18.126)(cosmiconfig@7.1.0)(typescript@4.9.5): + dependencies: + '@types/node': 16.18.126 + cosmiconfig: 7.1.0 + ts-node: 10.9.2(@types/node@16.18.126)(typescript@4.9.5) + typescript: 4.9.5 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + + cosmiconfig@7.1.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.1 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + + create-require@1.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-select@4.3.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 4.3.1 + domutils: 2.8.0 + nth-check: 2.1.1 + + css-tree@1.1.3: + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + + css-what@6.2.2: {} + + cssesc@3.0.0: {} + + csso@4.2.0: + dependencies: + css-tree: 1.1.3 + + csstype@3.1.3: {} + + d@1.0.2: + dependencies: + es5-ext: 0.10.64 + type: 2.7.3 + + dargs@7.0.0: {} + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + dayjs@1.11.19: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decamelize-keys@1.1.1: + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + + decamelize@1.2.0: {} + + decode-uri-component@0.2.2: {} + + deep-is@0.1.4: {} + + default-passive-events@2.0.0: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + define-property@0.2.5: + dependencies: + is-descriptor: 0.1.7 + + define-property@1.0.0: + dependencies: + is-descriptor: 1.0.3 + + define-property@2.0.2: + dependencies: + is-descriptor: 1.0.3 + isobject: 3.0.1 + + delayed-stream@1.0.0: {} + + detect-libc@1.0.3: + optional: true + + didyoumean@1.2.2: {} + + diff@4.0.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dlv@1.1.3: {} + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dom-serializer@0.2.2: + dependencies: + domelementtype: 2.3.0 + entities: 2.2.0 + + dom-serializer@1.4.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + + dom7@3.0.0: + dependencies: + ssr-window: 3.0.0 + + domelementtype@1.3.1: {} + + domelementtype@2.3.0: {} + + domhandler@2.4.2: + dependencies: + domelementtype: 1.3.1 + + domhandler@4.3.1: + dependencies: + domelementtype: 2.3.0 + + domutils@1.7.0: + dependencies: + dom-serializer: 0.2.2 + domelementtype: 1.3.1 + + domutils@2.8.0: + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + + dot-prop@5.3.0: + dependencies: + is-obj: 2.0.0 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + echarts@5.6.0: + dependencies: + tslib: 2.3.0 + zrender: 5.6.1 + + electron-to-chromium@1.5.248: {} + + element-plus@2.11.7(vue@3.5.24(typescript@4.9.5)): + dependencies: + '@ctrl/tinycolor': 3.6.1 + '@element-plus/icons-vue': 2.3.2(vue@3.5.24(typescript@4.9.5)) + '@floating-ui/dom': 1.7.4 + '@popperjs/core': '@sxzz/popperjs-es@2.11.7' + '@types/lodash': 4.17.20 + '@types/lodash-es': 4.17.12 + '@vueuse/core': 9.13.0(vue@3.5.24(typescript@4.9.5)) + async-validator: 4.2.5 + dayjs: 1.11.19 + lodash: 4.17.21 + lodash-es: 4.17.21 + lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21) + memoize-one: 6.0.0 + normalize-wheel-es: 1.2.0 + vue: 3.5.24(typescript@4.9.5) + transitivePeerDependencies: + - '@vue/composition-api' + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + emojis-list@3.0.0: {} + + entities@1.1.2: {} + + entities@2.2.0: {} + + entities@4.5.0: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-abstract@1.24.0: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + es5-ext@0.10.64: + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + esniff: 2.0.1 + next-tick: 1.1.0 + + es6-iterator@2.0.3: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-symbol: 3.1.4 + + es6-symbol@3.1.4: + dependencies: + d: 1.0.2 + ext: 1.7.0 + + esbuild@0.18.20: + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + + escalade@3.2.0: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@8.10.2(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + + eslint-plugin-prettier@4.2.5(eslint-config-prettier@8.10.2(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8): + dependencies: + eslint: 8.57.1 + prettier: 2.8.8 + prettier-linter-helpers: 1.0.0 + optionalDependencies: + eslint-config-prettier: 8.10.2(eslint@8.57.1) + + eslint-plugin-vue@8.7.1(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + eslint-utils: 3.0.0(eslint@8.57.1) + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.1.2 + semver: 7.7.3 + vue-eslint-parser: 8.3.0(eslint@8.57.1) + transitivePeerDependencies: + - supports-color + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-utils@3.0.0(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 2.1.0 + + eslint-visitor-keys@2.1.0: {} + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.3.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + esniff@2.0.1: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + event-emitter: 0.3.5 + type: 2.7.3 + + espree@9.6.1: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 3.4.3 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + event-emitter@0.3.5: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + expand-brackets@2.1.4: + dependencies: + debug: 2.6.9 + define-property: 0.2.5 + extend-shallow: 2.0.1 + posix-character-classes: 0.1.1 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + ext@1.7.0: + dependencies: + type: 2.7.3 + + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + + extend-shallow@3.0.2: + dependencies: + assign-symbols: 1.0.0 + is-extendable: 1.0.1 + + extglob@2.0.4: + dependencies: + array-unique: 0.3.2 + define-property: 1.0.0 + expand-brackets: 2.1.4 + extend-shallow: 2.0.1 + fragment-cache: 0.2.1 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + fill-range@4.0.0: + dependencies: + extend-shallow: 2.0.1 + is-number: 3.0.0 + repeat-string: 1.6.1 + to-regex-range: 2.1.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.3.3: {} + + follow-redirects@1.15.11: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + for-in@1.0.2: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fraction.js@4.3.7: {} + + fragment-cache@0.2.1: + dependencies: + map-cache: 0.2.2 + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@6.0.1: {} + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-value@2.0.6: {} + + git-raw-commits@2.0.11: + dependencies: + dargs: 7.0.0 + lodash: 4.17.21 + meow: 8.1.2 + split2: 3.2.2 + through2: 4.0.2 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + global-dirs@0.1.1: + dependencies: + ini: 1.3.8 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + hard-rejection@2.1.0: {} + + has-ansi@2.0.0: + dependencies: + ansi-regex: 2.1.1 + + has-bigints@1.1.0: {} + + has-flag@1.0.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + has-value@0.3.1: + dependencies: + get-value: 2.0.6 + has-values: 0.1.4 + isobject: 2.1.0 + + has-value@1.0.0: + dependencies: + get-value: 2.0.6 + has-values: 1.0.0 + isobject: 3.0.1 + + has-values@0.1.4: {} + + has-values@1.0.0: + dependencies: + is-number: 3.0.0 + kind-of: 4.0.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + hosted-git-info@2.8.9: {} + + hosted-git-info@4.1.0: + dependencies: + lru-cache: 6.0.0 + + html-void-elements@2.0.1: {} + + htmlparser2@3.10.1: + dependencies: + domelementtype: 1.3.1 + domhandler: 2.4.2 + domutils: 1.7.0 + entities: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + + human-signals@2.1.0: {} + + husky@7.0.4: {} + + i18next@20.6.1: + dependencies: + '@babel/runtime': 7.28.4 + + ignore@5.3.2: {} + + image-size@0.5.5: {} + + immer@9.0.21: {} + + immutable@5.1.4: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ini@1.3.8: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + is-accessor-descriptor@1.0.1: + dependencies: + hasown: 2.0.2 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-arrayish@0.2.1: {} + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-buffer@1.1.6: {} + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-descriptor@1.0.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-descriptor@0.1.7: + dependencies: + is-accessor-descriptor: 1.0.1 + is-data-descriptor: 1.0.1 + + is-descriptor@1.0.3: + dependencies: + is-accessor-descriptor: 1.0.1 + is-data-descriptor: 1.0.1 + + is-extendable@0.1.1: {} + + is-extendable@1.0.1: + dependencies: + is-plain-object: 2.0.4 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hotkey@0.2.0: {} + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@3.0.0: + dependencies: + kind-of: 3.2.2 + + is-number@7.0.0: {} + + is-obj@2.0.0: {} + + is-path-inside@3.0.3: {} + + is-plain-obj@1.1.0: {} + + is-plain-object@2.0.4: + dependencies: + isobject: 3.0.1 + + is-plain-object@5.0.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-stream@2.0.1: {} + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-text-path@1.0.1: + dependencies: + text-extensions: 1.9.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-url@1.2.4: {} + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-windows@1.0.2: {} + + isarray@1.0.0: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + isobject@2.1.0: + dependencies: + isarray: 1.0.0 + + isobject@3.0.1: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@1.21.7: {} + + js-base64@2.6.4: {} + + js-base64@3.7.8: {} + + js-cookie@3.0.5: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsencrypt@3.5.4: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsonparse@1.3.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kind-of@3.2.2: + dependencies: + is-buffer: 1.1.6 + + kind-of@4.0.0: + dependencies: + is-buffer: 1.1.6 + + kind-of@5.1.0: {} + + kind-of@6.0.3: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + loader-utils@1.4.2: + dependencies: + big.js: 5.2.2 + emojis-list: 3.0.0 + json5: 1.0.2 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash-es@4.17.21: {} + + lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21): + dependencies: + '@types/lodash-es': 4.17.12 + lodash: 4.17.21 + lodash-es: 4.17.21 + + lodash.camelcase@4.3.0: {} + + lodash.clonedeep@4.5.0: {} + + lodash.debounce@4.0.8: {} + + lodash.foreach@4.5.0: {} + + lodash.isequal@4.5.0: {} + + lodash.merge@4.6.2: {} + + lodash.throttle@4.1.1: {} + + lodash.toarray@4.4.0: {} + + lodash@4.17.21: {} + + lru-cache@10.4.3: {} + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + make-error@1.3.6: {} + + map-cache@0.2.2: {} + + map-obj@1.0.1: {} + + map-obj@4.3.0: {} + + map-visit@1.0.0: + dependencies: + object-visit: 1.0.1 + + math-intrinsics@1.1.0: {} + + mdn-data@2.0.14: {} + + memoize-one@6.0.0: {} + + meow@8.1.2: + dependencies: + '@types/minimist': 1.2.5 + camelcase-keys: 6.2.2 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.18.1 + yargs-parser: 20.2.9 + + merge-options@1.0.1: + dependencies: + is-plain-obj: 1.1.0 + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@3.1.0: + dependencies: + arr-diff: 4.0.0 + array-unique: 0.3.2 + braces: 2.3.2 + define-property: 1.0.0 + extend-shallow: 2.0.1 + extglob: 2.0.4 + fragment-cache: 0.2.1 + kind-of: 5.1.0 + nanomatch: 1.2.13 + object.pick: 1.3.0 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-match@1.0.2: + dependencies: + wildcard: 1.1.2 + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@2.1.0: {} + + min-indent@1.0.1: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist-options@4.1.0: + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + mixin-deep@1.3.2: + dependencies: + for-in: 1.0.2 + is-extendable: 1.0.1 + + ms@2.0.0: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + namespace-emitter@2.0.1: {} + + nanoid@3.3.11: {} + + nanomatch@1.2.13: + dependencies: + arr-diff: 4.0.0 + array-unique: 0.3.2 + define-property: 2.0.2 + extend-shallow: 3.0.2 + fragment-cache: 0.2.1 + is-windows: 1.0.2 + kind-of: 6.0.3 + object.pick: 1.3.0 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + natural-compare-lite@1.4.0: {} + + natural-compare@1.4.0: {} + + next-tick@1.1.0: {} + + node-addon-api@7.1.1: + optional: true + + node-releases@2.0.27: {} + + normalize-package-data@2.5.0: + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.11 + semver: 5.7.2 + validate-npm-package-license: 3.0.4 + + normalize-package-data@3.0.3: + dependencies: + hosted-git-info: 4.1.0 + is-core-module: 2.16.1 + semver: 7.7.3 + validate-npm-package-license: 3.0.4 + + normalize-path@3.0.0: {} + + normalize-range@0.1.2: {} + + normalize-wheel-es@1.2.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + nprogress@0.2.0: {} + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + object-assign@4.1.1: {} + + object-copy@0.1.0: + dependencies: + copy-descriptor: 0.1.1 + define-property: 0.2.5 + kind-of: 3.2.2 + + object-hash@3.0.0: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object-visit@1.0.1: + dependencies: + isobject: 3.0.1 + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.pick@1.3.0: + dependencies: + isobject: 3.0.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.27.1 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + pascalcase@0.1.1: {} + + path-browserify@1.0.1: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-to-regexp@6.3.0: {} + + path-type@4.0.0: {} + + pathe@0.2.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + pify@2.3.0: {} + + pinia@2.3.1(typescript@4.9.5)(vue@3.5.24(typescript@4.9.5)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.24(typescript@4.9.5) + vue-demi: 0.14.10(vue@3.5.24(typescript@4.9.5)) + optionalDependencies: + typescript: 4.9.5 + transitivePeerDependencies: + - '@vue/composition-api' + + pirates@4.0.7: {} + + posix-character-classes@0.1.1: {} + + possible-typed-array-names@1.1.0: {} + + postcss-import@15.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.11 + + postcss-js@4.1.0(postcss@8.5.6): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.6 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.6 + + postcss-nested@6.2.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-prefix-selector@1.16.1(postcss@5.2.18): + dependencies: + postcss: 5.2.18 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@5.2.18: + dependencies: + chalk: 1.1.3 + js-base64: 2.6.4 + source-map: 0.5.7 + supports-color: 3.2.3 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + posthtml-parser@0.2.1: + dependencies: + htmlparser2: 3.10.1 + isobject: 2.1.0 + + posthtml-rename-id@1.0.12: + dependencies: + escape-string-regexp: 1.0.5 + + posthtml-render@1.4.0: {} + + posthtml-svg-mode@1.0.3: + dependencies: + merge-options: 1.0.1 + posthtml: 0.9.2 + posthtml-parser: 0.2.1 + posthtml-render: 1.4.0 + + posthtml@0.9.2: + dependencies: + posthtml-parser: 0.2.1 + posthtml-render: 1.4.0 + + preact@10.27.2: {} + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@2.8.8: {} + + prismjs@1.30.0: {} + + proxy-from-env@1.1.0: {} + + punycode@2.3.1: {} + + q@1.5.1: {} + + query-string@4.3.4: + dependencies: + object-assign: 4.1.1 + strict-uri-encode: 1.1.0 + + queue-microtask@1.2.3: {} + + quick-lru@4.0.1: {} + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + read-pkg-up@7.0.1: + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + + read-pkg@5.2.0: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + readdirp@4.1.2: {} + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regex-not@1.0.2: + dependencies: + extend-shallow: 3.0.2 + safe-regex: 1.1.0 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + repeat-element@1.1.4: {} + + repeat-string@1.6.1: {} + + require-directory@2.1.1: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-global@1.0.0: + dependencies: + global-dirs: 0.1.1 + + resolve-url@0.2.1: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + ret@0.1.15: {} + + reusify@1.1.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup@3.29.5: + optionalDependencies: + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-buffer@5.2.1: {} + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safe-regex@1.1.0: + dependencies: + ret: 0.1.15 + + sass@1.93.3: + dependencies: + chokidar: 4.0.3 + immutable: 5.1.4 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.1 + + screenfull@6.0.2: {} + + scroll-into-view-if-needed@2.2.31: + dependencies: + compute-scroll-into-view: 1.0.20 + + semver@5.7.2: {} + + semver@7.3.7: + dependencies: + lru-cache: 6.0.0 + + semver@7.7.3: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + set-value@2.0.1: + dependencies: + extend-shallow: 2.0.1 + is-extendable: 0.1.1 + is-plain-object: 2.0.4 + split-string: 3.1.0 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + slate-history@0.66.0(slate@0.72.8): + dependencies: + is-plain-object: 5.0.0 + slate: 0.72.8 + + slate@0.72.8: + dependencies: + immer: 9.0.21 + is-plain-object: 5.0.0 + tiny-warning: 1.0.3 + + snabbdom@3.6.3: {} + + snapdragon-node@2.1.1: + dependencies: + define-property: 1.0.0 + isobject: 3.0.1 + snapdragon-util: 3.0.1 + + snapdragon-util@3.0.1: + dependencies: + kind-of: 3.2.2 + + snapdragon@0.8.2: + dependencies: + base: 0.11.2 + debug: 2.6.9 + define-property: 0.2.5 + extend-shallow: 2.0.1 + map-cache: 0.2.2 + source-map: 0.5.7 + source-map-resolve: 0.5.3 + use: 3.1.1 + transitivePeerDependencies: + - supports-color + + sortablejs@1.10.2: {} + + sortablejs@1.15.6: {} + + source-map-js@1.2.1: {} + + source-map-resolve@0.5.3: + dependencies: + atob: 2.1.2 + decode-uri-component: 0.2.2 + resolve-url: 0.2.1 + source-map-url: 0.4.1 + urix: 0.1.0 + + source-map-url@0.4.1: {} + + source-map@0.5.7: {} + + source-map@0.6.1: {} + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.22 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.22 + + spdx-license-ids@3.0.22: {} + + split-string@3.1.0: + dependencies: + extend-shallow: 3.0.2 + + split2@3.2.2: + dependencies: + readable-stream: 3.6.2 + + ssr-window@3.0.0: {} + + stable@0.1.8: {} + + static-extend@0.1.2: + dependencies: + define-property: 0.2.5 + object-copy: 0.1.0 + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + strict-uri-encode@1.1.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@3.0.1: + dependencies: + ansi-regex: 2.1.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-final-newline@2.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + ts-interface-checker: 0.1.13 + + supports-color@2.0.0: {} + + supports-color@3.2.3: + dependencies: + has-flag: 1.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + svg-baker@1.7.0: + dependencies: + bluebird: 3.7.2 + clone: 2.1.2 + he: 1.2.0 + image-size: 0.5.5 + loader-utils: 1.4.2 + merge-options: 1.0.1 + micromatch: 3.1.0 + postcss: 5.2.18 + postcss-prefix-selector: 1.16.1(postcss@5.2.18) + posthtml-rename-id: 1.0.12 + posthtml-svg-mode: 1.0.3 + query-string: 4.3.4 + traverse: 0.6.11 + transitivePeerDependencies: + - supports-color + + svgo@2.8.0: + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 4.3.0 + css-tree: 1.1.3 + csso: 4.2.0 + picocolors: 1.1.1 + stable: 0.1.8 + + tailwindcss@3.4.18: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-import: 15.1.0(postcss@8.5.6) + postcss-js: 4.1.0(postcss@8.5.6) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6) + postcss-nested: 6.2.0(postcss@8.5.6) + postcss-selector-parser: 6.1.2 + resolve: 1.22.11 + sucrase: 3.35.0 + transitivePeerDependencies: + - tsx + - yaml + + text-extensions@1.9.0: {} + + text-table@0.2.0: {} + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + through2@4.0.2: + dependencies: + readable-stream: 3.6.2 + + through@2.3.8: {} + + tiny-warning@1.0.3: {} + + to-object-path@0.3.0: + dependencies: + kind-of: 3.2.2 + + to-regex-range@2.1.1: + dependencies: + is-number: 3.0.0 + repeat-string: 1.6.1 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + to-regex@3.0.2: + dependencies: + define-property: 2.0.2 + extend-shallow: 3.0.2 + regex-not: 1.0.2 + safe-regex: 1.1.0 + + traverse@0.6.11: + dependencies: + gopd: 1.2.0 + typedarray.prototype.slice: 1.0.5 + which-typed-array: 1.1.19 + + trim-newlines@3.0.1: {} + + ts-interface-checker@0.1.13: {} + + ts-node@10.9.2(@types/node@16.18.126)(typescript@4.9.5): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 16.18.126 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.9.5 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tslib@1.14.1: {} + + tslib@2.3.0: {} + + tsutils@3.21.0(typescript@4.9.5): + dependencies: + tslib: 1.14.1 + typescript: 4.9.5 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.18.1: {} + + type-fest@0.20.2: {} + + type-fest@0.6.0: {} + + type-fest@0.8.1: {} + + type@2.7.3: {} + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typedarray.prototype.slice@1.0.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + get-proto: 1.0.1 + math-intrinsics: 1.1.0 + typed-array-buffer: 1.0.3 + typed-array-byte-offset: 1.0.4 + + typescript@4.9.5: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + union-value@1.0.1: + dependencies: + arr-union: 3.1.0 + get-value: 2.0.6 + is-extendable: 0.1.1 + set-value: 2.0.1 + + universalify@2.0.1: {} + + unset-value@1.0.0: + dependencies: + has-value: 0.3.1 + isobject: 3.0.1 + + update-browserslist-db@1.1.4(browserslist@4.27.0): + dependencies: + browserslist: 4.27.0 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + urix@0.1.0: {} + + use@3.1.1: {} + + util-deprecate@1.0.2: {} + + v8-compile-cache-lib@3.0.1: {} + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + vary@1.1.2: {} + + vite-plugin-svg-icons@2.0.1(vite@4.5.14(@types/node@16.18.126)(sass@1.93.3)): + dependencies: + '@types/svgo': 2.6.4 + cors: 2.8.5 + debug: 4.4.3 + etag: 1.8.1 + fs-extra: 10.1.0 + pathe: 0.2.0 + svg-baker: 1.7.0 + svgo: 2.8.0 + vite: 4.5.14(@types/node@16.18.126)(sass@1.93.3) + transitivePeerDependencies: + - supports-color + + vite@4.5.14(@types/node@16.18.126)(sass@1.93.3): + dependencies: + esbuild: 0.18.20 + postcss: 8.5.6 + rollup: 3.29.5 + optionalDependencies: + '@types/node': 16.18.126 + fsevents: 2.3.3 + sass: 1.93.3 + + vue-demi@0.14.10(vue@3.5.24(typescript@4.9.5)): + dependencies: + vue: 3.5.24(typescript@4.9.5) + + vue-eslint-parser@8.3.0(eslint@8.57.1): + dependencies: + debug: 4.4.3 + eslint: 8.57.1 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + lodash: 4.17.21 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + + vue-i18n@9.14.5(vue@3.5.24(typescript@4.9.5)): + dependencies: + '@intlify/core-base': 9.14.5 + '@intlify/shared': 9.14.5 + '@vue/devtools-api': 6.6.4 + vue: 3.5.24(typescript@4.9.5) + + vue-router@4.6.3(vue@3.5.24(typescript@4.9.5)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.24(typescript@4.9.5) + + vue-tsc@0.35.2(typescript@4.9.5): + dependencies: + '@volar/vue-typescript': 0.35.2 + typescript: 4.9.5 + + vue@3.5.24(typescript@4.9.5): + dependencies: + '@vue/compiler-dom': 3.5.24 + '@vue/compiler-sfc': 3.5.24 + '@vue/runtime-dom': 3.5.24 + '@vue/server-renderer': 3.5.24(vue@3.5.24(typescript@4.9.5)) + '@vue/shared': 3.5.24 + optionalDependencies: + typescript: 4.9.5 + + vuedraggable@2.24.3: + dependencies: + sortablejs: 1.10.2 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wildcard@1.1.2: {} + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + wrappy@1.0.2: {} + + y18n@5.0.8: {} + + yallist@4.0.0: {} + + yaml@1.10.2: {} + + yargs-parser@20.2.9: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yn@3.1.1: {} + + yocto-queue@0.1.0: {} + + zrender@5.6.1: + dependencies: + tslib: 2.3.0 diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/frontend/public/favicon.ico differ diff --git a/frontend/readme.md b/frontend/readme.md new file mode 100644 index 0000000..dfaf36f --- /dev/null +++ b/frontend/readme.md @@ -0,0 +1 @@ +这里放置前端项目的readme文件 \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..9fb00e0 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,11 @@ + + + diff --git a/frontend/src/api/auth/index.ts b/frontend/src/api/auth/index.ts new file mode 100644 index 0000000..2a98388 --- /dev/null +++ b/frontend/src/api/auth/index.ts @@ -0,0 +1,36 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { LoginData, TokenResult, VerifyCode } from './types'; + +/** + * + * @param data {LoginForm} + * @returns + */ +export function loginApi(data: LoginData): AxiosPromise { + return request({ + url: '/user/login', + method: 'post', + params: data + }); +} + +/** + * 注销 + */ +export function logoutApi() { + return request({ + url: '/user/logout', + method: 'post' + }); +} + +/** + * 获取图片验证码 + */ +export function getCaptcha(): AxiosPromise { + return request({ + url: '/user/code?t=' + new Date().getTime().toString(), + method: 'get' + }); +} diff --git a/frontend/src/api/auth/types.ts b/frontend/src/api/auth/types.ts new file mode 100644 index 0000000..f6b18ef --- /dev/null +++ b/frontend/src/api/auth/types.ts @@ -0,0 +1,34 @@ +/** + * 登录数据类型 + */ +export interface LoginData { + username: string; + password: string; + code: string; + uuid: string; + /** + * 验证码Code + */ + //verifyCode: string; + /** + * 验证码Code服务端缓存key(UUID) + */ + // verifyCodeKey: string; +} + +/** + * Token响应类型 + */ +export interface TokenResult { + token: string; + refreshToken: string; + expires: number; +} + +/** + * 验证码类型 + */ +export interface VerifyCode { + verifyCodeImg: string; + verifyCodeKey: string; +} diff --git a/frontend/src/api/dept/index.ts b/frontend/src/api/dept/index.ts new file mode 100644 index 0000000..4ab23f1 --- /dev/null +++ b/frontend/src/api/dept/index.ts @@ -0,0 +1,139 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { DeptForm, DeptQuery, Dept } from './types'; + + +//获取组织架构 + +export function getTreelist(queryParams:any) { + return request({ + url: '/system/organization/getOrgTree', + method: 'POST', + params: queryParams + }); +} +//新增企业或部门 + +export function addTreelist(queryParams:any) { + return request({ + url: '/system/organization/addOrg', + method: 'POST', + data: queryParams + }); +} +// 修改企业部门 + +export function updataTreelist(queryParams:any) { + return request({ + url: '/system/organization/updateById', + method: 'POST', + data: queryParams + }); +} +//删除企业 +export function delTreelist(queryParams:any) { + return request({ + url: '/system/organization/deleteById', + method: 'POST', + params: queryParams + }); +} +//获取部门信息 +export function gettableData(queryParams:any) { + return request({ + url: '/system/organization/getOrganizationById', + method: 'POST', + params: queryParams + }); +} +//部门是否有效 +export function deptIsVaild(queryParams:any) { + return request({ + url: '/system/organization/setIsValid', + method: 'POST', + params: queryParams + }); +} + +//修改部门信息 +export function reviseDepartment(queryParams:any) { + return request({ + url: '/system/organization/updateById', + method: 'POST', + data: queryParams + }); +} + +/** + * 部门树形表格 + * + * @param queryParams + */ +export function listDepartments(queryParams?: DeptQuery): AxiosPromise { + return request({ + url: '/api/v1/dept', + method: 'get', + params: queryParams + }); +} + +/** + * 部门下拉列表 + */ +export function listDeptOptions(): AxiosPromise { + return request({ + url: '/api/v1/dept/options', + method: 'get' + }); +} + +/** + * 获取部门详情 + * + * @param id + */ +export function getDeptForm(id: string): AxiosPromise { + return request({ + url: '/api/v1/dept/' + id + '/form', + method: 'get' + }); +} + +/** + * 新增部门 + * + * @param data + */ +export function addDept(data: DeptForm) { + return request({ + url: '/api/v1/dept', + method: 'post', + data: data + }); +} + +/** + * 修改部门 + * + * @param id + * @param data + */ +export function updateDept(id: string, data: DeptForm) { + return request({ + url: '/api/v1/dept/' + id, + method: 'put', + data: data + }); +} + +/** + * 删除部门 + * + * @param ids + */ +export function deleteDept(ids: string) { + return request({ + url: '/api/v1/dept/' + ids, + method: 'delete' + }); +} diff --git a/frontend/src/api/dept/types.ts b/frontend/src/api/dept/types.ts new file mode 100644 index 0000000..b99f819 --- /dev/null +++ b/frontend/src/api/dept/types.ts @@ -0,0 +1,34 @@ +/** + * 部门查询参数 + */ +export interface DeptQuery { + keywords: string | undefined; + status: number | undefined; +} + +/** + * 部门类型 + */ +export interface Dept { + id: string; + name: string; + parentId: string; + treePath: string; + sort: number; + status: number; + leader?: string; + mobile?: string; + email?: string; + children: Dept[]; +} + +/** + * 部门表单类型 + */ +export interface DeptForm { + id?: string; + parentId: string; + name: string; + sort: number; + status: number; +} diff --git a/frontend/src/api/dict/index.ts b/frontend/src/api/dict/index.ts new file mode 100644 index 0000000..7730676 --- /dev/null +++ b/frontend/src/api/dict/index.ts @@ -0,0 +1,242 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { + DictQuery, + DictPageResult, + DictTypeForm, + DictItemQuery, + DictItemPageResult, + DictItemForm +} from './types'; +// 查询字典 +export function getTreelist(params:any) { + return request({ + url: '/system/dictionary/dictList', + method: 'get', + params: params + }); +} + +// 新增字典 +export function addDict(params:any) { + return request({ + url: '/system/dictionary/addDict', + method: 'post', + data: params + }); +} + +// 修改字典 +export function updateDict(params:any) { + return request({ + url: '/system/dictionary/updateDict', + method: 'post', + data: params + }); +} +// 删除字典 +export function deleteById(params:any) { + return request({ + url: '/system/dictionary/deleteById', + method: 'post', + params: params + }); +} +// 字典排序 +export function changeDictOrder(params:any) { + return request({ + url: '/system/dictionary/changeDictOrder', + method: 'post', + params: params + }); +} +// 查询字典项 +export function getDictItemById(params:any) { + return request({ + url: '/system/dictionaryItems/page', + method: 'get', + params: params + }); +} + +// 新增字典项 +export function addDictionaryItem(params:any) { + return request({ + url: '/system/dictionaryItems/addDictionaryItem', + method: 'post', + data: params + }); +} + +// 修改字典项 +export function updateDictionaryItem(params:any) { + return request({ + url: '/system/dictionaryItems/updateDictionaryItem', + method: 'post', + data: params + }); +} +// 删除字典项 +export function deleteDictItemById(params:any) { + return request({ + url: '/system/dictionaryItems/deleteDictItemById', + method: 'post', + params: params + }); +} +// 批量删除字典项 +export function deleteDictItemByIds(params:any) { + return request({ + url: '/system/dictionaryItems/deleteDictItemByIds', + method: 'post', + params: params + }); +} + +// 字典项排序 +export function changeItemOrder(params:any) { + return request({ + url: '/system/dictionaryItems/changeItemOrder', + method: 'post', + params: params + }); +} +/** + * 获取字典类型分页列表 + * + * @param queryParams + */ +export function listDictTypePages( + queryParams: DictQuery +): AxiosPromise { + return request({ + url: '/api/v1/dict/types/pages', + method: 'get', + params: queryParams + }); +} + +/** + * 获取字典类型表单数据 + * + * @param id + */ +export function getDictTypeForm(id: number): AxiosPromise { + return request({ + url: '/api/v1/dict/types/' + id + '/form', + method: 'get' + }); +} + +/** + * 新增字典类型 + * + * @param data + */ +export function addDictType(data: DictTypeForm) { + return request({ + url: '/api/v1/dict/types', + method: 'post', + data: data + }); +} + +/** + * 修改字典类型 + * + * @param id + * @param data + */ +export function updateDictType(id: number, data: DictTypeForm) { + return request({ + url: '/api/v1/dict/types/' + id, + method: 'put', + data: data + }); +} + +/** + * 删除字典类型 + */ +export function deleteDictTypes(ids: string) { + return request({ + url: '/api/v1/dict/types/' + ids, + method: 'delete' + }); +} + +/** + * 获取字典类型的数据项 + * + * @param typeCode 字典类型编码 + */ +export function getDictionaries(typeCode: string): AxiosPromise { + return request({ + url: '/api/v1/dict/types/' + typeCode + '/items', + method: 'get' + }); +} + +/** + * 获取字典项分页列表 + */ +export function listDictItemPages( + queryParams: DictItemQuery +): AxiosPromise { + return request({ + url: '/api/v1/dict/items/pages', + method: 'get', + params: queryParams + }); +} + +/** + * 获取字典数据项表单数据 + * + * @param id + */ +export function getDictItemData(id: number): AxiosPromise { + return request({ + url: '/api/v1/dict/items/' + id + '/form', + method: 'get' + }); +} + +/** + * 新增字典项 + * + * @param data + */ +export function saveDictItem(data: DictItemForm) { + return request({ + url: '/api/v1/dict/items', + method: 'post', + data: data + }); +} + +/** + * 修改字典项 + * + * @param id + * @param data + */ +export function updateDictItem(id: number, data: DictItemForm) { + return request({ + url: '/api/v1/dict/items/' + id, + method: 'put', + data: data + }); +} + +/** + * 批量删除字典数据项 + * + * @param ids 字典项ID,多个以英文逗号(,)分割 + */ +export function deleteDictItems(ids: string) { + return request({ + url: '/api/v1/dict/items/' + ids, + method: 'delete' + }); +} diff --git a/frontend/src/api/dict/types.ts b/frontend/src/api/dict/types.ts new file mode 100644 index 0000000..3e301f2 --- /dev/null +++ b/frontend/src/api/dict/types.ts @@ -0,0 +1,84 @@ +/** + * 字典查询参数 + */ +export interface DictQuery extends PageQuery { + /** + * 字典名称 + */ + name?: string; +} + +/** + * 字典类型 + */ +export interface Dict { + id: number; + code: string; + name: string; + status: number; + remark: string; +} + +/** + * 字典分页项类型声明 + */ +export type DictPageResult = PageResult; + +/** + * 字典表单类型声明 + */ +export interface DictTypeForm { + id: number | undefined; + name: string; + code: string; + status: number; + remark: string; +} + +/** + * 字典项查询参数类型声明 + */ +export interface DictItemQuery extends PageQuery { + /** + * 字典项名称 + */ + name?: string; + /** + * 字典类型编码 + */ + typeCode?: string; +} + +/** + * 字典数据项类型 + */ +export interface DictItem { + id: number; + name: string; + value: string; + typeCode: string; + sort: number; + status: number; + defaulted: number; + remark?: string; +} + +/** + * 字典分页项类型声明 + */ +export type DictItemPageResult = PageResult; + +/** + * 字典表单类型声明 + */ +export interface DictItemForm { + id?: number; + typeCode?: string; + typeName?: string; + name: string; + code: string; + value: string; + status: number; + sort: number; + remark: string; +} diff --git a/frontend/src/api/file/index.ts b/frontend/src/api/file/index.ts new file mode 100644 index 0000000..350ce7d --- /dev/null +++ b/frontend/src/api/file/index.ts @@ -0,0 +1,34 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { FileInfo } from './types'; + +/** + * 上传文件 + * + * @param file + */ +export function uploadFileApi(file: File): AxiosPromise { + const formData = new FormData(); + formData.append('file', file); + return request({ + url: '/api/v1/files', + method: 'post', + data: formData, + headers: { + 'Content-Type': 'multipart/form-data' + } + }); +} + +/** + * 删除文件 + * + * @param filePath 文件完整路径 + */ +export function deleteFileApi(filePath?: string) { + return request({ + url: '/api/v1/files', + method: 'delete', + params: { filePath: filePath } + }); +} diff --git a/frontend/src/api/file/types.ts b/frontend/src/api/file/types.ts new file mode 100644 index 0000000..22b2be5 --- /dev/null +++ b/frontend/src/api/file/types.ts @@ -0,0 +1,7 @@ +/** + * 文件API类型声明 + */ +export interface FileInfo { + name: string; + url: string; +} diff --git a/frontend/src/api/menu/index.ts b/frontend/src/api/menu/index.ts new file mode 100644 index 0000000..fae4404 --- /dev/null +++ b/frontend/src/api/menu/index.ts @@ -0,0 +1,159 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { MenuQuery, Menu, Resource, MenuForm } from './types'; + +//获取菜单表格 +export function getdata(queryParams:any) { + return request({ + url: '/system/menu/getMenuButtonTree', + method: 'post', + params: queryParams + }); +} +//新增目录 +export function addmenu(queryParams:any) { + return request({ + url: '/system/menu/addMenu', + method: 'post', + data: queryParams + }); +} +//修改目录 +export function editmenu(queryParams:any) { + return request({ + url: '/system/menu/updateById', + method: 'post', + data: queryParams + }); +} +//删除 +export function deltmenu(queryParams:any) { + return request({ + url: '/system/menu/deleteById', + method: 'post', + params: queryParams + }); +} +//排序 +export function moveOrderno(params:any) { + return request({ + url: '/system/menu/changeMenuOrder', + method: 'post', + params: params + }); +} +//上传图标之前获取ID + +//上传单个图标 +export function uploadIcon (data:any) { + return request({ + headers: { + 'Content-Type': 'multipart/form-data' + }, + url: '/system/menu/uploadIcon', + method: 'POST', + data + }); +} +//删除单个图标 +export function moveIcon(params:any) { + return request({ + url: '/system/menu/deleteIcon', + method: 'post', + params: params + }); +} + + +/** + * 获取路由列表 + */ +export function listRoutes() { + return request({ + url: 'system/menu/treeRoutes', + method: 'get', + }); +} + +/** + * 获取菜单表格列表 + * + * @param queryParams + */ +export function listMenus(queryParams: MenuQuery): AxiosPromise { + return request({ + url: '/api/v1/menus', + method: 'get', + params: queryParams + }); +} + +/** + * 获取菜单下拉树形列表 + */ +export function listMenuOptions(): AxiosPromise { + return request({ + url: '/api/v1/menus/options', + method: 'get' + }); +} + +/** + * 获取资源(菜单+权限)树形列表 + */ +export function listResources(): AxiosPromise { + return request({ + url: '/api/v1/menus/resources', + method: 'get' + }); +} + +/** + * 获取菜单详情 + * @param id + */ +export function getMenuDetail(id: string): AxiosPromise { + return request({ + url: '/api/v1/menus/' + id, + method: 'get' + }); +} + +/** + * 添加菜单 + * + * @param data + */ +export function addMenu(data: MenuForm) { + return request({ + url: '/api/v1/menus', + method: 'post', + data: data + }); +} + +/** + * 修改菜单 + * + * @param id + * @param data + */ +export function updateMenu(id: string, data: MenuForm) { + return request({ + url: '/api/v1/menus/' + id, + method: 'put', + data: data + }); +} + +/** + * 批量删除菜单 + * + * @param ids 菜单ID,多个以英文逗号(,)分割 + */ +export function deleteMenus(ids: string) { + return request({ + url: '/api/v1/menus/' + ids, + method: 'delete' + }); +} diff --git a/frontend/src/api/menu/types.ts b/frontend/src/api/menu/types.ts new file mode 100644 index 0000000..8de7056 --- /dev/null +++ b/frontend/src/api/menu/types.ts @@ -0,0 +1,105 @@ +/** + * 菜单查询参数类型声明 + */ +export interface MenuQuery { + keywords?: string; +} + +/** + * 菜单分页列表项声明 + */ + +export interface Menu { + id?: number; + parentId: number; + type?: string | 'CATEGORY' | 'MENU' | 'EXTLINK'; + createTime: string; + updateTime: string; + name: string; + icon: string; + component: string; + sort: number; + visible: number; + children: Menu[]; +} + +/** + * 菜单表单类型声明 + */ +export interface MenuForm { + /** + * 菜单ID + */ + id?: string; + /** + * 父菜单ID + */ + parentId: string; + /** + * 菜单名称 + */ + name: string; + /** + * 菜单是否可见(1:是;0:否;) + */ + visible: number; + icon?: string; + /** + * 排序 + */ + sort: number; + /** + * 组件路径 + */ + component?: string; + /** + * 路由路径 + */ + path: string; + /** + * 跳转路由路径 + */ + redirect?: string; + + /** + * 菜单类型 + */ + type: string; + + /** + * 权限标识 + */ + perm?: string; +} + +/** + * 资源(菜单+权限)类型 + */ +export interface Resource { + /** + * 菜单值 + */ + value: string; + /** + * 菜单文本 + */ + label: string; + /** + * 子菜单 + */ + children: Resource[]; +} + +/** + * 权限类型 + */ +export interface Permission { + /** + * 权限值 + */ + value: string; + /** + * 权限文本 + */ + label: string; +} diff --git a/frontend/src/api/message/index.ts b/frontend/src/api/message/index.ts new file mode 100644 index 0000000..0280648 --- /dev/null +++ b/frontend/src/api/message/index.ts @@ -0,0 +1,28 @@ +import request from '@/utils/request'; + +export function getMessageList(params:any) { + return request({ + url: '/system/message/getMessageList', + method: 'get', + params + }); +} +export function setMessageStatus(data:any) { + return request({ + url: '/system/message/setMessageStatus?id=' + data, + method: 'post' + }); +} +export function setAllMessageStatus() { + return request({ + url: '/system/message/setAllMessageStatus', + method: 'post' + }); +} +export function deleteMessageById(data:any) { + return request({ + url: '/system/message/deleteMessageById?id=' + data, + method: 'post', + data + }); +} \ No newline at end of file diff --git a/frontend/src/api/record/index.ts b/frontend/src/api/record/index.ts new file mode 100644 index 0000000..9f263b1 --- /dev/null +++ b/frontend/src/api/record/index.ts @@ -0,0 +1,18 @@ +import request from '@/utils/request'; + +//获取所有角色 +export function getLogList(params:any){ + return request({ + url: '/system/log/getLogList' , + method: 'post', + params: params + }); +} +export function exportExcel(queryParams: any) { + return request({ + url: '/system/log/exportExcel', + method: 'get', + params: queryParams, + responseType: 'arraybuffer' + }); +} diff --git a/frontend/src/api/role/index.ts b/frontend/src/api/role/index.ts new file mode 100644 index 0000000..e6420bf --- /dev/null +++ b/frontend/src/api/role/index.ts @@ -0,0 +1,196 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { RoleQuery, RoleForm } from './types'; + +//获取所有角色 +export function listRolePages(queryParams:any){ + return request({ + url: '/system/role/list' , + method: 'post', + params:queryParams + }); +} +//角色是否有效 +export function isvaildTo(queryParams:any){ + return request({ + url: '/system/role/setIsvaild' , + method: 'post', + params:queryParams + }); +} +//新增角色 +export function addDept(queryParams:any){ + return request({ + url:'/system/role/addRole' , + method: 'post', + data: queryParams + }); +} +//更新角色信息 +export function renewDept (queryParams:any){ + return request({ + url:'/system/role/updateById' , + method: 'post', + data: queryParams + }); +} +//单个删除角色 +export function deleDept (queryParams:any){ + return request({ + url:'/system/role/deleteById' , + method: 'post', + params: queryParams + }); +} + +//获取分配权限 +export function assignmentPer (queryParams:any){ + return request({ + url:'/system/menu/permissionAssignment' , + method: 'post', + params: queryParams + }); +} +//发出分配权限 +export function setMenuById (queryParams:any){ + return request({ + url:'/system/role/setMenuById' , + method: 'post', + params: queryParams + }); +} +////获取组织范围 +export function setOrgscope (queryParams:any){ + return request({ + url:'/system/organization/getOrgScopeTree' , + method: 'post', + params: queryParams + }); +} +//修改组织范围 +export function postOrgscope (queryParams:any){ + return request({ + url:'/system/role/setOrgscope' , + method: 'post', + params: queryParams + }); +} + + +//删除角色 +// export function delDept(queryParams:any){ +// return request({ +// url:'/system/role/addRole' , +// method: 'post', +// data: queryParams +// }); +// } + +/** + * 获取角色分页数据 + * + * @param queryParam + */ +// export function listRolePages( +// queryParams?: RoleQuery +// ): AxiosPromise { +// return request({ +// url: '/system/role/list', +// method: 'post', +// params: queryParams +// }); +// } + +/** + * 获取角色下拉数据 + * + * @param queryParams + */ +export function listRoleOptions( + queryParams?: RoleQuery +): AxiosPromise { + return request({ + url: '/api/v1/roles/options', + method: 'get', + params: queryParams + }); +} + +/** + * 获取角色拥有的资源ID集合 + * + * @param queryParams + */ +export function getRoleMenuIds(roleId: string): AxiosPromise { + return request({ + url: '/api/v1/roles/' + roleId + '/menuIds', + method: 'get' + }); +} + +/** + * 修改角色资源权限 + * + * @param queryParams + */ +export function updateRoleMenus( + roleId: string, + data: number[] +): AxiosPromise { + return request({ + url: '/api/v1/roles/' + roleId + '/menus', + method: 'put', + data: data + }); +} + +/** + * 获取角色详情 + * + * @param id + */ +export function getRoleDetail(id: number): AxiosPromise { + return request({ + url: '/api/v1/roles/' + id, + method: 'get' + }); +} + +/** + * 添加角色 + * + * @param data + */ +export function addRole(data: RoleForm) { + return request({ + url: '/api/v1/roles', + method: 'post', + data: data + }); +} + +/** + * 更新角色 + * + * @param id + * @param data + */ +export function updateRole(id: number, data: RoleForm) { + return request({ + url: '/api/v1/roles/' + id, + method: 'put', + data: data + }); +} + +/** + * 批量删除角色,多个以英文逗号(,)分割 + * + * @param ids + */ +export function deleteRoles(ids: string) { + return request({ + url: '/api/v1/roles/' + ids, + method: 'delete' + }); +} diff --git a/frontend/src/api/role/types.ts b/frontend/src/api/role/types.ts new file mode 100644 index 0000000..4684c3b --- /dev/null +++ b/frontend/src/api/role/types.ts @@ -0,0 +1,40 @@ +/** + * 角色查询参数类型 + */ +export interface RoleQuery extends PageQuery { + keywords?: string; +} + +/** + * 角色分页列表项 + */ +export interface Role { + id: string; + name: string; + code: string; + sort: number; + status: number; + deleted: number; + menuIds?: any; + permissionIds?: any; +} + +/** + * 角色分页项类型 + */ +export type RolePageResult = PageResult; + +/** + * 角色表单 + */ +export interface RoleForm { + id?: number; + name: string; + code: string; + sort: number; + status: number; + /** + * 数据权限 + */ + dataScope: number; +} diff --git a/frontend/src/api/taxkSetting/index.ts b/frontend/src/api/taxkSetting/index.ts new file mode 100644 index 0000000..28ec26d --- /dev/null +++ b/frontend/src/api/taxkSetting/index.ts @@ -0,0 +1,51 @@ +import request from '@/utils/request'; + +//获取表格内容 +export function getTaskList(params: any) { + return request({ + url: '/system/quartzjob/getQuartzJobList', + method: 'get', + params: params + }); +} +//新增表格内容 +export function addTaskList(params: any) { + return request({ + url: '/system/quartzjob/addQuartzJob', + method: 'post', + data: params + }); +} + +//删除定时任务 +export function delTaskList(params: any) { + return request({ + url: '/system/quartzjob/deleteQuartzJob', + method: 'post', + params: params + }); +} +//修改定时任务 +export function updataTaskList(params: any) { + return request({ + url: '/system/quartzjob/updateQuartzJob', + method: 'post', + data: params + }); +} +//定时任务是否有效 +export function setTaskList(params: any) { + return request({ + url: '/system/quartzjob/setQuartzStatus', + method: 'post', + params: params + }); +} +//拖拽 +export function changeItemOrder(params: any) { + return request({ + url: '/system/quartzjob/changeDictOrder', + method: 'post', + params: params + }); +} \ No newline at end of file diff --git a/frontend/src/api/user/index.ts b/frontend/src/api/user/index.ts new file mode 100644 index 0000000..367454c --- /dev/null +++ b/frontend/src/api/user/index.ts @@ -0,0 +1,238 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { UserForm, UserInfo, UserPageResult, UserQuery } from './types'; +//获取企业树 数据 +export function getTreelist(queryParams:any) { + return request({ + url: '/system/organization/getOrgTree', + method: 'POST', + params: queryParams + }); +} +//获取用户列表信息 +export function gettableData(queryParams:any) { + return request({ + url: '/system/user/queryUsers', + method: 'get', + params: queryParams + }); +} +//用户-禁用,启用 +export function DataStatus (queryParams:any) { + return request({ + url: '/system/user/setStatus', + method: 'POST', + params: queryParams + }); +} +//删除用户 +export function deltableData (queryParams:any) { + return request({ + url: '/system/user/deleteById', + method: 'POST', + params: queryParams + }); +} +//批量删除 +export function delChoise (queryParams:any) { + return request({ + url: '/system/user/deleteUserByIds', + method: 'POST', + params: queryParams + }); +} +//获取角色 +export function getRole (queryParams:any) { + return request({ + url: '/system/role/list', + method: 'POST', + params: queryParams + }); +} +//新建用户 +export function addUsers (queryParams:any,roleids:any) { + return request({ + url: '/system/user/addUser?'+'roleids='+roleids, + method: 'POST', + data: queryParams, + }); +} +//更改用户 +export function updataUser (queryParams:any,roleids:any) { + return request({ + url: '/system/user/updateUser?'+'roleids='+roleids, + method: 'POST', + data: queryParams, + }); +} +//更改用户 +export function updatePersonalInfo (queryParams:any) { + return request({ + url: '/user/updatePersonalInfo', + method: 'POST', + data: queryParams, + }); +} +//更改头像 +export function updateAvatar (data:any) { + return request({ + headers: { + 'Content-Type': 'multipart/form-data' + }, + url: '/system/user/updateAvatar', + method: 'POST', + data + }); +} + + +//重置密码 +export function setpass (queryParams:any) { + return request({ + url: '/system/user/resetPassword', + method: 'POST', + params: queryParams, + }); +} + +export function updatePassword (queryParams:any) { + return request({ + url: '/user/updatePassword', + method: 'GET', + params: queryParams, + }); +} + +/** + * 登录成功后获取用户信息(昵称、头像、权限集合和角色集合) + */ +export function getUserInfo(): AxiosPromise { + return request({ + url: '/user/me', + method: 'get' + }); +} + +/** + * 获取用户分页列表 + * + * @param queryParams + */ +export function listUserPages( + queryParams: UserQuery +): AxiosPromise { + return request({ + url: '/api/v1/users/pages', + method: 'get', + params: queryParams + }); +} + +/** + * 获取用户表单详情 + * + * @param userId + */ +export function getUserForm(userId: number): AxiosPromise { + return request({ + url: '/api/v1/users/' + userId + '/form', + method: 'get' + }); +} + +/** + * 添加用户 + * + * @param data + */ +export function addUser(data: any) { + return request({ + url: '/api/v1/users', + method: 'post', + data: data + }); +} + +/** + * 修改用户 + * + * @param id + * @param data + */ +export function updateUser(id: number, data: UserForm) { + return request({ + url: '/api/v1/users/' + id, + method: 'put', + data: data + }); +} + +/** + * 修改用户状态 + * + * @param id + * @param status + */ +export function updateUserStatus(id: number, status: number) { + return request({ + url: '/api/v1/users/' + id + '/status', + method: 'patch', + params: { status: status } + }); +} + +/** + * 修改用户密码 + * + * @param id + * @param password + */ +export function updateUserPassword(id: number, password: string) { + return request({ + url: '/api/v1/users/' + id + '/password', + method: 'patch', + params: { password: password } + }); +} + +/** + * 删除用户 + * + * @param ids + */ +export function deleteUsers(ids: string) { + return request({ + url: '/api/v1/users/' + ids, + method: 'delete' + }); +} + +/** + * 下载用户导入模板 + * + * @returns + */ +export function downloadTemplate() { + return request({ + url: '/api/v1/users/template', + method: 'get', + responseType: 'arraybuffer' + }); +} + +/** + * 导出用户 + * + * @param queryParams + * @returns + */ +export function exportUser(queryParams: UserQuery) { + return request({ + url: '/api/v1/users/_export', + method: 'get', + params: queryParams, + responseType: 'arraybuffer' + }); +} + + diff --git a/frontend/src/api/user/types.ts b/frontend/src/api/user/types.ts new file mode 100644 index 0000000..07b734b --- /dev/null +++ b/frontend/src/api/user/types.ts @@ -0,0 +1,67 @@ +/** + * 登录用户信息 + */ +export interface UserInfo { + permissions: string[]; + userInfo: any; + nickname: string; + avatar: string; + roles: string[]; + perms: string[]; +} + +/** + * 用户查询参数 + */ +export interface UserQuery extends PageQuery { + keywords: string; + status: number; + deptId: number; +} + +/** + * 用户分页列表项声明 + */ +export interface UserType { + id: string; + username: string; + nickname: string; + mobile: string; + gender: number; + avatar: string; + email: string; + status: number; + deptName: string; + roleNames: string; + createTime: string; +} + +/** + * 用户分页项类型声明 + */ +export type UserPageResult = PageResult; + +/** + * 用户表单类型声明 + */ +export interface UserForm { + id: number | undefined; + deptId: number; + username: string; + nickname: string; + password: string; + mobile: string; + email: string; + gender: number; + status: number; + remark: string; + roleIds: number[]; +} + +/** + * 用户导入表单类型声明 + */ +export interface UserImportData { + deptId: number; + roleIds: number[]; +} diff --git a/frontend/src/assets/401_images/401.gif b/frontend/src/assets/401_images/401.gif new file mode 100644 index 0000000..cd6e0d9 Binary files /dev/null and b/frontend/src/assets/401_images/401.gif differ diff --git a/frontend/src/assets/404_images/404.png b/frontend/src/assets/404_images/404.png new file mode 100644 index 0000000..3d8e230 Binary files /dev/null and b/frontend/src/assets/404_images/404.png differ diff --git a/frontend/src/assets/404_images/404_cloud.png b/frontend/src/assets/404_images/404_cloud.png new file mode 100644 index 0000000..c6281d0 Binary files /dev/null and b/frontend/src/assets/404_images/404_cloud.png differ diff --git a/frontend/src/assets/MenuIcon/bq_gb.png b/frontend/src/assets/MenuIcon/bq_gb.png new file mode 100644 index 0000000..092115b Binary files /dev/null and b/frontend/src/assets/MenuIcon/bq_gb.png differ diff --git a/frontend/src/assets/MenuIcon/bq_gb1.png b/frontend/src/assets/MenuIcon/bq_gb1.png new file mode 100644 index 0000000..fd22718 Binary files /dev/null and b/frontend/src/assets/MenuIcon/bq_gb1.png differ diff --git a/frontend/src/assets/MenuIcon/czan_xz.png b/frontend/src/assets/MenuIcon/czan_xz.png new file mode 100644 index 0000000..fe03405 Binary files /dev/null and b/frontend/src/assets/MenuIcon/czan_xz.png differ diff --git a/frontend/src/assets/MenuIcon/dh_jt.png b/frontend/src/assets/MenuIcon/dh_jt.png new file mode 100644 index 0000000..e1ace57 Binary files /dev/null and b/frontend/src/assets/MenuIcon/dh_jt.png differ diff --git a/frontend/src/assets/MenuIcon/dh_jt1.png b/frontend/src/assets/MenuIcon/dh_jt1.png new file mode 100644 index 0000000..3055566 Binary files /dev/null and b/frontend/src/assets/MenuIcon/dh_jt1.png differ diff --git a/frontend/src/assets/MenuIcon/dh_qy.png b/frontend/src/assets/MenuIcon/dh_qy.png new file mode 100644 index 0000000..e7e0563 Binary files /dev/null and b/frontend/src/assets/MenuIcon/dh_qy.png differ diff --git a/frontend/src/assets/MenuIcon/dh_qy1.png b/frontend/src/assets/MenuIcon/dh_qy1.png new file mode 100644 index 0000000..85742ed Binary files /dev/null and b/frontend/src/assets/MenuIcon/dh_qy1.png differ diff --git a/frontend/src/assets/MenuIcon/dh_qz.png b/frontend/src/assets/MenuIcon/dh_qz.png new file mode 100644 index 0000000..54ce7e4 Binary files /dev/null and b/frontend/src/assets/MenuIcon/dh_qz.png differ diff --git a/frontend/src/assets/MenuIcon/dh_qz1.png b/frontend/src/assets/MenuIcon/dh_qz1.png new file mode 100644 index 0000000..36632cd Binary files /dev/null and b/frontend/src/assets/MenuIcon/dh_qz1.png differ diff --git a/frontend/src/assets/MenuIcon/dh_sq.png b/frontend/src/assets/MenuIcon/dh_sq.png new file mode 100644 index 0000000..adf9805 Binary files /dev/null and b/frontend/src/assets/MenuIcon/dh_sq.png differ diff --git a/frontend/src/assets/MenuIcon/dh_sq1.png b/frontend/src/assets/MenuIcon/dh_sq1.png new file mode 100644 index 0000000..57e1514 Binary files /dev/null and b/frontend/src/assets/MenuIcon/dh_sq1.png differ diff --git a/frontend/src/assets/MenuIcon/dh_sy.png b/frontend/src/assets/MenuIcon/dh_sy.png new file mode 100644 index 0000000..248c590 Binary files /dev/null and b/frontend/src/assets/MenuIcon/dh_sy.png differ diff --git a/frontend/src/assets/MenuIcon/dh_sy1.png b/frontend/src/assets/MenuIcon/dh_sy1.png new file mode 100644 index 0000000..d092df7 Binary files /dev/null and b/frontend/src/assets/MenuIcon/dh_sy1.png differ diff --git a/frontend/src/assets/MenuIcon/dh_sz.png b/frontend/src/assets/MenuIcon/dh_sz.png new file mode 100644 index 0000000..5fc1f6f Binary files /dev/null and b/frontend/src/assets/MenuIcon/dh_sz.png differ diff --git a/frontend/src/assets/MenuIcon/dh_sz1.png b/frontend/src/assets/MenuIcon/dh_sz1.png new file mode 100644 index 0000000..1a13ddd Binary files /dev/null and b/frontend/src/assets/MenuIcon/dh_sz1.png differ diff --git a/frontend/src/assets/MenuIcon/dh_xm.png b/frontend/src/assets/MenuIcon/dh_xm.png new file mode 100644 index 0000000..a750b02 Binary files /dev/null and b/frontend/src/assets/MenuIcon/dh_xm.png differ diff --git a/frontend/src/assets/MenuIcon/dh_xm1.png b/frontend/src/assets/MenuIcon/dh_xm1.png new file mode 100644 index 0000000..aead610 Binary files /dev/null and b/frontend/src/assets/MenuIcon/dh_xm1.png differ diff --git a/frontend/src/assets/MenuIcon/dh_xx.png b/frontend/src/assets/MenuIcon/dh_xx.png new file mode 100644 index 0000000..1fb6180 Binary files /dev/null and b/frontend/src/assets/MenuIcon/dh_xx.png differ diff --git a/frontend/src/assets/MenuIcon/dh_xx1.png b/frontend/src/assets/MenuIcon/dh_xx1.png new file mode 100644 index 0000000..535abe1 Binary files /dev/null and b/frontend/src/assets/MenuIcon/dh_xx1.png differ diff --git a/frontend/src/assets/MenuIcon/gb.png b/frontend/src/assets/MenuIcon/gb.png new file mode 100644 index 0000000..b276de2 Binary files /dev/null and b/frontend/src/assets/MenuIcon/gb.png differ diff --git a/frontend/src/assets/MenuIcon/grzx_tx.png b/frontend/src/assets/MenuIcon/grzx_tx.png new file mode 100644 index 0000000..0f21833 Binary files /dev/null and b/frontend/src/assets/MenuIcon/grzx_tx.png differ diff --git a/frontend/src/assets/MenuIcon/grzx_xg.png b/frontend/src/assets/MenuIcon/grzx_xg.png new file mode 100644 index 0000000..d6c8977 Binary files /dev/null and b/frontend/src/assets/MenuIcon/grzx_xg.png differ diff --git a/frontend/src/assets/MenuIcon/jscz_rl.png b/frontend/src/assets/MenuIcon/jscz_rl.png new file mode 100644 index 0000000..22a323d Binary files /dev/null and b/frontend/src/assets/MenuIcon/jscz_rl.png differ diff --git a/frontend/src/assets/MenuIcon/jscz_sc.png b/frontend/src/assets/MenuIcon/jscz_sc.png new file mode 100644 index 0000000..674cf09 Binary files /dev/null and b/frontend/src/assets/MenuIcon/jscz_sc.png differ diff --git a/frontend/src/assets/MenuIcon/jscz_sc1.png b/frontend/src/assets/MenuIcon/jscz_sc1.png new file mode 100644 index 0000000..cf2e0ca Binary files /dev/null and b/frontend/src/assets/MenuIcon/jscz_sc1.png differ diff --git a/frontend/src/assets/MenuIcon/jscz_scdc.png b/frontend/src/assets/MenuIcon/jscz_scdc.png new file mode 100644 index 0000000..f47a236 Binary files /dev/null and b/frontend/src/assets/MenuIcon/jscz_scdc.png differ diff --git a/frontend/src/assets/MenuIcon/jscz_xz.png b/frontend/src/assets/MenuIcon/jscz_xz.png new file mode 100644 index 0000000..8b2a892 Binary files /dev/null and b/frontend/src/assets/MenuIcon/jscz_xz.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_an.png b/frontend/src/assets/MenuIcon/lbcz_an.png new file mode 100644 index 0000000..3177faf Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_an.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_an1.png b/frontend/src/assets/MenuIcon/lbcz_an1.png new file mode 100644 index 0000000..81e544c Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_an1.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_cd.png b/frontend/src/assets/MenuIcon/lbcz_cd.png new file mode 100644 index 0000000..25b8ecc Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_cd.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_cd1.png b/frontend/src/assets/MenuIcon/lbcz_cd1.png new file mode 100644 index 0000000..19aaa85 Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_cd1.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_czmm.png b/frontend/src/assets/MenuIcon/lbcz_czmm.png new file mode 100644 index 0000000..8f5b131 Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_czmm.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_jt.png b/frontend/src/assets/MenuIcon/lbcz_jt.png new file mode 100644 index 0000000..68620fb Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_jt.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_jt1.png b/frontend/src/assets/MenuIcon/lbcz_jt1.png new file mode 100644 index 0000000..b36fe61 Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_jt1.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_qx.png b/frontend/src/assets/MenuIcon/lbcz_qx.png new file mode 100644 index 0000000..dd4d05e Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_qx.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_sc.png b/frontend/src/assets/MenuIcon/lbcz_sc.png new file mode 100644 index 0000000..2ca9d72 Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_sc.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_sc1.png b/frontend/src/assets/MenuIcon/lbcz_sc1.png new file mode 100644 index 0000000..c831998 Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_sc1.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_sc2.png b/frontend/src/assets/MenuIcon/lbcz_sc2.png new file mode 100644 index 0000000..95ebdb6 Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_sc2.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_td.png b/frontend/src/assets/MenuIcon/lbcz_td.png new file mode 100644 index 0000000..54ebc81 Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_td.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_td1.png b/frontend/src/assets/MenuIcon/lbcz_td1.png new file mode 100644 index 0000000..3e5b329 Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_td1.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_td2.png b/frontend/src/assets/MenuIcon/lbcz_td2.png new file mode 100644 index 0000000..6d0a88c Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_td2.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_xg.png b/frontend/src/assets/MenuIcon/lbcz_xg.png new file mode 100644 index 0000000..0533913 Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_xg.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_xg1.png b/frontend/src/assets/MenuIcon/lbcz_xg1.png new file mode 100644 index 0000000..0548fbc Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_xg1.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_xg2.png b/frontend/src/assets/MenuIcon/lbcz_xg2.png new file mode 100644 index 0000000..ed5a146 Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_xg2.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_zml.png b/frontend/src/assets/MenuIcon/lbcz_zml.png new file mode 100644 index 0000000..b352868 Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_zml.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_zml1.png b/frontend/src/assets/MenuIcon/lbcz_zml1.png new file mode 100644 index 0000000..618b429 Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_zml1.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_zyw.png b/frontend/src/assets/MenuIcon/lbcz_zyw.png new file mode 100644 index 0000000..76e14d1 Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_zyw.png differ diff --git a/frontend/src/assets/MenuIcon/lbcz_zz.png b/frontend/src/assets/MenuIcon/lbcz_zz.png new file mode 100644 index 0000000..36eb52a Binary files /dev/null and b/frontend/src/assets/MenuIcon/lbcz_zz.png differ diff --git a/frontend/src/assets/MenuIcon/top_qp.png b/frontend/src/assets/MenuIcon/top_qp.png new file mode 100644 index 0000000..14ce318 Binary files /dev/null and b/frontend/src/assets/MenuIcon/top_qp.png differ diff --git a/frontend/src/assets/MenuIcon/top_qp1.png b/frontend/src/assets/MenuIcon/top_qp1.png new file mode 100644 index 0000000..dda0cc9 Binary files /dev/null and b/frontend/src/assets/MenuIcon/top_qp1.png differ diff --git a/frontend/src/assets/MenuIcon/top_ss.png b/frontend/src/assets/MenuIcon/top_ss.png new file mode 100644 index 0000000..f053c8b Binary files /dev/null and b/frontend/src/assets/MenuIcon/top_ss.png differ diff --git a/frontend/src/assets/MenuIcon/top_tx.png b/frontend/src/assets/MenuIcon/top_tx.png new file mode 100644 index 0000000..dc301d3 Binary files /dev/null and b/frontend/src/assets/MenuIcon/top_tx.png differ diff --git a/frontend/src/assets/MenuIcon/top_zh.png b/frontend/src/assets/MenuIcon/top_zh.png new file mode 100644 index 0000000..79f945d Binary files /dev/null and b/frontend/src/assets/MenuIcon/top_zh.png differ diff --git a/frontend/src/assets/MenuIcon/ts.png b/frontend/src/assets/MenuIcon/ts.png new file mode 100644 index 0000000..f5787ca Binary files /dev/null and b/frontend/src/assets/MenuIcon/ts.png differ diff --git a/frontend/src/assets/MenuIcon/u117_mouseOver.png b/frontend/src/assets/MenuIcon/u117_mouseOver.png new file mode 100644 index 0000000..1d65625 Binary files /dev/null and b/frontend/src/assets/MenuIcon/u117_mouseOver.png differ diff --git a/frontend/src/assets/MenuIcon/u119.png b/frontend/src/assets/MenuIcon/u119.png new file mode 100644 index 0000000..1520447 Binary files /dev/null and b/frontend/src/assets/MenuIcon/u119.png differ diff --git a/frontend/src/assets/MenuIcon/u241.png b/frontend/src/assets/MenuIcon/u241.png new file mode 100644 index 0000000..250a19f Binary files /dev/null and b/frontend/src/assets/MenuIcon/u241.png differ diff --git a/frontend/src/assets/MenuIcon/u343.png b/frontend/src/assets/MenuIcon/u343.png new file mode 100644 index 0000000..36eb52a Binary files /dev/null and b/frontend/src/assets/MenuIcon/u343.png differ diff --git a/frontend/src/assets/MenuIcon/u697.png b/frontend/src/assets/MenuIcon/u697.png new file mode 100644 index 0000000..e0b432b Binary files /dev/null and b/frontend/src/assets/MenuIcon/u697.png differ diff --git a/frontend/src/assets/MenuIcon/u81.png b/frontend/src/assets/MenuIcon/u81.png new file mode 100644 index 0000000..5fc1f6f Binary files /dev/null and b/frontend/src/assets/MenuIcon/u81.png differ diff --git a/frontend/src/assets/MenuIcon/xqing.png b/frontend/src/assets/MenuIcon/xqing.png new file mode 100644 index 0000000..d54255b Binary files /dev/null and b/frontend/src/assets/MenuIcon/xqing.png differ diff --git a/frontend/src/assets/icons/advert.svg b/frontend/src/assets/icons/advert.svg new file mode 100644 index 0000000..5adcf43 --- /dev/null +++ b/frontend/src/assets/icons/advert.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/brand.svg b/frontend/src/assets/icons/brand.svg new file mode 100644 index 0000000..e4b7cee --- /dev/null +++ b/frontend/src/assets/icons/brand.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/bug.svg b/frontend/src/assets/icons/bug.svg new file mode 100644 index 0000000..05a150d --- /dev/null +++ b/frontend/src/assets/icons/bug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/cascader.svg b/frontend/src/assets/icons/cascader.svg new file mode 100644 index 0000000..e256024 --- /dev/null +++ b/frontend/src/assets/icons/cascader.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/chart.svg b/frontend/src/assets/icons/chart.svg new file mode 100644 index 0000000..27728fb --- /dev/null +++ b/frontend/src/assets/icons/chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/client.svg b/frontend/src/assets/icons/client.svg new file mode 100644 index 0000000..ad4bc15 --- /dev/null +++ b/frontend/src/assets/icons/client.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/close.svg b/frontend/src/assets/icons/close.svg new file mode 100644 index 0000000..5b5057f --- /dev/null +++ b/frontend/src/assets/icons/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/close_all.svg b/frontend/src/assets/icons/close_all.svg new file mode 100644 index 0000000..aa13cd7 --- /dev/null +++ b/frontend/src/assets/icons/close_all.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/close_left.svg b/frontend/src/assets/icons/close_left.svg new file mode 100644 index 0000000..e5708ea --- /dev/null +++ b/frontend/src/assets/icons/close_left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/close_other.svg b/frontend/src/assets/icons/close_other.svg new file mode 100644 index 0000000..212e6c2 --- /dev/null +++ b/frontend/src/assets/icons/close_other.svg @@ -0,0 +1 @@ + diff --git a/frontend/src/assets/icons/close_right.svg b/frontend/src/assets/icons/close_right.svg new file mode 100644 index 0000000..14d3cf3 --- /dev/null +++ b/frontend/src/assets/icons/close_right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/coupon.svg b/frontend/src/assets/icons/coupon.svg new file mode 100644 index 0000000..2f952b2 --- /dev/null +++ b/frontend/src/assets/icons/coupon.svg @@ -0,0 +1 @@ + diff --git a/frontend/src/assets/icons/dashboard.svg b/frontend/src/assets/icons/dashboard.svg new file mode 100644 index 0000000..5317d37 --- /dev/null +++ b/frontend/src/assets/icons/dashboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/dict.svg b/frontend/src/assets/icons/dict.svg new file mode 100644 index 0000000..22a8278 --- /dev/null +++ b/frontend/src/assets/icons/dict.svg @@ -0,0 +1,18 @@ + + + + + + + diff --git a/frontend/src/assets/icons/dict_item.svg b/frontend/src/assets/icons/dict_item.svg new file mode 100644 index 0000000..903109a --- /dev/null +++ b/frontend/src/assets/icons/dict_item.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/frontend/src/assets/icons/download.svg b/frontend/src/assets/icons/download.svg new file mode 100644 index 0000000..c896951 --- /dev/null +++ b/frontend/src/assets/icons/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/drag.svg b/frontend/src/assets/icons/drag.svg new file mode 100644 index 0000000..4185d3c --- /dev/null +++ b/frontend/src/assets/icons/drag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/edit.svg b/frontend/src/assets/icons/edit.svg new file mode 100644 index 0000000..d26101f --- /dev/null +++ b/frontend/src/assets/icons/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/exit-fullscreen.svg b/frontend/src/assets/icons/exit-fullscreen.svg new file mode 100644 index 0000000..485c128 --- /dev/null +++ b/frontend/src/assets/icons/exit-fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/eye-open.svg b/frontend/src/assets/icons/eye-open.svg new file mode 100644 index 0000000..88dcc98 --- /dev/null +++ b/frontend/src/assets/icons/eye-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/eye.svg b/frontend/src/assets/icons/eye.svg new file mode 100644 index 0000000..16ed2d8 --- /dev/null +++ b/frontend/src/assets/icons/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/fullscreen.svg b/frontend/src/assets/icons/fullscreen.svg new file mode 100644 index 0000000..0e86b6f --- /dev/null +++ b/frontend/src/assets/icons/fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/github.svg b/frontend/src/assets/icons/github.svg new file mode 100644 index 0000000..db0a0d4 --- /dev/null +++ b/frontend/src/assets/icons/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/goods-list.svg b/frontend/src/assets/icons/goods-list.svg new file mode 100644 index 0000000..fcb971e --- /dev/null +++ b/frontend/src/assets/icons/goods-list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/goods.svg b/frontend/src/assets/icons/goods.svg new file mode 100644 index 0000000..60c1c73 --- /dev/null +++ b/frontend/src/assets/icons/goods.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/guide.svg b/frontend/src/assets/icons/guide.svg new file mode 100644 index 0000000..b271001 --- /dev/null +++ b/frontend/src/assets/icons/guide.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/homepage.svg b/frontend/src/assets/icons/homepage.svg new file mode 100644 index 0000000..48f4e24 --- /dev/null +++ b/frontend/src/assets/icons/homepage.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/lab.svg b/frontend/src/assets/icons/lab.svg new file mode 100644 index 0000000..d4d60aa --- /dev/null +++ b/frontend/src/assets/icons/lab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/language.svg b/frontend/src/assets/icons/language.svg new file mode 100644 index 0000000..0082b57 --- /dev/null +++ b/frontend/src/assets/icons/language.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/link.svg b/frontend/src/assets/icons/link.svg new file mode 100644 index 0000000..d3f9e5a --- /dev/null +++ b/frontend/src/assets/icons/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/locationIcon.png b/frontend/src/assets/icons/locationIcon.png new file mode 100644 index 0000000..fc01013 Binary files /dev/null and b/frontend/src/assets/icons/locationIcon.png differ diff --git a/frontend/src/assets/icons/menu.svg b/frontend/src/assets/icons/menu.svg new file mode 100644 index 0000000..92c364c --- /dev/null +++ b/frontend/src/assets/icons/menu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/message.svg b/frontend/src/assets/icons/message.svg new file mode 100644 index 0000000..ea1ddef --- /dev/null +++ b/frontend/src/assets/icons/message.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/money.svg b/frontend/src/assets/icons/money.svg new file mode 100644 index 0000000..60f7acf --- /dev/null +++ b/frontend/src/assets/icons/money.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/monitor.svg b/frontend/src/assets/icons/monitor.svg new file mode 100644 index 0000000..bc308cb --- /dev/null +++ b/frontend/src/assets/icons/monitor.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/nested.svg b/frontend/src/assets/icons/nested.svg new file mode 100644 index 0000000..06713a8 --- /dev/null +++ b/frontend/src/assets/icons/nested.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/number.svg b/frontend/src/assets/icons/number.svg new file mode 100644 index 0000000..ad5ce9a --- /dev/null +++ b/frontend/src/assets/icons/number.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/order.svg b/frontend/src/assets/icons/order.svg new file mode 100644 index 0000000..8f2107e --- /dev/null +++ b/frontend/src/assets/icons/order.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/password.svg b/frontend/src/assets/icons/password.svg new file mode 100644 index 0000000..6c64def --- /dev/null +++ b/frontend/src/assets/icons/password.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/peoples.svg b/frontend/src/assets/icons/peoples.svg new file mode 100644 index 0000000..383b82d --- /dev/null +++ b/frontend/src/assets/icons/peoples.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/perm.svg b/frontend/src/assets/icons/perm.svg new file mode 100644 index 0000000..b38d065 --- /dev/null +++ b/frontend/src/assets/icons/perm.svg @@ -0,0 +1 @@ + diff --git a/frontend/src/assets/icons/publish.svg b/frontend/src/assets/icons/publish.svg new file mode 100644 index 0000000..e9b489c --- /dev/null +++ b/frontend/src/assets/icons/publish.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/qq.svg b/frontend/src/assets/icons/qq.svg new file mode 100644 index 0000000..98da395 --- /dev/null +++ b/frontend/src/assets/icons/qq.svg @@ -0,0 +1 @@ + diff --git a/frontend/src/assets/icons/rabbitmq.svg b/frontend/src/assets/icons/rabbitmq.svg new file mode 100644 index 0000000..65aa198 --- /dev/null +++ b/frontend/src/assets/icons/rabbitmq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/rate.svg b/frontend/src/assets/icons/rate.svg new file mode 100644 index 0000000..aa3b14d --- /dev/null +++ b/frontend/src/assets/icons/rate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/redis.svg b/frontend/src/assets/icons/redis.svg new file mode 100644 index 0000000..2f1d62d --- /dev/null +++ b/frontend/src/assets/icons/redis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/refresh.svg b/frontend/src/assets/icons/refresh.svg new file mode 100644 index 0000000..1f549f1 --- /dev/null +++ b/frontend/src/assets/icons/refresh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/role.svg b/frontend/src/assets/icons/role.svg new file mode 100644 index 0000000..c484b13 --- /dev/null +++ b/frontend/src/assets/icons/role.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/security.svg b/frontend/src/assets/icons/security.svg new file mode 100644 index 0000000..bcd9d2e --- /dev/null +++ b/frontend/src/assets/icons/security.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/shopping.svg b/frontend/src/assets/icons/shopping.svg new file mode 100644 index 0000000..8d2b4bf --- /dev/null +++ b/frontend/src/assets/icons/shopping.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/size.svg b/frontend/src/assets/icons/size.svg new file mode 100644 index 0000000..ddb25b8 --- /dev/null +++ b/frontend/src/assets/icons/size.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/skill.svg b/frontend/src/assets/icons/skill.svg new file mode 100644 index 0000000..a3b7312 --- /dev/null +++ b/frontend/src/assets/icons/skill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/system.svg b/frontend/src/assets/icons/system.svg new file mode 100644 index 0000000..63feb20 --- /dev/null +++ b/frontend/src/assets/icons/system.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/theme.svg b/frontend/src/assets/icons/theme.svg new file mode 100644 index 0000000..5982a2f --- /dev/null +++ b/frontend/src/assets/icons/theme.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/tree.svg b/frontend/src/assets/icons/tree.svg new file mode 100644 index 0000000..d40a414 --- /dev/null +++ b/frontend/src/assets/icons/tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/user.svg b/frontend/src/assets/icons/user.svg new file mode 100644 index 0000000..e4c7b38 --- /dev/null +++ b/frontend/src/assets/icons/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/uv.svg b/frontend/src/assets/icons/uv.svg new file mode 100644 index 0000000..ca4c301 --- /dev/null +++ b/frontend/src/assets/icons/uv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/valid_code.svg b/frontend/src/assets/icons/valid_code.svg new file mode 100644 index 0000000..39bf478 --- /dev/null +++ b/frontend/src/assets/icons/valid_code.svg @@ -0,0 +1,9 @@ + + + + diff --git a/frontend/src/assets/icons/wechat.svg b/frontend/src/assets/icons/wechat.svg new file mode 100644 index 0000000..35de4bc --- /dev/null +++ b/frontend/src/assets/icons/wechat.svg @@ -0,0 +1 @@ + diff --git a/frontend/src/assets/images/linefeed.png b/frontend/src/assets/images/linefeed.png new file mode 100644 index 0000000..54ebc81 Binary files /dev/null and b/frontend/src/assets/images/linefeed.png differ diff --git a/frontend/src/assets/images/linefeed.png_mouseOver.png b/frontend/src/assets/images/linefeed.png_mouseOver.png new file mode 100644 index 0000000..3e5b329 Binary files /dev/null and b/frontend/src/assets/images/linefeed.png_mouseOver.png differ diff --git a/frontend/src/assets/images/u287.gif b/frontend/src/assets/images/u287.gif new file mode 100644 index 0000000..89bcf89 Binary files /dev/null and b/frontend/src/assets/images/u287.gif differ diff --git a/frontend/src/assets/images/u3127.png b/frontend/src/assets/images/u3127.png new file mode 100644 index 0000000..a77833e Binary files /dev/null and b/frontend/src/assets/images/u3127.png differ diff --git a/frontend/src/assets/images/u3139.png b/frontend/src/assets/images/u3139.png new file mode 100644 index 0000000..c6347d7 Binary files /dev/null and b/frontend/src/assets/images/u3139.png differ diff --git a/frontend/src/assets/index/indicator.png b/frontend/src/assets/index/indicator.png new file mode 100644 index 0000000..2f53da6 Binary files /dev/null and b/frontend/src/assets/index/indicator.png differ diff --git a/frontend/src/assets/logo.png b/frontend/src/assets/logo.png new file mode 100644 index 0000000..f3d2503 Binary files /dev/null and b/frontend/src/assets/logo.png differ diff --git a/frontend/src/components.d.ts b/frontend/src/components.d.ts new file mode 100644 index 0000000..94e8b82 --- /dev/null +++ b/frontend/src/components.d.ts @@ -0,0 +1,9 @@ +// 全局组件类型声明 +import Pagination from '@/components/Pagination/index.vue'; + +declare module '@vue/runtime-core' { + export interface GlobalComponents { + Pagination: typeof Pagination; + } +} +export {}; diff --git a/frontend/src/components/Breadcrumb/index.vue b/frontend/src/components/Breadcrumb/index.vue new file mode 100644 index 0000000..7323114 --- /dev/null +++ b/frontend/src/components/Breadcrumb/index.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/frontend/src/components/GithubCorner/index.vue b/frontend/src/components/GithubCorner/index.vue new file mode 100644 index 0000000..c9a4b32 --- /dev/null +++ b/frontend/src/components/GithubCorner/index.vue @@ -0,0 +1,59 @@ + + + diff --git a/frontend/src/components/Hamburger/index.vue b/frontend/src/components/Hamburger/index.vue new file mode 100644 index 0000000..ec2a6c3 --- /dev/null +++ b/frontend/src/components/Hamburger/index.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/frontend/src/components/IconSelect/index.vue b/frontend/src/components/IconSelect/index.vue new file mode 100644 index 0000000..9670dcf --- /dev/null +++ b/frontend/src/components/IconSelect/index.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/frontend/src/components/LangSelect/index.vue b/frontend/src/components/LangSelect/index.vue new file mode 100644 index 0000000..88cdcd7 --- /dev/null +++ b/frontend/src/components/LangSelect/index.vue @@ -0,0 +1,44 @@ + + + diff --git a/frontend/src/components/Pagination/index.vue b/frontend/src/components/Pagination/index.vue new file mode 100644 index 0000000..7ae0c19 --- /dev/null +++ b/frontend/src/components/Pagination/index.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/frontend/src/components/Pagination/page.vue b/frontend/src/components/Pagination/page.vue new file mode 100644 index 0000000..da64123 --- /dev/null +++ b/frontend/src/components/Pagination/page.vue @@ -0,0 +1,131 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/components/RightPanel/index.vue b/frontend/src/components/RightPanel/index.vue new file mode 100644 index 0000000..dc09179 --- /dev/null +++ b/frontend/src/components/RightPanel/index.vue @@ -0,0 +1,160 @@ + + + + + + + diff --git a/frontend/src/components/Screenfull/index.vue b/frontend/src/components/Screenfull/index.vue new file mode 100644 index 0000000..7e9b837 --- /dev/null +++ b/frontend/src/components/Screenfull/index.vue @@ -0,0 +1,18 @@ + + + diff --git a/frontend/src/components/SizeSelect/index.vue b/frontend/src/components/SizeSelect/index.vue new file mode 100644 index 0000000..d9dcbc1 --- /dev/null +++ b/frontend/src/components/SizeSelect/index.vue @@ -0,0 +1,38 @@ + + + diff --git a/frontend/src/components/SvgIcon/index.vue b/frontend/src/components/SvgIcon/index.vue new file mode 100644 index 0000000..19d3cc9 --- /dev/null +++ b/frontend/src/components/SvgIcon/index.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/frontend/src/components/ThemePicker/index.vue b/frontend/src/components/ThemePicker/index.vue new file mode 100644 index 0000000..c51ce19 --- /dev/null +++ b/frontend/src/components/ThemePicker/index.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/frontend/src/components/Upload/MultiUpload.vue b/frontend/src/components/Upload/MultiUpload.vue new file mode 100644 index 0000000..5c34226 --- /dev/null +++ b/frontend/src/components/Upload/MultiUpload.vue @@ -0,0 +1,143 @@ + + + + + diff --git a/frontend/src/components/Upload/SingleUpload.vue b/frontend/src/components/Upload/SingleUpload.vue new file mode 100644 index 0000000..9937550 --- /dev/null +++ b/frontend/src/components/Upload/SingleUpload.vue @@ -0,0 +1,97 @@ + + + + + + + diff --git a/frontend/src/components/WangEditor/index.vue b/frontend/src/components/WangEditor/index.vue new file mode 100644 index 0000000..aeab4d1 --- /dev/null +++ b/frontend/src/components/WangEditor/index.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/frontend/src/directive/index.ts b/frontend/src/directive/index.ts new file mode 100644 index 0000000..a966376 --- /dev/null +++ b/frontend/src/directive/index.ts @@ -0,0 +1,2 @@ +export { hasPerm, hasRole } from './permission'; +export { deBounce } from './utils'; diff --git a/frontend/src/directive/permission/index.ts b/frontend/src/directive/permission/index.ts new file mode 100644 index 0000000..2b215dd --- /dev/null +++ b/frontend/src/directive/permission/index.ts @@ -0,0 +1,54 @@ +import { useUserStoreHook } from '@/store/modules/user'; +import { Directive, DirectiveBinding } from 'vue'; + +/** + * 按钮权限校验 + */ +export const hasPerm: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + // 「超级管理员」拥有所有的按钮权限 + const { roles, perms } = useUserStoreHook(); + if (roles.includes('超级管理员')) { + return true; + } + // 「其他角色」按钮权限校验 + const { value } = binding; + if (value) { + const requiredPerms = value; // DOM绑定需要的按钮权限标识 + const hasPerm = perms?.some(perm => { + return requiredPerms.includes(perm); + }); + + if (!hasPerm) { + el.parentNode && el.parentNode.removeChild(el); + } + } else { + throw new Error( + "need perms! Like v-has-perm=\"['sys:user:add','sys:user:edit']\"" + ); + } + } +}; + +/** + * 角色权限校验 + */ +export const hasRole: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + const { value } = binding; + + if (value) { + const requiredRoles = value; // DOM绑定需要的角色编码 + const { roles } = useUserStoreHook(); + const hasRole = roles.some(perm => { + return requiredRoles.includes(perm); + }); + + if (!hasRole) { + el.parentNode && el.parentNode.removeChild(el); + } + } else { + throw new Error("need roles! Like v-has-role=\"['admin','test']\""); + } + } +}; diff --git a/frontend/src/directive/utils/index.ts b/frontend/src/directive/utils/index.ts new file mode 100644 index 0000000..9eeaef9 --- /dev/null +++ b/frontend/src/directive/utils/index.ts @@ -0,0 +1,15 @@ +import { Directive } from 'vue'; + +/** + * 按钮防抖 + */ +export const deBounce:Directive = { + mounted(el:HTMLElement) { + el.addEventListener('click', () => { + el.classList.add('is-disabled') + setTimeout(() => { + el.classList.remove('is-disabled') + }, 2000) + }) + } +} diff --git a/frontend/src/env.d.ts b/frontend/src/env.d.ts new file mode 100644 index 0000000..bcddf3e --- /dev/null +++ b/frontend/src/env.d.ts @@ -0,0 +1,19 @@ +/// + +declare module '*.vue' { + import { DefineComponent } from 'vue'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any>; + export default component; +} + +// 环境变量 TypeScript的智能提示 +interface ImportMetaEnv { + VITE_APP_TITLE: string; + VITE_APP_PORT: string; + VITE_APP_BASE_API: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/frontend/src/lang/en.ts b/frontend/src/lang/en.ts new file mode 100644 index 0000000..63b4df7 --- /dev/null +++ b/frontend/src/lang/en.ts @@ -0,0 +1,29 @@ +export default { + // 路由国际化 + route: { + dashboard: 'Dashboard', + document: 'Document' + }, + // 登录页面国际化 + login: { + title: 'Jingyi system', + username: 'Username', + rulesUsername: 'enter one user name', + password: 'Password', + rulesPassword: 'Please input a password', + rulesPasswordPlace: 'The password cannot be less than 6 digits', + login: 'Login', + code: 'Verification Code', + copyright: '', + icp: '', + thirdPartyLogin: 'third-party login', + remember: 'Remember password' + }, + // 导航栏国际化 + navbar: { + dashboard: 'Dashboard', + logout: 'Logout', + document: 'Document', + gitee: 'Gitee' + } +}; diff --git a/frontend/src/lang/index.ts b/frontend/src/lang/index.ts new file mode 100644 index 0000000..5ca3f65 --- /dev/null +++ b/frontend/src/lang/index.ts @@ -0,0 +1,46 @@ +// 自定义国际化配置 +import { createI18n } from 'vue-i18n'; +import { localStorage } from '@/utils/localStorage'; + +// 本地语言包 +import enLocale from './en'; +import zhCnLocale from './zh-cn'; + +const messages = { + 'zh-cn': { + ...zhCnLocale + }, + en: { + ...enLocale + } +}; + +/** + * 获取当前系统使用语言字符串 + * + * @returns zh-cn|en ... + */ +export const getLanguage = () => { + // 本地缓存获取 + let language = localStorage.get('language'); + if (language) { + return language; + } + // 浏览器使用语言 + language = navigator.language.toLowerCase(); + const locales = Object.keys(messages); + for (const locale of locales) { + if (language.indexOf(locale) > -1) { + return locale; + } + } + return 'zh-cn'; +}; + +const i18n = createI18n({ + legacy: false, + locale: getLanguage(), + messages: messages +}); + +export default i18n; diff --git a/frontend/src/lang/zh-cn.ts b/frontend/src/lang/zh-cn.ts new file mode 100644 index 0000000..a52feb6 --- /dev/null +++ b/frontend/src/lang/zh-cn.ts @@ -0,0 +1,28 @@ +export default { + // 路由国际化 + route: { + dashboard: '首页', + document: '项目文档' + }, + // 登录页面国际化 + login: { + title: '公司开发平台框架', + username: '用户名', + rulesUsername: '请输入用户名', + password: '密码', + rulesPassword: '请输入密码', + rulesPasswordPlace: '密码不能少于6位', + login: '登 录', + code: '请输入验证码', + copyright: '', + icp: '', + thirdPartyLogin: '第三方登录', + remember: '记住密码' + }, + navbar: { + dashboard: '首页', + logout: '注销', + document: '项目文档', + gitee: '码云' + } +}; diff --git a/frontend/src/layout/components/AppMain.vue b/frontend/src/layout/components/AppMain.vue new file mode 100644 index 0000000..d83a57d --- /dev/null +++ b/frontend/src/layout/components/AppMain.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/frontend/src/layout/components/Navbar.vue b/frontend/src/layout/components/Navbar.vue new file mode 100644 index 0000000..ce6ab7a --- /dev/null +++ b/frontend/src/layout/components/Navbar.vue @@ -0,0 +1,166 @@ + + + + + diff --git a/frontend/src/layout/components/Settings/index.vue b/frontend/src/layout/components/Settings/index.vue new file mode 100644 index 0000000..605f23a --- /dev/null +++ b/frontend/src/layout/components/Settings/index.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/frontend/src/layout/components/Sidebar/Link.vue b/frontend/src/layout/components/Sidebar/Link.vue new file mode 100644 index 0000000..c592bbd --- /dev/null +++ b/frontend/src/layout/components/Sidebar/Link.vue @@ -0,0 +1,37 @@ + + + diff --git a/frontend/src/layout/components/Sidebar/Logo.vue b/frontend/src/layout/components/Sidebar/Logo.vue new file mode 100644 index 0000000..a46a32b --- /dev/null +++ b/frontend/src/layout/components/Sidebar/Logo.vue @@ -0,0 +1,33 @@ + + + diff --git a/frontend/src/layout/components/Sidebar/MixNav.vue b/frontend/src/layout/components/Sidebar/MixNav.vue new file mode 100644 index 0000000..31adfbe --- /dev/null +++ b/frontend/src/layout/components/Sidebar/MixNav.vue @@ -0,0 +1,159 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/layout/components/Sidebar/SidebarItem.vue b/frontend/src/layout/components/Sidebar/SidebarItem.vue new file mode 100644 index 0000000..e540a1f --- /dev/null +++ b/frontend/src/layout/components/Sidebar/SidebarItem.vue @@ -0,0 +1,125 @@ + + + + diff --git a/frontend/src/layout/components/Sidebar/index.vue b/frontend/src/layout/components/Sidebar/index.vue new file mode 100644 index 0000000..f150716 --- /dev/null +++ b/frontend/src/layout/components/Sidebar/index.vue @@ -0,0 +1,47 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/layout/components/TagsView/ScrollPane.vue b/frontend/src/layout/components/TagsView/ScrollPane.vue new file mode 100644 index 0000000..07f79c3 --- /dev/null +++ b/frontend/src/layout/components/TagsView/ScrollPane.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/frontend/src/layout/components/TagsView/index.vue b/frontend/src/layout/components/TagsView/index.vue new file mode 100644 index 0000000..1a5e103 --- /dev/null +++ b/frontend/src/layout/components/TagsView/index.vue @@ -0,0 +1,356 @@ + + + + + diff --git a/frontend/src/layout/components/index.ts b/frontend/src/layout/components/index.ts new file mode 100644 index 0000000..4dca96e --- /dev/null +++ b/frontend/src/layout/components/index.ts @@ -0,0 +1,4 @@ +export { default as Navbar } from './Navbar.vue'; +export { default as AppMain } from './AppMain.vue'; +export { default as Settings } from './Settings/index.vue'; +export { default as TagsView } from './TagsView/index.vue'; diff --git a/frontend/src/layout/components/news.vue b/frontend/src/layout/components/news.vue new file mode 100644 index 0000000..165f0c3 --- /dev/null +++ b/frontend/src/layout/components/news.vue @@ -0,0 +1,257 @@ + + + + diff --git a/frontend/src/layout/index.vue b/frontend/src/layout/index.vue new file mode 100644 index 0000000..4b217e6 --- /dev/null +++ b/frontend/src/layout/index.vue @@ -0,0 +1,134 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 0000000..b3a9a56 --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,42 @@ +import { createApp, Directive } from 'vue'; +import App from './App.vue'; +import router from '@/router'; +import { setupStore } from '@/store'; + +import ElementPlus from 'element-plus'; +import * as ElementPlusIconsVue from '@element-plus/icons-vue' +import Pagination from '@/components/Pagination/index.vue'; +import '@/permission'; + +// 引入svg注册脚本 +import 'virtual:svg-icons-register'; + +// 国际化 +import i18n from '@/lang/index'; + +import '@/styles/index.scss'; +import 'element-plus/theme-chalk/index.css'; +//import 'element-plus/theme-chalk/dark/css-vars.css'; + +const app = createApp(App); +// 自定义指令 +import * as directive from '@/directive'; +Object.keys(directive).forEach(key => { + app.directive(key, (directive as { [key: string]: Directive })[key]); +}); +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) +} + +// 全局方法 +import { getDictionaries } from '@/api/dict'; +app.config.globalProperties.$getDictionaries = getDictionaries; + +// 全局挂载 +setupStore(app); +app + .component('Pagination', Pagination) + .use(router) + .use(ElementPlus) + .use(i18n) + .mount('#app'); diff --git a/frontend/src/permission.ts b/frontend/src/permission.ts new file mode 100644 index 0000000..467c7af --- /dev/null +++ b/frontend/src/permission.ts @@ -0,0 +1,63 @@ +import router from '@/router'; +import { RouteRecordRaw } from 'vue-router'; +import { useUserStoreHook } from '@/store/modules/user'; +import { usePermissionStoreHook } from '@/store/modules/permission'; + +import NProgress from 'nprogress'; +import 'nprogress/nprogress.css'; +NProgress.configure({ showSpinner: false }); // 进度条 + +const permissionStore = usePermissionStoreHook(); + +// 白名单路由 +const whiteList = ['/login']; + +router.beforeEach(async (to, from, next) => { + NProgress.start(); + const userStore = useUserStoreHook(); + if (userStore.Token) { + // 登录成功,跳转到首页 + if (to.path === '/login') { + next({ path: '/' }); + NProgress.done(); + } else { + const hasGetUserInfo = userStore.roles.length > 0; + // const hasGetUserInfo = true; + if (hasGetUserInfo) { + if (to.matched.length === 0) { + from.name ? next({ name: from.name as any }) : next('/401'); + } else { + next(); + } + } else { + try { + const { roles } = await userStore.getInfo(); + const accessRoutes: RouteRecordRaw[] = + await permissionStore.generateRoutes(roles); + + accessRoutes.forEach((route: any) => { + router.addRoute(route); + }); + next({ ...to, replace: true }); + } catch (error) { + // 移除 token 并跳转登录页 + await userStore.resetToken(); + next(`/login?redirect=${to.path}`); + NProgress.done(); + } + } + } + } else { + // 未登录可以访问白名单页面 + if (whiteList.indexOf(to.path) !== -1) { + next(); + } else { + next(`/login?redirect=${to.path}`); + NProgress.done(); + } + } +}); + +router.afterEach(() => { + NProgress.done(); +}); diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts new file mode 100644 index 0000000..d45a4cf --- /dev/null +++ b/frontend/src/router/index.ts @@ -0,0 +1,77 @@ +import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'; +import { usePermissionStoreHook } from '@/store/modules/permission'; + +export const Layout = () => import('@/layout/index.vue'); + +// 静态路由 +export const constantRoutes: RouteRecordRaw[] = [ + { + path: '/redirect', + component: Layout, + meta: { hidden: true }, + children: [ + { + path: '/redirect/:path(.*)', + component: () => import('@/views/redirect/index.vue') + } + ] + }, + { + path: '/login', + component: () => import('@/views/login/index.vue'), + meta: { hidden: true } + }, + { + path: '/404', + component: () => import('@/views/error-page/404.vue'), + meta: { hidden: true } + }, + + { + path: '/', + component: Layout, + redirect: '/dashboard', + children: [ + { + path: 'dashboard', + opturl: '/dashboard', + component: () => import('@/views/dashboard/index.vue'), + name: '首页', + icon: 'homepage', + meta: { title: 'dashboard', icon: 'homepage', affix: true } + }, + { + path: 'personalCenter', + component: () => import('@/views/system/user/personalCenter.vue'), + name: '个人中心', + meta: { title: 'personalCenter',hidden: true, icon: 'personalCenter' } + }, + { + path: '401', + component: () => import('@/views/error-page/401.vue'), + meta: { hidden: true } + }, + ] + } +]; + +// 创建路由 +const router = createRouter({ + history: createWebHashHistory(), + routes: constantRoutes as RouteRecordRaw[], + // 刷新时,滚动条位置还原 + scrollBehavior: () => ({ left: 0, top: 0 }) +}); + +// 重置路由 +export function resetRouter() { + const permissionStore = usePermissionStoreHook(); + permissionStore.routes.forEach(route => { + const name = route.name; + if (name && router.hasRoute(name)) { + router.hasRoute(name) && router.removeRoute(name); + } + }); +} + +export default router; diff --git a/frontend/src/settings.ts b/frontend/src/settings.ts new file mode 100644 index 0000000..cd9b78b --- /dev/null +++ b/frontend/src/settings.ts @@ -0,0 +1,23 @@ +interface DefaultSettings { + title: string; + showSettings: boolean; + tagsView: boolean; + fixedHeader: boolean; + sidebarLogo: boolean; + errorLog: string; + layout: string; + theme: string; +} + +const defaultSettings: DefaultSettings = { + title: '公司开发平台框架', + showSettings: false, + tagsView: true, + fixedHeader: true, + sidebarLogo: true, + errorLog: 'production', + layout: 'left', + theme: 'light' +}; + +export default defaultSettings; diff --git a/frontend/src/shims.d.ts b/frontend/src/shims.d.ts new file mode 100644 index 0000000..288c4ac --- /dev/null +++ b/frontend/src/shims.d.ts @@ -0,0 +1,2 @@ +declare module 'jsencrypt' +declare module 'sortablejs' \ No newline at end of file diff --git a/frontend/src/store/index.ts b/frontend/src/store/index.ts new file mode 100644 index 0000000..fc0ba49 --- /dev/null +++ b/frontend/src/store/index.ts @@ -0,0 +1,11 @@ +import type { App } from 'vue'; +import { createPinia } from 'pinia'; + +const store = createPinia(); + +// 全局挂载store +export function setupStore(app: App) { + app.use(store); +} + +export { store }; diff --git a/frontend/src/store/modules/app.ts b/frontend/src/store/modules/app.ts new file mode 100644 index 0000000..eb4d628 --- /dev/null +++ b/frontend/src/store/modules/app.ts @@ -0,0 +1,97 @@ +import { + getSidebarStatus, + setSidebarStatus, + getSize, + setSize, + setLanguage +} from '@/utils/localStorage'; +import { defineStore } from 'pinia'; +import { getLanguage } from '@/lang/index'; +import { computed, reactive, ref } from 'vue'; +import { useStorage } from '@vueuse/core'; + +// Element Plus 语言包 +import zhCn from 'element-plus/es/locale/lang/zh-cn'; +import en from 'element-plus/es/locale/lang/en'; + +export enum DeviceType { + mobile, + desktop +} + +export enum SizeType { + default, + large, + small +} + +// setup +export const useAppStore = defineStore('app', () => { + // state + const device = useStorage('device', 'desktop'); + const size = ref(getSize() || 'default'); + const language = ref(getLanguage()); + const sidebar = reactive({ + opened: getSidebarStatus() !== 'closed', + withoutAnimation: false + }); + + const locale = computed(() => { + if (language?.value == 'en') { + return en; + } else { + return zhCn; + } + }); + + // actions + function toggleSidebar(withoutAnimation: boolean) { + sidebar.opened = !sidebar.opened; + sidebar.withoutAnimation = withoutAnimation; + if (sidebar.opened) { + setSidebarStatus('opened'); + } else { + setSidebarStatus('closed'); + } + } + + function closeSideBar(withoutAnimation: boolean) { + sidebar.opened = false; + sidebar.withoutAnimation = withoutAnimation; + setSidebarStatus('closed'); + } + + function openSideBar(withoutAnimation: boolean) { + sidebar.opened = true; + sidebar.withoutAnimation = withoutAnimation; + setSidebarStatus('opened'); + } + + function toggleDevice(val: string) { + device.value = val; + } + + function changeSize(val: string) { + size.value = val; + setSize(val); + } + + function changeLanguage(val: string) { + language.value = val; + setLanguage(val); + } + + return { + device, + sidebar, + language, + locale, + size, + toggleDevice, + changeSize, + changeLanguage, + toggleSidebar, + closeSideBar, + openSideBar + }; +}); diff --git a/frontend/src/store/modules/permission.ts b/frontend/src/store/modules/permission.ts new file mode 100644 index 0000000..1e632ea --- /dev/null +++ b/frontend/src/store/modules/permission.ts @@ -0,0 +1,82 @@ +import { RouteRecordRaw } from 'vue-router'; +import { defineStore } from 'pinia'; +import { constantRoutes } from '@/router'; +import { store } from '@/store'; +import { listRoutes } from '@/api/menu'; +import { ref } from 'vue'; + +const modules = import.meta.glob('../../views/**/**.vue'); +export const Layout = () => import('@/layout/index.vue'); + +// const hasPermission = (roles: string[], route: RouteRecordRaw) => { +// if (route.meta && route.meta.roles) { +// if (roles.includes('ROOT')) { +// return true; +// } +// return roles.some(role => { +// if (route.meta?.roles !== undefined) { +// return (route.meta.roles as string[]).includes(role); +// } +// }); +// } +// return false; +// }; + +const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => { + const res: RouteRecordRaw[] = []; + routes.forEach(route => { + const tmp = { ...route } as any; + // if (hasPermission(roles, tmp)) { + tmp.path = tmp.opturl; + if (tmp.type == '0') { + tmp.component = Layout; + } else { + const component = modules[`../../views${tmp.opturl}.vue`] as any; + if (component) { + tmp.component = component; + } else { + tmp.component = modules[`../../views/error-page/404.vue`]; + } + } + res.push(tmp) + if (tmp.children) { + tmp.children = filterAsyncRoutes(tmp.children, roles); + } + // } + }); + return res; +}; + +// setup +export const usePermissionStore = defineStore('permission', () => { + // state + const routes = ref([]); + const addRoutes = ref([]); + + // actions + function setRoutes(newRoutes: RouteRecordRaw[]) { + addRoutes.value = newRoutes; + routes.value = constantRoutes.concat(newRoutes); + } + + function generateRoutes(roles: string[]) { + return new Promise((resolve, reject) => { + listRoutes() + .then(response => { + const asyncRoutes :any = response; + const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles); + setRoutes(accessedRoutes); + resolve(accessedRoutes); + }) + .catch(error => { + reject(error); + }); + }); + } + return { routes, setRoutes, generateRoutes }; +}); + +// 非setup +export function usePermissionStoreHook() { + return usePermissionStore(store); +} diff --git a/frontend/src/store/modules/settings.ts b/frontend/src/store/modules/settings.ts new file mode 100644 index 0000000..0b69453 --- /dev/null +++ b/frontend/src/store/modules/settings.ts @@ -0,0 +1,64 @@ +import { defineStore } from 'pinia'; +import defaultSettings from '../../settings'; +import { ref } from 'vue'; +import { useStorage } from '@vueuse/core'; + +/** + * 主题类型 + */ +export enum ThemeType { + light, + dark +} + +/** + * 布局类型 + */ +export enum LayoutType { + left, + top, + mix +} + +export const useSettingsStore = defineStore('setting', () => { + // state + const showSettings = ref(defaultSettings.showSettings); + const tagsView = useStorage('tagsView', defaultSettings.tagsView); + const fixedHeader = ref(defaultSettings.fixedHeader); + const sidebarLogo = ref(defaultSettings.sidebarLogo); + + const layout = useStorage('layout', defaultSettings.layout); + + // actions + function changeSetting(param: { key: string; value: any }) { + const { key, value } = param; + switch (key) { + case 'showSettings': + showSettings.value = value; + break; + case 'fixedHeader': + fixedHeader.value = value; + break; + case 'tagsView': + tagsView.value = value; + break; + case 'sidevarLogo': + sidebarLogo.value = value; + break; + case 'layout': + layout.value = value; + break; + default: + break; + } + } + + return { + showSettings, + tagsView, + fixedHeader, + sidebarLogo, + layout, + changeSetting + }; +}); diff --git a/frontend/src/store/modules/tagsView.ts b/frontend/src/store/modules/tagsView.ts new file mode 100644 index 0000000..f8a69a2 --- /dev/null +++ b/frontend/src/store/modules/tagsView.ts @@ -0,0 +1,214 @@ +import { defineStore } from 'pinia'; +import { ref } from 'vue'; +import { RouteLocationNormalized } from 'vue-router'; + +export interface TagView extends Partial { + title?: string; +} + +// setup +export const useTagsViewStore = defineStore('tagsView', () => { + // state + const visitedViews = ref([]); + const cachedViews = ref([]); + + // actions + function addVisitedView(view: TagView) { + if (visitedViews.value.some(v => v.path === view.path)) return; + if (view.meta && view.meta.affix) { + visitedViews.value.unshift( + Object.assign({}, view, { + title: view.meta?.title || 'no-name' + }) + ); + } else { + visitedViews.value.push( + Object.assign({}, view, { + title: view.meta?.title || 'no-name' + }) + ); + } + } + + function addCachedView(view: TagView) { + const viewName = view.name as string; + if (cachedViews.value.includes(viewName)) return; + if (view.meta?.keepAlive) { + cachedViews.value.push(viewName); + } + } + + function delVisitedView(view: TagView) { + return new Promise(resolve => { + for (const [i, v] of visitedViews.value.entries()) { + if (v.path === view.path) { + visitedViews.value.splice(i, 1); + break; + } + } + resolve([...visitedViews.value]); + }); + } + + function delCachedView(view: TagView) { + const viewName = view.name as string; + return new Promise(resolve => { + const index = cachedViews.value.indexOf(viewName); + index > -1 && cachedViews.value.splice(index, 1); + resolve([...cachedViews.value]); + }); + } + + function delOtherVisitedViews(view: TagView) { + return new Promise(resolve => { + visitedViews.value = visitedViews.value.filter(v => { + return v.meta?.affix || v.path === view.path; + }); + resolve([...visitedViews.value]); + }); + } + + function delOtherCachedViews(view: TagView) { + const viewName = view.name as string; + return new Promise(resolve => { + const index = cachedViews.value.indexOf(viewName); + if (index > -1) { + cachedViews.value = cachedViews.value.slice(index, index + 1); + } else { + // if index = -1, there is no cached tags + cachedViews.value = []; + } + resolve([...cachedViews.value]); + }); + } + + function updateVisitedView(view: TagView) { + for (let v of visitedViews.value) { + if (v.path === view.path) { + v = Object.assign(v, view); + break; + } + } + } + + function addView(view: TagView) { + addVisitedView(view); + addCachedView(view); + } + + function delView(view: TagView) { + return new Promise(resolve => { + delVisitedView(view); + delCachedView(view); + resolve({ + visitedViews: [...visitedViews.value], + cachedViews: [...cachedViews.value] + }); + }); + } + + function delOtherViews(view: TagView) { + return new Promise(resolve => { + delOtherVisitedViews(view); + delOtherCachedViews(view); + resolve({ + visitedViews: [...visitedViews.value], + cachedViews: [...cachedViews.value] + }); + }); + } + + function delLeftViews(view: TagView) { + return new Promise(resolve => { + const currIndex = visitedViews.value.findIndex(v => v.path === view.path); + if (currIndex === -1) { + return; + } + visitedViews.value = visitedViews.value.filter((item, index) => { + // affix:true 固定tag,例如“首页” + if (index >= currIndex || (item.meta && item.meta.affix)) { + return true; + } + + const cacheIndex = cachedViews.value.indexOf(item.name as string); + if (cacheIndex > -1) { + cachedViews.value.splice(cacheIndex, 1); + } + return false; + }); + resolve({ + visitedViews: [...visitedViews.value] + }); + }); + } + function delRightViews(view: TagView) { + return new Promise(resolve => { + const currIndex = visitedViews.value.findIndex(v => v.path === view.path); + if (currIndex === -1) { + return; + } + visitedViews.value = visitedViews.value.filter((item, index) => { + // affix:true 固定tag,例如“首页” + if (index <= currIndex || (item.meta && item.meta.affix)) { + return true; + } + + const cacheIndex = cachedViews.value.indexOf(item.name as string); + if (cacheIndex > -1) { + cachedViews.value.splice(cacheIndex, 1); + } + return false; + }); + resolve({ + visitedViews: [...visitedViews.value] + }); + }); + } + + function delAllViews() { + return new Promise(resolve => { + const affixTags = visitedViews.value.filter(tag => tag.meta?.affix); + visitedViews.value = affixTags; + cachedViews.value = []; + resolve({ + visitedViews: [...visitedViews.value], + cachedViews: [...cachedViews.value] + }); + }); + } + + function delAllVisitedViews() { + return new Promise(resolve => { + const affixTags = visitedViews.value.filter(tag => tag.meta?.affix); + visitedViews.value = affixTags; + resolve([...visitedViews.value]); + }); + } + + function delAllCachedViews() { + return new Promise(resolve => { + cachedViews.value = []; + resolve([...cachedViews.value]); + }); + } + + return { + visitedViews, + cachedViews, + addVisitedView, + addCachedView, + delVisitedView, + delCachedView, + delOtherVisitedViews, + delOtherCachedViews, + updateVisitedView, + addView, + delView, + delOtherViews, + delLeftViews, + delRightViews, + delAllViews, + delAllVisitedViews, + delAllCachedViews + }; +}); diff --git a/frontend/src/store/modules/user.ts b/frontend/src/store/modules/user.ts new file mode 100644 index 0000000..d825937 --- /dev/null +++ b/frontend/src/store/modules/user.ts @@ -0,0 +1,103 @@ +import { defineStore } from 'pinia'; + +import { getToken, setToken, removeToken } from '@/utils/auth'; +import { loginApi, logoutApi } from '@/api/auth'; +import { getUserInfo } from '@/api/user'; +import { resetRouter } from '@/router'; +import { store } from '@/store'; +import { LoginData } from '@/api/auth/types'; +import { ref } from 'vue'; +import { UserInfo } from '@/api/user/types'; + +export const useUserStore = defineStore('user', () => { + // state + const Token = ref(getToken() || ''); + const nickname = ref(''); + const avatar = ref(''); + const roles = ref>([]); // 用户角色编码集合 → 判断路由权限 + const perms = ref>([]); // 用户权限编码集合 → 判断按钮权限 + const badgeval = ref('') + + // actions + + // 登录 + function login(loginData: LoginData) { + return new Promise((resolve, reject) => { + loginApi(loginData) + .then(response => { + const { token } = response.data; + Token.value = token; + setToken(token); + resolve(); + }) + .catch(error => { + reject(error); + }); + }); + } + + // 获取信息(用户昵称、头像、角色集合、权限集合) + function getInfo() { + return new Promise((resolve, reject) => { + getUserInfo() + .then(({ data }) => { + if (!data) { + return reject('Verification failed, please Login again.'); + } + if (!data.roles || data.roles.length <= 0) { + reject('getUserInfo: roles must be a non-null array!'); + } + nickname.value = data.userInfo.nickname; + avatar.value = data.userInfo.avatar; + roles.value = data.roles; + perms.value = data.permissions; + resolve(data); + }) + .catch(error => { + reject(error); + }); + }); + } + + // 注销 + function logout() { + return new Promise((resolve, reject) => { + logoutApi() + .then(() => { + resetRouter(); + resetToken(); + resolve(); + }) + .catch(error => { + reject(error); + }); + }); + } + + // 重置 + function resetToken() { + removeToken(); + Token.value = ''; + nickname.value = ''; + avatar.value = ''; + roles.value = []; + perms.value = []; + } + return { + Token, + nickname, + avatar, + roles, + perms, + login, + getInfo, + logout, + resetToken, + badgeval + }; +}); + +// 非setup +export function useUserStoreHook() { + return useUserStore(store); +} diff --git a/frontend/src/styles/element-plus.scss b/frontend/src/styles/element-plus.scss new file mode 100644 index 0000000..a54cd78 --- /dev/null +++ b/frontend/src/styles/element-plus.scss @@ -0,0 +1,59 @@ +:root { + // 这里可以设置你自定义的颜色变量 + // 这个是element主要按钮:active的颜色,当主题更改后此变量的值也随之更改 + --el-color-primary-dark: #0d84ff; + // --el-font-size-base: 16px !important; +} +.el-button--large, .el-input--large, .el-table--large, .el-form--large, .el-select__tags-text{ + font-size: 16px !important; + .el-form-item__label{ + font-size: 16px !important; + } + --el-font-size-base: 16px !important; +} + + +// 覆盖 element-plus 的样式 +.el-breadcrumb__inner, +.el-breadcrumb__inner a { + font-weight: 400 !important; +} + +.el-upload { + input[type='file'] { + display: none !important; + } +} + +.el-upload__input { + display: none; +} + +// dropdown +.el-dropdown-menu { + a { + display: block; + } +} + +// to fix el-date-picker css style +.el-range-separator { + box-sizing: content-box; +} + +// 选中行背景色值 +.el-table__body tr.current-row td { + background-color: #e1f3d8b5 !important; +} + +// card 的header统一高度 +.el-card__header { + height: 60px !important; +} + +// 表格表头和表体未对齐 +.el-table__header col[name='gutter'] { + display: table-cell !important; +} + + diff --git a/frontend/src/styles/index.scss b/frontend/src/styles/index.scss new file mode 100644 index 0000000..e6e58e4 --- /dev/null +++ b/frontend/src/styles/index.scss @@ -0,0 +1,39 @@ +@use 'src/styles/variables.module' as variables; +@use 'src/styles/element-plus' as element-plus; +@use './sidebar' as sidebar; +@use './tailwind' as tailwind; + +html,body,#app{ + height: 100%; +} + +// main-container global css +.app-container { + padding: 20px; +} + +.search{ + padding: 18px 0 0 10px; + margin-bottom: 10px; + border-radius: 5px; + border: 1px solid #ddd; + box-shadow: 6px 2px 6px #CCC; +} + +svg{ + display: inline-block; +} +.el-dialog { + display: flex !important; + flex-direction: column !important; + margin: auto !important; + position: absolute !important; + top: 0 ; + left: 0 ; + right: 0; + bottom: 0; + height: max-content; +} +.el-dialog__body { + padding: 20px !important; +} \ No newline at end of file diff --git a/frontend/src/styles/mixin.scss b/frontend/src/styles/mixin.scss new file mode 100644 index 0000000..3ca7168 --- /dev/null +++ b/frontend/src/styles/mixin.scss @@ -0,0 +1,28 @@ +@mixin clearfix { + &:after { + content: ''; + display: table; + clear: both; + } +} + +@mixin scrollBar { + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } +} + +@mixin relative { + position: relative; + width: 100%; + height: 100%; +} diff --git a/frontend/src/styles/sidebar.scss b/frontend/src/styles/sidebar.scss new file mode 100644 index 0000000..a0e5c74 --- /dev/null +++ b/frontend/src/styles/sidebar.scss @@ -0,0 +1,255 @@ +@use 'src/styles/variables.module' as variables; + +svg { + vertical-align: text-bottom !important; +} +#app { + .main-container { + // min-height: 100%; + transition: margin-left 0.28s; + margin-left: variables.$sideBarWidth; + position: relative; + } + + .sidebar-container { + transition: width 0.28s; + width: variables.$sideBarWidth !important; + background-color: variables.$menuBg; + height: calc(100vh - 60px); + padding-top: 15px; + position: absolute; + top: 60px; + bottom: 0; + left: 0; + z-index: 98; + overflow: hidden; + + // reset element-ui css + .horizontal-collapse-transition { + transition: 0s width ease-in-out, 0s padding-left ease-in-out, + 0s padding-right ease-in-out; + } + + .scrollbar-wrapper { + overflow-x: hidden !important; + } + + .el-scrollbar__bar.is-vertical { + right: 0px; + } + + .el-scrollbar { + height: 100%; + } + + &.has-logo { + .el-scrollbar { + height: calc(100% - 50px); + } + } + + .is-horizontal { + display: none; + } + + + .svg-icon { + margin-right: 16px; + } + + .sub-el-icon { + margin-right: 12px; + margin-left: -2px; + } + + .el-menu { + border: none; + height: 100%; + width: 100% !important; + } + + // menu hover + .submenu-title-noDropdown, + .el-sub-menu__title { + &:hover { + color: variables.$menuHover !important; + } + } + + // .is-active > .el-sub-menu__title { + // color: variables.$subMenuActiveText !important; + // } + + & .nest-menu .el-sub-menu > .el-sub-menu__title, + & .el-sub-menu .el-menu-item { + min-width: variables.$sideBarWidth !important; + background-color: variables.$subMenuBg !important; + + &:hover { + color: variables.$subMenuHover !important; + } + } + .el-menu-item.is-active { + border-right: 3px solid variables.$subMenuHover; + background: #e8f3ff !important; + } + } + + .hideSidebar { + .sidebar-container { + width: 54px !important; + .svg-icon { + margin-right: 0px; + } + } + + .main-container { + margin-left: 54px; + } + + .submenu-title-noDropdown { + padding: 0 !important; + position: relative; + + .el-tooltip { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + + .sub-el-icon { + margin-left: 19px; + } + } + } + + .el-sub-menu { + overflow: hidden; + + & > .el-sub-menu__title { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + + .sub-el-icon { + margin-left: 19px; + } + + .el-sub-menu__icon-arrow { + display: none; + } + } + + } + + .el-menu--collapse { + .el-sub-menu { + & > .el-sub-menu__title { + & > span { + height: 0; + width: 0; + overflow: hidden; + visibility: hidden; + display: inline-block; + } + } + } + } + } + + .el-menu--collapse .el-menu .el-sub-menu { + min-width: variables.$sideBarWidth !important; + } + + // mobile responsive + .mobile { + .main-container { + margin-left: 0px; + } + + .sidebar-container { + transition: transform 0.28s; + width: variables.$sideBarWidth !important; + } + + &.hideSidebar { + .sidebar-container { + pointer-events: none; + transition-duration: 0.3s; + transform: translate3d(- variables.$sideBarWidth, 0, 0); + } + } + } + + .withoutAnimation { + .main-container, + .sidebar-container { + transition: none; + } + } +} + +// when menu collapsed +.el-menu--vertical { + & > .el-menu { + .svg-icon { + margin-right: 16px; + } + .sub-el-icon { + margin-right: 12px; + margin-left: -2px; + } + } + + .nest-menu .el-sub-menu > .el-sub-menu__title, + .el-menu-item { + &:hover { + background-color: #ffffff; + // you can use variables.$subMenuHover + color: variables.$menuHover !important; + } + } + + // the scroll bar appears when the subMenu is too long + > .el-menu--popup { + max-height: 100vh; + overflow-y: auto; + + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } + } +} +body[layout="mix"] { + + .horizontal-header{ + .el-menu-item{ + height: 50px!important; + line-height: 50px!important; + } + + .el-sub-menu__title { + background-color: #001529!important; + height: 50px!important; + } + } + .horizontal-header-right>div { + color: #FFF; + } + .svg-icon{ + margin-right: 16px; + } + +} \ No newline at end of file diff --git a/frontend/src/styles/tailwind.scss b/frontend/src/styles/tailwind.scss new file mode 100644 index 0000000..9bb37ee --- /dev/null +++ b/frontend/src/styles/tailwind.scss @@ -0,0 +1,4 @@ + +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/frontend/src/styles/variables.module.css b/frontend/src/styles/variables.module.css new file mode 100644 index 0000000..518a1f9 --- /dev/null +++ b/frontend/src/styles/variables.module.css @@ -0,0 +1,11 @@ +:export { + menuText: #409eff; + menuActiveText: #409eff; + subMenuActiveText: #409eff; + menuBg: #ffffff; + menuHover: #409eff; + subMenuBg: #ffffff; + subMenuHover: #409eff; + sideBarWidth: 210px; +} +/*# sourceMappingURL=variables.module.css.map */ \ No newline at end of file diff --git a/frontend/src/styles/variables.module.css.map b/frontend/src/styles/variables.module.css.map new file mode 100644 index 0000000..b95fb87 --- /dev/null +++ b/frontend/src/styles/variables.module.css.map @@ -0,0 +1,9 @@ +{ + "version": 3, + "mappings": "AAeA,AAAA,OAAO,CAAC;EACN,QAAQ,EAfC,OAAO;EAgBhB,cAAc,EAfC,OAAO;EAgBtB,iBAAiB,EAfC,OAAO;EAgBzB,MAAM,EAdC,OAAO;EAed,SAAS,EAdC,OAAO;EAejB,SAAS,EAbC,OAAO;EAcjB,YAAY,EAbC,OAAO;EAcpB,YAAY,EAZC,KAAK;CAanB", + "sources": [ + "variables.module.scss" + ], + "names": [], + "file": "variables.module.css" +} \ No newline at end of file diff --git a/frontend/src/styles/variables.module.scss b/frontend/src/styles/variables.module.scss new file mode 100644 index 0000000..0376220 --- /dev/null +++ b/frontend/src/styles/variables.module.scss @@ -0,0 +1,25 @@ +// sidebar +$menuText: #505050; +$menuActiveText: #409eff; +$subMenuActiveText: #409eff; + +$menuBg: #ffffff; +$menuHover: #409eff; + +$subMenuBg: #ffffff; +$subMenuHover: #409eff; + +$sideBarWidth: 210px; + +// the :export directive is the magic sauce for webpack +// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass +:export { + menuText: $menuText; + menuActiveText: $menuActiveText; + subMenuActiveText: $subMenuActiveText; + menuBg: $menuBg; + menuHover: $menuHover; + subMenuBg: $subMenuBg; + subMenuHover: $subMenuHover; + sideBarWidth: $sideBarWidth; +} diff --git a/frontend/src/utils/auth.ts b/frontend/src/utils/auth.ts new file mode 100644 index 0000000..c6adc52 --- /dev/null +++ b/frontend/src/utils/auth.ts @@ -0,0 +1,15 @@ +import Cookies from 'js-cookie'; + +const TokenKey = 'token'; + +export function getToken() { + return Cookies.get(TokenKey); +} + +export function setToken(token: any) { + Cookies.set(TokenKey, token); +} + +export function removeToken() { + return Cookies.remove(TokenKey); +} diff --git a/frontend/src/utils/filter.ts b/frontend/src/utils/filter.ts new file mode 100644 index 0000000..38c28bc --- /dev/null +++ b/frontend/src/utils/filter.ts @@ -0,0 +1,80 @@ +/** + * Show plural label if time is plural number + * @param {number} time + * @param {string} label + * @return {string} + */ +function pluralize(time: number, label: string) { + if (time === 1) { + return time + label; + } + return time + label + 's'; +} + +/** + * @param {number} time + */ +export function timeAgo(time: number) { + const between = Date.now() / 1000 - Number(time); + if (between < 3600) { + return pluralize(~~(between / 60), ' minute'); + } else if (between < 86400) { + return pluralize(~~(between / 3600), ' hour'); + } else { + return pluralize(~~(between / 86400), ' day'); + } +} + +/** + * Number formatting + * like 10000 => 10k + * @param {number} num + * @param {number} digits + */ +export function numberFormatter(num: number, digits: number) { + const si = [ + { value: 1e18, symbol: 'E' }, + { value: 1e15, symbol: 'P' }, + { value: 1e12, symbol: 'T' }, + { value: 1e9, symbol: 'G' }, + { value: 1e6, symbol: 'M' }, + { value: 1e3, symbol: 'k' } + ]; + for (let i = 0; i < si.length; i++) { + if (num >= si[i].value) { + return ( + (num / si[i].value) + .toFixed(digits) + .replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol + ); + } + } + return num.toString(); +} + +/** + * 10000 => "10,000" + * @param {number} num + */ +export function toThousandFilter(num: number) { + return (+num || 0) + .toString() + .replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ',')); +} + +/** + * Upper case first char + * @param {String} string + */ +export function uppercaseFirst(string: string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} + +/** + * 金额转换(分->元) + * 100 => 1 + * @param {number} num + */ +export function moneyFormatter(num: number) { + return '¥' + (isNaN(num) ? 0.0 : parseFloat((num / 100).toFixed(2))); +} diff --git a/frontend/src/utils/i18n.ts b/frontend/src/utils/i18n.ts new file mode 100644 index 0000000..b95552a --- /dev/null +++ b/frontend/src/utils/i18n.ts @@ -0,0 +1,12 @@ +// translate router.meta.title, be used in breadcrumb sidebar tagsview +import i18n from '@/lang/index'; + +export function generateTitle(title: any) { + // 判断是否存在国际化配置,如果没有原生返回 + const hasKey = i18n.global.te('route.' + title); + if (hasKey) { + const translatedTitle = i18n.global.t('route.' + title); + return translatedTitle; + } + return title; +} diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts new file mode 100644 index 0000000..cbf844d --- /dev/null +++ b/frontend/src/utils/index.ts @@ -0,0 +1,97 @@ +/** + * Check if an element has a class + * @param {HTMLElement} elm + * @param {string} cls + * @returns {boolean} + */ +export function hasClass(ele: HTMLElement, cls: string) { + return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')); +} + +/** + * Add class to element + * @param {HTMLElement} elm + * @param {string} cls + */ +export function addClass(ele: HTMLElement, cls: string) { + if (!hasClass(ele, cls)) ele.className += ' ' + cls; +} + +/** + * Remove class from element + * @param {HTMLElement} elm + * @param {string} cls + */ +export function removeClass(ele: HTMLElement, cls: string) { + if (hasClass(ele, cls)) { + const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)'); + ele.className = ele.className.replace(reg, ' '); + } +} + +export function mix(color1: string, color2: string, weight: number) { + weight = Math.max(Math.min(Number(weight), 1), 0); + const r1 = parseInt(color1.substring(1, 3), 16); + const g1 = parseInt(color1.substring(3, 5), 16); + const b1 = parseInt(color1.substring(5, 7), 16); + const r2 = parseInt(color2.substring(1, 3), 16); + const g2 = parseInt(color2.substring(3, 5), 16); + const b2 = parseInt(color2.substring(5, 7), 16); + const r = Math.round(r1 * (1 - weight) + r2 * weight); + const g = Math.round(g1 * (1 - weight) + g2 * weight); + const b = Math.round(b1 * (1 - weight) + b2 * weight); + const rStr = ('0' + (r || 0).toString(16)).slice(-2); + const gStr = ('0' + (g || 0).toString(16)).slice(-2); + const bStr = ('0' + (b || 0).toString(16)).slice(-2); + return '#' + rStr + gStr + bStr; +} + +export function parseTime(time :any, cFormat :any) { + if (arguments.length === 0) { + return null + } + const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'undefined' || time === null || time === 'null') { + return '' + } else if (typeof time === 'object') { + date = time + } else { + if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { + time = parseInt(time) + } + if ((typeof time === 'number') && (time.toString().length === 10)) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result :any, key :any) => { + let value : any = formatObj[key] + if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return time_str +} +export function downloadFile(obj :any, name :any, suffix :any) { + const url = window.URL.createObjectURL(new Blob([obj])) + const link = document.createElement('a') + link.style.display = 'none' + link.href = url + const fileName = parseTime(new Date(),'') + '-' + name + '.' + suffix + link.setAttribute('download', fileName) + document.body.appendChild(link) + link.click() + document.body.removeChild(link) +} \ No newline at end of file diff --git a/frontend/src/utils/localStorage.ts b/frontend/src/utils/localStorage.ts new file mode 100644 index 0000000..d5628e5 --- /dev/null +++ b/frontend/src/utils/localStorage.ts @@ -0,0 +1,53 @@ +/** + * window.localStorage 浏览器永久缓存 + */ +export const localStorage = { + // 设置永久缓存 + set(key: string, val: any) { + window.localStorage.setItem(key, JSON.stringify(val)); + }, + // 获取永久缓存 + get(key: string) { + const json: any = window.localStorage.getItem(key); + return JSON.parse(json); + }, + // 移除永久缓存 + remove(key: string) { + window.localStorage.removeItem(key); + }, + // 移除全部永久缓存 + clear() { + window.localStorage.clear(); + } +}; + +// 侧边栏状态(显示/隐藏) +const SidebarStatusKey = 'sidebarStatus'; +export function getSidebarStatus() { + return localStorage.get(SidebarStatusKey); +} + +export function setSidebarStatus(sidebarStatus: string) { + localStorage.set(SidebarStatusKey, sidebarStatus); +} +// 布局大小 +const SizeKey = 'size'; + +export function getSize() { + return localStorage.get(SizeKey); +} + +export function setSize(size: string) { + localStorage.set(SizeKey, size); +} + +// 语言 +const LanguageKey = 'language'; + +export function getLanguage() { + return localStorage.get(LanguageKey); +} + +export function setLanguage(language: string) { + localStorage.set(LanguageKey, language); +} diff --git a/frontend/src/utils/request.ts b/frontend/src/utils/request.ts new file mode 100644 index 0000000..abe108e --- /dev/null +++ b/frontend/src/utils/request.ts @@ -0,0 +1,87 @@ +import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; +import { ElMessage, ElMessageBox } from 'element-plus'; +import { getToken } from '@/utils/auth'; +import { useUserStoreHook } from '@/store/modules/user'; + +// 创建 axios 实例 +const service = axios.create({ + baseURL: import.meta.env.VITE_APP_BASE_API, + timeout: 50000, + headers: { 'Content-Type': 'application/json;charset=utf-8' } +}); + +// 请求拦截器 +service.interceptors.request.use( + (config: AxiosRequestConfig) => { + if (!config.headers) { + throw new Error( + `Expected 'config' and 'config.headers' not to be undefined` + ); + } + const user = useUserStoreHook(); + if (user.Token) { + config.headers.token = getToken(); + } + return config; + }, + (error: any) => { + return Promise.reject(error); + } +); + +// 响应拦截器 +service.interceptors.response.use( + (response: AxiosResponse) => { + const { status, msg } = response; + if (status === 200) { + if (response.data.code == 401) { + ElMessage({ + message: '用户名或密码错误,请重试!', + type: 'error' + }); + return; + }else if(response.data.code == 1){ + ElMessage({ + message: response.data.msg, + type: 'error' + }); + return; + } + return response.data; + } else { + // 响应数据为二进制流处理(Excel导出) + if (response.data instanceof ArrayBuffer) { + return response; + } + ElMessage({ + message: msg || '系统出错', + type: 'error' + }); + return Promise.reject(new Error(msg || 'Error')); + } + }, + (error: any) => { + if (error.response.data) { + const { status, msg } = error.response.data; + // token 过期,重新登录 + if (status === '403') { + ElMessageBox.confirm('当前页面已失效,请重新登录', '提示', { + confirmButtonText: 'OK', + type: 'warning' + }).then(() => { + localStorage.clear(); + window.location.href = '/'; + }); + } else { + ElMessage({ + message: msg || '当前页面已失效', + type: 'error' + }); + } + } + return Promise.reject(error.message); + } +); + +// 导出 axios 实例 +export default service; diff --git a/frontend/src/utils/resize.ts b/frontend/src/utils/resize.ts new file mode 100644 index 0000000..343bb0f --- /dev/null +++ b/frontend/src/utils/resize.ts @@ -0,0 +1,73 @@ +import { ref } from 'vue'; +export default function () { + const chart = ref(); + const sidebarElm = ref(); + + const chartResizeHandler = () => { + if (chart.value) { + chart.value.resize(); + } + }; + + const sidebarResizeHandler = (e: TransitionEvent) => { + if (e.propertyName === 'width') { + chartResizeHandler(); + } + }; + + const initResizeEvent = () => { + window.addEventListener('resize', chartResizeHandler, {passive:true}); + }; + + const destroyResizeEvent = () => { + window.removeEventListener('resize', chartResizeHandler); + }; + + const initSidebarResizeEvent = () => { + sidebarElm.value = document.getElementsByClassName('sidebar-container')[0]; + if (sidebarElm.value) { + sidebarElm.value.addEventListener( + 'transitionend', + sidebarResizeHandler as EventListener, + {passive:true} + ); + } + }; + + const destroySidebarResizeEvent = () => { + if (sidebarElm.value) { + sidebarElm.value.removeEventListener( + 'transitionend', + sidebarResizeHandler as EventListener + ); + } + }; + + const mounted = () => { + initResizeEvent(); + initSidebarResizeEvent(); + }; + + const beforeDestroy = () => { + destroyResizeEvent(); + destroySidebarResizeEvent(); + }; + + const activated = () => { + initResizeEvent(); + initSidebarResizeEvent(); + }; + + const deactivated = () => { + destroyResizeEvent(); + destroySidebarResizeEvent(); + }; + + return { + chart, + mounted, + beforeDestroy, + activated, + deactivated + }; +} diff --git a/frontend/src/utils/rsaEncrypt.ts b/frontend/src/utils/rsaEncrypt.ts new file mode 100644 index 0000000..7bbc829 --- /dev/null +++ b/frontend/src/utils/rsaEncrypt.ts @@ -0,0 +1,29 @@ +import JSEncrypt from 'jsencrypt' +// 密钥对生成 http://web.chacuo.net/netrsakeypair + +const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANL378k3RiZHWx5AfJqdH9xRNBmD9wGD\n' + + '2iRe41HdTNF8RUhNnHit5NpMNtGL0NPTSSpPjjI1kJfVorRvaQerUgkCAwEAAQ==' + +const privateKey = 'MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8\n' + + 'mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9p\n' + + 'B6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue\n' + + '/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZ\n' + + 'UBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6\n' + + 'vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha\n' + + '4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3\n' + + 'tTbklZkD2A==' + +// 加密 +export function encrypt(txt:string) { + const encryptor = new JSEncrypt() + encryptor.setPublicKey(publicKey) // 设置公钥 + return encryptor.encrypt(txt) // 对需要加密的数据进行加密 +} + +// 解密 +export function decrypt(txt:string) { + const encryptor = new JSEncrypt() + encryptor.setPrivateKey(privateKey) + return encryptor.decrypt(txt) +} + diff --git a/frontend/src/utils/scroll-to.ts b/frontend/src/utils/scroll-to.ts new file mode 100644 index 0000000..591e3ec --- /dev/null +++ b/frontend/src/utils/scroll-to.ts @@ -0,0 +1,69 @@ +const easeInOutQuad = (t: number, b: number, c: number, d: number) => { + t /= d / 2; + if (t < 1) { + return (c / 2) * t * t + b; + } + t--; + return (-c / 2) * (t * (t - 2) - 1) + b; +}; + +// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts +const requestAnimFrame = (function () { + return ( + window.requestAnimationFrame || + (window as any).webkitRequestAnimationFrame || + (window as any).mozRequestAnimationFrame || + function (callback) { + window.setTimeout(callback, 1000 / 60); + } + ); +})(); + +/** + * Because it's so fucking difficult to detect the scrolling element, just move them all + * @param {number} amount + */ +const move = (amount: number) => { + document.documentElement.scrollTop = amount; + (document.body.parentNode as HTMLElement).scrollTop = amount; + document.body.scrollTop = amount; +}; + +const position = () => { + return ( + document.documentElement.scrollTop || + (document.body.parentNode as HTMLElement).scrollTop || + document.body.scrollTop + ); +}; + +/** + * @param {number} to + * @param {number} duration + * @param {Function} callback + */ +export const scrollTo = (to: number, duration: number, callback?: any) => { + const start = position(); + const change = to - start; + const increment = 20; + let currentTime = 0; + duration = typeof duration === 'undefined' ? 500 : duration; + const animateScroll = function () { + // increment the time + currentTime += increment; + // find the value with the quadratic in-out easing function + const val = easeInOutQuad(currentTime, start, change, duration); + // move the document.body + move(val); + // do the animation unless its over + if (currentTime < duration) { + requestAnimFrame(animateScroll); + } else { + if (callback && typeof callback === 'function') { + // the animation is done so lets callback + callback(); + } + } + }; + animateScroll(); +}; diff --git a/frontend/src/utils/sessionStorage.ts b/frontend/src/utils/sessionStorage.ts new file mode 100644 index 0000000..fae9a21 --- /dev/null +++ b/frontend/src/utils/sessionStorage.ts @@ -0,0 +1,22 @@ +/** + * window.sessionStorage 浏览器临时缓存 + */ +export const sessionStorage = { + // 设置临时缓存 + set(key: string, val: any) { + window.sessionStorage.setItem(key, JSON.stringify(val)); + }, + // 获取临时缓存 + get(key: string) { + const json: any = window.sessionStorage.getItem(key); + return JSON.parse(json); + }, + // 移除临时缓存 + remove(key: string) { + window.sessionStorage.removeItem(key); + }, + // 移除全部临时缓存 + clear() { + window.sessionStorage.clear(); + } +}; diff --git a/frontend/src/utils/validate.ts b/frontend/src/utils/validate.ts new file mode 100644 index 0000000..bc8ccee --- /dev/null +++ b/frontend/src/utils/validate.ts @@ -0,0 +1,12 @@ +/** + * Created by PanJiaChen on 16/11/18. + */ + +/** + * @param {string} path + * @returns {Boolean} + */ +export function isExternal(path: string) { + const isExternal = /^(https?:|http?:|mailto:|tel:)/.test(path); + return isExternal; +} diff --git a/frontend/src/views/TaskSetting/index.vue b/frontend/src/views/TaskSetting/index.vue new file mode 100644 index 0000000..e295a48 --- /dev/null +++ b/frontend/src/views/TaskSetting/index.vue @@ -0,0 +1,1288 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/views/component/editor.vue b/frontend/src/views/component/editor.vue new file mode 100644 index 0000000..b0f9a87 --- /dev/null +++ b/frontend/src/views/component/editor.vue @@ -0,0 +1,22 @@ + + diff --git a/frontend/src/views/component/uploader.vue b/frontend/src/views/component/uploader.vue new file mode 100644 index 0000000..8a4ab8b --- /dev/null +++ b/frontend/src/views/component/uploader.vue @@ -0,0 +1,33 @@ + + diff --git a/frontend/src/views/dashboard/components/Chart/BarChart.vue b/frontend/src/views/dashboard/components/Chart/BarChart.vue new file mode 100644 index 0000000..05be0c4 --- /dev/null +++ b/frontend/src/views/dashboard/components/Chart/BarChart.vue @@ -0,0 +1,174 @@ + + + + diff --git a/frontend/src/views/dashboard/components/Chart/FunnelChart.vue b/frontend/src/views/dashboard/components/Chart/FunnelChart.vue new file mode 100644 index 0000000..44206d2 --- /dev/null +++ b/frontend/src/views/dashboard/components/Chart/FunnelChart.vue @@ -0,0 +1,131 @@ + + + + + + diff --git a/frontend/src/views/dashboard/components/Chart/PieChart.vue b/frontend/src/views/dashboard/components/Chart/PieChart.vue new file mode 100644 index 0000000..2ad159f --- /dev/null +++ b/frontend/src/views/dashboard/components/Chart/PieChart.vue @@ -0,0 +1,113 @@ + + + + + + diff --git a/frontend/src/views/dashboard/components/Chart/RadarChart.vue b/frontend/src/views/dashboard/components/Chart/RadarChart.vue new file mode 100644 index 0000000..998d62c --- /dev/null +++ b/frontend/src/views/dashboard/components/Chart/RadarChart.vue @@ -0,0 +1,132 @@ + + + + + + diff --git a/frontend/src/views/dashboard/components/Project/index.vue b/frontend/src/views/dashboard/components/Project/index.vue new file mode 100644 index 0000000..63e471f --- /dev/null +++ b/frontend/src/views/dashboard/components/Project/index.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/frontend/src/views/dashboard/components/Team/index.vue b/frontend/src/views/dashboard/components/Team/index.vue new file mode 100644 index 0000000..8b0cf8d --- /dev/null +++ b/frontend/src/views/dashboard/components/Team/index.vue @@ -0,0 +1,240 @@ + + + + + + diff --git a/frontend/src/views/dashboard/index.vue b/frontend/src/views/dashboard/index.vue new file mode 100644 index 0000000..d0f23a2 --- /dev/null +++ b/frontend/src/views/dashboard/index.vue @@ -0,0 +1,19 @@ + + + + + + + diff --git a/frontend/src/views/error-page/401.vue b/frontend/src/views/error-page/401.vue new file mode 100644 index 0000000..59b6f1a --- /dev/null +++ b/frontend/src/views/error-page/401.vue @@ -0,0 +1,114 @@ + + + + + + + + diff --git a/frontend/src/views/error-page/404.vue b/frontend/src/views/error-page/404.vue new file mode 100644 index 0000000..8559404 --- /dev/null +++ b/frontend/src/views/error-page/404.vue @@ -0,0 +1,280 @@ + + + + + + + + diff --git a/frontend/src/views/login/index.vue b/frontend/src/views/login/index.vue new file mode 100644 index 0000000..e49fb8b --- /dev/null +++ b/frontend/src/views/login/index.vue @@ -0,0 +1,513 @@ + + + + + + + diff --git a/frontend/src/views/nested/level1/index.vue b/frontend/src/views/nested/level1/index.vue new file mode 100644 index 0000000..7daf19c --- /dev/null +++ b/frontend/src/views/nested/level1/index.vue @@ -0,0 +1,7 @@ + diff --git a/frontend/src/views/nested/level1/level2/index.vue b/frontend/src/views/nested/level1/level2/index.vue new file mode 100644 index 0000000..abcc3a7 --- /dev/null +++ b/frontend/src/views/nested/level1/level2/index.vue @@ -0,0 +1,7 @@ + diff --git a/frontend/src/views/nested/level1/level2/level3/index1.vue b/frontend/src/views/nested/level1/level2/level3/index1.vue new file mode 100644 index 0000000..888f58e --- /dev/null +++ b/frontend/src/views/nested/level1/level2/level3/index1.vue @@ -0,0 +1,5 @@ + diff --git a/frontend/src/views/nested/level1/level2/level3/index2.vue b/frontend/src/views/nested/level1/level2/level3/index2.vue new file mode 100644 index 0000000..a99c98e --- /dev/null +++ b/frontend/src/views/nested/level1/level2/level3/index2.vue @@ -0,0 +1,5 @@ + diff --git a/frontend/src/views/redirect/index.vue b/frontend/src/views/redirect/index.vue new file mode 100644 index 0000000..47cad96 --- /dev/null +++ b/frontend/src/views/redirect/index.vue @@ -0,0 +1,15 @@ + + + diff --git a/frontend/src/views/system/dept/index.vue b/frontend/src/views/system/dept/index.vue new file mode 100644 index 0000000..a10061b --- /dev/null +++ b/frontend/src/views/system/dept/index.vue @@ -0,0 +1,638 @@ + + + + + + + diff --git a/frontend/src/views/system/dict/index.vue b/frontend/src/views/system/dict/index.vue new file mode 100644 index 0000000..bf14f6a --- /dev/null +++ b/frontend/src/views/system/dict/index.vue @@ -0,0 +1,708 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/system/menu/index.vue b/frontend/src/views/system/menu/index.vue new file mode 100644 index 0000000..f9877ee --- /dev/null +++ b/frontend/src/views/system/menu/index.vue @@ -0,0 +1,832 @@ + + + \ No newline at end of file diff --git a/frontend/src/views/system/record/index.vue b/frontend/src/views/system/record/index.vue new file mode 100644 index 0000000..eb25244 --- /dev/null +++ b/frontend/src/views/system/record/index.vue @@ -0,0 +1,207 @@ + + + + + + + diff --git a/frontend/src/views/system/role/index.vue b/frontend/src/views/system/role/index.vue new file mode 100644 index 0000000..2d458c7 --- /dev/null +++ b/frontend/src/views/system/role/index.vue @@ -0,0 +1,510 @@ + + + + + + + diff --git a/frontend/src/views/system/user/index.vue b/frontend/src/views/system/user/index.vue new file mode 100644 index 0000000..979b4bc --- /dev/null +++ b/frontend/src/views/system/user/index.vue @@ -0,0 +1,559 @@ + + + + + + + + + diff --git a/frontend/src/views/system/user/personalCenter.vue b/frontend/src/views/system/user/personalCenter.vue new file mode 100644 index 0000000..1184351 --- /dev/null +++ b/frontend/src/views/system/user/personalCenter.vue @@ -0,0 +1,289 @@ + + + + + + + diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000..5b86b53 --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,15 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], + darkMode: 'class', + theme: { + + backgroundColor: theme => ({ + ...theme('colors'), + "sidebar-logo":'#2b2f3a' + }) + }, + plugins: [], + + +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..4f8945a --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "jsx": "preserve", + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "lib": ["esnext", "dom"], + "baseUrl": ".", + "allowJs": true, + "paths": { + "@/*": ["src/*"] + }, + "skipLibCheck": true /* Skip type checking all .d.ts files. */, + "allowSyntheticDefaultImports": true /* 允许默认导入 */, + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + "types": ["element-plus/global"], + "typeRoots": [ + "./node_modules/@types/", + "./types" + ] /* 指定多个文件夹,这些文件夹的作用类似于 './node_modules/@types'. */ + }, + "include": ["src/**/*.ts", "src/**/*.vue", "types/**/*.d.ts"], + "exclude": ["node_modules", "dist", "**/*.js"] +} diff --git a/frontend/types/components.d.ts b/frontend/types/components.d.ts new file mode 100644 index 0000000..94e8b82 --- /dev/null +++ b/frontend/types/components.d.ts @@ -0,0 +1,9 @@ +// 全局组件类型声明 +import Pagination from '@/components/Pagination/index.vue'; + +declare module '@vue/runtime-core' { + export interface GlobalComponents { + Pagination: typeof Pagination; + } +} +export {}; diff --git a/frontend/types/env.d.ts b/frontend/types/env.d.ts new file mode 100644 index 0000000..bcddf3e --- /dev/null +++ b/frontend/types/env.d.ts @@ -0,0 +1,19 @@ +/// + +declare module '*.vue' { + import { DefineComponent } from 'vue'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any>; + export default component; +} + +// 环境变量 TypeScript的智能提示 +interface ImportMetaEnv { + VITE_APP_TITLE: string; + VITE_APP_PORT: string; + VITE_APP_BASE_API: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/frontend/types/global.d.ts b/frontend/types/global.d.ts new file mode 100644 index 0000000..63d85cc --- /dev/null +++ b/frontend/types/global.d.ts @@ -0,0 +1,23 @@ +declare global { + interface PageQuery { + pageNum: number; + pageSize: number; + } + + interface PageResult { + list: T; + total: number; + } + type DialogType = { + title?: string; + visible: boolean; + }; + + type OptionType = { + value: string; + label: string; + checked?: boolean; + children?: OptionType[]; + }; +} +export {}; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..e458fbb --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,52 @@ +import { UserConfig, ConfigEnv, loadEnv } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'; +import path from 'path'; + +export default ({ mode }: ConfigEnv): UserConfig => { + // 获取 .env 环境配置文件 + const env = loadEnv(mode, process.cwd()); + + return { + plugins: [ + vue(), + createSvgIconsPlugin({ + // 指定需要缓存的图标文件夹 + iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')], + // 指定symbolId格式 + symbolId: 'icon-[dir]-[name]' + }) + ], + // 本地反向代理解决浏览器跨域限制 + server: { + host: '0.0.0.0', + port: Number(env.VITE_APP_PORT), + open: true, // 运行自动打开浏览器 + proxy: { + [env.VITE_APP_BASE_API]: { + // 线上API地址 + //target: 'http://192.168.1.20:8090/', + // 本地API地址 + target: 'http://localhost:8093', + changeOrigin: true, + rewrite: path => + path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '') + } + } + }, + resolve: { + // Vite路径别名配置 + alias: { + '@': path.resolve('./src') + } + }, + css: { + preprocessorOptions: { + scss: { + silenceDeprecations: ['legacy-js-api'], + api: 'modern-compiler' // 使用现代API + } + } + } + }; +}; \ No newline at end of file