From 0799f9cb4df49af457a1fad8945fffe829e2765b Mon Sep 17 00:00:00 2001 From: sansenhoshi Date: Mon, 29 Jun 2026 13:55:42 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E4=BC=98=E5=8C=96=EF=BC=8C?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E6=96=87=E4=BB=B6Lombok=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 94 +++--- README.md | 241 ++++++--------- README.zh-CN.md | 161 ++++++++++ pom.xml | 19 +- system-admin/CLAUDE.md | 220 +++++++------- system-admin/db/weather_data_system.sql | 56 +++- .../modules/job/config/ScheduleConfig.java | 66 ----- .../modules/job/utils/ScheduleJob.java | 2 + .../security/password/PasswordUtils.java | 17 -- .../weather/modules/sys/dto/SysDeptDTO.java | 10 +- .../weather/modules/sys/dto/SysMenuDTO.java | 10 +- .../weather/modules/sys/entity/DictData.java | 8 - .../weather/modules/sys/entity/DictType.java | 8 - .../dailydata/WeatherDataListener.java | 2 +- .../task/WeatherSummarizeCacheTask.java | 2 +- .../weather/dailydata/vo/WeatherExcelVO.java | 4 +- .../src/main/resources/application-dev.yml | 18 +- .../src/main/resources/application-prod.yml | 4 +- .../src/main/resources/application-test.yml | 4 +- .../src/main/resources/application.yml | 2 +- .../src/main/resources/logback-spring.xml | 4 +- system-common/CLAUDE.md | 73 ++++- .../com/weather/common/utils/TreeNode.java | 23 +- .../com/weather/common/utils/TreeUtils.java | 6 +- .../common/validator/ValidatorUtils.java | 25 +- system-dynamic-datasource/CLAUDE.md | 65 ++++ weather-data-ui/.env.development | 2 +- weather-data-ui/CLAUDE.md | 280 ++++++++---------- weather-data-ui/README.md | 6 - weather-data-ui/package-lock.json | 266 +++++++++-------- weather-data-ui/src/assets/css/setting.less | 2 +- .../sys-radio-group/src/sys-radio-group.vue | 2 +- .../components/sys-select/src/sys-select.vue | 2 +- .../src/composables/useAlertMarquee.ts | 6 +- .../src/composables/useFloatingDrag.ts | 20 +- .../src/composables/useWeatherConstants.ts | 2 +- weather-data-ui/src/layout/index.vue | 2 +- .../src/layout/sidebar/base-sidebar.vue | 21 +- .../layout/sidebar/sidebar-menus-items.vue | 2 +- weather-data-ui/src/router/index.ts | 41 +-- weather-data-ui/src/utils/chartBuilder.ts | 38 +-- weather-data-ui/src/utils/theme.ts | 12 +- weather-data-ui/src/utils/utils.ts | 20 +- .../weatherdailydata-add-or-update.vue | 16 +- weather-data-ui/src/views/error.vue | 10 +- weather-data-ui/src/views/home.vue | 3 +- .../src/views/weather/prediction.vue | 2 +- 47 files changed, 1034 insertions(+), 865 deletions(-) create mode 100644 README.zh-CN.md create mode 100644 system-dynamic-datasource/CLAUDE.md delete mode 100644 weather-data-ui/README.md diff --git a/CLAUDE.md b/CLAUDE.md index 803c5b3..1619777 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,58 +2,66 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -Subdirectories have their own `CLAUDE.md` with module-specific details — see: -- `system-admin/CLAUDE.md` — backend build, patterns, conventions, weather domain -- `system-common/CLAUDE.md` — shared base classes, i18n -- `weather-data-ui/CLAUDE.md` — frontend stack, patterns, critical rules +## Project Overview ---- +Weather data management system -- a multi-module Maven project with a Spring Boot backend and Vue 3 frontend for managing meteorological monitoring data from weather stations. -## Project overview +- **Backend**: Spring Boot 3.5.11, Java 17, Apache Shiro, MyBatis-Plus, Quartz +- **Frontend**: Vue 3 + TypeScript + Vite 5, Element Plus, ECharts, Pinia +- **Database**: MySQL 8.0 (primary), supports Oracle / SQL Server / PostgreSQL / Dameng via dynamic datasource +- **Cache**: Redis (Lettuce client) -**weather-data** is a full-stack meteorological data analysis platform: Java 17 / Spring Boot 3.5 multi-module backend + Vue 3 / Vite 5 / TypeScript frontend. Forked from [renren-security](https://gitee.com/renrenio/renren-security). +## Project Conventions -``` -weather-data/ -├── system-common/ → shared Java lib -├── system-admin/ → admin backend (port 8080, /system-admin) -├── system-dynamic-datasource → multi-DS routing via @DataSource annotation + AOP -├── renren-generator/ → code generator (commented out of build) -└── weather-data-ui/ → Vue 3 SPA frontend +- **No emoji**: Avoid using emoji in code (including variable names, comments, commit messages) and in response messages. Use plain text instead. +- **Keep CLAUDE.md files current**: Any operation that adds, deletes, or modifies files affecting project structure (new modules, new packages, renamed components, dependency changes, config changes, schema changes, etc.) must update the relevant CLAUDE.md file(s) -- top-level for cross-cutting changes, module-level for module-specific changes. + +## Module Index + +| Module | Path | Purpose | +|--------|------|---------| +| system-common | `system-common/` | Shared library: base classes, Redis, validation, XSS, utilities | +| system-dynamic-datasource | `system-dynamic-datasource/` | Multi-datasource support via AbstractRoutingDataSource | +| system-admin | `system-admin/` | Main Spring Boot application, REST API, jobs, security | +| weather-data-ui | `weather-data-ui/` | Vue 3 frontend | + +Each module has its own `CLAUDE.md` with module-specific architecture and conventions. When working in a module, read that module's CLAUDE.md for context. + +## Build & Run Commands + +### Backend + +```bash +# Build everything (skip tests) +mvn clean package -DskipTests + +# Build only system-admin module +cd system-admin && mvn package -DskipTests + +# Run with dev profile +cd system-admin && mvn spring-boot:run -Dspring-boot.run.profiles=dev ``` -**Database**: `weather_data_system` (MySQL). Init from `system-admin/db/weather_data_system.sql` (includes schema + seed data). Default admin: `admin` / `admin` (BCrypt-encoded). Key custom tables: `weather_daily_data`, `weather_station`, `weather_file_scan_record`. No Flyway/Liquibase — all schema changes are manual SQL. +Main entry: `system-admin/src/main/java/com/weather/AdminApplication.java` -### Port & context path reference +### Frontend -| Service | Port | Context Path | App Class | -|---|---|---|---| -| Admin | 8080 | `/system-admin` | `AdminApplication` | -| Frontend (dev) | 8001 | `/` | Vite dev server | -| Frontend (prod) | 80 | `/` | Nginx via gateway | +```bash +cd weather-data-ui +npm install +npm run dev # dev server on port 8001 +npm run build # production build +npm run lint # ESLint on src/**/*.{vue,ts} +``` ---- +### Docker Compose -## Code Style +```bash +docker compose up -d # MySQL + Redis + admin + api + UI + nginx gateway +``` -- **No emoji in UI strings.** Plain Chinese text for labels, buttons, status. Functional Unicode symbols are allowed and used: status dots `●`/`○`, checkmark `✓`, and box-drawing chars (`╔ ═ ╗ ━`) in log banners. -- **No emoji in code comments or docstrings.** Plain text only. -- **Keep CLAUDE.md current** — whenever code is modified, added, deleted, or any file change affects the project structure, build process, architecture, or conventions, update the relevant `CLAUDE.md` in the same commit to reflect the new state. This file and its children are the source of truth for both humans and Claude; stale documentation is a bug. +## Key Configuration ---- - -## Docker deployment - -Five services in `docker-compose.yml`: mysql (8.0), redis (7 Alpine + AOF), admin JAR, UI (Nginx + built frontend), gateway (Nginx reverse proxy on port 80). The `api` service definition still references the deleted `system-api` module — remove it before deploying. - -**Important**: The `deploy/` directory referenced by Docker Compose (`deploy/mysql/init/`, `deploy/nginx/`) **does not exist locally** — it must be created for deployment. - -Environment variables from `.env` at project root. Two frontend Dockerfiles: standard multi-stage (`Dockerfile`) and pre-built (`Dockerfile.offline`). - -## Repository notes - -- `README.md` does not contain substantive guidance — this file is the primary operational reference. -- No CI configuration exists. Only pre-commit is frontend lint-staged via yarn git hooks. -- `renren-generator` module exists but is commented out of the root POM build. -- `system-dynamic-datasource` provides `@DataSource` annotation-driven multi-DS routing; the slave DS config in `application-dev.yml` is commented out. -- `.gitignore` excludes `.idea/` but `.idea/` is tracked (committed IDE config; `.idea/.gitignore` only excludes local files like `workspace.xml`). +- **Dev profile**: `system-admin/src/main/resources/application-dev.yml` -- local MySQL (weather_data_system), Redis 127.0.0.1:6379 +- **Prod profile**: `application-prod.yml` -- expects env variables for DB/Redis (see docker-compose.yml) +- **project-options.redis.open**: toggles Redis caching globally (set via sys_params) diff --git a/README.md b/README.md index cfc4247..21343b3 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,109 @@ -# 气象数据分析平台 +# Weather Data Management System -基于 Spring Boot 3.5 / Vue 3 的全栈气象数据管理与分析系统,支持气象站点管理、逐日观测数据批量导入、数据统计分析与可视化展示。 +A full-stack meteorological data management and analysis platform built with Spring Boot 3.5 and Vue 3. -> **开发者文档** → [CLAUDE.md](CLAUDE.md) +> Developer documentation: [CLAUDE.md](CLAUDE.md) | Module docs: [system-admin](system-admin/CLAUDE.md) | [system-common](system-common/CLAUDE.md) | [system-dynamic-datasource](system-dynamic-datasource/CLAUDE.md) | [weather-data-ui](weather-data-ui/CLAUDE.md) +> +> Chinese version: [README.zh-CN.md](README.zh-CN.md) ---- - -## 项目结构 +## Project Structure ``` weather-data/ -├── system-common/ → 公共模块:基础实体、工具类、Redis、校验 -├── system-admin/ → 管理后台 (port 8080, /system-admin) -│ └── modules/weather/ -│ ├── dailydata/ → 逐日观测数据(Excel 批量导入、统计汇总) -│ ├── station/ → 气象站点管理 -│ └── filescan/ → 气象资料文件监控与查阅 -├── system-api/ → 外部 API 服务 (port 8081, /renren-api) -├── system-dynamic-datasource/ → 多数据源支持(预留) -├── renren-generator/ → 代码生成器 -└── weather-data-ui/ → Vue 3 前端 +├── system-common/ Shared library (base classes, Redis, validation, utilities) +├── system-dynamic-datasource/ Multi-datasource support (AbstractRoutingDataSource) +├── system-admin/ Main application (REST API, security, jobs, file scanning) +└── weather-data-ui/ Vue 3 frontend (Element Plus, ECharts, Pinia) ``` -### 模块说明 +### Module Relationships -| 模块 | 说明 | -|---|---| -| **system-admin** | 管理后台,包含气象数据管理、站点管理、文件查阅、系统管理(用户/角色/菜单/部门)、定时任务、日志管理 | -| **system-api** | 对外开放的 REST API 服务,提供用户注册/登录等接口 | -| **system-common** | 共享库,被所有子模块依赖 | -| **weather-data-ui** | 前端 SPA,气象数据仪表盘、CRUD 页面、图表可视化 | +``` +system-common <-- depended on by all backend modules +system-dynamic-datasource <-- depended on by system-admin +system-admin <-- runnable Spring Boot app, depends on both above +weather-data-ui <-- independent frontend, communicates with system-admin via REST API +``` ---- +`system-common` provides the base class hierarchy (`BaseEntity`, `CrudService`, `CrudServiceImpl`), Redis utilities, validation framework, XSS protection, and shared utilities. All backend modules consume it. -## 技术栈 +`system-dynamic-datasource` provides the `@DataSource` annotation and `DynamicDataSource` (extends `AbstractRoutingDataSource`) for switching between multiple databases at runtime. Used by `system-admin` when queries need to target different data sources. -| 层级 | 技术 | -|---|---| -| 后端框架 | Spring Boot 3.5, MyBatis-Plus 3.5, Apache Shiro 1.12 | -| 数据库 | MySQL 8.0(也支持达梦、Oracle、SQL Server、PostgreSQL) | -| 缓存 | Redis 7(可选) | -| 定时任务 | Quartz | -| 接口文档 | Knife4j (Swagger) | -| 前端框架 | Vue 3, TypeScript, Vite 5, Element Plus, Pinia, ECharts | +`system-admin` is the main runnable application. It contains all business logic organized under `modules/`: -**环境要求**:JDK 17+, Maven 3.6+, Node.js 18+, MySQL 8.0+ +| Domain | Package | Description | +|--------|---------|-------------| +| Weather daily data | `modules/weather/dailydata/` | Excel batch import, CRUD, statistical summaries with Redis caching | +| Weather stations | `modules/weather/station/` | Station registry, dept association for data scoping | +| File scanning | `modules/weather/filescan/` | WatchService-based directory monitoring, auto-import of meteorological files | +| Region management | `modules/region/` | Geographic region tree (province/city/county) | +| System management | `modules/sys/` | Users, roles, menus, departments, dictionaries, parameters | +| Alerts / notifications | `modules/sys/alert/` | SSE real-time push, Spring event-driven broadcast | +| Security | `modules/security/` | Shiro + token-based auth, OAuth2 filter, password hashing | +| Job scheduling | `modules/job/` | Quartz dynamic job management, online start/stop | +| Audit logs | `modules/log/` | Operation log, login log, error log | +| Cloud storage | `modules/oss/` | Alibaba Cloud / Qiniu / Tencent Cloud file storage | ---- +`weather-data-ui` is a Vue 3 SPA that communicates with `system-admin` via REST API. It features dynamic routing (routes loaded from server menus on login), tab-based navigation, ECharts visualization, and real-time SSE alert streaming. -## 快速启动(本地开发) +## Tech Stack -### 1. 初始化数据库 +| Layer | Technology | +|-------|-----------| +| Backend framework | Spring Boot 3.5.11, MyBatis-Plus 3.5.8, Apache Shiro 1.12 | +| Database | MySQL 8.0 (also supports Oracle, SQL Server, PostgreSQL, Dameng) | +| Connection pool | Druid 1.2 | +| Cache | Redis 7 (Lettuce client, optional via `project-options.redis.open`) | +| Job scheduling | Quartz 2.3 | +| API documentation | Knife4j 4.5 (Swagger) | +| Excel processing | EasyExcel 3.2 | +| Frontend | Vue 3.5, TypeScript 5.7, Vite 5.4, Element Plus 2.10, Pinia 2.3, ECharts 5 | -创建数据库 `weather_data_system`(UTF-8mb4),执行初始化脚本: +**Requirements**: JDK 17+, Maven 3.6+, Node.js 18+, MySQL 8.0+ + +## Quick Start (Local Development) + +### 1. Initialize Database ```bash mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS weather_data_system DEFAULT CHARSET utf8mb4" mysql -u root -p weather_data_system < system-admin/db/weather_data_system.sql ``` -### 2. 配置数据源 +### 2. Configure Datasource -编辑 `system-admin/src/main/resources/application-dev.yml`,修改 MySQL 连接信息: +Edit `system-admin/src/main/resources/application-dev.yml`, update MySQL and Redis connection settings: ```yaml spring: datasource: druid: - url: jdbc:mysql://localhost:3306/weather_data_system?... + url: jdbc:mysql://localhost:3306/weather_data_system?... username: root - password: 你的密码 + password: your_password + data: + redis: + host: 127.0.0.1 + port: 6379 + password: ``` -### 3. 启动后端 +### 3. Start Backend ```bash -# 完整构建(跳过测试) +# Full build mvn clean install -DskipTests -# IDEA 中直接运行 AdminApplication.java +# Run from IDE: AdminApplication.java +# Or from CLI: +cd system-admin && mvn spring-boot:run -Dspring-boot.run.profiles=dev ``` -管理后台:http://localhost:8080/system-admin -接口文档:http://localhost:8080/system-admin/doc.html(默认关闭,需在 `application.yml` 中启用 `knife4j.enable: true`) +Admin backend: http://localhost:8080/system-admin +API docs: http://localhost:8080/system-admin/doc.html +Default account: `admin` / `admin` -默认账号:`admin` / `admin` - -### 4. 启动前端 +### 4. Start Frontend ```bash cd weather-data-ui @@ -94,121 +111,47 @@ npm install npm run dev ``` -前端开发服务器:http://localhost:8001 +Frontend dev server: http://localhost:8001 -> **注意**:前端开发环境 `.env.development` 中 `VITE_APP_API` 默认指向 `http://192.168.2.186:8080/system-admin`,请按需修改为你的后端地址。 +Edit `weather-data-ui/.env.development` and set `VITE_APP_API` to your backend URL if needed. ---- +## Deployment -## 生产部署 - -项目提供两种部署方式,根据目标环境选用。 - -### Windows 一键部署 - -适用于 Windows Server 生产环境,自动安装 MySQL / Redis / Nginx 并部署前后端。 - -**部署包结构**(`deploy/` 目录,可独立拷贝到目标服务器): - -``` -deploy/ -├── deploy.bat ← 一键部署(推荐,双击运行) -├── deploy.ps1 ← PowerShell 版本 -├── templates/ ← 配置模板(deploy.bat 使用) -├── project/ -│ ├── client-side/dist/ ← 前端构建产物 -│ └── server-side/ -│ ├── system-admin.jar ← 管理后台 -│ └── system-api.jar ← API 服务(可选) -├── mysql/init/ -│ └── weather_data_system.sql ← 数据库初始化脚本 -├── nginx/nginx.conf ← Docker 网关配置 -└── software/ ← 第三方安装包 - ├── mysql-installer-community-8.0.46.0.msi - ├── nginx-1.28.3.zip - └── Redis-8.8.0-Windows-x64-Service.zip -``` - -```cmd -REM 以管理员身份运行命令提示符 - -deploy\deploy.bat :: 默认安装到 C:\weather-data - -REM 部署完成后: -C:\weather-data\start.bat :: 启动平台 -C:\weather-data\stop.bat :: 停止平台 -``` - -> **前置条件**:Windows 10 1803+ 或 Windows Server 2019+,需预装 JDK 17+。将 `deploy/` 目录完整拷贝至目标服务器后以管理员身份运行 `deploy.bat`。 - -### Docker 部署 - -适用于 Linux 服务器或支持 Docker 的 Windows 环境。编排包含 6 个服务:MySQL、Redis、Admin、API、UI、Gateway。 - -### 前置条件 - -1. 安装 Docker Engine 20.10+ 和 Docker Compose v2 -2. JAR 已构建到各模块的 `target/` 目录 -3. 前端已构建到 `weather-data-ui/dist/` - -### 部署步骤 +### Docker Compose ```bash -# 1. 构建所有模块 +# Build backend JARs and frontend dist mvn clean install -DskipTests cd weather-data-ui && npm run build && cd .. -# 2. 修改环境变量(密码等敏感信息) -cp .env .env.local # 修改 .env.local 中的密码 +# Configure environment +cp .env .env.local # Edit passwords in .env.local -# 3. 创建部署所需的 nginx 配置目录 -mkdir -p deploy/nginx deploy/mysql/init - -# 4. 复制初始化 SQL -cp system-admin/db/weather_data_system.sql deploy/mysql/init/ - -# 5. 启动 +# Start all services docker compose up -d -# 6. 访问 -# http://localhost → 前端页面 -# http://localhost:8080/system-admin → 管理后台 -# http://localhost:8081/renren-api → API 服务 +# Access +# http://localhost -> Frontend (via nginx gateway) +# http://localhost:8080 -> Admin backend ``` -### 常用命令 +Services: MySQL 8.0, Redis 7, Admin (Spring Boot), API (optional, port 8081), UI (nginx), Gateway (nginx reverse proxy, port 80). -```bash -docker compose up -d # 启动所有服务 -docker compose down # 停止所有服务 -docker compose logs -f admin # 查看管理后台日志 -docker compose restart admin # 重启管理后台 -``` +### Windows Server ---- +See `deploy/` directory for one-click deployment scripts (`deploy.bat` / `deploy.ps1`). Requires Windows 10 1803+ or Windows Server 2019+, JDK 17+ pre-installed. Run as Administrator. -## 主要功能 +## Key Features -### 气象数据管理 -- **逐日观测数据**:支持 Excel 批量导入(异步双遍扫描,进度可查)、按站点/日期查询、统计分析、导出 -- **气象站点管理**:站点 CRUD,关联部门实现数据权限隔离 -- **气象资料查阅**:自动监控资料文件目录,支持在线查阅 +- **Weather data import**: Excel batch import with async two-pass processing, progress tracking, multi-row INSERT optimization +- **Statistical analysis**: Same-date-across-years summarization with Redis caching, multi-dimensional queries +- **File monitoring**: Automatic directory watching (WatchService), MD5 deduplication, file lifecycle management (receive -> display -> archive) +- **Real-time notifications**: SSE-based alert streaming, Spring event-driven broadcast +- **Data scoping**: Dept-level row-level security via MyBatis-Plus interceptor with `@DataFilter` annotation +- **Dynamic datasource**: Runtime datasource switching via `@DataSource` annotation with ThreadLocal context +- **Job scheduling**: Quartz-based dynamic job management with online start/stop/configure +- **Cloud storage**: Alibaba Cloud OSS, Qiniu, Tencent Cloud COS integration -### 系统管理 -- 用户 / 角色 / 菜单 / 部门管理 -- 数据权限:`@DataFilter` 注解实现部门级数据隔离 -- 定时任务:Quartz 动态管理,支持在线启停 -- 操作日志 / 登录日志 / 异常日志 -- 文件存储(支持本地及云存储) +## License -### 前端仪表盘 -- 气象数据多维度统计与图表可视化(ECharts) -- 筛选、排序、导出(PNG/PDF) -- 导入进度实时展示 - ---- - -## 参考链接 - -- [CLAUDE.md](CLAUDE.md) — 开发者文档(架构、规范、注意事项) -- 基于 [renren-security](https://gitee.com/renrenio/renren-security) 二次开发 +Apache License 2.0 diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..4a70696 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,161 @@ +# 气象数据管理系统 + +基于 Spring Boot 3.5 与 Vue 3 的全栈气象数据管理与分析平台。 + +> 开发者文档:[CLAUDE.md](CLAUDE.md) | 模块文档:[system-admin](system-admin/CLAUDE.md) | [system-common](system-common/CLAUDE.md) | [system-dynamic-datasource](system-dynamic-datasource/CLAUDE.md) | [weather-data-ui](weather-data-ui/CLAUDE.md) +> +> English version: [README.md](README.md) + +## 项目结构 + +``` +weather-data/ +├── system-common/ 公共模块(基础类、Redis、校验、工具类) +├── system-dynamic-datasource/ 多数据源支持(AbstractRoutingDataSource) +├── system-admin/ 主应用(REST API、安全、定时任务、文件扫描) +└── weather-data-ui/ Vue 3 前端(Element Plus、ECharts、Pinia) +``` + +### 模块关系 + +``` +system-common <-- 被所有后端模块依赖 +system-dynamic-datasource <-- 被 system-admin 依赖 +system-admin <-- 可运行的 Spring Boot 应用,依赖上述两个模块 +weather-data-ui <-- 独立前端,通过 REST API 与 system-admin 通信 +``` + +**[system-common](system-common/)** 提供基础类层次([BaseEntity](system-common/src/main/java/com/weather/common/entity/BaseEntity.java)、[CrudService](system-common/src/main/java/com/weather/common/service/CrudService.java)、[CrudServiceImpl](system-common/src/main/java/com/weather/common/service/impl/CrudServiceImpl.java))、[Redis 工具](system-common/src/main/java/com/weather/common/redis/RedisUtils.java)、校验框架、XSS 防护及通用工具类。所有后端模块均依赖此模块。 + +**[system-dynamic-datasource](system-dynamic-datasource/)** 提供 [@DataSource](system-dynamic-datasource/src/main/java/com/weather/commons/dynamic/datasource/annotation/DataSource.java) 注解与 [DynamicDataSource](system-dynamic-datasource/src/main/java/com/weather/commons/dynamic/datasource/config/DynamicDataSource.java)(继承 `AbstractRoutingDataSource`),用于运行时切换多数据源。当 system-admin 需要查询不同数据库时使用。 + +**[system-admin](system-admin/)** 是主应用入口([AdminApplication.java](system-admin/src/main/java/com/weather/AdminApplication.java)),所有业务逻辑按 domain 组织在 `modules/` 下: + +| 业务域 | 包路径 | 功能说明 | +|--------|--------|----------| +| 逐日气象数据 | [modules/weather/dailydata/](system-admin/src/main/java/com/weather/modules/weather/dailydata/) | Excel 批量导入、CRUD、Redis 缓存统计汇总 | +| 气象站点 | [modules/weather/station/](system-admin/src/main/java/com/weather/modules/weather/station/) | 站点注册,关联部门实现数据权限隔离 | +| 文件扫描 | [modules/weather/filescan/](system-admin/src/main/java/com/weather/modules/weather/filescan/) | WatchService 目录监控,气象资料自动入库 | +| 区域管理 | [modules/region/](system-admin/src/main/java/com/weather/modules/region/) | 行政区域树(省/市/县) | +| 系统管理 | [modules/sys/](system-admin/src/main/java/com/weather/modules/sys/) | 用户、角色、菜单、部门、字典、参数 | +| 通知消息 | [modules/sys/alert/](system-admin/src/main/java/com/weather/modules/sys/alert/) | SSE 实时推送,Spring 事件驱动广播 | +| 安全认证 | [modules/security/](system-admin/src/main/java/com/weather/modules/security/) | Shiro + Token 认证,OAuth2 过滤器,密码加密 | +| 定时任务 | [modules/job/](system-admin/src/main/java/com/weather/modules/job/) | Quartz 动态任务管理,在线启停 | +| 审计日志 | [modules/log/](system-admin/src/main/java/com/weather/modules/log/) | 操作日志、登录日志、异常日志 | +| 云存储 | [modules/oss/](system-admin/src/main/java/com/weather/modules/oss/) | 阿里云 OSS / 七牛云 / 腾讯云 COS 文件存储 | + +**[weather-data-ui](weather-data-ui/)** 是 Vue 3 单页应用,通过 REST API 与 system-admin 通信。特性包括:动态路由(登录时从服务端菜单加载)、标签页导航、ECharts 图表可视化、SSE 实时通知滚动。 + +## 技术栈 + +| 层级 | 技术 | +|------|------| +| 后端框架 | Spring Boot 3.5.11, MyBatis-Plus 3.5.8, Apache Shiro 1.12 | +| 数据库 | MySQL 8.0(同时支持 Oracle、SQL Server、PostgreSQL、达梦) | +| 连接池 | Druid 1.2 | +| 缓存 | Redis 7(Lettuce 客户端,通过 `project-options.redis.open` 控制开关) | +| 定时任务 | Quartz 2.3 | +| 接口文档 | Knife4j 4.5 (Swagger) | +| Excel 处理 | EasyExcel 3.2 | +| 前端 | Vue 3.5, TypeScript 5.7, Vite 5.4, Element Plus 2.10, Pinia 2.3, ECharts 5 | + +**环境要求**:JDK 17+、Maven 3.6+、Node.js 18+、MySQL 8.0+ + +## 快速启动(本地开发) + +### 1. 初始化数据库 + +```bash +mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS weather_data_system DEFAULT CHARSET utf8mb4" +mysql -u root -p weather_data_system < system-admin/db/weather_data_system.sql +``` + +数据库脚本:[system-admin/db/weather_data_system.sql](system-admin/db/weather_data_system.sql) + +### 2. 配置数据源 + +编辑 [system-admin/src/main/resources/application-dev.yml](system-admin/src/main/resources/application-dev.yml),修改 MySQL 和 Redis 连接信息: + +```yaml +spring: + datasource: + druid: + url: jdbc:mysql://localhost:3306/weather_data_system?... + username: root + password: 你的密码 + data: + redis: + host: 127.0.0.1 + port: 6379 + password: +``` + +### 3. 启动后端 + +```bash +# 完整构建 +mvn clean install -DskipTests + +# 在 IDE 中运行 AdminApplication.java +# 或命令行启动: +cd system-admin && mvn spring-boot:run -Dspring-boot.run.profiles=dev +``` + +管理后台: +接口文档: +默认账号:`admin` / `admin` + +### 4. 启动前端 + +```bash +cd weather-data-ui +npm install +npm run dev +``` + +前端开发服务器: + +如需修改后端地址,编辑 [weather-data-ui/.env.development](weather-data-ui/.env.development) 中的 `VITE_APP_API`。 + +## 部署 + +### Docker Compose + +编排文件:[docker-compose.yml](docker-compose.yml) + +```bash +# 构建后端 JAR 和前端产物 +mvn clean install -DskipTests +cd weather-data-ui && npm run build && cd .. + +# 配置环境变量 +cp .env .env.local # 在 .env.local 中修改密码等敏感配置 + +# 启动所有服务 +docker compose up -d + +# 访问地址 +# http://localhost -> 前端页面(通过 nginx 网关) +# http://localhost:8080 -> 管理后台 +``` + +服务清单:MySQL 8.0、Redis 7、Admin(Spring Boot)、API(可选,端口 8081)、UI(nginx)、Gateway(nginx 反向代理,端口 80)。 + +### Windows Server + +参见 `deploy/` 目录下的一键部署脚本([deploy.bat](deploy/deploy.bat) / [deploy.ps1](deploy/deploy.ps1))。要求 Windows 10 1803+ 或 Windows Server 2019+,需预装 JDK 17+。以管理员身份运行。 + +## 核心功能 + +- **气象数据导入**:Excel 异步双遍扫描批量导入,进度可查,多行 INSERT 优化。入口:[WeatherDataImportManager](system-admin/src/main/java/com/weather/modules/weather/dailydata/WeatherDataImportManager.java) +- **统计汇总**:历年同月同日数据汇总,Redis 缓存加速查询,缓存命中自动降级至数据库。缓存任务:[WeatherSummarizeCacheTask](system-admin/src/main/java/com/weather/modules/weather/dailydata/task/WeatherSummarizeCacheTask.java) +- **文件监控**:基于 WatchService 自动监控资料目录,MD5 去重,文件生命周期管理(接收 -> 展示 -> 归档)。服务:[FileWatchServiceManager](system-admin/src/main/java/com/weather/modules/weather/filescan/FileWatchServiceManager.java) +- **实时通知**:基于 SSE 的服务端推送,Spring 事件驱动广播。服务:[SseAlertService](system-admin/src/main/java/com/weather/modules/sys/alert/SseAlertService.java) +- **数据权限**:基于 MyBatis-Plus 拦截器的部门级行级安全,通过 `@DataFilter` 注解启用。拦截器:[DataFilterInterceptor](system-admin/src/main/java/com/weather/common/interceptor/DataFilterInterceptor.java) +- **动态数据源**:运行时通过 `@DataSource` 注解切换数据源,ThreadLocal 上下文传递。实现:[DynamicDataSource](system-dynamic-datasource/src/main/java/com/weather/commons/dynamic/datasource/config/DynamicDataSource.java) +- **定时任务**:Quartz 动态任务管理,支持在线创建、启停、修改 Cron 表达式。配置:[ScheduleConfig](system-admin/src/main/java/com/weather/modules/job/config/ScheduleConfig.java) +- **云存储**:支持阿里云 OSS、七牛云、腾讯云 COS 文件上传与管理。工厂:[OSSFactory](system-admin/src/main/java/com/weather/modules/oss/cloud/OSSFactory.java) + +## 许可证 + +[Apache License 2.0](LICENSE) diff --git a/pom.xml b/pom.xml index 2192ee2..afed1f4 100644 --- a/pom.xml +++ b/pom.xml @@ -19,8 +19,6 @@ system-common system-dynamic-datasource system-admin - - @@ -135,6 +133,23 @@ + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + + + + + + + diff --git a/system-admin/CLAUDE.md b/system-admin/CLAUDE.md index 133800b..20d42c5 100644 --- a/system-admin/CLAUDE.md +++ b/system-admin/CLAUDE.md @@ -1,144 +1,146 @@ -# CLAUDE.md — system-admin +# CLAUDE.md - system-admin -Backend module: Spring Boot 3.5 admin application (port 8080, context `/system-admin`). +This file provides guidance to Claude Code when working in the system-admin module. ---- +## Purpose -## Build & Run +Main Spring Boot application module. Entry point: `com.weather.AdminApplication`. Depends on `system-common` and `system-dynamic-datasource`. -```bash -mvn clean install -DskipTests # full build (tests skipped by default) -mvn clean install -DskipTests=false # build with tests -mvn -pl system-admin -DskipTests=false -Dtest=YourTestClass test # single test +## Module Structure + +``` +com.weather/ +├── AdminApplication.java # @SpringBootApplication, @EnableAsync +├── common/ # Module-level shared code +│ ├── annotation/ # @DataFilter, @LogOperation +│ ├── aspect/ # DataFilterAspect, LogOperationAspect +│ ├── config/ # JacksonConfig, MybatisPlusConfig, SwaggerConfig, FileServerConfig +│ ├── exception/ # CustomExceptionHandler +│ ├── handler/ # FieldMetaObjectHandler (MyBatis-Plus auto-fill) +│ ├── interceptor/ # DataFilterInterceptor, DataScope +│ ├── utils/ # ExcelUtils, TimeUtils +│ └── validator/group/ # Cloud storage validator groups +└── modules/ + ├── job/ # Quartz scheduling + ├── log/ # Audit logs (operation, login, error) + ├── oss/ # Cloud file storage (Alibaba/Qiniu/Tencent) + ├── region/ # Geographic region tree + ├── security/ # Shiro auth, OAuth2 filter, login + ├── sys/ # System management (users, roles, menus, depts, dicts, params, alerts) + └── weather/ # Weather domain + ├── dailydata/ # Daily weather data CRUD, import, caching + ├── filescan/ # WatchService file scanning pipeline + └── station/ # Weather station management ``` -Tests use **JUnit 4** (`@RunWith(SpringRunner.class)`), not JUnit 5. Only 2 test files exist (Redis, dynamic datasource). No Maven wrapper (`mvnw`). Maven remote repository: Aliyun mirror. +## Service Layer Pattern -Launch from IntelliJ: -- `AdminApplication` (`system-admin/`) → port 8080, context `/system-admin` -- `GeneratorApplication` (`renren-generator/`) — commented out of build +All business modules follow this structure under `modules//`: -## Service layer pattern - -Two base classes in `system-common`: - -| Base | Purpose | -|---|---| -| `CrudService` | Generic CRUD: `page()`, `get()`, `save()`, `update()`, `delete()` | -| `BaseService` | Lighter base without DTO generic | - -Module convention: ``` -modules// -├── controller/ → @RestController, returns Result -├── dao/ → extends BaseMapper (MyBatis-Plus) -├── dto/ → request/query DTOs (often extends BaseEntity) -├── entity/ → @TableName JPA entity -├── service/ → interface extends CrudService/BaseService -│ └── impl/ → @Service implementation -├── excel/ → EasyExcel VO classes (optional) -└── vo/ → response VO classes (optional) +controller/ → @RestController, delegates to service interface +service/ → Interface extends CrudService +service/impl/ → Extends CrudServiceImpl, implements getWrapper() +dao/ → Interface extends BaseDao / BaseMapper +entity/ → Extends BaseEntity, maps to DB table +dto/ → Request/response DTOs +excel/ → @ExcelProperty-annotated beans for EasyExcel import/export ``` -Mapper XMLs: `src/main/resources/mapper//**/*.xml` +Entity-to-DTO conversion uses `ConvertUtils.sourceToTarget()`. -### Conventions +## Security / Auth -- Lombok used throughout: `@Data`, `@AllArgsConstructor`, `@Slf4j` are standard on entity/service classes. -- DTO/Entity/VO separation per module — request DTOs often extend `BaseEntity`. +- **Framework**: Apache Shiro with Jakarta-compatible artifacts (classifier: `jakarta`) +- **Token flow**: Client sends `token` header/param -> `Oauth2Filter` extracts it -> `Oauth2Realm` validates against `sys_user_token` table -> returns `UserDetail` (user + dept scope + permissions) +- **Public paths** (no auth): `/login`, `/captcha`, `/druid/**`, `/doc.html`, `/swagger/**`, `/v3/api-docs/**`, `/favicon.ico` +- **Password hashing**: Custom `BCryptPasswordEncoder` with `PasswordUtils` (configurable rounds via sys_params) +- **Async context**: `UserContextHolder` (ThreadLocal) preserves user context across async task boundaries (used in file import, scheduled tasks). `SecurityUser.getUser()` falls back to this when Shiro Subject is unavailable. -## PK & Auth +## Data Scoping (Dept-Based Row-Level Security) -- PK: `ASSIGN_ID` (Snowflake) via `IdUtil.getSnowflakeNextId()`. All entities extend `BaseEntity`. Exception: `WeatherStationEntity` uses `AUTO_INCREMENT`. -- Auth: Apache Shiro 1.12 (**Jakarta classifier**) + OAuth2 token. Login → `token` header. -- **Do not introduce Spring Security** — the project uses Shiro exclusively. +`DataFilterInterceptor` is a MyBatis-Plus `InnerInterceptor`. When a service method is annotated with `@DataFilter`, the `DataFilterAspect` injects a `DataScope` object containing a SQL filter clause into the query parameters. The interceptor then appends this WHERE clause to the SQL, restricting results to the user's authorized departments. -## Key cross-cutting mechanisms +Example service usage: +```java +@DataFilter +public PageData page(Map params) { + // SQL automatically filtered by dept scope +} +``` -| Mechanism | How | -|---|---| -| **Data permissions** | `@DataFilter` on controller → `DataFilterAspect` → MyBatis interceptor injects dept-based SQL | -| **Auto-fill** | `FieldMetaObjectHandler` fills creator/date via MyBatis-Plus. **Only works with `insert()`/`updateById()`** — batch inserts (e.g. `insertBatchMultiRow`) bypass auto-fill; fields must be set manually. | -| **Scheduled jobs** | Quartz. `schedule_job` table, implements `ITask`, `@Component("beanName")`. Jobs auto-register at startup via `JobCommandLineRunner`. Seed data: `testTask` (paused, every 30 min), `fileScanTask` (paused, every 5 min), `weatherSummarizeCacheTask` (**active**, daily 1 AM). | -| **File scanning** | `WatchService` (primary, background thread) + Quartz fallback (`FileScanTask`) + startup runner (`FileScanStartupRunner`). Files identified by MD5 hash. | -| **Excel import** | EasyExcel + async dual-pass via `WeatherDataImportManager`. Progress tracked in-memory (`ConcurrentHashMap`), lost on restart. | -| **API responses** | Always wrapped in `Result`. Frontend expects `code === 0` for success. | -| **Validation** | Hibernate Validator. XSS filter via `XssFilter`. i18n messages in `system-common/src/main/resources/i18n/validation.properties`. | +## File Scanning Pipeline -## Exception handling +`FileWatchServiceManager` (`modules/weather/filescan/`) monitors `{FILE_SCAN_ROOT_PATH}/receive/` using `java.nio.file.WatchService`: -Single `@RestControllerAdvice` handler in the admin module: +1. **Directory layout**: `receive//`, `display//`, `archive//` +2. **Detection**: WatchService detects `ENTRY_CREATE` and `ENTRY_MODIFY` events +3. **Wait**: `waitForFileReady()` polls file size stability + file lock to ensure write completion +4. **Dedup**: MD5 hash check against `weather_file_scan_record` table +5. **Parse**: `FileNameParser` extracts region, category, period from filename +6. **Store**: Record inserted; file copied to `display/`; old version moved to `archive/` +7. **Root files**: Files in `receive/` root (no dept subdir) are classified as "model forecast" with `deptId = null` -| Handler | Catches | Persists errors? | -|---|---|---| -| `CustomExceptionHandler` | `CommonException`, `DuplicateKeyException`, `UnauthorizedException`, generic `Exception` | **Yes** — saves to `SysLogErrorService` (IP, user-agent, URI, params, stack trace) | +`FileScanStartupRunner` (ApplicationRunner) triggers full directory scan + WatchService registration on startup. -Both return a generic error for caught `Exception` (not the exception message). `CommonException` uses i18n message lookup via `MessageUtils.getMessage(code)`. Error codes follow `int` scheme: 5 digits, first 2 = module, last 3 = business (e.g. `10001`-`10029`). +## Weather Daily Data -## Logging +Core table: `weather_daily_data` (entity: `WeatherDailyDataEntity`). Fields: stationId, observeDate, avgTemp, maxTemp/minTemp (with time), rainfall (20-20 and 08-08), relativeHumidity, atmospheres, wind (avg/max/extreme speed + direction + time), deptId. -- Logback-spring config in `system-admin/src/main/resources/logback-spring.xml`. Logger names use `io.renren` (fork legacy), **not** `com.weather`. -- Admin dev profile enables MyBatis SQL stdout logging (`StdOutImpl`). -- When adding `@Slf4j` to `com.weather.*` classes, add a `com.weather` level override or change the existing `io.renren` logger scope. +### Import Flow -## Redis & Docs +`WeatherDataImportManager` handles Excel uploads: +1. Save MultipartFile to temp file +2. Submit async task (`CompletableFuture`), returns taskId immediately +3. First pass: `EasyExcel.read()` with count listener to get total rows +4. Second pass: `WeatherDataListener` reads rows in batches, calls `insertBatch()` +5. `insertBatch()` resolves station-to-dept mapping, fills audit fields, uses `insertBatchMultiRow()` (custom MySQL multi-row INSERT) +6. Clears weather summarize Redis cache on completion +7. Progress tracked in-memory (`ConcurrentHashMap`) -- Redis: `project-options.redis.open: true` in dev YAML controls whether `RedisAspect` intercepts Redis calls (default `false` — Redis operations silently skipped when disabled). -- Knife4j: disabled by default (`knife4j.enable: false`). Docs at `/doc.html` when enabled. -- `RedisAspect` wraps `@RedisCache` annotations with channel publish for cache invalidation. +### Summarize Caching -## MyBatis-Plus gotchas +`WeatherSummarizeCacheTask` (Quartz job) precomputes "same month-day across years" summary data per station into Redis. Cache key: `weather:summarize:{month}:{day}`. On cache hit, `getCachedSummarize()` filters by station + year range. On miss, falls back to `selectSummarizeByMonthDay()` DB query. -- **Batch inserts bypass auto-fill** — `FieldMetaObjectHandler` only fires on `insert()`/`updateById()`. Custom batch methods must manually set `creator`, `createDate`, `updater`, `updateDate`, `deptId`. -- Column names with special characters (e.g. `rain_20_20`) require explicit `@TableField` annotations — MyBatis-Plus cannot auto-map them from camelCase. -- `typeAliasesPackage: com.weather.modules.*.entity` — all entity classes must reside under a `modules` sub-package. +## Alert / Notification System -## Weather domain +- **Storage**: `SysAlertEntity` in `sys_alert` table +- **Real-time push**: `SseAlertService` manages active SSE connections (`CopyOnWriteArraySet`), broadcasts on Spring events: + - `AlertCreatedEvent` -> SSE event `alert` + - `AlertWithdrawnEvent` -> SSE event `alert-withdrawn` + - `AlertDeletedEvent` -> SSE event `alert-deleted` +- **External sources**: `AlertSourcePollingTask` (Quartz) polls external services; `AlertSourceCollector` gathers results -Three sub-modules under `system-admin/.../modules/weather/`: +## Job Scheduling (Quartz) -| Module | Purpose | -|---|---| -| `dailydata/` | Daily observations, Excel batch import (async dual-pass), EasyExcel listener, summary export | -| `station/` | Weather station CRUD, linked to dept via `dept_id` | -| `filescan/` | File monitoring + serving. Format: `<地区>地区-<指标>.png` / `<地区>地区631信息.txt` | +Tables: `schedule_job`, `schedule_job_log`, plus standard `qrtz_*` tables. -### Weather data import flow +- `ScheduleConfig`: Quartz `SchedulerFactoryBean` configuration +- `ScheduleJob` entity: bean class, cron expression, params, status (PAUSE/NORMAL) +- `ScheduleUtils`: Create/update/delete/pause/resume Quartz triggers +- `JobCommandLineRunner`: On startup, restores all NORMAL-status jobs from DB +- `ITask` interface: `run(String params)` method -- all job classes implement this +- Concurrency control via `@DisallowConcurrentExecution` -1. **First pass**: `AnalysisEventListener` counts total rows. -2. **Second pass**: `WeatherDataListener` processes with batch insert (2000 records/batch). -3. Progress tracked in-memory via `ConcurrentHashMap` (`volatile` fields + `AtomicInteger`). -4. Runs on `CompletableFuture` with manual `UserContextHolder` propagation for security context. (Note: `UserContextHolder` is in `system-admin/.../security/user/`, not the empty `system-common/.../holder/` package.) -5. On completion, clears Redis summary cache (`weather:summarize:*`). +## Database -### Weather summarize cache +Schema: `system-admin/db/weather_data_system.sql` -`WeatherSummarizeCacheTask` (Quartz job) pre-computes daily historical summaries into Redis. Uses **MySQL-specific** SQL functions (`MONTH()`, `DAY()` on `observe_date`). Cache key: `weather:summarize:{month}:{day}`, non-expiring. Service checks cache first for queries spanning same month/day across years. +### Core Tables -### Station priority ordering - -`WeatherDailyDataServiceImpl.page()` uses custom `CASE WHEN` SQL to order stations belonging to the user's department + sub-departments first. - -## System alert module - -CRUD + polling notification system under `modules/sys/`: - -| Layer | File | -|---|---| -| Entity | `entity/SysAlertEntity.java` — `sys_alert` table, Snowflake PK | -| DAO | `dao/SysAlertDao.java` + `mapper/sys/SysAlertDao.xml` — `selectActiveAlerts`, `selectActiveAlertsSince`, `countBySource` | -| DTO | `dto/SysAlertDTO.java` — `publishTime`/`createDate`/`updateDate` read-only | -| VO | `vo/SysAlertVO.java` — `id` as String (mapped from Long) | -| Service | `service/SysAlertService.java` + `impl/` — `publishIfNotExists` (dedup by source), `withdraw` (soft), CRUD | -| Controller | `controller/SysAlertController.java` — polling: `GET active`, `GET active/since` (no auth); CRUD: `page`/`get`/`save`/`update`/`delete`/`withdraw` (Shiro permissions) | -| Enums | `enums/AlertLevelEnum.java` (info/warning/danger), `enums/AlertSourceTypeEnum.java` (manual/import/schedule/datasource) | - -### Data source extension - -| File | Role | -|---|---| -| `alert/AlertSource.java` | SPI interface: `getName()`, `check()` returning `AlertMessage` records | -| `alert/AlertSourceCollector.java` | `@Autowired(required=false) List` auto-discovery, calls `publishIfNotExists` for each | -| `alert/task/AlertSourcePollingTask.java` | Quartz job (`alertSourcePollingTask`), default paused in `schedule_job` | - -To add a new alert source: implement `AlertSource`, register as `@Component`, enable the polling task. +| Table | Purpose | +|-------|---------| +| `sys_user`, `sys_role`, `sys_menu`, `sys_dept` | RBAC | +| `sys_role_user`, `sys_role_menu`, `sys_role_data_scope` | RBAC associations | +| `sys_user_token` | Shiro auth tokens | +| `sys_dict_type`, `sys_dict_data` | Dictionary system | +| `sys_params` | Key-value system parameters | +| `sys_alert` | Alert/notification records | +| `weather_station` | Weather station registry | +| `weather_daily_data` | Daily meteorological observations | +| `weather_file_scan_record` | File tracking (receive -> display -> archive) | +| `sys_region` | Geographic region tree (province/city/county) | +| `schedule_job`, `schedule_job_log` | Quartz job definitions and execution history | +| `qrtz_*` | Quartz internal scheduler tables | +| `sys_log_operation`, `sys_log_login`, `sys_log_error` | Audit logs | +| `sys_oss` | Cloud storage object records | diff --git a/system-admin/db/weather_data_system.sql b/system-admin/db/weather_data_system.sql index 88c42d0..33eb2e4 100644 --- a/system-admin/db/weather_data_system.sql +++ b/system-admin/db/weather_data_system.sql @@ -11,7 +11,7 @@ Target Server Version : 80044 (8.0.44) File Encoding : 65001 - Date: 24/06/2026 13:44:11 + Date: 27/06/2026 22:38:33 */ SET NAMES utf8mb4; @@ -276,9 +276,9 @@ CREATE TABLE `schedule_job` ( -- ---------------------------- -- Records of schedule_job -- ---------------------------- -INSERT INTO `schedule_job` VALUES (1067246875800000076, 'testTask', '123456', '0 0/30 * * * ?', 0, '有参测试,多个参数使用json', 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-06-16 19:18:58'); -INSERT INTO `schedule_job` VALUES (2029504525156528130, 'fileScanTask', '', '0 0/5 * * * ?', 0, '文件扫描定时任务', 1067246875800000001, '2026-03-05 18:29:20', 1067246875800000001, '2026-06-24 12:14:59'); -INSERT INTO `schedule_job` VALUES (2069614094374367234, 'weatherSummarizeCacheTask', '', '0 0 1 * * ?', 1, '天气汇总缓存', 1067246875800000001, '2026-06-24 10:50:26', 1067246875800000001, '2026-06-24 12:14:51'); +INSERT INTO `schedule_job` VALUES (2029504525156528130, 'fileScanTask', '', '0 0/1 * * * ?', 1, '文件扫描定时任务,每分钟', 1067246875800000001, '2026-03-05 18:29:20', 1067246875800000001, '2026-06-27 19:02:10'); +INSERT INTO `schedule_job` VALUES (2069614094374367234, 'weatherSummarizeCacheTask', '', '0 0 1 * * ?', 1, '天气汇总缓存,每天凌晨1点', 1067246875800000001, '2026-06-24 10:50:26', 1067246875800000001, '2026-06-27 19:02:02'); +INSERT INTO `schedule_job` VALUES (2070458228203012097, 'alertSourcePollingTask', '', '0 */10 * * * ?', 1, '通知数据源轮询,每10分钟', 1067246875800000001, '2026-06-26 18:44:44', 1067246875800000001, '2026-06-26 18:44:44'); -- ---------------------------- -- Table structure for schedule_job_log @@ -302,6 +302,33 @@ CREATE TABLE `schedule_job_log` ( -- Records of schedule_job_log -- ---------------------------- +-- ---------------------------- +-- Table structure for sys_alert +-- ---------------------------- +DROP TABLE IF EXISTS `sys_alert`; +CREATE TABLE `sys_alert` ( + `id` bigint NOT NULL COMMENT 'Snowflake ID', + `level` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'info' COMMENT '级别: info|warning|danger', + `title` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '通知标题', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '通知正文', + `source_type` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'manual' COMMENT '来源: manual|import|schedule|datasource', + `source_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '业务关联ID', + `is_active` tinyint(1) NULL DEFAULT 1 COMMENT '1=有效 0=已撤回', + `publish_time` datetime NOT NULL COMMENT '发布时间', + `expire_time` datetime NULL DEFAULT NULL COMMENT '过期时间(null=永不过期)', + `creator` bigint NULL DEFAULT NULL COMMENT '创建人ID', + `create_date` datetime NULL DEFAULT NULL COMMENT '创建时间', + `updater` bigint NULL DEFAULT NULL COMMENT '更新人ID', + `update_date` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_active_time`(`is_active` ASC, `publish_time` ASC) USING BTREE, + INDEX `idx_source`(`source_type` ASC, `source_id` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统通知表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_alert +-- ---------------------------- + -- ---------------------------- -- Table structure for sys_dept -- ---------------------------- @@ -516,7 +543,7 @@ INSERT INTO `sys_menu` VALUES (1067246875800000014, 1067246875800000012, '查看 INSERT INTO `sys_menu` VALUES (1067246875800000015, 1067246875800000012, '新增', NULL, 'sys:dept:save', 1, NULL, 1, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); INSERT INTO `sys_menu` VALUES (1067246875800000016, 1067246875800000012, '修改', NULL, 'sys:dept:update', 1, NULL, 2, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); INSERT INTO `sys_menu` VALUES (1067246875800000017, 1067246875800000012, '删除', NULL, 'sys:dept:delete', 1, NULL, 3, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); -INSERT INTO `sys_menu` VALUES (1067246875800000025, 1067246875800000035, '菜单管理', 'sys/menu', NULL, 0, 'icon-unorderedlist', 0, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); +INSERT INTO `sys_menu` VALUES (1067246875800000025, 1067246875800000035, '菜单管理', 'sys/menu', NULL, 0, 'icon-unorderedlist', 1, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-06-26 18:49:40'); INSERT INTO `sys_menu` VALUES (1067246875800000026, 1067246875800000025, '查看', NULL, 'sys:menu:list,sys:menu:info', 1, NULL, 0, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); INSERT INTO `sys_menu` VALUES (1067246875800000027, 1067246875800000025, '新增', NULL, 'sys:menu:save', 1, NULL, 1, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); INSERT INTO `sys_menu` VALUES (1067246875800000028, 1067246875800000025, '修改', NULL, 'sys:menu:update', 1, NULL, 2, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); @@ -531,14 +558,14 @@ INSERT INTO `sys_menu` VALUES (1067246875800000036, 1067246875800000030, '暂停 INSERT INTO `sys_menu` VALUES (1067246875800000037, 1067246875800000030, '恢复', NULL, 'sys:schedule:resume', 1, NULL, 5, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); INSERT INTO `sys_menu` VALUES (1067246875800000038, 1067246875800000030, '立即执行', NULL, 'sys:schedule:run', 1, NULL, 6, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); INSERT INTO `sys_menu` VALUES (1067246875800000039, 1067246875800000030, '日志列表', NULL, 'sys:schedule:log', 1, NULL, 7, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); -INSERT INTO `sys_menu` VALUES (1067246875800000040, 1067246875800000035, '参数管理', 'sys/params', '', 0, 'icon-fileprotect', 1, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); +INSERT INTO `sys_menu` VALUES (1067246875800000040, 1067246875800000035, '参数管理', 'sys/params', '', 0, 'icon-fileprotect', 4, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-06-26 18:49:56'); INSERT INTO `sys_menu` VALUES (1067246875800000041, 1067246875800000035, '字典管理', 'sys/dict-type', NULL, 0, 'icon-golden-fill', 2, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); INSERT INTO `sys_menu` VALUES (1067246875800000042, 1067246875800000041, '查看', NULL, 'sys:dict:page,sys:dict:info', 1, NULL, 0, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); INSERT INTO `sys_menu` VALUES (1067246875800000043, 1067246875800000041, '新增', NULL, 'sys:dict:save', 1, NULL, 1, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); INSERT INTO `sys_menu` VALUES (1067246875800000044, 1067246875800000041, '修改', NULL, 'sys:dict:update', 1, NULL, 2, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); INSERT INTO `sys_menu` VALUES (1067246875800000045, 1067246875800000041, '删除', NULL, 'sys:dict:delete', 1, NULL, 3, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); INSERT INTO `sys_menu` VALUES (1067246875800000046, 0, '日志管理', NULL, NULL, 0, 'icon-container', 5, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 17:28:42'); -INSERT INTO `sys_menu` VALUES (1067246875800000047, 1067246875800000035, '文件上传', 'oss/oss', 'sys:oss:all', 0, 'icon-upload', 4, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); +INSERT INTO `sys_menu` VALUES (1067246875800000047, 1067246875800000035, '文件上传', 'oss/oss', 'sys:oss:all', 0, 'icon-upload', 5, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-06-26 18:50:12'); INSERT INTO `sys_menu` VALUES (1067246875800000048, 1067246875800000046, '登录日志', 'sys/log-login', 'sys:log:login', 0, 'icon-filedone', 0, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); INSERT INTO `sys_menu` VALUES (1067246875800000049, 1067246875800000046, '操作日志', 'sys/log-operation', 'sys:log:operation', 0, 'icon-solution', 1, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); INSERT INTO `sys_menu` VALUES (1067246875800000050, 1067246875800000046, '异常日志', 'sys/log-error', 'sys:log:error', 0, 'icon-file-exception', 2, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); @@ -573,12 +600,19 @@ INSERT INTO `sys_menu` VALUES (2029484183805616134, 2029484183805616129, '导出 INSERT INTO `sys_menu` VALUES (2063582074925912065, 2029484183805616129, '汇总', '', 'dailyweather:weatherdailydata:query', 1, '', 5, 1067246875800000001, '2026-06-07 19:21:21', 1067246875800000001, '2026-06-07 19:21:29'); INSERT INTO `sys_menu` VALUES (2064677221264756738, 2029421667205255169, '列表', '', 'station:weatherstation:list', 1, '', 5, 1067246875800000001, '2026-06-10 19:53:04', 1067246875800000001, '2026-06-10 19:53:04'); INSERT INTO `sys_menu` VALUES (2065416928948871170, 0, '气象模块', '', '', 0, 'icon-ungroup', 0, 1067246875800000001, '2026-06-12 20:52:24', 1067246875800000001, '2026-06-12 21:14:12'); -INSERT INTO `sys_menu` VALUES (2065422543658115073, 2065416928948871170, '实时监测', '/weather/realtime-monitoring', '', 0, 'icon-time-circle-fill', 0, 1067246875800000001, '2026-06-12 21:14:43', 1067246875800000001, '2026-06-17 19:14:06'); +INSERT INTO `sys_menu` VALUES (2065422543658115073, 2065416928948871170, '实时监测', 'weather/realtime-monitoring', '', 0, 'icon-time-circle-fill', 0, 1067246875800000001, '2026-06-12 21:14:43', 1067246875800000001, '2026-06-26 18:52:32'); INSERT INTO `sys_menu` VALUES (2065422609408024577, 2065416928948871170, '回波预测', 'weather/prediction', '', 0, 'icon-earth', 1, 1067246875800000001, '2026-06-12 21:14:58', 1067246875800000001, '2026-06-17 20:17:17'); INSERT INTO `sys_menu` VALUES (2065422708557176834, 2065416928948871170, '631气象信息', 'weather/631weather-data', '', 0, 'icon-pic-left', 2, 1067246875800000001, '2026-06-12 21:15:22', 1067246875800000001, '2026-06-17 22:07:37'); INSERT INTO `sys_menu` VALUES (2067153650086825985, 2065422543658115073, '获取实时监测', '', 'filescan:record:tree', 1, '', 0, 1067246875800000001, '2026-06-17 15:53:31', 1067246875800000001, '2026-06-17 15:53:31'); INSERT INTO `sys_menu` VALUES (2067153761810501634, 2065422609408024577, '获取模式预测', '', 'filescan:record:model:list', 1, '', 0, 1067246875800000001, '2026-06-17 15:53:57', 1067246875800000001, '2026-06-17 20:17:47'); INSERT INTO `sys_menu` VALUES (2067203040717520898, 2065416928948871170, '展示文件', '', 'filescan:record:display', 1, '', 0, 1067246875800000001, '2026-06-17 19:09:46', 1067246875800000001, '2026-06-17 19:09:46'); +INSERT INTO `sys_menu` VALUES (2070459758339641346, 1067246875800000035, '通知管理', 'sys/system-alert', '', 0, 'icon-sound-fill', 0, 1067246875800000001, '2026-06-26 18:50:48', 1067246875800000001, '2026-06-26 18:52:17'); +INSERT INTO `sys_menu` VALUES (2070462059406118913, 2070459758339641346, '查询', '', 'sys:alert:page', 1, '', 0, 1067246875800000001, '2026-06-26 18:59:57', 1067246875800000001, '2026-06-26 18:59:57'); +INSERT INTO `sys_menu` VALUES (2070462181372284929, 2070459758339641346, '详情', '', 'sys:alert:info', 1, '', 1, 1067246875800000001, '2026-06-26 19:00:26', 1067246875800000001, '2026-06-26 19:00:26'); +INSERT INTO `sys_menu` VALUES (2070462282153021442, 2070459758339641346, '新增', '', 'sys:alert:save', 1, '', 2, 1067246875800000001, '2026-06-26 19:00:50', 1067246875800000001, '2026-06-26 19:00:50'); +INSERT INTO `sys_menu` VALUES (2070462368132059138, 2070459758339641346, '修改', '', 'sys:alert:update', 1, '', 3, 1067246875800000001, '2026-06-26 19:01:11', 1067246875800000001, '2026-06-26 19:01:11'); +INSERT INTO `sys_menu` VALUES (2070462442044084226, 2070459758339641346, '删除', '', 'sys:alert:delete', 1, '', 4, 1067246875800000001, '2026-06-26 19:01:28', 1067246875800000001, '2026-06-26 19:01:28'); +INSERT INTO `sys_menu` VALUES (2070462786237059073, 2070459758339641346, '撤回', '', 'sys:alert:retract', 1, '', 5, 1067246875800000001, '2026-06-26 19:02:50', 1067246875800000001, '2026-06-26 19:02:50'); -- ---------------------------- -- Table structure for sys_oss @@ -619,8 +653,8 @@ CREATE TABLE `sys_params` ( -- ---------------------------- -- Records of sys_params -- ---------------------------- -INSERT INTO `sys_params` VALUES (1067246875800000073, 'CLOUD_STORAGE_CONFIG_KEY', '{\"type\":1,\"qiniuDomain\":\"http://test.oss.renren.io\",\"qiniuPrefix\":\"upload\",\"qiniuAccessKey\":\"NrgMfABZxWLo5B-YYSjoE8-AZ1EISdi1Z3ubLOeZ\",\"qiniuSecretKey\":\"uIwJHevMRWU0VLxFvgy0tAcOdGqasdtVlJkdy6vV\",\"qiniuBucketName\":\"renren-oss\",\"aliyunDomain\":\"\",\"aliyunPrefix\":\"\",\"aliyunEndPoint\":\"\",\"aliyunAccessKeyId\":\"\",\"aliyunAccessKeySecret\":\"\",\"aliyunBucketName\":\"\",\"qcloudDomain\":\"\",\"qcloudPrefix\":\"\",\"qcloudSecretId\":\"\",\"qcloudSecretKey\":\"\",\"qcloudBucketName\":\"\"}', 0, '云存储配置信息', 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); -INSERT INTO `sys_params` VALUES (2066834685586268161, 'FILE_SCAN_ROOT_PATH', 'D:/weather-data-files', 1, '气象文件扫描根路径', 1067246875800000001, '2026-06-16 18:46:04', 1067246875800000001, '2026-06-24 12:33:37'); +INSERT INTO `sys_params` VALUES (1067246875800000073, 'CLOUD_STORAGE_CONFIG_KEY', '{\"type\":1,\"qiniuDomain\":\"http://test.oss.com\",\"qiniuPrefix\":\"upload\",\"qiniuAccessKey\":\"AccessKey\",\"qiniuSecretKey\":\"SecretKey\",\"qiniuBucketName\":\"test-oss\",\"aliyunDomain\":\"\",\"aliyunPrefix\":\"\",\"aliyunEndPoint\":\"\",\"aliyunAccessKeyId\":\"\",\"aliyunAccessKeySecret\":\"\",\"aliyunBucketName\":\"\",\"qcloudDomain\":\"\",\"qcloudPrefix\":\"\",\"qcloudSecretId\":\"\",\"qcloudSecretKey\":\"\",\"qcloudBucketName\":\"\"}', 0, '云存储配置信息', 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); +INSERT INTO `sys_params` VALUES (2066834685586268161, 'FILE_SCAN_ROOT_PATH', 0, 1, '气象文件扫描根路径', 1067246875800000001, '2026-06-16 18:46:04', 1067246875800000001, '2026-06-27 18:59:36'); -- ---------------------------- -- Table structure for sys_region @@ -4106,7 +4140,7 @@ CREATE TABLE `sys_user` ( -- ---------------------------- -- Records of sys_user -- ---------------------------- -INSERT INTO `sys_user` VALUES (1067246875800000001, 'admin', '$2a$10$012Kx2ba5jzqr9gLlG4MX.bnQJTD9UWqF57XDo2N3.fPtLne02u/m', '管理员', NULL, 0, 'root@renren.io', '13612345678', 1067246875800000066, 1, 1, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); +INSERT INTO `sys_user` VALUES (1067246875800000001, 'admin', '$2a$10$012Kx2ba5jzqr9gLlG4MX.bnQJTD9UWqF57XDo2N3.fPtLne02u/m', '管理员', NULL, 0, 'root@weather.com', '13111111111', 1067246875800000066, 1, 1, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04'); -- ---------------------------- -- Table structure for sys_user_token diff --git a/system-admin/src/main/java/com/weather/modules/job/config/ScheduleConfig.java b/system-admin/src/main/java/com/weather/modules/job/config/ScheduleConfig.java index 8cb1a0b..e69de29 100644 --- a/system-admin/src/main/java/com/weather/modules/job/config/ScheduleConfig.java +++ b/system-admin/src/main/java/com/weather/modules/job/config/ScheduleConfig.java @@ -1,66 +0,0 @@ -///** -// * Copyright (c) 2018 人人开源 All rights reserved. -// * -// * https://www.renren.io -// * -// * 版权所有,侵权必究! -// */ -// -//package io.weather.modules.job.config; -// -//import org.springframework.context.annotation.Bean; -//import org.springframework.context.annotation.Configuration; -//import org.springframework.scheduling.quartz.SchedulerFactoryBean; -// -//import javax.sql.DataSource; -//import java.util.Properties; -// -///** -// * 定时任务配置(备注:集群需要打开注释) -// * -// * @author 123 -// */ -//@Configuration -//public class ScheduleConfig { -// -// @Bean -// public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) { -// SchedulerFactoryBean factory = new SchedulerFactoryBean(); -// factory.setDataSource(dataSource); -// -// //quartz参数 -// Properties prop = new Properties(); -// prop.put("org.quartz.scheduler.instanceName", "RenrenScheduler"); -// prop.put("org.quartz.scheduler.instanceId", "AUTO"); -// //线程池配置 -// prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); -// prop.put("org.quartz.threadPool.threadCount", "20"); -// prop.put("org.quartz.threadPool.threadPriority", "5"); -// //JobStore配置 -// prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore"); -// //集群配置 -// prop.put("org.quartz.jobStore.isClustered", "true"); -// prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000"); -// prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1"); -// -// prop.put("org.quartz.jobStore.misfireThreshold", "12000"); -// prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_"); -// prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?"); -// -// //PostgreSQL数据库,需要打开此注释 -// //prop.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate"); -// -// factory.setQuartzProperties(prop); -// -// factory.setSchedulerName("RenrenScheduler"); -// //延时启动 -// factory.setStartupDelay(30); -// factory.setApplicationContextSchedulerContextKey("applicationContextKey"); -// //可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 -// factory.setOverwriteExistingJobs(true); -// //设置自动启动,默认为true -// factory.setAutoStartup(true); -// -// return factory; -// } -//} diff --git a/system-admin/src/main/java/com/weather/modules/job/utils/ScheduleJob.java b/system-admin/src/main/java/com/weather/modules/job/utils/ScheduleJob.java index 7a4614d..57d4f34 100644 --- a/system-admin/src/main/java/com/weather/modules/job/utils/ScheduleJob.java +++ b/system-admin/src/main/java/com/weather/modules/job/utils/ScheduleJob.java @@ -8,6 +8,7 @@ import com.weather.common.utils.SpringContextUtils; import com.weather.modules.job.entity.ScheduleJobEntity; import com.weather.modules.job.entity.ScheduleJobLogEntity; import com.weather.modules.job.service.ScheduleJobLogService; +import org.quartz.DisallowConcurrentExecution; import org.quartz.JobExecutionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,6 +23,7 @@ import java.util.Date; * * @author 123 */ +@DisallowConcurrentExecution public class ScheduleJob extends QuartzJobBean { private Logger logger = LoggerFactory.getLogger(getClass()); diff --git a/system-admin/src/main/java/com/weather/modules/security/password/PasswordUtils.java b/system-admin/src/main/java/com/weather/modules/security/password/PasswordUtils.java index 51dd111..7327392 100644 --- a/system-admin/src/main/java/com/weather/modules/security/password/PasswordUtils.java +++ b/system-admin/src/main/java/com/weather/modules/security/password/PasswordUtils.java @@ -1,20 +1,3 @@ -/** - * Copyright 2018 人人开源 https://www.renren.io - *

- * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - *

- * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - *

- * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - package com.weather.modules.security.password; /** diff --git a/system-admin/src/main/java/com/weather/modules/sys/dto/SysDeptDTO.java b/system-admin/src/main/java/com/weather/modules/sys/dto/SysDeptDTO.java index 46b990e..2f912ab 100644 --- a/system-admin/src/main/java/com/weather/modules/sys/dto/SysDeptDTO.java +++ b/system-admin/src/main/java/com/weather/modules/sys/dto/SysDeptDTO.java @@ -15,8 +15,9 @@ import jakarta.validation.constraints.Null; import lombok.Data; import lombok.EqualsAndHashCode; -import java.io.Serializable; +import java.util.ArrayList; import java.util.Date; +import java.util.List; /** * 部门管理 @@ -25,9 +26,9 @@ import java.util.Date; * @since 1.0.0 */ @Data -@EqualsAndHashCode(callSuper = true) +@EqualsAndHashCode @Schema(title = "部门管理") -public class SysDeptDTO extends TreeNode implements Serializable { +public class SysDeptDTO implements TreeNode { private static final long serialVersionUID = 1L; @Schema(title = "id") @@ -39,6 +40,9 @@ public class SysDeptDTO extends TreeNode implements Serializable { @NotNull(message="{sysdept.pid.require}", groups = DefaultGroup.class) private Long pid; + @Schema(title = "子部门列表") + private List children = new ArrayList<>(); + @Schema(title = "部门名称") @NotBlank(message="{sysdept.name.require}", groups = DefaultGroup.class) private String name; diff --git a/system-admin/src/main/java/com/weather/modules/sys/dto/SysMenuDTO.java b/system-admin/src/main/java/com/weather/modules/sys/dto/SysMenuDTO.java index 96b1cc1..46c9066 100644 --- a/system-admin/src/main/java/com/weather/modules/sys/dto/SysMenuDTO.java +++ b/system-admin/src/main/java/com/weather/modules/sys/dto/SysMenuDTO.java @@ -16,8 +16,9 @@ import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.validator.constraints.Range; -import java.io.Serializable; +import java.util.ArrayList; import java.util.Date; +import java.util.List; /** * 菜单管理 @@ -26,9 +27,9 @@ import java.util.Date; * @since 1.0.0 */ @Data -@EqualsAndHashCode(callSuper = true) +@EqualsAndHashCode @Schema(title = "菜单管理") -public class SysMenuDTO extends TreeNode implements Serializable { +public class SysMenuDTO implements TreeNode { private static final long serialVersionUID = 1L; @Schema(title = "id") @@ -40,6 +41,9 @@ public class SysMenuDTO extends TreeNode implements Serializable { @NotNull(message="{sysmenu.pid.require}", groups = DefaultGroup.class) private Long pid; + @Schema(title = "子菜单列表") + private List children = new ArrayList<>(); + @Schema(title = "菜单名称") @NotBlank(message="sysmenu.name.require", groups = DefaultGroup.class) private String name; diff --git a/system-admin/src/main/java/com/weather/modules/sys/entity/DictData.java b/system-admin/src/main/java/com/weather/modules/sys/entity/DictData.java index 1909008..12a392d 100644 --- a/system-admin/src/main/java/com/weather/modules/sys/entity/DictData.java +++ b/system-admin/src/main/java/com/weather/modules/sys/entity/DictData.java @@ -1,11 +1,3 @@ -/** - * Copyright (c) 2019 人人开源 All rights reserved. - *

- * https://www.renren.io - *

- * 版权所有,侵权必究! - */ - package com.weather.modules.sys.entity; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/system-admin/src/main/java/com/weather/modules/sys/entity/DictType.java b/system-admin/src/main/java/com/weather/modules/sys/entity/DictType.java index b46e4e4..f545e7a 100644 --- a/system-admin/src/main/java/com/weather/modules/sys/entity/DictType.java +++ b/system-admin/src/main/java/com/weather/modules/sys/entity/DictType.java @@ -1,11 +1,3 @@ -/** - * Copyright (c) 2019 人人开源 All rights reserved. - *

- * https://www.renren.io - *

- * 版权所有,侵权必究! - */ - package com.weather.modules.sys.entity; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/system-admin/src/main/java/com/weather/modules/weather/dailydata/WeatherDataListener.java b/system-admin/src/main/java/com/weather/modules/weather/dailydata/WeatherDataListener.java index e32fd5a..8fcef1b 100644 --- a/system-admin/src/main/java/com/weather/modules/weather/dailydata/WeatherDataListener.java +++ b/system-admin/src/main/java/com/weather/modules/weather/dailydata/WeatherDataListener.java @@ -39,7 +39,7 @@ public class WeatherDataListener extends AnalysisEventListener { try { LocalDate localDate = LocalDate.of(data.getYear(), data.getMonth(), data.getDay()); - entity.setObserveDate(Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant())); + entity.setObserveDate(Date.from(localDate.atStartOfDay(ZoneId.of("GMT+8")).toInstant())); } catch (Exception e) { log.warn("第{}行日期转换失败: {}年{}月{}日,已跳过", context.readRowHolder().getRowIndex(), data.getYear(), data.getMonth(), data.getDay()); diff --git a/system-admin/src/main/java/com/weather/modules/weather/dailydata/task/WeatherSummarizeCacheTask.java b/system-admin/src/main/java/com/weather/modules/weather/dailydata/task/WeatherSummarizeCacheTask.java index bf4eb91..decda7d 100644 --- a/system-admin/src/main/java/com/weather/modules/weather/dailydata/task/WeatherSummarizeCacheTask.java +++ b/system-admin/src/main/java/com/weather/modules/weather/dailydata/task/WeatherSummarizeCacheTask.java @@ -55,7 +55,7 @@ public class WeatherSummarizeCacheTask implements ITask { .collect(Collectors.groupingBy(dto -> String.valueOf(dto.getStationId()))); String key = RedisKeys.getWeatherSummarizeKey(month, day); - redisUtils.set(key, grouped, RedisUtils.NOT_EXPIRE); + redisUtils.set(key, grouped, RedisUtils.HOUR_SIX_EXPIRE); log.info("天气汇总缓存刷新完成,共 {} 条记录,{} 个站点", list.size(), grouped.size()); } catch (Exception e) { diff --git a/system-admin/src/main/java/com/weather/modules/weather/dailydata/vo/WeatherExcelVO.java b/system-admin/src/main/java/com/weather/modules/weather/dailydata/vo/WeatherExcelVO.java index 5cfcd58..cb781d3 100644 --- a/system-admin/src/main/java/com/weather/modules/weather/dailydata/vo/WeatherExcelVO.java +++ b/system-admin/src/main/java/com/weather/modules/weather/dailydata/vo/WeatherExcelVO.java @@ -61,10 +61,10 @@ public class WeatherExcelVO { @ExcelProperty("最大风速出现时间") private Integer maxWindTime; - @ExcelProperty("极大风速的风向(角度)") + @ExcelProperty("极大风速") private BigDecimal extremeWindSpeed; - @ExcelProperty("极大风速") + @ExcelProperty("极大风速的风向(角度)") private Integer extremeWindDirection; @ExcelProperty("极大风速出现时间") diff --git a/system-admin/src/main/resources/application-dev.yml b/system-admin/src/main/resources/application-dev.yml index 5087b7f..0cfad29 100644 --- a/system-admin/src/main/resources/application-dev.yml +++ b/system-admin/src/main/resources/application-dev.yml @@ -2,7 +2,7 @@ spring: data: redis: database: 0 - host: 127.0.0.1 + host: 192.168.2.186 port: 6379 password: # 密码(默认为空) timeout: 6000ms # 连接超时时长(毫秒) @@ -16,7 +16,7 @@ spring: druid: #MySQL driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/weather_data_system?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true + url: jdbc:mysql://192.168.2.186:3306/weather_data_system?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true username: root password: root initial-size: 10 @@ -51,17 +51,3 @@ spring: project-options: redis: open: true - -##多数据源的配置,需要引用renren-dynamic-datasource -#dynamic: -# datasource: -# slave1: -# driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver -# url: jdbc:sqlserver://123456:1433;DatabaseName=renren_security -# username: sa -# password: 123456 -# slave2: -# driver-class-name: org.postgresql.Driver -# url: jdbc:postgresql://123456:5432/renren_security -# username: postgres -# password: 123456 diff --git a/system-admin/src/main/resources/application-prod.yml b/system-admin/src/main/resources/application-prod.yml index 777b81c..05fffe2 100644 --- a/system-admin/src/main/resources/application-prod.yml +++ b/system-admin/src/main/resources/application-prod.yml @@ -2,7 +2,7 @@ spring: data: redis: database: 0 - host: 127.0.0.1 + host: 192.168.2.186 port: 6379 password: # 生产环境请设置密码 timeout: 6000ms # 连接超时时长(毫秒) @@ -16,7 +16,7 @@ spring: druid: #MySQL driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/weather_data_system?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true + url: jdbc:mysql://192.168.2.186:3306/weather_data_system?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true username: weather password: 123456 initial-size: 10 diff --git a/system-admin/src/main/resources/application-test.yml b/system-admin/src/main/resources/application-test.yml index d073e11..0f233d0 100644 --- a/system-admin/src/main/resources/application-test.yml +++ b/system-admin/src/main/resources/application-test.yml @@ -2,7 +2,7 @@ spring: data: redis: database: 0 - host: 127.0.0.1 + host: 192.168.2.186 port: 6379 password: # 密码(默认为空) timeout: 6000ms # 连接超时时长(毫秒) @@ -16,7 +16,7 @@ spring: druid: #MySQL driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/weather_data_system?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true + url: jdbc:mysql://192.168.2.186:3306/weather_data_system?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true username: weather password: 123456 initial-size: 10 diff --git a/system-admin/src/main/resources/application.yml b/system-admin/src/main/resources/application.yml index d8f1552..6728cf1 100644 --- a/system-admin/src/main/resources/application.yml +++ b/system-admin/src/main/resources/application.yml @@ -5,7 +5,7 @@ server: threads: max: 1000 min-spare: 30 - port: 8080 + port: 48080 servlet: context-path: /system-admin session: diff --git a/system-admin/src/main/resources/logback-spring.xml b/system-admin/src/main/resources/logback-spring.xml index cda96b5..7710f52 100644 --- a/system-admin/src/main/resources/logback-spring.xml +++ b/system-admin/src/main/resources/logback-spring.xml @@ -8,14 +8,14 @@ - + - + \ No newline at end of file diff --git a/system-common/CLAUDE.md b/system-common/CLAUDE.md index 1bf56b7..4950081 100644 --- a/system-common/CLAUDE.md +++ b/system-common/CLAUDE.md @@ -1,20 +1,69 @@ -# CLAUDE.md — system-common +# CLAUDE.md - system-common -Shared Java library used by all backend modules. +This file provides guidance to Claude Code when working in the system-common module. ---- +## Purpose -## Service layer pattern +Shared library module used by all other backend modules. Provides base classes, utilities, and cross-cutting concerns. This module has no main class and is not runnable on its own. -Two base classes defined here: +## Base Class Hierarchy -| Base | Purpose | -|---|---| -| `CrudService` | Generic CRUD: `page()`, `get()`, `save()`, `update()`, `delete()` | -| `BaseService` | Lighter base without DTO generic | +### BaseEntity (`com.weather.common.entity.BaseEntity`) -These are used by all service implementations in `system-admin` and other modules. See `system-admin/CLAUDE.md` for the full module convention. +All entities must extend this. Provides: +- `id` (Long, `@TableId`) +- `creator` (Long, `@TableField(fill = INSERT)`) +- `createDate` (Date, `@TableField(fill = INSERT)`) -## i18n validation messages +### BaseDao (`com.weather.common.dao.BaseDao`) -Located at `src/main/resources/i18n/validation.properties`. Hibernate Validator messages used by `@RestControllerAdvice` exception handler in `system-admin`. Error codes follow `int` scheme: 5 digits, first 2 = module, last 3 = business (e.g. `10001`-`10029`). +Extends MyBatis-Plus `BaseMapper`. Provides `getById(Long id)` as an alias. + +### Service Layer + +``` +BaseService # tag interface + └── CrudService # page/list/get/save/update/delete + └── BaseServiceImpl # dao injection + insert/updateById wrappers + └── CrudServiceImpl # generic CRUD with getWrapper() +``` + +`CrudServiceImpl` is the key base class for all business services. Subclasses only need to implement `getWrapper(Map params)` to define query conditions. Entity-to-DTO conversion uses `ConvertUtils.sourceToTarget()`. + +## Redis + +- `RedisConfig`: Creates `RedisTemplate` with Jackson JSON serialization +- `RedisUtils`: Wraps `RedisTemplate` operations -- `set/get/delete/deleteByPattern/hGet/hSet/hDelete/expire` +- `RedisKeys`: Static factory for standardized key names (e.g. `getWeatherSummarizeKey()`, `getWeatherSummarizePattern()`) +- `RedisAspect`: AOP aspect that logs Redis operation errors + +## Validation + +- `ValidatorUtils`: Bean Validation wrapper using Jakarta Validator +- `AssertUtils`: Assertion helpers that throw `CommonException` on failure +- `group/`: Validation groups -- `AddGroup`, `UpdateGroup`, `DefaultGroup` + +## XSS Protection + +- `XssFilter`: Servlet filter that wraps requests with `XssHttpServletRequestWrapper` +- `XssUtils`: HTML entity encoding using Jsoup + +## Exception Handling + +- `CommonException`: Application-level runtime exception with error code +- `ExceptionUtils`: Factory methods for creating typed exceptions +- `ErrorCode`: Interface with error code constants + +## Utilities + +- `ConvertUtils`: Bean copy with recursive conversion support for nested objects +- `DateUtils`: Date parsing/formatting +- `JsonUtils`: JSON serialization via Jackson +- `TreeUtils`: Build tree structures from flat lists (used for menus, depts, regions) +- `HttpContextUtils`: Servlet request/response helpers +- `IpUtils`: Extract client IP from request +- `MessageUtils`: i18n message resolution +- `SpringContextUtils`: Access Spring ApplicationContext statically +- `Result`: Standard API response wrapper with `code`, `msg`, `data` +- `PageData`: Paginated response with `total`, `list` +- `TreeNode`: Tree node interface with `getId/setId/getPid/setPid/getChildren/setChildren` diff --git a/system-common/src/main/java/com/weather/common/utils/TreeNode.java b/system-common/src/main/java/com/weather/common/utils/TreeNode.java index 04f78a9..efaa6ed 100644 --- a/system-common/src/main/java/com/weather/common/utils/TreeNode.java +++ b/system-common/src/main/java/com/weather/common/utils/TreeNode.java @@ -2,30 +2,35 @@ package com.weather.common.utils; -import lombok.Data; import java.io.Serializable; -import java.util.ArrayList; import java.util.List; /** - * 树节点,所有需要实现树节点的,都需要继承该类 + * 树节点接口,所有需要实现树节点的,都需要实现该接口 * * @author 123 * @since 1.0.0 */ - @Data -public class TreeNode implements Serializable { - private static final long serialVersionUID = 1L; +public interface TreeNode extends Serializable { + /** * 主键 */ - private Long id; + Long getId(); + + void setId(Long id); + /** * 上级ID */ - private Long pid; + Long getPid(); + + void setPid(Long pid); + /** * 子节点列表 */ - private List children = new ArrayList<>(); + List getChildren(); + + void setChildren(List children); } \ No newline at end of file diff --git a/system-common/src/main/java/com/weather/common/utils/TreeUtils.java b/system-common/src/main/java/com/weather/common/utils/TreeUtils.java index 6f14c27..493a0c1 100644 --- a/system-common/src/main/java/com/weather/common/utils/TreeUtils.java +++ b/system-common/src/main/java/com/weather/common/utils/TreeUtils.java @@ -20,7 +20,7 @@ public class TreeUtils { /** * 根据pid,构建树节点 */ - public static List build(List treeNodes, Long pid) { + public static > List build(List treeNodes, Long pid) { //pid不能为空 AssertUtils.isNull(pid, "pid"); @@ -37,7 +37,7 @@ public class TreeUtils { /** * 查找子节点 */ - private static T findChildren(List treeNodes, T rootNode) { + private static > T findChildren(List treeNodes, T rootNode) { for(T treeNode : treeNodes) { if(rootNode.getId().equals(treeNode.getPid())) { rootNode.getChildren().add(findChildren(treeNodes, treeNode)); @@ -49,7 +49,7 @@ public class TreeUtils { /** * 构建树节点 */ - public static List build(List treeNodes) { + public static > List build(List treeNodes) { List result = new ArrayList<>(); //list转map diff --git a/system-common/src/main/java/com/weather/common/validator/ValidatorUtils.java b/system-common/src/main/java/com/weather/common/validator/ValidatorUtils.java index 707b0b6..795b9cd 100644 --- a/system-common/src/main/java/com/weather/common/validator/ValidatorUtils.java +++ b/system-common/src/main/java/com/weather/common/validator/ValidatorUtils.java @@ -6,12 +6,11 @@ import com.weather.common.exception.CommonException; import jakarta.validation.ConstraintViolation; import jakarta.validation.Validation; import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator; -import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.validation.beanvalidation.MessageSourceResourceBundleLocator; -import java.util.Locale; import java.util.Set; /** @@ -23,11 +22,16 @@ import java.util.Set; */ public class ValidatorUtils { - private static ResourceBundleMessageSource getMessageSource() { - ResourceBundleMessageSource bundleMessageSource = new ResourceBundleMessageSource(); - bundleMessageSource.setDefaultEncoding("UTF-8"); - bundleMessageSource.setBasenames("i18n/validation"); - return bundleMessageSource; + private static final ValidatorFactory VALIDATOR_FACTORY; + + static { + ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); + messageSource.setDefaultEncoding("UTF-8"); + messageSource.setBasenames("i18n/validation"); + + VALIDATOR_FACTORY = Validation.byDefaultProvider().configure().messageInterpolator( + new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(messageSource))) + .buildValidatorFactory(); } /** @@ -37,14 +41,11 @@ public class ValidatorUtils { */ public static void validateEntity(Object object, Class... groups) throws CommonException { - Locale.setDefault(LocaleContextHolder.getLocale()); - Validator validator = Validation.byDefaultProvider().configure().messageInterpolator( - new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(getMessageSource()))) - .buildValidatorFactory().getValidator(); + Validator validator = VALIDATOR_FACTORY.getValidator(); Set> constraintViolations = validator.validate(object, groups); if (!constraintViolations.isEmpty()) { - ConstraintViolation constraint = constraintViolations.iterator().next(); + ConstraintViolation constraint = constraintViolations.iterator().next(); throw new CommonException(constraint.getMessage()); } } diff --git a/system-dynamic-datasource/CLAUDE.md b/system-dynamic-datasource/CLAUDE.md new file mode 100644 index 0000000..426ec73 --- /dev/null +++ b/system-dynamic-datasource/CLAUDE.md @@ -0,0 +1,65 @@ +# CLAUDE.md - system-dynamic-datasource + +This file provides guidance to Claude Code when working in the system-dynamic-datasource module. + +## Purpose + +Provides dynamic multi-datasource switching at runtime using Spring's `AbstractRoutingDataSource`. This module has no main class and is not runnable on its own. + +## Architecture + +``` +@DataSource (annotation) + ↓ triggers +DataSourceAspect (AOP around advice) + ↓ sets/clears +DynamicContextHolder (ThreadLocal>) + ↓ peek() returns key to +DynamicDataSource (extends AbstractRoutingDataSource) + ↓ routes to +Multiple DataSources (configured in DynamicDataSourceProperties) +``` + +## Key Classes + +### `@DataSource` annotation + +Apply to methods or classes to specify which datasource to use. Takes a datasource name string. + +### `DataSourceAspect` + +AOP aspect that intercepts `@DataSource`-annotated methods. Before invocation, pushes the datasource name onto `DynamicContextHolder`. After invocation (finally block), pops it back off. + +### `DynamicContextHolder` + +Thread-safe holder using `ThreadLocal>`. Uses a deque (stack) to support nested `@DataSource` calls -- each method restores the previous datasource on exit. +- `push(String ds)` -- set current datasource +- `poll()` -- restore previous +- `peek()` -- get current (used by DynamicDataSource) + +### `DynamicDataSource extends AbstractRoutingDataSource` + +`determineCurrentLookupKey()` returns `DynamicContextHolder.peek()`. If the holder is empty, falls back to the default datasource. + +### `DynamicDataSourceFactory` + +Creates Druid datasource instances from configuration properties. + +### Configuration Properties + +`DynamicDataSourceProperties`: Maps to YAML `spring.datasource.dynamic.*` with multiple named datasource configs. +`DataSourceProperties`: Individual datasource config (url, username, password, driver-class-name, etc.). + +## Usage + +```java +@Service +public class SomeService { + @DataSource("oracle") + public List queryFromOracle() { + // queries run against the "oracle" datasource + } +} +``` + +When no `@DataSource` is present, queries use the default (primary) datasource configured in `application-*.yml`. diff --git a/weather-data-ui/.env.development b/weather-data-ui/.env.development index 1041591..3a4d50e 100644 --- a/weather-data-ui/.env.development +++ b/weather-data-ui/.env.development @@ -1,3 +1,3 @@ NODE_ENV=development -VITE_APP_API=http://192.168.2.186:8080/system-admin +VITE_APP_API=http://192.168.2.151:48080/system-admin diff --git a/weather-data-ui/CLAUDE.md b/weather-data-ui/CLAUDE.md index 17d5df4..494ea90 100644 --- a/weather-data-ui/CLAUDE.md +++ b/weather-data-ui/CLAUDE.md @@ -1,184 +1,148 @@ -# CLAUDE.md — weather-data-ui +# CLAUDE.md - weather-data-ui -Frontend module: Vue 3 / Vite 5 / TypeScript SPA. +This file provides guidance to Claude Code when working in the weather-data-ui module. ---- +## Purpose -## Code Style +Vue 3 + TypeScript frontend for the weather data management system. Single-page application built with Vite 5. -- **No emoji in UI strings.** Plain Chinese text for labels, buttons, status. -- **No emoji in code comments or docstrings.** Plain text only. -- **Keep CLAUDE.md current** — whenever code is modified, added, deleted, or any file change affects the module structure, build, conventions, or component patterns, update this file (and the root `CLAUDE.md` if cross-cutting) in the same commit to reflect the new state. Stale documentation is a bug. +## Tech Stack ---- +- Vue 3.5 (Composition API), TypeScript 5.7, Vite 5.4 +- Element Plus 2.10 (UI library) +- Pinia 2.3 (state management) +- Vue Router 4.2 (hash mode routing) +- ECharts 5 + vue-echarts 6 (charts) +- Axios 1.11 (HTTP client) +- Less/Sass for styling -## Commands +## Build Commands ```bash -npm install # install dependencies -npm run dev # Vite dev server (port 8001, host 0.0.0.0) -npm run build / npm run build:prod # production build -npm run serve # preview production build -npm run lint # lint with autofix (ESLint) -npx vue-tsc --noEmit # type-check (not in pre-commit) +npm install +npm run dev # dev server on port 8001, hot reload, proxies /api to backend +npm run build # production build (vite build --mode production), outputs to dist/ +npm run lint # ESLint on src/**/*.{vue,ts} with --fix +npm run serve # build + vite preview ``` -Pre-commit: `lint-staged` runs `eslint --fix` on `*.ts`/`*.vue` via `yorkie` git hooks (not husky). No test runner configured. +## Project Structure -## Stack - -- Vite 5 + Vue 3 + TypeScript SPA -- Element Plus + Element Plus Icons (all icons registered globally) -- `vue-router` with **hash history** (`createWebHashHistory`) -- Pinia for state management -- Axios via `src/utils/http.ts` + `src/service/baseService.ts` -- API base URL: `VITE_APP_API` env var, overridable at runtime by `window.SITE_CONFIG.apiURL` - -## Environment config - -- Dev: `VITE_APP_API=http://192.168.2.186:8080/system-admin` (hardcoded IP — new devs must change) -- Prod: `VITE_APP_API=/system-admin` (relative, proxied via Nginx) -- Runtime override takes priority: `window.SITE_CONFIG.apiURL` - -## Vite config - -- `base: "./"` (relative paths), `chunkSizeWarningLimit: 1024` -- Manual chunks: `lodash` and `vlib` (vue/vue-router/element-plus) -- Dev: HMR overlay disabled, `host: "0.0.0.0"`, port 8001 - -## Axios HTTP pattern - -- Success check: `response.data.code === 0` (not `=== 200`) -- Request interceptor: adds `token` header, `X-Requested-With`, request timing, cache-busting `_t` on GET -- On `code === 401`: auto-redirects to `/login` -- Response unwrapped: callers receive `response.data` -- File exports: bypass Axios, use `window.location.href` with token as query param -- Uploads: no `Content-Type` set (browser auto-sets for `FormData`) - -## Routing & state - -- `src/router/base.ts`: 7 base routes (`/`, `/home`, `/login`, `/user/password`, `/iframe/:id?`, `/error`, 404 catch-all) -- `src/router/index.ts`: `beforeEach` guard — auth check, dynamic route registration from backend menus, tab management. Routes are dynamically added via `addRoute` with **flattened nested routes** (keep-alive limitation). View components resolved via `import.meta.glob("/src/views/**/*.vue")`. -- `src/store/index.ts` (`useAppStore`): monolithic store — all state nested in `state.state` (double nesting, e.g. `store.state.appIsLogin`). `initApp` fetches menus/permissions/user/dicts in 4 parallel requests. -- `src/store/importTasks.ts`: separate store for import task tracking (computed getters: `activeTasks`, `hasActiveTasks`, `recentTasks`). -- `src/utils/router.ts`: converts backend menu records → Vue router records, supports iframe/external links with `openStyle` flags. - -Layout is event-driven: `src/layout/` shell + `mitt` event bus (`src/utils/emits.ts`). The `EMitt` enum defines events for sidebar, theme, tabs, layout changes. **Trace both the Pinia store and mitt events** when changing navigation/sidebar/tabs/theme. - -Header right side: notification bell (combined badge) → `expand` (user menu). The old `import-task-indicator.vue` has been removed in favor of the notification center drawer. - -## Common page pattern: `useView` hook - -Admin CRUD pages use `src/hooks/useView.ts` for shared list-page workflow. - -Key behaviors to know before refactoring: -- `closeCurrentTab()`: if tabs enabled, emits `OnCloseCurrTab` mitt event; otherwise navigates to `/home`. -- `exportHandle()`: uses `window.location.href` with token as query param (NOT Axios). -- `dataListSortChangeHandle()`: converts camelCase → snake_case for backend (e.g. `stationId` → `station_id`). -- `createdIsNeed: true` / `activatedIsNeed: false` by default. Pages needing refresh on tab activation must set `activatedIsNeed: true`. -- Includes workflow helpers (`handleFlowRoute`, `flowDetailRoute`) hardcoded to `/flow/task-form`. - -## Cache utility - -All cache keys prefixed with `v1@` to avoid collisions. Supports `localStorage` and `sessionStorage` (token uses sessionStorage). JSON serialization is automatic. `getCache` supports auto-delete-after-read (`isDelete` flag). - -## Weather frontend module - -The home dashboard (`src/views/home.vue`) uses a **composable-based architecture**: - -| Composable | Responsibility | -|---|---| -| `useWeatherConstants.ts` | Rain levels, temperature thresholds, filter field definitions, `fmtVal()`, level/class helpers | -| `useWeatherFilter.ts` | Filter state, toggle/reset/match logic, `matchOp()` | -| `useWeatherStats.ts` | `computeStats()`, `buildStatCards()`, `buildSummary()`, `rainLevelDistribution`, `WeatherDataRow` type | -| `useWeatherChart.ts` | ECharts dynamic import, `buildChartOption()`, `ResizeObserver`, precise trigger key (not deep watch) | -| `useWeatherExport.ts` | PNG/PDF export with dynamic `html2canvas`/`jspdf` imports, loading indicator | - -Supporting utils: `src/utils/chartBuilder.ts`, `src/utils/exportReport.ts`. - -## Critical rules - -### 1. Null ≠ zero — missing data MUST be preserved as null -When mapping backend API responses to frontend models, **never** default missing numeric values to `0`. Rainfall of `0mm` means "no rain that day" (valid measurement); `null` means "no data available" (missing record). Use `: null` not `: 0` in data mapping, and display `"—"` for null values via `fmtVal()`. - -```typescript -// ✅ Correct -rainfall: row.rain2020 != null ? +row.rain2020 : null, - -// ❌ Wrong — confuses "no data" with "measured zero" -rainfall: row.rain2020 != null ? +row.rain2020 : 0, +``` +src/ +├── main.ts # App bootstrap, Pinia init +├── App.vue # Root component +├── assets/ # Static assets (css, icons, images, theme) +├── components/ # Reusable components +│ ├── alert-marquee/ # Alert scrolling marquee (SSE-driven) +│ ├── base/ # Base table/form/dialog wrappers +│ ├── sys-dept-tree/ # Department tree selector +│ ├── sys-radio-group/ # Radio group with dict-driven options +│ ├── sys-region-tree/ # Region tree selector +│ ├── sys-select/ # Dict-driven select dropdown +│ └── wang-editor/ # Rich text editor wrapper +├── composables/ # Vue 3 composables +│ ├── useAlertMarquee.ts # SSE alert streaming logic +│ ├── useFloatingDrag.ts # Draggable floating panel logic +│ ├── useWeatherChart.ts # ECharts chart configuration +│ ├── useWeatherConstants.ts # Weather domain constants +│ ├── useWeatherExport.ts # Export to Excel/PDF +│ ├── useWeatherFilter.ts # Query filter state management +│ └── useWeatherStats.ts # Statistical computation +├── constants/ # Application constants +│ ├── app.ts # API base URL, request timeout +│ ├── cacheKey.ts # Cache key enums +│ ├── config.ts # App config +│ └── enum.ts # Enums (EMitt events, etc.) +├── hooks/ # Legacy hooks +│ └── useView.ts # View loader for dynamic routes +├── layout/ # Layout components +│ ├── index.vue # Main layout with sidebar + header +│ ├── layout.vue # Alternative layout +│ ├── fullscreen-layout.vue # Fullscreen page layout +│ ├── header/ # Top header bar +│ ├── sidebar/ # Left sidebar navigation +│ └── view/ # Content view wrapper (tabs) +├── router/ +│ ├── index.ts # Router instance + dynamic route registration +│ └── base.ts # Static base routes (login, home, error, iframe) +├── service/ +│ └── baseService.ts # HTTP helpers: get/post/put/delete/upload +├── store/ +│ ├── index.ts # useAppStore (user, permissions, menus, routes, tabs) +│ └── importTasks.ts # Import task progress store +├── types/ # TypeScript type definitions +├── utils/ +│ ├── cache.ts # Cookie/localStorage cache helpers (token storage) +│ ├── chartBuilder.ts # ECharts option builder +│ ├── emits.ts # Event bus (mitt) +│ ├── exportReport.ts # PDF/Excel export utilities +│ ├── http.ts # Axios instance with interceptors +│ ├── router.ts # Route merging and registration helpers +│ ├── theme.ts # Theme switching logic +│ └── utils.ts # General utility functions +└── views/ # Feature views + ├── dailyweather/ # Daily weather data query/import/charts + ├── home.vue # Dashboard home page + ├── iframe.vue # Iframe wrapper for external pages + ├── job/ # Scheduled job management + ├── login.vue # Login page + ├── oss/ # Cloud file storage management + ├── region/ # Region management + ├── station/ # Weather station management + ├── sys/ # System management (users, roles, menus, depts, dicts, alerts) + ├── tools/ # Utility tools + └── weather/ # Weather data views ``` -All helper functions must accept `number | null` and return `"—"` or `""` for null. Stats computations must skip null values. +## Router Architecture -### 2. Heavy libraries must use dynamic imports -`html2canvas`, `jspdf`, and `echarts` are NOT imported at module level. Load them via `await import()` only when triggered by user action. This saves ~600KB from the initial bundle. +- **Mode**: `createWebHashHistory()` (hash mode) +- **Dynamic routing**: On login, `useAppStore.initApp()` fetches `/sys/menu/nav` (menu tree), `/sys/menu/permissions` (perms), `/sys/user/info` (user), `/sys/dict/type/all` (dicts). Menu data is merged with `src/views/**/*.vue` component map via `mergeServerRoute()` to build the full route table. +- **Auto-registration**: `registerDynamicToRouterAndNext()` can register a new route at runtime by matching path to a view component file. +- **Tab tracking**: Router `beforeEach` guard emits tab push events; tabs are tracked in store state. +- **404 fallback**: Unmatched routes redirect to `/error` with `to=404` query param. -### 3. Fonts are self-hosted — no external network dependency -Fonts (Noto Sans SC, JetBrains Mono) are bundled via `@fontsource/*` packages, imported in `src/main.ts`. Do NOT add Google Fonts `` tags or `@import` back — the system runs on intranet where external network may be unavailable. To add a new font weight, import the corresponding fontsource CSS file in `main.ts`. +## State Management (Pinia) -### 4. Export must show user feedback -Always show `ElLoading.service` fullscreen and `ElMessage` success/failure. Disable the export button during rendering. +`useAppStore` holds all application state: +- `appIsLogin`, `appIsReady`, `appIsRender` -- lifecycle flags +- `permissions[]` -- user permission set (strings) +- `user` -- current user object +- `dicts[]` -- dictionary data array +- `routes[]` -- resolved route records +- `routeToMeta` -- path-to-metadata mapping for tab titles +- `tabs[]`, `activeTabName`, `closedTabs` -- tab management -### 5. Deep watchers on filter objects are banned -Never use `watch(filters, callback, { deep: true })`. Derive a precise computed trigger key (e.g. `dataHash`, `filteredHash`, `extremesVersion`) and watch that instead. +`initApp()` is called once on login -- returns merged routes that are then registered with the router. -## Notification center +## HTTP Layer -The global notification system (`src/components/alert-marquee/index.vue`) aggregates two feed types: +`utils/http.ts` creates an Axios instance: +- **Request interceptor**: Injects `token` header from cache, adds `_t` timestamp to GET requests, handles form-urlencoded serialization +- **Response interceptor**: `code === 0` means success; `code === 401` triggers redirect to `/login`; other codes show ElMessage error -| Feed | Source | Clickable? | -|---|---|---| -| System alerts | `useAlertMarquee.ts` composable (polls `/sys/alert/active/since` every 30s) | Yes — opens detail dialog | -| Import tasks | `useImportTaskStore` Pinia store | No — shows real-time progress inline | +`service/baseService.ts` wraps HTTP methods: +- `get(path, params, headers)` -- adds cache-busting `_t` +- `post(path, body, headers)` -- JSON content-type +- `put(path, params, headers)` -- JSON content-type +- `delete(path, params)` -- sends body +- `upload(path, formData, headers)` -- multipart form upload -### Components & composables +## SSE Alert Integration -| File | Role | -|---|---| -| `src/composables/useAlertMarquee.ts` | Module-level singleton: message queue, drawer toggle, detail dialog state, scrollbar position. Real-time delivery: SSE (`/sys/alert/stream`) primary + 10s polling fallback. Exports danger-specific computed: `dangerMessages`, `latestDangerMessage`, `dangerCount`, `hasDangerMessages` | -| `src/composables/useFloatingDrag.ts` | Pointer Events drag logic: `setPointerCapture`, viewport clamping, deferred `isDragging` (activates only on >3px move), `dragMoved` flag to distinguish drag vs click | -| `src/components/alert-marquee/index.vue` | Floating scrollbar (640px wide, centered top, draggable) + `el-drawer` notification center + `el-dialog` detail popup | -| `src/layout/header/base-header.vue` | Bell button with combined badge (alerts + active import tasks), toggles drawer | -| `src/store/importTasks.ts` | Import task CRUD: `addTask`, `updateTask`, `removeTask`, `clearCompleted`; getters: `activeTasks`, `recentTasks` | +`composables/useAlertMarquee.ts` manages SSE connection lifecycle. It connects to the backend SSE endpoint and dispatches `alert`, `alert-withdrawn`, and `alert-deleted` events to the `alert-marquee` component for real-time notification display. -### Level-based routing +## Environment Variables -- **danger (紧急)**: triggers the floating scrollbar + appears in notification center drawer + bell badge -- **warning / info (警告 / 提示)**: notification center drawer + bell badge only (no scrollbar) -- The bell badge in base-header always shows total count (all levels + active import tasks) +- `VITE_APP_API`: Backend API base URL (injected into `constants/app.ts` at build time) +- `.env.development` / `.env.production`: Environment-specific configs -### Floating scrollbar behavior +## Key Dependencies -- Visible only when logged in (`appStore.state.appIsLogin`) and has danger-level alerts (`hasDangerMessages`) -- Positioned centered at top (`y: 56` below header), draggable to reposition, re-centers on window resize -- Shows latest alert headline + count badge -- **Close button** hides the bar; **auto-reappears** when new danger alerts arrive (watch on `dangerCount`) -- **查看详情** button opens `el-dialog` with full alert text -- The old `import-task-indicator.vue` (bell icon with popover in header) has been **removed** - -### Import progress integration - -1. Upload via `baseService.upload()` (FormData, no explicit Content-Type). -2. On upload start, toast: `"导入已开始,可在通知中心查看进度"`. -3. Poll `GET .../import/progress/{backendTaskId}` every 3 seconds. -4. Update Pinia store (`useImportTaskStore`) — progress bar + status tag render reactively in the drawer. -5. On completion: green checkmark; on failure: red cross + error message. Completed/failed tasks show a dismiss button. - -### Alert management page - -Manual alert CRUD at route `sys/system-alert`: - -| File | Role | -|---|---| -| `src/views/sys/system-alert.vue` | List page: `useView`-based, search by level/title/sourceType, batch delete, per-row withdraw | -| `src/views/sys/system-alert-add-or-update.vue` | Add/edit dialog: level (info/warning/danger), title, content (textarea), sourceType, expireTime (datetime picker) | - -Backend endpoints under `/sys/alert`: -- `GET /page` — paginated list (`sys:alert:page`) -- `GET /{id}` — detail (`sys:alert:info`) -- `POST /` — create (`sys:alert:save`) -- `PUT /` — update (`sys:alert:update`) -- `DELETE /` — batch delete (`sys:alert:delete`) -- `PUT /{id}/withdraw` — soft-withdraw (`sys:alert:update`) -- `GET /active`, `GET /active/since` — frontend polling (no permission required) +- `@vueuse/core`: Vue composition utilities +- `mitt`: Lightweight event emitter +- `nprogress`: Page load progress bar +- `html2canvas` + `jspdf`: Client-side PDF export +- `js-cookie`: Cookie management (token storage) +- `qs`: Query string parsing/serialization diff --git a/weather-data-ui/README.md b/weather-data-ui/README.md deleted file mode 100644 index 6776924..0000000 --- a/weather-data-ui/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# weather-data-ui - -气象数据分析平台前端项目。 - -> 项目说明、快速启动、部署文档 → [../README.md](../README.md) -> 开发者文档(架构、规范) → [../CLAUDE.md](../CLAUDE.md) diff --git a/weather-data-ui/package-lock.json b/weather-data-ui/package-lock.json index 3f8f148..fff75b1 100644 --- a/weather-data-ui/package-lock.json +++ b/weather-data-ui/package-lock.json @@ -1250,6 +1250,7 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", "dev": true, + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -1260,6 +1261,7 @@ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", "dev": true, + "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -1305,7 +1307,6 @@ "version": "4.17.9", "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.9.tgz", "integrity": "sha512-ZTcmhiI3NNU7dEvWLZJkzG6ao49zOIjEgIE0RgV7wbPxU0f2xT3VSAHw2gmst8swH6V0YkLRGp4qPlX/6I90MQ==", - "peer": true, "dependencies": { "@types/lodash": "*" } @@ -1429,7 +1430,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -1592,7 +1592,6 @@ "version": "2.3.4", "resolved": "https://registry.npmjs.org/@uppy/core/-/core-2.3.4.tgz", "integrity": "sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==", - "peer": true, "dependencies": { "@transloadit/prettier-bytes": "0.0.7", "@uppy/store-default": "^2.1.1", @@ -1621,7 +1620,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/@uppy/xhr-upload/-/xhr-upload-2.1.3.tgz", "integrity": "sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==", - "peer": true, "dependencies": { "@uppy/companion-client": "^2.2.2", "@uppy/utils": "^4.1.2", @@ -1923,7 +1921,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/@wangeditor/basic-modules/-/basic-modules-1.1.7.tgz", "integrity": "sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==", - "peer": true, "dependencies": { "is-url": "^1.2.4" }, @@ -1954,7 +1951,6 @@ "version": "1.1.19", "resolved": "https://registry.npmjs.org/@wangeditor/core/-/core-1.1.19.tgz", "integrity": "sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==", - "peer": true, "dependencies": { "@types/event-emitter": "^0.3.3", "event-emitter": "^0.3.5", @@ -1984,7 +1980,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/@wangeditor/editor/-/editor-5.1.1.tgz", "integrity": "sha512-BtccuHFm0QvYunIhIu7tllQWkwppkmEkD3OJ5Mn+F0REPQ/Z3HiEXbtlss2t9c/kHO4CtiFwv2XD/k/VEg7taA==", - "peer": true, "dependencies": { "@uppy/core": "^2.1.1", "@uppy/xhr-upload": "^2.0.3", @@ -2077,6 +2072,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6" @@ -2086,25 +2082,29 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6", @@ -2115,13 +2115,15 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6", @@ -2134,6 +2136,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, + "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -2143,6 +2146,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, + "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -2151,13 +2155,15 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6", @@ -2174,6 +2180,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", @@ -2187,6 +2194,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6", @@ -2199,6 +2207,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6", @@ -2213,6 +2222,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", "dev": true, + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.6", "@xtuc/long": "4.2.2" @@ -2222,20 +2232,21 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true, - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2248,6 +2259,7 @@ "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "dev": true, + "peer": true, "peerDependencies": { "acorn": "^8" } @@ -2279,7 +2291,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2296,6 +2307,7 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, + "peer": true, "peerDependencies": { "ajv": "^6.9.1" } @@ -2719,7 +2731,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "peer": true }, "node_modules/canvg": { "version": "3.0.11", @@ -2801,6 +2814,7 @@ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true, + "peer": true, "engines": { "node": ">=6.0" } @@ -3408,7 +3422,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/dom7/-/dom7-3.0.0.tgz", "integrity": "sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==", - "peer": true, "dependencies": { "ssr-window": "^3.0.0-alpha.1" } @@ -3497,7 +3510,6 @@ "version": "5.4.3", "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.3.tgz", "integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==", - "peer": true, "dependencies": { "tslib": "2.3.0", "zrender": "5.4.4" @@ -3522,7 +3534,8 @@ "version": "1.4.523", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.523.tgz", "integrity": "sha512-9AreocSUWnzNtvLcbpng6N+GkXnCcBR80IQkxRC9Dfdyg4gaWNUPBujAHUpKkiUkoSoR9UlhA4zD/IgBklmhzg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/element-plus": { "version": "2.10.5", @@ -3570,6 +3583,7 @@ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dev": true, + "peer": true, "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -3583,7 +3597,6 @@ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, - "peer": true, "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" @@ -3642,7 +3655,8 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", - "dev": true + "dev": true, + "peer": true }, "node_modules/es-object-atoms": { "version": "1.1.1", @@ -3748,6 +3762,7 @@ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -3774,7 +3789,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", "dev": true, - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -3862,7 +3876,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-8.7.1.tgz", "integrity": "sha512-28sbtm4l4cOzoO1LtzQPxfxhQABararUb1JtqusQqObJpWX2e/gmVyeYVfepizPFne0Q5cILkYGiBoV36L12Wg==", "dev": true, - "peer": true, "dependencies": { "eslint-utils": "^3.0.0", "natural-compare": "^1.4.0", @@ -4060,6 +4073,7 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, + "peer": true, "engines": { "node": ">=0.8.x" } @@ -4612,7 +4626,8 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/globals": { "version": "13.21.0", @@ -5139,8 +5154,7 @@ "node_modules/is-hotkey": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.2.0.tgz", - "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==", - "peer": true + "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==" }, "node_modules/is-number": { "version": "7.0.0", @@ -5274,6 +5288,7 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, + "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -5288,6 +5303,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -5428,7 +5444,6 @@ "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", "dev": true, - "peer": true, "dependencies": { "copy-anything": "^2.0.1", "parse-node-version": "^1.0.1", @@ -5570,6 +5585,7 @@ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true, + "peer": true, "engines": { "node": ">=6.11.5" } @@ -5606,14 +5622,12 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "peer": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "peer": true + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, "node_modules/lodash-unified": { "version": "1.0.3", @@ -5628,32 +5642,27 @@ "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "peer": true + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, "node_modules/lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", - "peer": true + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "peer": true + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, "node_modules/lodash.foreach": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==", - "peer": true + "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==" }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "peer": true + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -5664,14 +5673,12 @@ "node_modules/lodash.throttle": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "peer": true + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" }, "node_modules/lodash.toarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", - "integrity": "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==", - "peer": true + "integrity": "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==" }, "node_modules/log-update": { "version": "4.0.0", @@ -6018,7 +6025,6 @@ } ], "license": "MIT", - "peer": true, "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -6160,7 +6166,8 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/next-tick": { "version": "1.1.0", @@ -6191,7 +6198,8 @@ "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/normalize-path": { "version": "3.0.0", @@ -6699,7 +6707,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -6816,7 +6823,6 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, - "peer": true, "bin": { "prettier": "bin-prettier.js" }, @@ -6930,6 +6936,7 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "peer": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -7269,7 +7276,6 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.67.0.tgz", "integrity": "sha512-SVrO9ZeX/QQyEGtuZYCVxoeAL5vGlYjJ9p4i4HFuekWl8y/LtJ7tJc10Z+ck1c8xOuoBm2MYzcLfTAffD0pl/A==", "dev": true, - "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -7294,6 +7300,7 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, + "peer": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -7341,6 +7348,7 @@ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "dev": true, + "peer": true, "dependencies": { "randombytes": "^2.1.0" } @@ -7434,7 +7442,6 @@ "version": "0.72.8", "resolved": "https://registry.npmjs.org/slate/-/slate-0.72.8.tgz", "integrity": "sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==", - "peer": true, "dependencies": { "immer": "^9.0.6", "is-plain-object": "^5.0.0", @@ -7470,7 +7477,6 @@ "version": "3.5.1", "resolved": "https://registry.npmjs.org/snabbdom/-/snabbdom-3.5.1.tgz", "integrity": "sha512-wHMNIOjkm/YNE5EM3RCbr/+DVgPg6AqQAX1eOxO46zYNvCXjKP5Y865tqQj3EXnaMBjkxmQA5jFuDpDK/dbfiA==", - "peer": true, "engines": { "node": ">=8.3.0" } @@ -8296,6 +8302,7 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -8323,6 +8330,7 @@ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", "dev": true, + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", @@ -8603,7 +8611,6 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8712,6 +8719,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0" @@ -8778,7 +8786,6 @@ "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -8908,7 +8915,6 @@ "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.18.tgz", "integrity": "sha512-7W4Y4ZbMiQ3SEo+m9lnoNpV9xG7QVMLa+/0RFwwiAVkeYoyGXqWE85jabU4pllJNUzqfLShJ5YLptewhCWUgNA==", "license": "MIT", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.18", "@vue/compiler-sfc": "3.5.18", @@ -9082,6 +9088,7 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "dev": true, + "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -9143,6 +9150,7 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true, + "peer": true, "engines": { "node": ">=10.13.0" } @@ -9918,6 +9926,7 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", "dev": true, + "peer": true, "requires": { "@types/estree": "*", "@types/json-schema": "*" @@ -9928,6 +9937,7 @@ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", "dev": true, + "peer": true, "requires": { "@types/eslint": "*", "@types/estree": "*" @@ -9971,7 +9981,6 @@ "version": "4.17.9", "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.9.tgz", "integrity": "sha512-ZTcmhiI3NNU7dEvWLZJkzG6ao49zOIjEgIE0RgV7wbPxU0f2xT3VSAHw2gmst8swH6V0YkLRGp4qPlX/6I90MQ==", - "peer": true, "requires": { "@types/lodash": "*" } @@ -10075,7 +10084,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, - "peer": true, "requires": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -10165,7 +10173,6 @@ "version": "2.3.4", "resolved": "https://registry.npmjs.org/@uppy/core/-/core-2.3.4.tgz", "integrity": "sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==", - "peer": true, "requires": { "@transloadit/prettier-bytes": "0.0.7", "@uppy/store-default": "^2.1.1", @@ -10194,7 +10201,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/@uppy/xhr-upload/-/xhr-upload-2.1.3.tgz", "integrity": "sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==", - "peer": true, "requires": { "@uppy/companion-client": "^2.2.2", "@uppy/utils": "^4.1.2", @@ -10429,7 +10435,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/@wangeditor/basic-modules/-/basic-modules-1.1.7.tgz", "integrity": "sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==", - "peer": true, "requires": { "is-url": "^1.2.4" } @@ -10446,7 +10451,6 @@ "version": "1.1.19", "resolved": "https://registry.npmjs.org/@wangeditor/core/-/core-1.1.19.tgz", "integrity": "sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==", - "peer": true, "requires": { "@types/event-emitter": "^0.3.3", "event-emitter": "^0.3.5", @@ -10460,7 +10464,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/@wangeditor/editor/-/editor-5.1.1.tgz", "integrity": "sha512-BtccuHFm0QvYunIhIu7tllQWkwppkmEkD3OJ5Mn+F0REPQ/Z3HiEXbtlss2t9c/kHO4CtiFwv2XD/k/VEg7taA==", - "peer": true, "requires": { "@uppy/core": "^2.1.1", "@uppy/xhr-upload": "^2.0.3", @@ -10520,6 +10523,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", "dev": true, + "peer": true, "requires": { "@webassemblyjs/helper-numbers": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6" @@ -10529,25 +10533,29 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true + "dev": true, + "peer": true }, "@webassemblyjs/helper-api-error": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true + "dev": true, + "peer": true }, "@webassemblyjs/helper-buffer": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", - "dev": true + "dev": true, + "peer": true }, "@webassemblyjs/helper-numbers": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, + "peer": true, "requires": { "@webassemblyjs/floating-point-hex-parser": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6", @@ -10558,13 +10566,15 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true + "dev": true, + "peer": true }, "@webassemblyjs/helper-wasm-section": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", "dev": true, + "peer": true, "requires": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6", @@ -10577,6 +10587,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, + "peer": true, "requires": { "@xtuc/ieee754": "^1.2.0" } @@ -10586,6 +10597,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, + "peer": true, "requires": { "@xtuc/long": "4.2.2" } @@ -10594,13 +10606,15 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true + "dev": true, + "peer": true }, "@webassemblyjs/wasm-edit": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", "dev": true, + "peer": true, "requires": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6", @@ -10617,6 +10631,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", "dev": true, + "peer": true, "requires": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", @@ -10630,6 +10645,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", "dev": true, + "peer": true, "requires": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6", @@ -10642,6 +10658,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", "dev": true, + "peer": true, "requires": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6", @@ -10656,6 +10673,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", "dev": true, + "peer": true, "requires": { "@webassemblyjs/ast": "1.11.6", "@xtuc/long": "4.2.2" @@ -10665,26 +10683,28 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true + "dev": true, + "peer": true }, "@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true + "dev": true, + "peer": true }, "acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true, - "peer": true + "dev": true }, "acorn-import-assertions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "dev": true, + "peer": true, "requires": {} }, "acorn-jsx": { @@ -10709,7 +10729,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -10722,6 +10741,7 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, + "peer": true, "requires": {} }, "alien-signals": { @@ -11023,7 +11043,8 @@ "version": "1.0.30001534", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001534.tgz", "integrity": "sha512-vlPVrhsCS7XaSh2VvWluIQEzVhefrUQcEsQWSS5A5V+dM07uv1qHeQzAOTGIMy9i3e9bH15+muvI/UHojVgS/Q==", - "dev": true + "dev": true, + "peer": true }, "canvg": { "version": "3.0.11", @@ -11082,7 +11103,8 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true + "dev": true, + "peer": true }, "class-utils": { "version": "0.3.6", @@ -11540,7 +11562,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/dom7/-/dom7-3.0.0.tgz", "integrity": "sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==", - "peer": true, "requires": { "ssr-window": "^3.0.0-alpha.1" } @@ -11615,7 +11636,6 @@ "version": "5.4.3", "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.3.tgz", "integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==", - "peer": true, "requires": { "tslib": "2.3.0", "zrender": "5.4.4" @@ -11634,7 +11654,8 @@ "version": "1.4.523", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.523.tgz", "integrity": "sha512-9AreocSUWnzNtvLcbpng6N+GkXnCcBR80IQkxRC9Dfdyg4gaWNUPBujAHUpKkiUkoSoR9UlhA4zD/IgBklmhzg==", - "dev": true + "dev": true, + "peer": true }, "element-plus": { "version": "2.10.5", @@ -11675,6 +11696,7 @@ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dev": true, + "peer": true, "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -11685,7 +11707,6 @@ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, - "peer": true, "requires": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" @@ -11730,7 +11751,8 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", - "dev": true + "dev": true, + "peer": true }, "es-object-atoms": { "version": "1.1.1", @@ -11815,7 +11837,8 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true + "dev": true, + "peer": true }, "escape-html": { "version": "1.0.3", @@ -11833,7 +11856,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", "dev": true, - "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -11913,7 +11935,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-8.7.1.tgz", "integrity": "sha512-28sbtm4l4cOzoO1LtzQPxfxhQABararUb1JtqusQqObJpWX2e/gmVyeYVfepizPFne0Q5cILkYGiBoV36L12Wg==", "dev": true, - "peer": true, "requires": { "eslint-utils": "^3.0.0", "natural-compare": "^1.4.0", @@ -12037,7 +12058,8 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true + "dev": true, + "peer": true }, "execa": { "version": "5.1.1", @@ -12472,7 +12494,8 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true + "dev": true, + "peer": true }, "globals": { "version": "13.21.0", @@ -12867,8 +12890,7 @@ "is-hotkey": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.2.0.tgz", - "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==", - "peer": true + "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==" }, "is-number": { "version": "7.0.0", @@ -12966,6 +12988,7 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, + "peer": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -12977,6 +13000,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "peer": true, "requires": { "has-flag": "^4.0.0" } @@ -13092,7 +13116,6 @@ "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", "dev": true, - "peer": true, "requires": { "copy-anything": "^2.0.1", "errno": "^0.1.1", @@ -13192,7 +13215,8 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true + "dev": true, + "peer": true }, "loader-utils": { "version": "1.4.2", @@ -13217,14 +13241,12 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "peer": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "peer": true + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, "lodash-unified": { "version": "1.0.3", @@ -13235,32 +13257,27 @@ "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "peer": true + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", - "peer": true + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "peer": true + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, "lodash.foreach": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==", - "peer": true + "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==" }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "peer": true + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" }, "lodash.merge": { "version": "4.6.2", @@ -13271,14 +13288,12 @@ "lodash.throttle": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "peer": true + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" }, "lodash.toarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", - "integrity": "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==", - "peer": true + "integrity": "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==" }, "log-update": { "version": "4.0.0", @@ -13545,8 +13560,7 @@ "nanoid": { "version": "3.3.11", "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "peer": true + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==" }, "nanomatch": { "version": "1.2.13", @@ -13659,7 +13673,8 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "dev": true, + "peer": true }, "next-tick": { "version": "1.1.0", @@ -13690,7 +13705,8 @@ "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true + "dev": true, + "peer": true }, "normalize-path": { "version": "3.0.0", @@ -14056,7 +14072,6 @@ "version": "8.5.6", "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "peer": true, "requires": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14150,8 +14165,7 @@ "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "peer": true + "dev": true }, "prettier-linter-helpers": { "version": "1.0.0", @@ -14223,6 +14237,7 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "peer": true, "requires": { "safe-buffer": "^5.1.0" } @@ -14473,7 +14488,6 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.67.0.tgz", "integrity": "sha512-SVrO9ZeX/QQyEGtuZYCVxoeAL5vGlYjJ9p4i4HFuekWl8y/LtJ7tJc10Z+ck1c8xOuoBm2MYzcLfTAffD0pl/A==", "dev": true, - "peer": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -14492,6 +14506,7 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, + "peer": true, "requires": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -14526,6 +14541,7 @@ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "dev": true, + "peer": true, "requires": { "randombytes": "^2.1.0" } @@ -14600,7 +14616,6 @@ "version": "0.72.8", "resolved": "https://registry.npmjs.org/slate/-/slate-0.72.8.tgz", "integrity": "sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==", - "peer": true, "requires": { "immer": "^9.0.6", "is-plain-object": "^5.0.0", @@ -14629,8 +14644,7 @@ "snabbdom": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/snabbdom/-/snabbdom-3.5.1.tgz", - "integrity": "sha512-wHMNIOjkm/YNE5EM3RCbr/+DVgPg6AqQAX1eOxO46zYNvCXjKP5Y865tqQj3EXnaMBjkxmQA5jFuDpDK/dbfiA==", - "peer": true + "integrity": "sha512-wHMNIOjkm/YNE5EM3RCbr/+DVgPg6AqQAX1eOxO46zYNvCXjKP5Y865tqQj3EXnaMBjkxmQA5jFuDpDK/dbfiA==" }, "snapdragon": { "version": "0.8.2", @@ -15284,7 +15298,8 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true + "dev": true, + "peer": true }, "terser": { "version": "5.19.4", @@ -15311,6 +15326,7 @@ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", "dev": true, + "peer": true, "requires": { "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", @@ -15519,8 +15535,7 @@ "version": "5.9.2", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.2.tgz", "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", - "devOptional": true, - "peer": true + "devOptional": true }, "union-value": { "version": "1.0.1", @@ -15591,6 +15606,7 @@ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", "dev": true, + "peer": true, "requires": { "escalade": "^3.1.1", "picocolors": "^1.0.0" @@ -15642,7 +15658,6 @@ "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.19.tgz", "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", "dev": true, - "peer": true, "requires": { "esbuild": "^0.21.3", "fsevents": "~2.3.3", @@ -15716,7 +15731,6 @@ "version": "3.5.18", "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.18.tgz", "integrity": "sha512-7W4Y4ZbMiQ3SEo+m9lnoNpV9xG7QVMLa+/0RFwwiAVkeYoyGXqWE85jabU4pllJNUzqfLShJ5YLptewhCWUgNA==", - "peer": true, "requires": { "@vue/compiler-dom": "3.5.18", "@vue/compiler-sfc": "3.5.18", @@ -15805,6 +15819,7 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "dev": true, + "peer": true, "requires": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -15847,7 +15862,8 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true + "dev": true, + "peer": true }, "which": { "version": "2.0.2", diff --git a/weather-data-ui/src/assets/css/setting.less b/weather-data-ui/src/assets/css/setting.less index f3758db..8095a51 100644 --- a/weather-data-ui/src/assets/css/setting.less +++ b/weather-data-ui/src/assets/css/setting.less @@ -127,7 +127,7 @@ margin: 8px 8px 0 0; border-radius: 2px; display: inline-block; - box-shadow: 0 1px 3px rgba(0 0 0, 0.1); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); vertical-align: top; position: relative; cursor: pointer; diff --git a/weather-data-ui/src/components/sys-radio-group/src/sys-radio-group.vue b/weather-data-ui/src/components/sys-radio-group/src/sys-radio-group.vue index ca8709e..3a9a168 100644 --- a/weather-data-ui/src/components/sys-radio-group/src/sys-radio-group.vue +++ b/weather-data-ui/src/components/sys-radio-group/src/sys-radio-group.vue @@ -17,7 +17,7 @@ export default defineComponent({ const store = useAppStore(); return { value: computed(() => `${props.modelValue}`), - dataList: getDictDataList(store.state.dicts, props.dictType) + dataList: computed(() => getDictDataList(store.state.dicts, props.dictType)) }; } }); diff --git a/weather-data-ui/src/components/sys-select/src/sys-select.vue b/weather-data-ui/src/components/sys-select/src/sys-select.vue index 8aeb65b..689fa3f 100644 --- a/weather-data-ui/src/components/sys-select/src/sys-select.vue +++ b/weather-data-ui/src/components/sys-select/src/sys-select.vue @@ -18,7 +18,7 @@ export default defineComponent({ const store = useAppStore(); return { value: computed(() => `${props.modelValue}`), - dataList: getDictDataList(store.state.dicts, props.dictType) + dataList: computed(() => getDictDataList(store.state.dicts, props.dictType)) }; } }); diff --git a/weather-data-ui/src/composables/useAlertMarquee.ts b/weather-data-ui/src/composables/useAlertMarquee.ts index 01a188e..a6733d3 100644 --- a/weather-data-ui/src/composables/useAlertMarquee.ts +++ b/weather-data-ui/src/composables/useAlertMarquee.ts @@ -3,7 +3,7 @@ // 消息队列 / 通知中心抽屉 / 详情弹窗 / 悬浮滚动条位置 // 获取:SSE 主通道 + 10s 轮询降级 + 每60s全量对账 -import { ref, computed } from "vue"; +import { ref, computed, onBeforeUnmount } from "vue"; import baseService from "@/service/baseService"; import app from "@/constants/app"; import { getToken } from "@/utils/cache"; @@ -329,6 +329,10 @@ function setPosition(pos: { x: number; y: number }): void { // ---------- composable 导出 ---------- export function useAlertMarquee() { + onBeforeUnmount(() => { + stopPolling(); + }); + return { // 消息 messages, diff --git a/weather-data-ui/src/composables/useFloatingDrag.ts b/weather-data-ui/src/composables/useFloatingDrag.ts index 0b9c123..2faa91b 100644 --- a/weather-data-ui/src/composables/useFloatingDrag.ts +++ b/weather-data-ui/src/composables/useFloatingDrag.ts @@ -1,7 +1,7 @@ // composables/useFloatingDrag.ts // 浮窗拖动逻辑 — Pointer Events 统一处理鼠标和触屏 -import { ref, type Ref } from "vue"; +import { ref, onBeforeUnmount, type Ref } from "vue"; export interface DragPosition { x: number; @@ -16,6 +16,24 @@ export function useFloatingDrag( const dragMoved = ref(false); let dragResetTimer: ReturnType | null = null; + /** 清理拖拽期间注册的事件监听器(组件卸载时使用) */ + function cleanupDragListeners(): void { + const el = elementRef.value; + if (el) { + el.removeEventListener("pointermove", onPointerMove); + el.removeEventListener("pointerup", onPointerUp); + el.removeEventListener("pointercancel", onPointerUp); + } + if (dragResetTimer) { + clearTimeout(dragResetTimer); + dragResetTimer = null; + } + } + + onBeforeUnmount(() => { + cleanupDragListeners(); + }); + /** pointerdown 时记录的元素尺寸和指针在元素内的偏移 */ let dragMeta: { width: number; diff --git a/weather-data-ui/src/composables/useWeatherConstants.ts b/weather-data-ui/src/composables/useWeatherConstants.ts index 964d387..a371665 100644 --- a/weather-data-ui/src/composables/useWeatherConstants.ts +++ b/weather-data-ui/src/composables/useWeatherConstants.ts @@ -9,7 +9,7 @@ export const RAIN_LEVELS = [ { label: "大雨", min: 25, max: 50 }, { label: "暴雨", min: 50, max: 100 }, { label: "大暴雨", min: 100, max: 250 }, - { label: "特大暴雨", min: 250, max: 9999 } + { label: "特大暴雨", min: 250, max: Infinity } ] as const; /** 可筛选的降雨等级(排除"无雨",因为无雨通常单独判断) */ diff --git a/weather-data-ui/src/layout/index.vue b/weather-data-ui/src/layout/index.vue index 08b4666..be40167 100644 --- a/weather-data-ui/src/layout/index.vue +++ b/weather-data-ui/src/layout/index.vue @@ -58,7 +58,7 @@ export default defineComponent({ });