diff --git a/admin-system/frontend/.env.example b/admin-system/.env.example similarity index 100% rename from admin-system/frontend/.env.example rename to admin-system/.env.example diff --git a/admin-system/frontend/Dockerfile b/admin-system/Dockerfile similarity index 96% rename from admin-system/frontend/Dockerfile rename to admin-system/Dockerfile index b2021e0..3962499 100644 --- a/admin-system/frontend/Dockerfile +++ b/admin-system/Dockerfile @@ -1,65 +1,65 @@ -# 宁夏智慧养殖监管平台 - 前端管理系统容器 -# 多阶段构建,优化镜像大小 - -# 阶段1:构建阶段 -FROM node:18-alpine as builder - -# 设置工作目录 -WORKDIR /app - -# 复制package.json和package-lock.json -COPY package*.json ./ - -# 安装构建依赖 -RUN npm ci && npm cache clean --force - -# 复制源代码 -COPY . . - -# 构建应用 -RUN npm run build - -# 阶段2:生产阶段 -FROM nginx:alpine - -# 安装基本工具 -RUN apk add --no-cache curl - -# 创建nginx用户目录 -RUN mkdir -p /var/cache/nginx/client_temp \ - && mkdir -p /var/cache/nginx/proxy_temp \ - && mkdir -p /var/cache/nginx/fastcgi_temp \ - && mkdir -p /var/cache/nginx/uwsgi_temp \ - && mkdir -p /var/cache/nginx/scgi_temp - -# 复制构建产物 -COPY --from=builder /app/dist /usr/share/nginx/html - -# 复制Nginx配置 -COPY nginx.conf /etc/nginx/nginx.conf -COPY default.conf /etc/nginx/conf.d/default.conf - -# 创建日志目录 -RUN mkdir -p /var/log/nginx - -# 设置文件权限 -RUN chown -R nginx:nginx /usr/share/nginx/html \ - && chown -R nginx:nginx /var/cache/nginx \ - && chown -R nginx:nginx /var/log/nginx - -# 暴露端口 -EXPOSE 80 - -# 健康检查 -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD curl -f http://localhost:80/ || exit 1 - -# 启动Nginx -CMD ["nginx", "-g", "daemon off;"] - -# 元数据标签 -LABEL maintainer="宁夏智慧养殖监管平台 " \ - version="2.1.0" \ - description="宁夏智慧养殖监管平台前端管理系统" \ - application="nxxm-farming-platform" \ - tier="frontend" +# 宁夏智慧养殖监管平台 - 前端管理系统容器 +# 多阶段构建,优化镜像大小 + +# 阶段1:构建阶段 +FROM node:18-alpine as builder + +# 设置工作目录 +WORKDIR /app + +# 复制package.json和package-lock.json +COPY package*.json ./ + +# 安装构建依赖 +RUN npm ci && npm cache clean --force + +# 复制源代码 +COPY . . + +# 构建应用 +RUN npm run build + +# 阶段2:生产阶段 +FROM nginx:alpine + +# 安装基本工具 +RUN apk add --no-cache curl + +# 创建nginx用户目录 +RUN mkdir -p /var/cache/nginx/client_temp \ + && mkdir -p /var/cache/nginx/proxy_temp \ + && mkdir -p /var/cache/nginx/fastcgi_temp \ + && mkdir -p /var/cache/nginx/uwsgi_temp \ + && mkdir -p /var/cache/nginx/scgi_temp + +# 复制构建产物 +COPY --from=builder /app/dist /usr/share/nginx/html + +# 复制Nginx配置 +COPY nginx.conf /etc/nginx/nginx.conf +COPY default.conf /etc/nginx/conf.d/default.conf + +# 创建日志目录 +RUN mkdir -p /var/log/nginx + +# 设置文件权限 +RUN chown -R nginx:nginx /usr/share/nginx/html \ + && chown -R nginx:nginx /var/cache/nginx \ + && chown -R nginx:nginx /var/log/nginx + +# 暴露端口 +EXPOSE 80 + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:80/ || exit 1 + +# 启动Nginx +CMD ["nginx", "-g", "daemon off;"] + +# 元数据标签 +LABEL maintainer="宁夏智慧养殖监管平台 " \ + version="2.1.0" \ + description="宁夏智慧养殖监管平台前端管理系统" \ + application="nxxm-farming-platform" \ + tier="frontend" diff --git a/admin-system/frontend/ENV_CONFIG.md b/admin-system/ENV_CONFIG.md similarity index 96% rename from admin-system/frontend/ENV_CONFIG.md rename to admin-system/ENV_CONFIG.md index 0dd9872..46b72fe 100644 --- a/admin-system/frontend/ENV_CONFIG.md +++ b/admin-system/ENV_CONFIG.md @@ -1,131 +1,131 @@ -# 环境变量配置说明 - -## 概述 -本项目支持通过环境变量配置API地址和其他配置项,实现开发、测试、生产环境的灵活切换。 - -## 环境变量列表 - -### API配置 -| 变量名 | 默认值 | 说明 | -|--------|--------|------| -| `VITE_API_BASE_URL` | `/api` | API基础路径(相对路径,通过代理转发) | -| `VITE_API_FULL_URL` | `http://localhost:5350/api` | 完整API地址(直接调用) | -| `VITE_API_TIMEOUT` | `10000` | 请求超时时间(毫秒) | -| `VITE_USE_PROXY` | `true` | 是否使用Vite代理 | - -### 百度地图配置 -| 变量名 | 默认值 | 说明 | -|--------|--------|------| -| `VITE_BAIDU_MAP_API_KEY` | `SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo` | 百度地图API密钥 | - -### 应用配置 -| 变量名 | 默认值 | 说明 | -|--------|--------|------| -| `VITE_APP_TITLE` | `宁夏智慧养殖监管平台` | 应用标题 | -| `VITE_APP_VERSION` | `1.0.0` | 应用版本 | - -## 环境配置文件 - -### 开发环境 (.env.development) -```bash -# API配置 -VITE_API_BASE_URL=/api -VITE_API_FULL_URL=http://localhost:5350/api -VITE_API_TIMEOUT=10000 -VITE_USE_PROXY=true - -# 百度地图API -VITE_BAIDU_MAP_API_KEY=your_baidu_map_api_key - -# 应用配置 -VITE_APP_TITLE=宁夏智慧养殖监管平台 -VITE_APP_VERSION=1.0.0 -``` - -### 生产环境 (.env.production) -```bash -# API配置 -VITE_API_BASE_URL=/api -VITE_API_FULL_URL=https://your-domain.com/api -VITE_API_TIMEOUT=15000 -VITE_USE_PROXY=false - -# 百度地图API -VITE_BAIDU_MAP_API_KEY=your_production_baidu_map_api_key - -# 应用配置 -VITE_APP_TITLE=宁夏智慧养殖监管平台 -VITE_APP_VERSION=1.0.0 -``` - -## 使用方法 - -### 1. 创建环境文件 -```bash -# 复制示例文件 -cp .env.example .env.development -cp .env.example .env.production - -# 编辑配置文件 -vim .env.development -vim .env.production -``` - -### 2. 在代码中使用 -```javascript -// 在组件中使用环境变量 -import { API_CONFIG } from '@/config/env.js' - -// 获取API基础URL -const apiUrl = API_CONFIG.baseUrl - -// 获取完整API URL -const fullApiUrl = API_CONFIG.fullBaseUrl - -// 检查是否为开发环境 -if (API_CONFIG.isDev) { - console.log('开发环境') -} -``` - -### 3. API工具使用 -```javascript -// 使用代理模式(推荐) -import { api } from '@/utils/api' -const result = await api.get('/farms') - -// 使用直接调用模式 -import { directApi } from '@/utils/api' -const result = await directApi.get('/farms') -``` - -## 配置优先级 - -1. 环境变量文件 (.env.development, .env.production) -2. 默认配置 (env.js) -3. 硬编码默认值 - -## 注意事项 - -1. **环境变量必须以 `VITE_` 开头**,才能在客户端代码中访问 -2. **生产环境建议使用HTTPS**,确保安全性 -3. **API密钥不要提交到版本控制系统**,使用环境变量管理 -4. **代理模式适用于开发环境**,生产环境建议使用直接调用 -5. **修改环境变量后需要重启开发服务器** - -## 故障排除 - -### 1. 环境变量不生效 -- 检查变量名是否以 `VITE_` 开头 -- 确认环境文件位置正确 -- 重启开发服务器 - -### 2. API请求失败 -- 检查 `VITE_API_FULL_URL` 配置是否正确 -- 确认后端服务是否启动 -- 检查网络连接 - -### 3. 代理不工作 -- 检查 `vite.config.js` 中的代理配置 -- 确认 `VITE_USE_PROXY` 设置为 `true` -- 检查后端服务端口是否正确 +# 环境变量配置说明 + +## 概述 +本项目支持通过环境变量配置API地址和其他配置项,实现开发、测试、生产环境的灵活切换。 + +## 环境变量列表 + +### API配置 +| 变量名 | 默认值 | 说明 | +|--------|--------|------| +| `VITE_API_BASE_URL` | `/api` | API基础路径(相对路径,通过代理转发) | +| `VITE_API_FULL_URL` | `http://localhost:5350/api` | 完整API地址(直接调用) | +| `VITE_API_TIMEOUT` | `10000` | 请求超时时间(毫秒) | +| `VITE_USE_PROXY` | `true` | 是否使用Vite代理 | + +### 百度地图配置 +| 变量名 | 默认值 | 说明 | +|--------|--------|------| +| `VITE_BAIDU_MAP_API_KEY` | `SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo` | 百度地图API密钥 | + +### 应用配置 +| 变量名 | 默认值 | 说明 | +|--------|--------|------| +| `VITE_APP_TITLE` | `宁夏智慧养殖监管平台` | 应用标题 | +| `VITE_APP_VERSION` | `1.0.0` | 应用版本 | + +## 环境配置文件 + +### 开发环境 (.env.development) +```bash +# API配置 +VITE_API_BASE_URL=/api +VITE_API_FULL_URL=http://localhost:5350/api +VITE_API_TIMEOUT=10000 +VITE_USE_PROXY=true + +# 百度地图API +VITE_BAIDU_MAP_API_KEY=your_baidu_map_api_key + +# 应用配置 +VITE_APP_TITLE=宁夏智慧养殖监管平台 +VITE_APP_VERSION=1.0.0 +``` + +### 生产环境 (.env.production) +```bash +# API配置 +VITE_API_BASE_URL=/api +VITE_API_FULL_URL=https://your-domain.com/api +VITE_API_TIMEOUT=15000 +VITE_USE_PROXY=false + +# 百度地图API +VITE_BAIDU_MAP_API_KEY=your_production_baidu_map_api_key + +# 应用配置 +VITE_APP_TITLE=宁夏智慧养殖监管平台 +VITE_APP_VERSION=1.0.0 +``` + +## 使用方法 + +### 1. 创建环境文件 +```bash +# 复制示例文件 +cp .env.example .env.development +cp .env.example .env.production + +# 编辑配置文件 +vim .env.development +vim .env.production +``` + +### 2. 在代码中使用 +```javascript +// 在组件中使用环境变量 +import { API_CONFIG } from '@/config/env.js' + +// 获取API基础URL +const apiUrl = API_CONFIG.baseUrl + +// 获取完整API URL +const fullApiUrl = API_CONFIG.fullBaseUrl + +// 检查是否为开发环境 +if (API_CONFIG.isDev) { + console.log('开发环境') +} +``` + +### 3. API工具使用 +```javascript +// 使用代理模式(推荐) +import { api } from '@/utils/api' +const result = await api.get('/farms') + +// 使用直接调用模式 +import { directApi } from '@/utils/api' +const result = await directApi.get('/farms') +``` + +## 配置优先级 + +1. 环境变量文件 (.env.development, .env.production) +2. 默认配置 (env.js) +3. 硬编码默认值 + +## 注意事项 + +1. **环境变量必须以 `VITE_` 开头**,才能在客户端代码中访问 +2. **生产环境建议使用HTTPS**,确保安全性 +3. **API密钥不要提交到版本控制系统**,使用环境变量管理 +4. **代理模式适用于开发环境**,生产环境建议使用直接调用 +5. **修改环境变量后需要重启开发服务器** + +## 故障排除 + +### 1. 环境变量不生效 +- 检查变量名是否以 `VITE_` 开头 +- 确认环境文件位置正确 +- 重启开发服务器 + +### 2. API请求失败 +- 检查 `VITE_API_FULL_URL` 配置是否正确 +- 确认后端服务是否启动 +- 检查网络连接 + +### 3. 代理不工作 +- 检查 `vite.config.js` 中的代理配置 +- 确认 `VITE_USE_PROXY` 设置为 `true` +- 检查后端服务端口是否正确 diff --git a/admin-system/frontend/components/PerformanceMonitor.vue b/admin-system/components/PerformanceMonitor.vue similarity index 100% rename from admin-system/frontend/components/PerformanceMonitor.vue rename to admin-system/components/PerformanceMonitor.vue diff --git a/admin-system/frontend/cows.jpg b/admin-system/cows.jpg similarity index 100% rename from admin-system/frontend/cows.jpg rename to admin-system/cows.jpg diff --git a/admin-system/frontend/default.conf b/admin-system/default.conf similarity index 96% rename from admin-system/frontend/default.conf rename to admin-system/default.conf index 1189aad..8bd2663 100644 --- a/admin-system/frontend/default.conf +++ b/admin-system/default.conf @@ -1,114 +1,114 @@ -# 宁夏智慧养殖监管平台 - 前端服务配置 -server { - listen 80; - server_name localhost; - root /usr/share/nginx/html; - index index.html index.htm; - - # 字符编码 - charset utf-8; - - # 访问日志 - access_log /var/log/nginx/access.log main; - - # 静态资源缓存配置 - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { - expires 1y; - add_header Cache-Control "public, immutable"; - add_header Vary Accept-Encoding; - access_log off; - } - - # 处理Vue Router的history模式 - location / { - try_files $uri $uri/ /index.html; - add_header Cache-Control "no-cache, no-store, must-revalidate"; - add_header Pragma "no-cache"; - add_header Expires "0"; - } - - # API代理到后端服务 - location /api/ { - # 后端服务地址(在docker-compose中定义) - proxy_pass http://backend:5350; - - # 代理头设置 - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # 超时设置 - proxy_connect_timeout 10s; - proxy_send_timeout 10s; - proxy_read_timeout 10s; - - # 缓冲设置 - proxy_buffering on; - proxy_buffer_size 4k; - proxy_buffers 8 4k; - - # WebSocket支持 - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } - - # WebSocket专用代理 - location /socket.io/ { - proxy_pass http://backend:5350; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # WebSocket特定超时 - proxy_read_timeout 86400; - } - - # 百度地图API代理(解决跨域问题) - location /map-api/ { - proxy_pass https://api.map.baidu.com/; - proxy_set_header Host api.map.baidu.com; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_ssl_server_name on; - - # 添加CORS头 - add_header Access-Control-Allow-Origin *; - add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; - add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; - } - - # 健康检查端点 - location /health { - access_log off; - return 200 "healthy\n"; - add_header Content-Type text/plain; - } - - # 安全配置 - location ~ /\. { - deny all; - access_log off; - log_not_found off; - } - - # 防止访问敏感文件 - location ~* \.(env|log|sql)$ { - deny all; - access_log off; - log_not_found off; - } - - # 错误页面 - error_page 404 /404.html; - error_page 500 502 503 504 /50x.html; - - location = /50x.html { - root /usr/share/nginx/html; - } -} +# 宁夏智慧养殖监管平台 - 前端服务配置 +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html index.htm; + + # 字符编码 + charset utf-8; + + # 访问日志 + access_log /var/log/nginx/access.log main; + + # 静态资源缓存配置 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + add_header Vary Accept-Encoding; + access_log off; + } + + # 处理Vue Router的history模式 + location / { + try_files $uri $uri/ /index.html; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires "0"; + } + + # API代理到后端服务 + location /api/ { + # 后端服务地址(在docker-compose中定义) + proxy_pass http://backend:5350; + + # 代理头设置 + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 超时设置 + proxy_connect_timeout 10s; + proxy_send_timeout 10s; + proxy_read_timeout 10s; + + # 缓冲设置 + proxy_buffering on; + proxy_buffer_size 4k; + proxy_buffers 8 4k; + + # WebSocket支持 + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + # WebSocket专用代理 + location /socket.io/ { + proxy_pass http://backend:5350; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket特定超时 + proxy_read_timeout 86400; + } + + # 百度地图API代理(解决跨域问题) + location /map-api/ { + proxy_pass https://api.map.baidu.com/; + proxy_set_header Host api.map.baidu.com; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_ssl_server_name on; + + # 添加CORS头 + add_header Access-Control-Allow-Origin *; + add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; + add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; + } + + # 健康检查端点 + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # 安全配置 + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } + + # 防止访问敏感文件 + location ~* \.(env|log|sql)$ { + deny all; + access_log off; + log_not_found off; + } + + # 错误页面 + error_page 404 /404.html; + error_page 500 502 503 504 /50x.html; + + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/admin-system/frontend/index.html b/admin-system/index.html similarity index 100% rename from admin-system/frontend/index.html rename to admin-system/index.html diff --git a/admin-system/frontend/nginx.conf b/admin-system/nginx.conf similarity index 96% rename from admin-system/frontend/nginx.conf rename to admin-system/nginx.conf index 02b9b26..c67f7ee 100644 --- a/admin-system/frontend/nginx.conf +++ b/admin-system/nginx.conf @@ -1,66 +1,66 @@ -# 宁夏智慧养殖监管平台 - Nginx主配置 -user nginx; -worker_processes auto; - -# 错误日志配置 -error_log /var/log/nginx/error.log warn; -pid /var/run/nginx.pid; - -# 事件配置 -events { - worker_connections 1024; - use epoll; - multi_accept on; -} - -# HTTP配置 -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - # 日志格式 - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log main; - - # 基础配置 - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - server_tokens off; - - # 文件上传大小限制 - client_max_body_size 50M; - client_body_buffer_size 128k; - - # Gzip压缩 - gzip on; - gzip_vary on; - gzip_proxied any; - gzip_comp_level 6; - gzip_min_length 1000; - gzip_types - text/plain - text/css - text/xml - text/javascript - application/json - application/javascript - application/xml+rss - application/atom+xml - image/svg+xml; - - # 安全头 - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-XSS-Protection "1; mode=block" always; - add_header X-Content-Type-Options "nosniff" always; - add_header Referrer-Policy "no-referrer-when-downgrade" always; - add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; - - # 包含站点配置 - include /etc/nginx/conf.d/*.conf; -} +# 宁夏智慧养殖监管平台 - Nginx主配置 +user nginx; +worker_processes auto; + +# 错误日志配置 +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +# 事件配置 +events { + worker_connections 1024; + use epoll; + multi_accept on; +} + +# HTTP配置 +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # 日志格式 + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + # 基础配置 + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + server_tokens off; + + # 文件上传大小限制 + client_max_body_size 50M; + client_body_buffer_size 128k; + + # Gzip压缩 + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_min_length 1000; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml; + + # 安全头 + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; + + # 包含站点配置 + include /etc/nginx/conf.d/*.conf; +} diff --git a/admin-system/frontend/package-lock.json b/admin-system/package-lock.json similarity index 100% rename from admin-system/frontend/package-lock.json rename to admin-system/package-lock.json diff --git a/admin-system/frontend/package.json b/admin-system/package.json similarity index 100% rename from admin-system/frontend/package.json rename to admin-system/package.json diff --git a/admin-system/frontend/public/electronic-fence-demo.html b/admin-system/public/electronic-fence-demo.html similarity index 97% rename from admin-system/frontend/public/electronic-fence-demo.html rename to admin-system/public/electronic-fence-demo.html index 302c47f..6c7fc82 100644 --- a/admin-system/frontend/public/electronic-fence-demo.html +++ b/admin-system/public/electronic-fence-demo.html @@ -1,256 +1,256 @@ - - - - - - 电子围栏功能演示 - - - -
-
-

电子围栏功能演示

-

宁夏智慧养殖监管平台 - 智能设备模块

-
- -
-
-

🎯 功能概述

-

电子围栏功能允许用户在地图上绘制围栏区域,监控动物活动范围,并提供完整的围栏管理功能。支持多种围栏类型,实时统计区域内外的动物数量,为智慧养殖提供精准的地理围栏管理。

- -
- 📍 地图界面截图区域
- 显示围栏绘制和选择功能 -
- - 进入电子围栏页面 - 查看API文档 -
- -
-
-

🗺️ 地图绘制功能

-
    -
  • 支持多边形围栏绘制
  • -
  • 实时预览绘制过程
  • -
  • 自动保存坐标数据
  • -
  • 支持地图缩放和平移
  • -
  • 地图类型切换(地图/卫星)
  • -
-
- -
-

📊 围栏管理功能

-
    -
  • 围栏列表展示
  • -
  • 围栏搜索和筛选
  • -
  • 围栏信息面板
  • -
  • 围栏类型管理
  • -
  • 围栏状态监控
  • -
-
- -
-

📈 统计功能

-
    -
  • 区域内动物数量统计
  • -
  • 区域外动物数量统计
  • -
  • 放牧状态监控
  • -
  • 围栏使用率分析
  • -
  • 实时数据更新
  • -
-
- -
-

🔧 围栏类型

-
    -
  • 采集器电子围栏
  • -
  • 放牧围栏
  • -
  • 安全围栏
  • -
  • 自定义围栏类型
  • -
  • 围栏权限控制
  • -
-
-
- -
-

🛠️ 技术栈

-
-
- 前端
- Vue 3 + Vite -
-
- UI组件
- Ant Design Vue -
-
- 地图服务
- 百度地图API -
-
- 后端
- Node.js + Express -
-
- 数据库
- MySQL + Sequelize -
-
- 认证
- JWT Token -
-
-
- -
-

🚀 快速开始

-
    -
  1. 登录系统 - 使用管理员账号登录管理后台
  2. -
  3. 导航到电子围栏 - 进入"智能设备" → "电子围栏"页面
  4. -
  5. 开始绘制 - 点击"开始绘制"按钮,在地图上点击绘制围栏
  6. -
  7. 保存围栏 - 完成绘制后填写围栏信息并保存
  8. -
  9. 管理围栏 - 使用下拉框选择和管理现有围栏
  10. -
-
- -
-

📋 API接口

-

电子围栏功能提供完整的RESTful API接口:

-
    -
  • GET /api/electronic-fence - 获取围栏列表
  • -
  • POST /api/electronic-fence - 创建围栏
  • -
  • PUT /api/electronic-fence/:id - 更新围栏
  • -
  • DELETE /api/electronic-fence/:id - 删除围栏
  • -
  • GET /api/electronic-fence/stats/overview - 获取统计概览
  • -
- 查看完整API文档 -
-
-
- - + + + + + + 电子围栏功能演示 + + + +
+
+

电子围栏功能演示

+

宁夏智慧养殖监管平台 - 智能设备模块

+
+ +
+
+

🎯 功能概述

+

电子围栏功能允许用户在地图上绘制围栏区域,监控动物活动范围,并提供完整的围栏管理功能。支持多种围栏类型,实时统计区域内外的动物数量,为智慧养殖提供精准的地理围栏管理。

+ +
+ 📍 地图界面截图区域
+ 显示围栏绘制和选择功能 +
+ + 进入电子围栏页面 + 查看API文档 +
+ +
+
+

🗺️ 地图绘制功能

+
    +
  • 支持多边形围栏绘制
  • +
  • 实时预览绘制过程
  • +
  • 自动保存坐标数据
  • +
  • 支持地图缩放和平移
  • +
  • 地图类型切换(地图/卫星)
  • +
+
+ +
+

📊 围栏管理功能

+
    +
  • 围栏列表展示
  • +
  • 围栏搜索和筛选
  • +
  • 围栏信息面板
  • +
  • 围栏类型管理
  • +
  • 围栏状态监控
  • +
+
+ +
+

📈 统计功能

+
    +
  • 区域内动物数量统计
  • +
  • 区域外动物数量统计
  • +
  • 放牧状态监控
  • +
  • 围栏使用率分析
  • +
  • 实时数据更新
  • +
+
+ +
+

🔧 围栏类型

+
    +
  • 采集器电子围栏
  • +
  • 放牧围栏
  • +
  • 安全围栏
  • +
  • 自定义围栏类型
  • +
  • 围栏权限控制
  • +
+
+
+ +
+

🛠️ 技术栈

+
+
+ 前端
+ Vue 3 + Vite +
+
+ UI组件
+ Ant Design Vue +
+
+ 地图服务
+ 百度地图API +
+
+ 后端
+ Node.js + Express +
+
+ 数据库
+ MySQL + Sequelize +
+
+ 认证
+ JWT Token +
+
+
+ +
+

🚀 快速开始

+
    +
  1. 登录系统 - 使用管理员账号登录管理后台
  2. +
  3. 导航到电子围栏 - 进入"智能设备" → "电子围栏"页面
  4. +
  5. 开始绘制 - 点击"开始绘制"按钮,在地图上点击绘制围栏
  6. +
  7. 保存围栏 - 完成绘制后填写围栏信息并保存
  8. +
  9. 管理围栏 - 使用下拉框选择和管理现有围栏
  10. +
+
+ +
+

📋 API接口

+

电子围栏功能提供完整的RESTful API接口:

+
    +
  • GET /api/electronic-fence - 获取围栏列表
  • +
  • POST /api/electronic-fence - 创建围栏
  • +
  • PUT /api/electronic-fence/:id - 更新围栏
  • +
  • DELETE /api/electronic-fence/:id - 删除围栏
  • +
  • GET /api/electronic-fence/stats/overview - 获取统计概览
  • +
+ 查看完整API文档 +
+
+
+ + diff --git a/admin-system/frontend/public/map-test.html b/admin-system/public/map-test.html similarity index 100% rename from admin-system/frontend/public/map-test.html rename to admin-system/public/map-test.html diff --git a/admin-system/frontend/public/test-export.html b/admin-system/public/test-export.html similarity index 97% rename from admin-system/frontend/public/test-export.html rename to admin-system/public/test-export.html index 68f97f4..940ecdd 100644 --- a/admin-system/frontend/public/test-export.html +++ b/admin-system/public/test-export.html @@ -1,150 +1,150 @@ - - - - - - 导出功能测试 - - - -
-

智能项圈预警导出功能测试

- -
- - - -
- -
-
- - - - + + + + + + 导出功能测试 + + + +
+

智能项圈预警导出功能测试

+ +
+ + + +
+ +
+
+ + + + diff --git a/admin-system/frontend/src/App.vue b/admin-system/src/App.vue similarity index 100% rename from admin-system/frontend/src/App.vue rename to admin-system/src/App.vue diff --git a/admin-system/frontend/src/components/AlertStats.vue b/admin-system/src/components/AlertStats.vue similarity index 100% rename from admin-system/frontend/src/components/AlertStats.vue rename to admin-system/src/components/AlertStats.vue diff --git a/admin-system/frontend/src/components/AnimalStats.vue b/admin-system/src/components/AnimalStats.vue similarity index 100% rename from admin-system/frontend/src/components/AnimalStats.vue rename to admin-system/src/components/AnimalStats.vue diff --git a/admin-system/frontend/src/components/BaiduMap.vue b/admin-system/src/components/BaiduMap.vue similarity index 100% rename from admin-system/frontend/src/components/BaiduMap.vue rename to admin-system/src/components/BaiduMap.vue diff --git a/admin-system/frontend/src/components/ChartPerformanceMonitor.vue b/admin-system/src/components/ChartPerformanceMonitor.vue similarity index 100% rename from admin-system/frontend/src/components/ChartPerformanceMonitor.vue rename to admin-system/src/components/ChartPerformanceMonitor.vue diff --git a/admin-system/frontend/src/components/Dashboard.vue b/admin-system/src/components/Dashboard.vue similarity index 100% rename from admin-system/frontend/src/components/Dashboard.vue rename to admin-system/src/components/Dashboard.vue diff --git a/admin-system/frontend/src/components/DeviceStats.vue b/admin-system/src/components/DeviceStats.vue similarity index 100% rename from admin-system/frontend/src/components/DeviceStats.vue rename to admin-system/src/components/DeviceStats.vue diff --git a/admin-system/frontend/src/components/DynamicMenu.vue b/admin-system/src/components/DynamicMenu.vue similarity index 96% rename from admin-system/frontend/src/components/DynamicMenu.vue rename to admin-system/src/components/DynamicMenu.vue index 432f8b8..f66f3f9 100644 --- a/admin-system/frontend/src/components/DynamicMenu.vue +++ b/admin-system/src/components/DynamicMenu.vue @@ -1,544 +1,544 @@ - - - - - + + + + + diff --git a/admin-system/frontend/src/components/EChart.vue b/admin-system/src/components/EChart.vue similarity index 100% rename from admin-system/frontend/src/components/EChart.vue rename to admin-system/src/components/EChart.vue diff --git a/admin-system/frontend/src/components/FarmDetail.vue b/admin-system/src/components/FarmDetail.vue similarity index 100% rename from admin-system/frontend/src/components/FarmDetail.vue rename to admin-system/src/components/FarmDetail.vue diff --git a/admin-system/frontend/src/components/Menu.vue b/admin-system/src/components/Menu.vue similarity index 100% rename from admin-system/frontend/src/components/Menu.vue rename to admin-system/src/components/Menu.vue diff --git a/admin-system/frontend/src/components/MobileNav.vue b/admin-system/src/components/MobileNav.vue similarity index 96% rename from admin-system/frontend/src/components/MobileNav.vue rename to admin-system/src/components/MobileNav.vue index 5133f2b..0a03377 100644 --- a/admin-system/frontend/src/components/MobileNav.vue +++ b/admin-system/src/components/MobileNav.vue @@ -1,476 +1,476 @@ - - - - - + + + + + diff --git a/admin-system/frontend/src/components/MonitorChart.vue b/admin-system/src/components/MonitorChart.vue similarity index 100% rename from admin-system/frontend/src/components/MonitorChart.vue rename to admin-system/src/components/MonitorChart.vue diff --git a/admin-system/frontend/src/components/PermissionGuard.vue b/admin-system/src/components/PermissionGuard.vue similarity index 95% rename from admin-system/frontend/src/components/PermissionGuard.vue rename to admin-system/src/components/PermissionGuard.vue index 7f46c27..e832eca 100644 --- a/admin-system/frontend/src/components/PermissionGuard.vue +++ b/admin-system/src/components/PermissionGuard.vue @@ -1,75 +1,75 @@ - - - - - + + + + + diff --git a/admin-system/frontend/src/components/SimpleDeviceTest.vue b/admin-system/src/components/SimpleDeviceTest.vue similarity index 100% rename from admin-system/frontend/src/components/SimpleDeviceTest.vue rename to admin-system/src/components/SimpleDeviceTest.vue diff --git a/admin-system/frontend/src/components/VirtualScrollChart.vue b/admin-system/src/components/VirtualScrollChart.vue similarity index 100% rename from admin-system/frontend/src/components/VirtualScrollChart.vue rename to admin-system/src/components/VirtualScrollChart.vue diff --git a/admin-system/frontend/src/config/env.js b/admin-system/src/config/env.js similarity index 100% rename from admin-system/frontend/src/config/env.js rename to admin-system/src/config/env.js diff --git a/admin-system/frontend/src/main.js b/admin-system/src/main.js similarity index 100% rename from admin-system/frontend/src/main.js rename to admin-system/src/main.js diff --git a/admin-system/frontend/src/router/history.js b/admin-system/src/router/history.js similarity index 100% rename from admin-system/frontend/src/router/history.js rename to admin-system/src/router/history.js diff --git a/admin-system/frontend/src/router/index.js b/admin-system/src/router/index.js similarity index 100% rename from admin-system/frontend/src/router/index.js rename to admin-system/src/router/index.js diff --git a/admin-system/frontend/src/router/routes.js b/admin-system/src/router/routes.js similarity index 100% rename from admin-system/frontend/src/router/routes.js rename to admin-system/src/router/routes.js diff --git a/admin-system/frontend/src/stores/data.js b/admin-system/src/stores/data.js similarity index 100% rename from admin-system/frontend/src/stores/data.js rename to admin-system/src/stores/data.js diff --git a/admin-system/frontend/src/stores/index.js b/admin-system/src/stores/index.js similarity index 100% rename from admin-system/frontend/src/stores/index.js rename to admin-system/src/stores/index.js diff --git a/admin-system/frontend/src/stores/settings.js b/admin-system/src/stores/settings.js similarity index 100% rename from admin-system/frontend/src/stores/settings.js rename to admin-system/src/stores/settings.js diff --git a/admin-system/frontend/src/stores/user.js b/admin-system/src/stores/user.js similarity index 100% rename from admin-system/frontend/src/stores/user.js rename to admin-system/src/stores/user.js diff --git a/admin-system/frontend/src/styles/global.css b/admin-system/src/styles/global.css similarity index 100% rename from admin-system/frontend/src/styles/global.css rename to admin-system/src/styles/global.css diff --git a/admin-system/frontend/src/styles/responsive.css b/admin-system/src/styles/responsive.css similarity index 94% rename from admin-system/frontend/src/styles/responsive.css rename to admin-system/src/styles/responsive.css index c2cafce..b6e8dd2 100644 --- a/admin-system/frontend/src/styles/responsive.css +++ b/admin-system/src/styles/responsive.css @@ -1,719 +1,719 @@ -/** - * 响应式设计样式 - * @file responsive.css - * @description 提供全面的响应式设计支持,确保在各种设备上的良好体验 - */ - -/* ========== 断点定义 ========== */ -:root { - /* 屏幕断点 */ - --screen-xs: 480px; /* 超小屏幕 */ - --screen-sm: 576px; /* 小屏幕 */ - --screen-md: 768px; /* 中等屏幕 */ - --screen-lg: 992px; /* 大屏幕 */ - --screen-xl: 1200px; /* 超大屏幕 */ - --screen-xxl: 1600px; /* 超超大屏幕 */ - - /* 移动端优化变量 */ - --mobile-padding: 12px; - --mobile-margin: 8px; - --mobile-font-size: 14px; - --mobile-button-height: 40px; - --mobile-input-height: 40px; - - /* 触摸友好的最小点击区域 */ - --touch-target-min: 44px; -} - -/* ========== 全局响应式基础 ========== */ -* { - box-sizing: border-box; -} - -body { - overflow-x: hidden; - -webkit-overflow-scrolling: touch; /* iOS 平滑滚动 */ -} - -/* ========== 移动端基础布局 ========== */ -@media (max-width: 768px) { - /* 页面标题区域 */ - .page-header { - flex-direction: column; - gap: 12px; - align-items: stretch !important; - } - - .page-header h1 { - font-size: 20px; - margin-bottom: 8px; - } - - .page-actions { - justify-content: center; - } - - /* 搜索区域移动端优化 */ - .search-area { - flex-direction: column; - gap: 8px; - } - - .search-input { - width: 100% !important; - } - - .search-buttons { - display: flex; - gap: 8px; - width: 100%; - } - - .search-buttons .ant-btn { - flex: 1; - height: var(--mobile-button-height); - } - - /* 表格移动端优化 */ - .ant-table-wrapper { - overflow-x: auto; - -webkit-overflow-scrolling: touch; - } - - .ant-table { - min-width: 600px; /* 确保表格在小屏幕上可滚动 */ - } - - .ant-table-thead > tr > th { - padding: 8px 4px; - font-size: 12px; - } - - .ant-table-tbody > tr > td { - padding: 8px 4px; - font-size: 12px; - } - - /* 操作按钮移动端优化 */ - .table-actions { - display: flex; - flex-direction: column; - gap: 4px; - } - - .table-actions .ant-btn { - width: 100%; - height: 32px; - font-size: 12px; - } - - /* 模态框移动端优化 */ - .ant-modal { - margin: 0 !important; - width: 100vw !important; - max-width: 100vw !important; - top: 0 !important; - padding-bottom: 0 !important; - } - - .ant-modal-content { - border-radius: 0; - height: 100vh; - display: flex; - flex-direction: column; - } - - .ant-modal-body { - flex: 1; - overflow-y: auto; - padding: 16px; - } - - .ant-modal-footer { - border-top: 1px solid #f0f0f0; - padding: 12px 16px; - } - - .ant-modal-footer .ant-btn { - height: var(--mobile-button-height); - margin: 0 4px; - } - - /* 表单移动端优化 */ - .ant-form-item-label { - font-size: var(--mobile-font-size); - } - - .ant-input, - .ant-input-number, - .ant-select-selector, - .ant-picker { - height: var(--mobile-input-height) !important; - font-size: var(--mobile-font-size); - } - - .ant-select-selection-item { - line-height: calc(var(--mobile-input-height) - 2px); - } - - /* 卡片组件移动端优化 */ - .ant-card { - margin-bottom: 12px; - } - - .ant-card-head { - padding: 0 12px; - min-height: 48px; - } - - .ant-card-body { - padding: 12px; - } - - /* 分页器移动端优化 */ - .ant-pagination { - text-align: center; - margin-top: 16px; - } - - .ant-pagination-item, - .ant-pagination-prev, - .ant-pagination-next { - min-width: var(--touch-target-min); - height: var(--touch-target-min); - line-height: var(--touch-target-min); - } -} - -/* ========== 平板端优化 (768px - 992px) ========== */ -@media (min-width: 768px) and (max-width: 992px) { - .page-header { - padding: 16px 20px; - } - - .ant-table-thead > tr > th, - .ant-table-tbody > tr > td { - padding: 12px 8px; - } - - .ant-modal { - width: 90vw !important; - max-width: 800px !important; - } - - .search-area { - flex-wrap: wrap; - gap: 12px; - } - - .search-input { - min-width: 250px; - flex: 1; - } -} - -/* ========== 大屏幕优化 (>1600px) ========== */ -@media (min-width: 1600px) { - .page-container { - max-width: 1400px; - margin: 0 auto; - } - - .dashboard-grid { - grid-template-columns: repeat(4, 1fr); - } - - .chart-container { - height: 500px; - } -} - -/* ========== 移动端导航优化 ========== */ -@media (max-width: 768px) { - /* 侧边栏移动端优化 */ - .ant-layout-sider { - position: fixed !important; - left: 0; - top: 0; - bottom: 0; - z-index: 1001; - transform: translateX(-100%); - transition: transform 0.3s ease; - } - - .ant-layout-sider.ant-layout-sider-collapsed { - transform: translateX(-100%); - } - - .ant-layout-sider:not(.ant-layout-sider-collapsed) { - transform: translateX(0); - } - - /* 移动端顶部栏 */ - .mobile-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 16px; - background: #fff; - border-bottom: 1px solid #f0f0f0; - position: sticky; - top: 0; - z-index: 1000; - } - - .mobile-menu-button { - display: flex; - align-items: center; - justify-content: center; - width: var(--touch-target-min); - height: var(--touch-target-min); - border: none; - background: transparent; - cursor: pointer; - } - - .mobile-title { - font-size: 18px; - font-weight: 600; - color: #262626; - } - - /* 内容区域移动端调整 */ - .ant-layout-content { - padding: 12px !important; - margin-left: 0 !important; - } - - /* 面包屑移动端优化 */ - .ant-breadcrumb { - font-size: 12px; - margin-bottom: 8px; - } -} - -/* ========== 移动端表格优化 ========== */ -@media (max-width: 576px) { - .responsive-table { - display: block; - } - - .responsive-table .ant-table-wrapper { - display: block; - overflow-x: auto; - white-space: nowrap; - } - - .responsive-table .ant-table { - display: block; - min-width: auto; - } - - .responsive-table .ant-table-thead { - display: none; /* 在超小屏幕上隐藏表头 */ - } - - .responsive-table .ant-table-tbody { - display: block; - } - - .responsive-table .ant-table-row { - display: block; - border: 1px solid #f0f0f0; - border-radius: 8px; - margin-bottom: 12px; - padding: 12px; - background: #fff; - } - - .responsive-table .ant-table-cell { - display: block; - border: none; - padding: 4px 0; - text-align: left !important; - } - - .responsive-table .ant-table-cell:before { - content: attr(data-label) ": "; - font-weight: 600; - color: #595959; - min-width: 80px; - display: inline-block; - } -} - -/* ========== 移动端图表优化 ========== */ -@media (max-width: 768px) { - .chart-container { - height: 300px !important; - margin-bottom: 16px; - } - - .dashboard-stats { - grid-template-columns: repeat(2, 1fr) !important; - gap: 8px; - } - - .stat-card { - padding: 12px !important; - } - - .stat-card .stat-value { - font-size: 20px !important; - } - - .stat-card .stat-label { - font-size: 12px !important; - } -} - -/* ========== 移动端地图优化 ========== */ -@media (max-width: 768px) { - .map-container { - height: 60vh !important; - margin: 0 -12px; - } - - .map-controls { - position: absolute; - top: 10px; - right: 10px; - z-index: 1000; - display: flex; - flex-direction: column; - gap: 8px; - } - - .map-controls .ant-btn { - width: var(--touch-target-min); - height: var(--touch-target-min); - padding: 0; - display: flex; - align-items: center; - justify-content: center; - } -} - -/* ========== 移动端性能优化 ========== */ -@media (max-width: 768px) { - /* 减少阴影效果以提升性能 */ - .ant-card, - .ant-modal-content, - .ant-drawer-content { - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1) !important; - } - - /* 优化动画性能 */ - .ant-table-tbody > tr:hover > td { - background: #f5f5f5 !important; - } - - /* 禁用某些在移动端不必要的动画 */ - .ant-table-tbody > tr { - transition: none !important; - } -} - -/* ========== 触摸设备优化 ========== */ -@media (hover: none) and (pointer: coarse) { - /* 为触摸设备优化点击区域 */ - .ant-btn, - .ant-input, - .ant-select-selector, - .ant-picker, - .ant-table-row { - min-height: var(--touch-target-min); - } - - /* 移除悬停效果,因为触摸设备不需要 */ - .ant-btn:hover, - .ant-table-tbody > tr:hover > td { - background: inherit !important; - } - - /* 触摸反馈 */ - .ant-btn:active { - transform: scale(0.98); - transition: transform 0.1s ease; - } -} - -/* ========== 横屏模式优化 ========== */ -@media (max-width: 768px) and (orientation: landscape) { - .mobile-header { - padding: 8px 16px; - } - - .chart-container { - height: 40vh !important; - } - - .ant-modal-content { - height: 100vh; - } -} - -/* ========== 可访问性改进 ========== */ -@media (prefers-reduced-motion: reduce) { - /* 为喜欢减少动画的用户提供静态体验 */ - *, - *::before, - *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - } -} - -/* ========== 高对比度模式支持 ========== */ -@media (prefers-contrast: high) { - .ant-btn-primary { - background: #000 !important; - border-color: #000 !important; - } - - .ant-table-thead > tr > th { - background: #f0f0f0 !important; - border: 1px solid #d9d9d9 !important; - } -} - -/* ========== 深色模式基础支持 ========== */ -@media (prefers-color-scheme: dark) { - .ant-layout { - background: #141414 !important; - } - - .ant-layout-content { - background: #000 !important; - } - - .ant-card { - background: #1f1f1f !important; - border-color: #434343 !important; - } - - .ant-table-wrapper { - background: #1f1f1f !important; - } -} - -/* ========== 特定组件移动端优化 ========== */ - -/* 分页器移动端优化 */ -@media (max-width: 576px) { - .ant-pagination { - display: flex; - justify-content: center; - flex-wrap: wrap; - } - - .ant-pagination-options { - display: none; /* 隐藏页面大小选择器 */ - } - - .ant-pagination-simple { - margin: 8px 0; - } -} - -/* 标签移动端优化 */ -@media (max-width: 768px) { - .ant-tag { - margin: 2px; - padding: 0 8px; - font-size: 12px; - } -} - -/* 统计卡片移动端优化 */ -@media (max-width: 768px) { - .stats-grid { - grid-template-columns: repeat(2, 1fr) !important; - gap: 8px !important; - } - - .stats-grid .ant-col { - margin-bottom: 8px; - } -} - -/* 图表容器移动端优化 */ -@media (max-width: 768px) { - .charts-row { - flex-direction: column; - } - - .chart-card { - margin-bottom: 16px; - } - - .chart-card .ant-card-body { - padding: 12px; - } -} - -/* ========== 自定义移动端布局类 ========== */ -.mobile-container { - padding: var(--mobile-padding); -} - -.mobile-hidden { - display: none; -} - -.mobile-only { - display: none; -} - -@media (max-width: 768px) { - .mobile-hidden { - display: none !important; - } - - .mobile-only { - display: block; - } - - .mobile-flex { - display: flex !important; - } - - .mobile-full-width { - width: 100% !important; - } - - .mobile-text-center { - text-align: center !important; - } -} - -/* ========== 加载状态移动端优化 ========== */ -@media (max-width: 768px) { - .ant-spin-container { - min-height: 200px; - } - - .ant-empty { - margin: 32px 0; - } - - .ant-empty-description { - font-size: var(--mobile-font-size); - } -} - -/* ========== 消息提示移动端优化 ========== */ -@media (max-width: 768px) { - .ant-message { - top: 20px !important; - } - - .ant-message-notice { - margin-bottom: 8px; - } - - .ant-notification { - width: calc(100vw - 32px) !important; - margin-right: 16px !important; - } -} - -/* ========== 菜单移动端优化 ========== */ -@media (max-width: 768px) { - .ant-menu-item { - height: 48px; - line-height: 48px; - padding: 0 16px !important; - } - - .ant-menu-item-icon { - font-size: 16px; - } - - .ant-menu-submenu-title { - height: 48px; - line-height: 48px; - padding: 0 16px !important; - } -} - -/* ========== 工具提示移动端优化 ========== */ -@media (max-width: 768px) { - .ant-tooltip { - font-size: 12px; - } - - /* 在移动端禁用工具提示,因为没有悬停 */ - .ant-tooltip-hidden-arrow { - display: none !important; - } -} - -/* ========== 表单布局移动端优化 ========== */ -@media (max-width: 768px) { - .ant-row { - margin: 0 !important; - } - - .ant-col { - padding: 0 4px !important; - margin-bottom: 12px; - } - - .ant-form-item { - margin-bottom: 16px; - } - - .ant-form-item-control { - width: 100%; - } -} - -/* ========== 调试辅助类 ========== */ -.debug-breakpoint { - position: fixed; - top: 10px; - left: 10px; - background: rgba(255, 0, 0, 0.8); - color: white; - padding: 4px 8px; - border-radius: 4px; - font-size: 12px; - z-index: 9999; - font-family: monospace; -} - -.debug-breakpoint:after { - content: 'Desktop'; -} - -@media (max-width: 1600px) { - .debug-breakpoint:after { - content: 'XL'; - } -} - -@media (max-width: 1200px) { - .debug-breakpoint:after { - content: 'LG'; - } -} - -@media (max-width: 992px) { - .debug-breakpoint:after { - content: 'MD'; - } -} - -@media (max-width: 768px) { - .debug-breakpoint:after { - content: 'SM'; - } -} - -@media (max-width: 576px) { - .debug-breakpoint:after { - content: 'XS'; - } -} +/** + * 响应式设计样式 + * @file responsive.css + * @description 提供全面的响应式设计支持,确保在各种设备上的良好体验 + */ + +/* ========== 断点定义 ========== */ +:root { + /* 屏幕断点 */ + --screen-xs: 480px; /* 超小屏幕 */ + --screen-sm: 576px; /* 小屏幕 */ + --screen-md: 768px; /* 中等屏幕 */ + --screen-lg: 992px; /* 大屏幕 */ + --screen-xl: 1200px; /* 超大屏幕 */ + --screen-xxl: 1600px; /* 超超大屏幕 */ + + /* 移动端优化变量 */ + --mobile-padding: 12px; + --mobile-margin: 8px; + --mobile-font-size: 14px; + --mobile-button-height: 40px; + --mobile-input-height: 40px; + + /* 触摸友好的最小点击区域 */ + --touch-target-min: 44px; +} + +/* ========== 全局响应式基础 ========== */ +* { + box-sizing: border-box; +} + +body { + overflow-x: hidden; + -webkit-overflow-scrolling: touch; /* iOS 平滑滚动 */ +} + +/* ========== 移动端基础布局 ========== */ +@media (max-width: 768px) { + /* 页面标题区域 */ + .page-header { + flex-direction: column; + gap: 12px; + align-items: stretch !important; + } + + .page-header h1 { + font-size: 20px; + margin-bottom: 8px; + } + + .page-actions { + justify-content: center; + } + + /* 搜索区域移动端优化 */ + .search-area { + flex-direction: column; + gap: 8px; + } + + .search-input { + width: 100% !important; + } + + .search-buttons { + display: flex; + gap: 8px; + width: 100%; + } + + .search-buttons .ant-btn { + flex: 1; + height: var(--mobile-button-height); + } + + /* 表格移动端优化 */ + .ant-table-wrapper { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + + .ant-table { + min-width: 600px; /* 确保表格在小屏幕上可滚动 */ + } + + .ant-table-thead > tr > th { + padding: 8px 4px; + font-size: 12px; + } + + .ant-table-tbody > tr > td { + padding: 8px 4px; + font-size: 12px; + } + + /* 操作按钮移动端优化 */ + .table-actions { + display: flex; + flex-direction: column; + gap: 4px; + } + + .table-actions .ant-btn { + width: 100%; + height: 32px; + font-size: 12px; + } + + /* 模态框移动端优化 */ + .ant-modal { + margin: 0 !important; + width: 100vw !important; + max-width: 100vw !important; + top: 0 !important; + padding-bottom: 0 !important; + } + + .ant-modal-content { + border-radius: 0; + height: 100vh; + display: flex; + flex-direction: column; + } + + .ant-modal-body { + flex: 1; + overflow-y: auto; + padding: 16px; + } + + .ant-modal-footer { + border-top: 1px solid #f0f0f0; + padding: 12px 16px; + } + + .ant-modal-footer .ant-btn { + height: var(--mobile-button-height); + margin: 0 4px; + } + + /* 表单移动端优化 */ + .ant-form-item-label { + font-size: var(--mobile-font-size); + } + + .ant-input, + .ant-input-number, + .ant-select-selector, + .ant-picker { + height: var(--mobile-input-height) !important; + font-size: var(--mobile-font-size); + } + + .ant-select-selection-item { + line-height: calc(var(--mobile-input-height) - 2px); + } + + /* 卡片组件移动端优化 */ + .ant-card { + margin-bottom: 12px; + } + + .ant-card-head { + padding: 0 12px; + min-height: 48px; + } + + .ant-card-body { + padding: 12px; + } + + /* 分页器移动端优化 */ + .ant-pagination { + text-align: center; + margin-top: 16px; + } + + .ant-pagination-item, + .ant-pagination-prev, + .ant-pagination-next { + min-width: var(--touch-target-min); + height: var(--touch-target-min); + line-height: var(--touch-target-min); + } +} + +/* ========== 平板端优化 (768px - 992px) ========== */ +@media (min-width: 768px) and (max-width: 992px) { + .page-header { + padding: 16px 20px; + } + + .ant-table-thead > tr > th, + .ant-table-tbody > tr > td { + padding: 12px 8px; + } + + .ant-modal { + width: 90vw !important; + max-width: 800px !important; + } + + .search-area { + flex-wrap: wrap; + gap: 12px; + } + + .search-input { + min-width: 250px; + flex: 1; + } +} + +/* ========== 大屏幕优化 (>1600px) ========== */ +@media (min-width: 1600px) { + .page-container { + max-width: 1400px; + margin: 0 auto; + } + + .dashboard-grid { + grid-template-columns: repeat(4, 1fr); + } + + .chart-container { + height: 500px; + } +} + +/* ========== 移动端导航优化 ========== */ +@media (max-width: 768px) { + /* 侧边栏移动端优化 */ + .ant-layout-sider { + position: fixed !important; + left: 0; + top: 0; + bottom: 0; + z-index: 1001; + transform: translateX(-100%); + transition: transform 0.3s ease; + } + + .ant-layout-sider.ant-layout-sider-collapsed { + transform: translateX(-100%); + } + + .ant-layout-sider:not(.ant-layout-sider-collapsed) { + transform: translateX(0); + } + + /* 移动端顶部栏 */ + .mobile-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + background: #fff; + border-bottom: 1px solid #f0f0f0; + position: sticky; + top: 0; + z-index: 1000; + } + + .mobile-menu-button { + display: flex; + align-items: center; + justify-content: center; + width: var(--touch-target-min); + height: var(--touch-target-min); + border: none; + background: transparent; + cursor: pointer; + } + + .mobile-title { + font-size: 18px; + font-weight: 600; + color: #262626; + } + + /* 内容区域移动端调整 */ + .ant-layout-content { + padding: 12px !important; + margin-left: 0 !important; + } + + /* 面包屑移动端优化 */ + .ant-breadcrumb { + font-size: 12px; + margin-bottom: 8px; + } +} + +/* ========== 移动端表格优化 ========== */ +@media (max-width: 576px) { + .responsive-table { + display: block; + } + + .responsive-table .ant-table-wrapper { + display: block; + overflow-x: auto; + white-space: nowrap; + } + + .responsive-table .ant-table { + display: block; + min-width: auto; + } + + .responsive-table .ant-table-thead { + display: none; /* 在超小屏幕上隐藏表头 */ + } + + .responsive-table .ant-table-tbody { + display: block; + } + + .responsive-table .ant-table-row { + display: block; + border: 1px solid #f0f0f0; + border-radius: 8px; + margin-bottom: 12px; + padding: 12px; + background: #fff; + } + + .responsive-table .ant-table-cell { + display: block; + border: none; + padding: 4px 0; + text-align: left !important; + } + + .responsive-table .ant-table-cell:before { + content: attr(data-label) ": "; + font-weight: 600; + color: #595959; + min-width: 80px; + display: inline-block; + } +} + +/* ========== 移动端图表优化 ========== */ +@media (max-width: 768px) { + .chart-container { + height: 300px !important; + margin-bottom: 16px; + } + + .dashboard-stats { + grid-template-columns: repeat(2, 1fr) !important; + gap: 8px; + } + + .stat-card { + padding: 12px !important; + } + + .stat-card .stat-value { + font-size: 20px !important; + } + + .stat-card .stat-label { + font-size: 12px !important; + } +} + +/* ========== 移动端地图优化 ========== */ +@media (max-width: 768px) { + .map-container { + height: 60vh !important; + margin: 0 -12px; + } + + .map-controls { + position: absolute; + top: 10px; + right: 10px; + z-index: 1000; + display: flex; + flex-direction: column; + gap: 8px; + } + + .map-controls .ant-btn { + width: var(--touch-target-min); + height: var(--touch-target-min); + padding: 0; + display: flex; + align-items: center; + justify-content: center; + } +} + +/* ========== 移动端性能优化 ========== */ +@media (max-width: 768px) { + /* 减少阴影效果以提升性能 */ + .ant-card, + .ant-modal-content, + .ant-drawer-content { + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1) !important; + } + + /* 优化动画性能 */ + .ant-table-tbody > tr:hover > td { + background: #f5f5f5 !important; + } + + /* 禁用某些在移动端不必要的动画 */ + .ant-table-tbody > tr { + transition: none !important; + } +} + +/* ========== 触摸设备优化 ========== */ +@media (hover: none) and (pointer: coarse) { + /* 为触摸设备优化点击区域 */ + .ant-btn, + .ant-input, + .ant-select-selector, + .ant-picker, + .ant-table-row { + min-height: var(--touch-target-min); + } + + /* 移除悬停效果,因为触摸设备不需要 */ + .ant-btn:hover, + .ant-table-tbody > tr:hover > td { + background: inherit !important; + } + + /* 触摸反馈 */ + .ant-btn:active { + transform: scale(0.98); + transition: transform 0.1s ease; + } +} + +/* ========== 横屏模式优化 ========== */ +@media (max-width: 768px) and (orientation: landscape) { + .mobile-header { + padding: 8px 16px; + } + + .chart-container { + height: 40vh !important; + } + + .ant-modal-content { + height: 100vh; + } +} + +/* ========== 可访问性改进 ========== */ +@media (prefers-reduced-motion: reduce) { + /* 为喜欢减少动画的用户提供静态体验 */ + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} + +/* ========== 高对比度模式支持 ========== */ +@media (prefers-contrast: high) { + .ant-btn-primary { + background: #000 !important; + border-color: #000 !important; + } + + .ant-table-thead > tr > th { + background: #f0f0f0 !important; + border: 1px solid #d9d9d9 !important; + } +} + +/* ========== 深色模式基础支持 ========== */ +@media (prefers-color-scheme: dark) { + .ant-layout { + background: #141414 !important; + } + + .ant-layout-content { + background: #000 !important; + } + + .ant-card { + background: #1f1f1f !important; + border-color: #434343 !important; + } + + .ant-table-wrapper { + background: #1f1f1f !important; + } +} + +/* ========== 特定组件移动端优化 ========== */ + +/* 分页器移动端优化 */ +@media (max-width: 576px) { + .ant-pagination { + display: flex; + justify-content: center; + flex-wrap: wrap; + } + + .ant-pagination-options { + display: none; /* 隐藏页面大小选择器 */ + } + + .ant-pagination-simple { + margin: 8px 0; + } +} + +/* 标签移动端优化 */ +@media (max-width: 768px) { + .ant-tag { + margin: 2px; + padding: 0 8px; + font-size: 12px; + } +} + +/* 统计卡片移动端优化 */ +@media (max-width: 768px) { + .stats-grid { + grid-template-columns: repeat(2, 1fr) !important; + gap: 8px !important; + } + + .stats-grid .ant-col { + margin-bottom: 8px; + } +} + +/* 图表容器移动端优化 */ +@media (max-width: 768px) { + .charts-row { + flex-direction: column; + } + + .chart-card { + margin-bottom: 16px; + } + + .chart-card .ant-card-body { + padding: 12px; + } +} + +/* ========== 自定义移动端布局类 ========== */ +.mobile-container { + padding: var(--mobile-padding); +} + +.mobile-hidden { + display: none; +} + +.mobile-only { + display: none; +} + +@media (max-width: 768px) { + .mobile-hidden { + display: none !important; + } + + .mobile-only { + display: block; + } + + .mobile-flex { + display: flex !important; + } + + .mobile-full-width { + width: 100% !important; + } + + .mobile-text-center { + text-align: center !important; + } +} + +/* ========== 加载状态移动端优化 ========== */ +@media (max-width: 768px) { + .ant-spin-container { + min-height: 200px; + } + + .ant-empty { + margin: 32px 0; + } + + .ant-empty-description { + font-size: var(--mobile-font-size); + } +} + +/* ========== 消息提示移动端优化 ========== */ +@media (max-width: 768px) { + .ant-message { + top: 20px !important; + } + + .ant-message-notice { + margin-bottom: 8px; + } + + .ant-notification { + width: calc(100vw - 32px) !important; + margin-right: 16px !important; + } +} + +/* ========== 菜单移动端优化 ========== */ +@media (max-width: 768px) { + .ant-menu-item { + height: 48px; + line-height: 48px; + padding: 0 16px !important; + } + + .ant-menu-item-icon { + font-size: 16px; + } + + .ant-menu-submenu-title { + height: 48px; + line-height: 48px; + padding: 0 16px !important; + } +} + +/* ========== 工具提示移动端优化 ========== */ +@media (max-width: 768px) { + .ant-tooltip { + font-size: 12px; + } + + /* 在移动端禁用工具提示,因为没有悬停 */ + .ant-tooltip-hidden-arrow { + display: none !important; + } +} + +/* ========== 表单布局移动端优化 ========== */ +@media (max-width: 768px) { + .ant-row { + margin: 0 !important; + } + + .ant-col { + padding: 0 4px !important; + margin-bottom: 12px; + } + + .ant-form-item { + margin-bottom: 16px; + } + + .ant-form-item-control { + width: 100%; + } +} + +/* ========== 调试辅助类 ========== */ +.debug-breakpoint { + position: fixed; + top: 10px; + left: 10px; + background: rgba(255, 0, 0, 0.8); + color: white; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + z-index: 9999; + font-family: monospace; +} + +.debug-breakpoint:after { + content: 'Desktop'; +} + +@media (max-width: 1600px) { + .debug-breakpoint:after { + content: 'XL'; + } +} + +@media (max-width: 1200px) { + .debug-breakpoint:after { + content: 'LG'; + } +} + +@media (max-width: 992px) { + .debug-breakpoint:after { + content: 'MD'; + } +} + +@media (max-width: 768px) { + .debug-breakpoint:after { + content: 'SM'; + } +} + +@media (max-width: 576px) { + .debug-breakpoint:after { + content: 'XS'; + } +} diff --git a/admin-system/frontend/src/styles/theme.js b/admin-system/src/styles/theme.js similarity index 100% rename from admin-system/frontend/src/styles/theme.js rename to admin-system/src/styles/theme.js diff --git a/admin-system/frontend/src/test-api-direct.js b/admin-system/src/test-api-direct.js similarity index 100% rename from admin-system/frontend/src/test-api-direct.js rename to admin-system/src/test-api-direct.js diff --git a/admin-system/frontend/src/test-data-store.js b/admin-system/src/test-data-store.js similarity index 100% rename from admin-system/frontend/src/test-data-store.js rename to admin-system/src/test-data-store.js diff --git a/admin-system/frontend/src/utils/api.js b/admin-system/src/utils/api.js similarity index 100% rename from admin-system/frontend/src/utils/api.js rename to admin-system/src/utils/api.js diff --git a/admin-system/frontend/src/utils/baiduMapLoader.js b/admin-system/src/utils/baiduMapLoader.js similarity index 96% rename from admin-system/frontend/src/utils/baiduMapLoader.js rename to admin-system/src/utils/baiduMapLoader.js index 2090b2a..1d24539 100644 --- a/admin-system/frontend/src/utils/baiduMapLoader.js +++ b/admin-system/src/utils/baiduMapLoader.js @@ -1,273 +1,273 @@ -/** - * 百度地图加载器 - * 提供更健壮的百度地图API加载和初始化功能 - */ - -// 百度地图API加载状态 -let BMapLoaded = false; -let loadingPromise = null; -let retryCount = 0; -const MAX_RETRY = 3; - -/** - * 加载百度地图API - * @param {string} apiKey - 百度地图API密钥 - * @returns {Promise} 加载完成的Promise - */ -export const loadBaiduMapAPI = async (apiKey = 'SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo') => { - // 如果已经加载过,直接返回 - if (BMapLoaded && window.BMap) { - console.log('百度地图API已加载'); - return Promise.resolve(); - } - - // 如果正在加载中,返回加载Promise - if (loadingPromise) { - console.log('百度地图API正在加载中...'); - return loadingPromise; - } - - console.log('开始加载百度地图API...'); - - // 创建加载Promise - loadingPromise = new Promise(async (resolve, reject) => { - try { - // 检查API密钥 - if (!apiKey || apiKey === 'YOUR_VALID_BAIDU_MAP_API_KEY') { - const error = new Error('百度地图API密钥未配置或无效'); - console.error('API密钥错误:', error); - reject(error); - return; - } - - // 检查是否已经存在BMap - if (typeof window.BMap !== 'undefined' && window.BMap.Map) { - console.log('BMap已存在,直接使用'); - BMapLoaded = true; - resolve(); - return; - } - - // 创建全局回调函数 - window.initBaiduMapCallback = () => { - console.log('百度地图API脚本加载完成'); - - // 等待BMap对象完全初始化 - const checkBMap = () => { - if (window.BMap && typeof window.BMap.Map === 'function') { - console.log('BMap对象初始化完成'); - BMapLoaded = true; - resolve(); - // 清理全局回调 - delete window.initBaiduMapCallback; - } else { - console.log('等待BMap对象初始化...'); - setTimeout(checkBMap, 100); - } - }; - - // 开始检查BMap对象 - setTimeout(checkBMap, 50); - }; - - // 创建script标签 - const script = document.createElement('script'); - script.type = 'text/javascript'; - script.src = `https://api.map.baidu.com/api?v=3.0&ak=${apiKey}&callback=initBaiduMapCallback`; - - console.log('百度地图API URL:', script.src); - - script.onerror = (error) => { - console.error('百度地图脚本加载失败:', error); - reject(new Error('百度地图脚本加载失败')); - }; - - script.onload = () => { - console.log('百度地图脚本加载成功'); - }; - - // 设置超时 - const timeout = setTimeout(() => { - if (!BMapLoaded) { - console.error('百度地图API加载超时'); - reject(new Error('百度地图API加载超时')); - } - }, 20000); - - // 成功加载后清除超时 - const originalResolve = resolve; - resolve = () => { - clearTimeout(timeout); - originalResolve(); - }; - - // 添加到文档中 - document.head.appendChild(script); - - } catch (error) { - console.error('加载百度地图API时出错:', error); - reject(error); - } - }); - - return loadingPromise; -}; - -/** - * 重试加载百度地图API - * @param {string} apiKey - 百度地图API密钥 - * @returns {Promise} 加载完成的Promise - */ -export const retryLoadBaiduMapAPI = async (apiKey = 'SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo') => { - if (retryCount >= MAX_RETRY) { - throw new Error('百度地图API加载重试次数已达上限'); - } - - retryCount++; - console.log(`第 ${retryCount} 次尝试加载百度地图API...`); - - // 重置状态 - BMapLoaded = false; - loadingPromise = null; - - // 清理可能存在的旧脚本 - const existingScript = document.querySelector('script[src*="api.map.baidu.com"]'); - if (existingScript) { - existingScript.remove(); - } - - // 清理全局回调 - if (window.initBaiduMapCallback) { - delete window.initBaiduMapCallback; - } - - return loadBaiduMapAPI(apiKey); -}; - -/** - * 检查百度地图API是否可用 - * @returns {boolean} 是否可用 - */ -export const isBaiduMapAvailable = () => { - return BMapLoaded && window.BMap && typeof window.BMap.Map === 'function'; -}; - -/** - * 等待百度地图API加载完成 - * @param {number} timeout - 超时时间(毫秒) - * @returns {Promise} 加载完成的Promise - */ -export const waitForBaiduMap = (timeout = 10000) => { - return new Promise((resolve, reject) => { - if (isBaiduMapAvailable()) { - resolve(); - return; - } - - const startTime = Date.now(); - const checkInterval = setInterval(() => { - if (isBaiduMapAvailable()) { - clearInterval(checkInterval); - resolve(); - } else if (Date.now() - startTime > timeout) { - clearInterval(checkInterval); - reject(new Error('等待百度地图API加载超时')); - } - }, 100); - }); -}; - -/** - * 创建百度地图实例 - * @param {string|HTMLElement} container - 地图容器ID或元素 - * @param {Object} options - 地图配置选项 - * @returns {Promise} 地图实例 - */ -export const createBaiduMap = async (container, options = {}) => { - try { - // 确保百度地图API已加载 - await loadBaiduMapAPI(); - - // 等待BMap完全可用 - await waitForBaiduMap(); - - // 获取容器元素 - let mapContainer = typeof container === 'string' - ? document.getElementById(container) - : container; - - // 如果容器不存在,等待一段时间后重试 - if (!mapContainer) { - console.log('地图容器不存在,等待DOM渲染...'); - await new Promise(resolve => setTimeout(resolve, 200)); - mapContainer = typeof container === 'string' - ? document.getElementById(container) - : container; - } - - if (!mapContainer) { - throw new Error('地图容器不存在,请确保DOM已正确渲染'); - } - - // 检查容器是否有尺寸 - if (mapContainer.offsetWidth === 0 || mapContainer.offsetHeight === 0) { - console.warn('地图容器尺寸为0,等待尺寸计算...'); - await new Promise(resolve => setTimeout(resolve, 100)); - } - - // 默认配置 - const defaultOptions = { - center: new window.BMap.Point(106.27, 38.47), // 宁夏中心点 - zoom: 8, - enableMapClick: true, - enableScrollWheelZoom: true, - enableDragging: true, - enableDoubleClickZoom: true, - enableKeyboard: true - }; - - // 合并配置 - const mergedOptions = { ...defaultOptions, ...options }; - - // 创建地图实例 - const map = new window.BMap.Map(mapContainer); - - // 设置中心点和缩放级别 - map.centerAndZoom(mergedOptions.center, mergedOptions.zoom); - - // 配置地图功能 - if (mergedOptions.enableScrollWheelZoom) { - map.enableScrollWheelZoom(); - } - - if (mergedOptions.enableDragging) { - map.enableDragging(); - } else { - map.disableDragging(); - } - - if (mergedOptions.enableDoubleClickZoom) { - map.enableDoubleClickZoom(); - } else { - map.disableDoubleClickZoom(); - } - - if (mergedOptions.enableKeyboard) { - map.enableKeyboard(); - } else { - map.disableKeyboard(); - } - - // 添加地图控件 - map.addControl(new window.BMap.NavigationControl()); - map.addControl(new window.BMap.ScaleControl()); - map.addControl(new window.BMap.OverviewMapControl()); - - console.log('百度地图创建成功'); - return map; - - } catch (error) { - console.error('创建百度地图失败:', error); - throw error; - } -}; +/** + * 百度地图加载器 + * 提供更健壮的百度地图API加载和初始化功能 + */ + +// 百度地图API加载状态 +let BMapLoaded = false; +let loadingPromise = null; +let retryCount = 0; +const MAX_RETRY = 3; + +/** + * 加载百度地图API + * @param {string} apiKey - 百度地图API密钥 + * @returns {Promise} 加载完成的Promise + */ +export const loadBaiduMapAPI = async (apiKey = 'SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo') => { + // 如果已经加载过,直接返回 + if (BMapLoaded && window.BMap) { + console.log('百度地图API已加载'); + return Promise.resolve(); + } + + // 如果正在加载中,返回加载Promise + if (loadingPromise) { + console.log('百度地图API正在加载中...'); + return loadingPromise; + } + + console.log('开始加载百度地图API...'); + + // 创建加载Promise + loadingPromise = new Promise(async (resolve, reject) => { + try { + // 检查API密钥 + if (!apiKey || apiKey === 'YOUR_VALID_BAIDU_MAP_API_KEY') { + const error = new Error('百度地图API密钥未配置或无效'); + console.error('API密钥错误:', error); + reject(error); + return; + } + + // 检查是否已经存在BMap + if (typeof window.BMap !== 'undefined' && window.BMap.Map) { + console.log('BMap已存在,直接使用'); + BMapLoaded = true; + resolve(); + return; + } + + // 创建全局回调函数 + window.initBaiduMapCallback = () => { + console.log('百度地图API脚本加载完成'); + + // 等待BMap对象完全初始化 + const checkBMap = () => { + if (window.BMap && typeof window.BMap.Map === 'function') { + console.log('BMap对象初始化完成'); + BMapLoaded = true; + resolve(); + // 清理全局回调 + delete window.initBaiduMapCallback; + } else { + console.log('等待BMap对象初始化...'); + setTimeout(checkBMap, 100); + } + }; + + // 开始检查BMap对象 + setTimeout(checkBMap, 50); + }; + + // 创建script标签 + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = `https://api.map.baidu.com/api?v=3.0&ak=${apiKey}&callback=initBaiduMapCallback`; + + console.log('百度地图API URL:', script.src); + + script.onerror = (error) => { + console.error('百度地图脚本加载失败:', error); + reject(new Error('百度地图脚本加载失败')); + }; + + script.onload = () => { + console.log('百度地图脚本加载成功'); + }; + + // 设置超时 + const timeout = setTimeout(() => { + if (!BMapLoaded) { + console.error('百度地图API加载超时'); + reject(new Error('百度地图API加载超时')); + } + }, 20000); + + // 成功加载后清除超时 + const originalResolve = resolve; + resolve = () => { + clearTimeout(timeout); + originalResolve(); + }; + + // 添加到文档中 + document.head.appendChild(script); + + } catch (error) { + console.error('加载百度地图API时出错:', error); + reject(error); + } + }); + + return loadingPromise; +}; + +/** + * 重试加载百度地图API + * @param {string} apiKey - 百度地图API密钥 + * @returns {Promise} 加载完成的Promise + */ +export const retryLoadBaiduMapAPI = async (apiKey = 'SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo') => { + if (retryCount >= MAX_RETRY) { + throw new Error('百度地图API加载重试次数已达上限'); + } + + retryCount++; + console.log(`第 ${retryCount} 次尝试加载百度地图API...`); + + // 重置状态 + BMapLoaded = false; + loadingPromise = null; + + // 清理可能存在的旧脚本 + const existingScript = document.querySelector('script[src*="api.map.baidu.com"]'); + if (existingScript) { + existingScript.remove(); + } + + // 清理全局回调 + if (window.initBaiduMapCallback) { + delete window.initBaiduMapCallback; + } + + return loadBaiduMapAPI(apiKey); +}; + +/** + * 检查百度地图API是否可用 + * @returns {boolean} 是否可用 + */ +export const isBaiduMapAvailable = () => { + return BMapLoaded && window.BMap && typeof window.BMap.Map === 'function'; +}; + +/** + * 等待百度地图API加载完成 + * @param {number} timeout - 超时时间(毫秒) + * @returns {Promise} 加载完成的Promise + */ +export const waitForBaiduMap = (timeout = 10000) => { + return new Promise((resolve, reject) => { + if (isBaiduMapAvailable()) { + resolve(); + return; + } + + const startTime = Date.now(); + const checkInterval = setInterval(() => { + if (isBaiduMapAvailable()) { + clearInterval(checkInterval); + resolve(); + } else if (Date.now() - startTime > timeout) { + clearInterval(checkInterval); + reject(new Error('等待百度地图API加载超时')); + } + }, 100); + }); +}; + +/** + * 创建百度地图实例 + * @param {string|HTMLElement} container - 地图容器ID或元素 + * @param {Object} options - 地图配置选项 + * @returns {Promise} 地图实例 + */ +export const createBaiduMap = async (container, options = {}) => { + try { + // 确保百度地图API已加载 + await loadBaiduMapAPI(); + + // 等待BMap完全可用 + await waitForBaiduMap(); + + // 获取容器元素 + let mapContainer = typeof container === 'string' + ? document.getElementById(container) + : container; + + // 如果容器不存在,等待一段时间后重试 + if (!mapContainer) { + console.log('地图容器不存在,等待DOM渲染...'); + await new Promise(resolve => setTimeout(resolve, 200)); + mapContainer = typeof container === 'string' + ? document.getElementById(container) + : container; + } + + if (!mapContainer) { + throw new Error('地图容器不存在,请确保DOM已正确渲染'); + } + + // 检查容器是否有尺寸 + if (mapContainer.offsetWidth === 0 || mapContainer.offsetHeight === 0) { + console.warn('地图容器尺寸为0,等待尺寸计算...'); + await new Promise(resolve => setTimeout(resolve, 100)); + } + + // 默认配置 + const defaultOptions = { + center: new window.BMap.Point(106.27, 38.47), // 宁夏中心点 + zoom: 8, + enableMapClick: true, + enableScrollWheelZoom: true, + enableDragging: true, + enableDoubleClickZoom: true, + enableKeyboard: true + }; + + // 合并配置 + const mergedOptions = { ...defaultOptions, ...options }; + + // 创建地图实例 + const map = new window.BMap.Map(mapContainer); + + // 设置中心点和缩放级别 + map.centerAndZoom(mergedOptions.center, mergedOptions.zoom); + + // 配置地图功能 + if (mergedOptions.enableScrollWheelZoom) { + map.enableScrollWheelZoom(); + } + + if (mergedOptions.enableDragging) { + map.enableDragging(); + } else { + map.disableDragging(); + } + + if (mergedOptions.enableDoubleClickZoom) { + map.enableDoubleClickZoom(); + } else { + map.disableDoubleClickZoom(); + } + + if (mergedOptions.enableKeyboard) { + map.enableKeyboard(); + } else { + map.disableKeyboard(); + } + + // 添加地图控件 + map.addControl(new window.BMap.NavigationControl()); + map.addControl(new window.BMap.ScaleControl()); + map.addControl(new window.BMap.OverviewMapControl()); + + console.log('百度地图创建成功'); + return map; + + } catch (error) { + console.error('创建百度地图失败:', error); + throw error; + } +}; diff --git a/admin-system/frontend/src/utils/baiduMapTest.js b/admin-system/src/utils/baiduMapTest.js similarity index 96% rename from admin-system/frontend/src/utils/baiduMapTest.js rename to admin-system/src/utils/baiduMapTest.js index 366a0bb..4ae7062 100644 --- a/admin-system/frontend/src/utils/baiduMapTest.js +++ b/admin-system/src/utils/baiduMapTest.js @@ -1,337 +1,337 @@ -/** - * 百度地图API测试工具 - * 用于诊断和修复百度地图API加载问题 - */ - -export class BaiduMapTester { - constructor() { - this.testResults = []; - this.isLoading = false; - } - - /** - * 运行完整的API测试 - */ - async runFullTest() { - console.log('🔍 开始百度地图API完整测试...'); - this.testResults = []; - - try { - // 测试1: 检查当前BMap状态 - await this.testCurrentBMapStatus(); - - // 测试2: 测试API加载 - await this.testApiLoading(); - - // 测试3: 测试BMap对象功能 - await this.testBMapFunctionality(); - - // 测试4: 测试地图创建 - await this.testMapCreation(); - - // 输出测试结果 - this.outputTestResults(); - - } catch (error) { - console.error('❌ 测试过程中出现错误:', error); - this.testResults.push({ - name: '测试过程错误', - status: 'error', - message: error.message - }); - } - - return this.testResults; - } - - /** - * 测试当前BMap状态 - */ - async testCurrentBMapStatus() { - console.log('📋 测试1: 检查当前BMap状态'); - - const result = { - name: 'BMap状态检查', - status: 'info', - details: {} - }; - - result.details.bMapExists = typeof window.BMap !== 'undefined'; - result.details.bMapType = typeof window.BMap; - result.details.bMapValue = window.BMap; - - if (window.BMap) { - result.details.bMapVersion = window.BMap.version || '未知'; - result.details.mapConstructor = typeof window.BMap.Map; - result.details.pointConstructor = typeof window.BMap.Point; - result.details.markerConstructor = typeof window.BMap.Marker; - result.details.infoWindowConstructor = typeof window.BMap.InfoWindow; - - if (typeof window.BMap.Map === 'function') { - result.status = 'success'; - result.message = 'BMap对象已存在且功能完整'; - } else { - result.status = 'warning'; - result.message = 'BMap对象存在但功能不完整'; - } - } else { - result.status = 'error'; - result.message = 'BMap对象不存在,需要加载API'; - } - - this.testResults.push(result); - console.log(`✅ 测试1完成: ${result.message}`); - } - - /** - * 测试API加载 - */ - async testApiLoading() { - console.log('📋 测试2: 测试API加载'); - - const result = { - name: 'API加载测试', - status: 'info', - details: {} - }; - - try { - // 检查是否已有BMap - if (typeof window.BMap !== 'undefined') { - result.status = 'success'; - result.message = 'BMap已存在,跳过API加载测试'; - this.testResults.push(result); - return; - } - - // 尝试加载API - result.details.loadingStarted = true; - const loadPromise = this.loadBMapApi(); - const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error('API加载超时')), 10000) - ); - - await Promise.race([loadPromise, timeoutPromise]); - - result.status = 'success'; - result.message = 'API加载成功'; - result.details.loadingCompleted = true; - - } catch (error) { - result.status = 'error'; - result.message = `API加载失败: ${error.message}`; - result.details.error = error.message; - } - - this.testResults.push(result); - console.log(`✅ 测试2完成: ${result.message}`); - } - - /** - * 测试BMap对象功能 - */ - async testBMapFunctionality() { - console.log('📋 测试3: 测试BMap对象功能'); - - const result = { - name: 'BMap功能测试', - status: 'info', - details: {} - }; - - try { - if (typeof window.BMap === 'undefined') { - throw new Error('BMap对象不存在'); - } - - // 测试Point创建 - const point = new window.BMap.Point(106.27, 38.47); - result.details.pointTest = { - success: true, - lng: point.lng, - lat: point.lat - }; - - // 测试Marker创建 - const marker = new window.BMap.Marker(point); - result.details.markerTest = { - success: true, - position: marker.getPosition() - }; - - // 测试InfoWindow创建 - const infoWindow = new window.BMap.InfoWindow('
测试
'); - result.details.infoWindowTest = { - success: true, - type: typeof infoWindow - }; - - result.status = 'success'; - result.message = 'BMap对象功能测试通过'; - - } catch (error) { - result.status = 'error'; - result.message = `BMap功能测试失败: ${error.message}`; - result.details.error = error.message; - } - - this.testResults.push(result); - console.log(`✅ 测试3完成: ${result.message}`); - } - - /** - * 测试地图创建 - */ - async testMapCreation() { - console.log('📋 测试4: 测试地图创建'); - - const result = { - name: '地图创建测试', - status: 'info', - details: {} - }; - - try { - if (typeof window.BMap === 'undefined') { - throw new Error('BMap对象不存在'); - } - - // 创建测试容器 - const testContainer = document.createElement('div'); - testContainer.style.width = '100px'; - testContainer.style.height = '100px'; - testContainer.style.position = 'absolute'; - testContainer.style.top = '-1000px'; - testContainer.style.left = '-1000px'; - document.body.appendChild(testContainer); - - // 创建地图 - const map = new window.BMap.Map(testContainer); - result.details.mapCreated = true; - result.details.mapType = typeof map; - - // 测试地图基本功能 - const center = new window.BMap.Point(106.27, 38.47); - map.centerAndZoom(center, 10); - result.details.mapConfigured = true; - - // 清理测试容器 - document.body.removeChild(testContainer); - - result.status = 'success'; - result.message = '地图创建测试通过'; - - } catch (error) { - result.status = 'error'; - result.message = `地图创建测试失败: ${error.message}`; - result.details.error = error.message; - } - - this.testResults.push(result); - console.log(`✅ 测试4完成: ${result.message}`); - } - - /** - * 加载百度地图API - */ - loadBMapApi() { - return new Promise((resolve, reject) => { - if (this.isLoading) { - reject(new Error('API正在加载中')); - return; - } - - this.isLoading = true; - - const script = document.createElement('script'); - script.src = 'https://api.map.baidu.com/api?v=3.0&ak=SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo&callback=initBMapTest'; - - window.initBMapTest = () => { - this.isLoading = false; - delete window.initBMapTest; - resolve(); - }; - - script.onerror = () => { - this.isLoading = false; - delete window.initBMapTest; - reject(new Error('API脚本加载失败')); - }; - - document.head.appendChild(script); - }); - } - - /** - * 输出测试结果 - */ - outputTestResults() { - console.log('\n📊 百度地图API测试结果:'); - console.log('='.repeat(50)); - - this.testResults.forEach((result, index) => { - const statusIcon = { - success: '✅', - error: '❌', - warning: '⚠️', - info: 'ℹ️' - }[result.status] || '❓'; - - console.log(`${index + 1}. ${statusIcon} ${result.name}: ${result.message}`); - - if (result.details && Object.keys(result.details).length > 0) { - console.log(' 详细信息:', result.details); - } - }); - - console.log('='.repeat(50)); - - const successCount = this.testResults.filter(r => r.status === 'success').length; - const totalCount = this.testResults.length; - - console.log(`📈 测试总结: ${successCount}/${totalCount} 项测试通过`); - - if (successCount === totalCount) { - console.log('🎉 所有测试通过!百度地图API工作正常。'); - } else { - console.log('⚠️ 部分测试失败,请检查上述错误信息。'); - } - } - - /** - * 获取修复建议 - */ - getFixSuggestions() { - const suggestions = []; - - this.testResults.forEach(result => { - if (result.status === 'error') { - switch (result.name) { - case 'BMap状态检查': - suggestions.push('1. 检查网络连接是否正常'); - suggestions.push('2. 检查百度地图API密钥是否有效'); - suggestions.push('3. 检查域名白名单配置'); - break; - case 'API加载测试': - suggestions.push('1. 尝试刷新页面'); - suggestions.push('2. 检查浏览器控制台是否有CORS错误'); - suggestions.push('3. 尝试使用备用API密钥'); - break; - case 'BMap功能测试': - suggestions.push('1. 等待API完全加载后再使用'); - suggestions.push('2. 检查API版本兼容性'); - break; - case '地图创建测试': - suggestions.push('1. 确保容器元素存在且有尺寸'); - suggestions.push('2. 检查容器是否被隐藏'); - break; - } - } - }); - - return suggestions; - } -} - -// 导出单例实例 -export const baiduMapTester = new BaiduMapTester(); +/** + * 百度地图API测试工具 + * 用于诊断和修复百度地图API加载问题 + */ + +export class BaiduMapTester { + constructor() { + this.testResults = []; + this.isLoading = false; + } + + /** + * 运行完整的API测试 + */ + async runFullTest() { + console.log('🔍 开始百度地图API完整测试...'); + this.testResults = []; + + try { + // 测试1: 检查当前BMap状态 + await this.testCurrentBMapStatus(); + + // 测试2: 测试API加载 + await this.testApiLoading(); + + // 测试3: 测试BMap对象功能 + await this.testBMapFunctionality(); + + // 测试4: 测试地图创建 + await this.testMapCreation(); + + // 输出测试结果 + this.outputTestResults(); + + } catch (error) { + console.error('❌ 测试过程中出现错误:', error); + this.testResults.push({ + name: '测试过程错误', + status: 'error', + message: error.message + }); + } + + return this.testResults; + } + + /** + * 测试当前BMap状态 + */ + async testCurrentBMapStatus() { + console.log('📋 测试1: 检查当前BMap状态'); + + const result = { + name: 'BMap状态检查', + status: 'info', + details: {} + }; + + result.details.bMapExists = typeof window.BMap !== 'undefined'; + result.details.bMapType = typeof window.BMap; + result.details.bMapValue = window.BMap; + + if (window.BMap) { + result.details.bMapVersion = window.BMap.version || '未知'; + result.details.mapConstructor = typeof window.BMap.Map; + result.details.pointConstructor = typeof window.BMap.Point; + result.details.markerConstructor = typeof window.BMap.Marker; + result.details.infoWindowConstructor = typeof window.BMap.InfoWindow; + + if (typeof window.BMap.Map === 'function') { + result.status = 'success'; + result.message = 'BMap对象已存在且功能完整'; + } else { + result.status = 'warning'; + result.message = 'BMap对象存在但功能不完整'; + } + } else { + result.status = 'error'; + result.message = 'BMap对象不存在,需要加载API'; + } + + this.testResults.push(result); + console.log(`✅ 测试1完成: ${result.message}`); + } + + /** + * 测试API加载 + */ + async testApiLoading() { + console.log('📋 测试2: 测试API加载'); + + const result = { + name: 'API加载测试', + status: 'info', + details: {} + }; + + try { + // 检查是否已有BMap + if (typeof window.BMap !== 'undefined') { + result.status = 'success'; + result.message = 'BMap已存在,跳过API加载测试'; + this.testResults.push(result); + return; + } + + // 尝试加载API + result.details.loadingStarted = true; + const loadPromise = this.loadBMapApi(); + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('API加载超时')), 10000) + ); + + await Promise.race([loadPromise, timeoutPromise]); + + result.status = 'success'; + result.message = 'API加载成功'; + result.details.loadingCompleted = true; + + } catch (error) { + result.status = 'error'; + result.message = `API加载失败: ${error.message}`; + result.details.error = error.message; + } + + this.testResults.push(result); + console.log(`✅ 测试2完成: ${result.message}`); + } + + /** + * 测试BMap对象功能 + */ + async testBMapFunctionality() { + console.log('📋 测试3: 测试BMap对象功能'); + + const result = { + name: 'BMap功能测试', + status: 'info', + details: {} + }; + + try { + if (typeof window.BMap === 'undefined') { + throw new Error('BMap对象不存在'); + } + + // 测试Point创建 + const point = new window.BMap.Point(106.27, 38.47); + result.details.pointTest = { + success: true, + lng: point.lng, + lat: point.lat + }; + + // 测试Marker创建 + const marker = new window.BMap.Marker(point); + result.details.markerTest = { + success: true, + position: marker.getPosition() + }; + + // 测试InfoWindow创建 + const infoWindow = new window.BMap.InfoWindow('
测试
'); + result.details.infoWindowTest = { + success: true, + type: typeof infoWindow + }; + + result.status = 'success'; + result.message = 'BMap对象功能测试通过'; + + } catch (error) { + result.status = 'error'; + result.message = `BMap功能测试失败: ${error.message}`; + result.details.error = error.message; + } + + this.testResults.push(result); + console.log(`✅ 测试3完成: ${result.message}`); + } + + /** + * 测试地图创建 + */ + async testMapCreation() { + console.log('📋 测试4: 测试地图创建'); + + const result = { + name: '地图创建测试', + status: 'info', + details: {} + }; + + try { + if (typeof window.BMap === 'undefined') { + throw new Error('BMap对象不存在'); + } + + // 创建测试容器 + const testContainer = document.createElement('div'); + testContainer.style.width = '100px'; + testContainer.style.height = '100px'; + testContainer.style.position = 'absolute'; + testContainer.style.top = '-1000px'; + testContainer.style.left = '-1000px'; + document.body.appendChild(testContainer); + + // 创建地图 + const map = new window.BMap.Map(testContainer); + result.details.mapCreated = true; + result.details.mapType = typeof map; + + // 测试地图基本功能 + const center = new window.BMap.Point(106.27, 38.47); + map.centerAndZoom(center, 10); + result.details.mapConfigured = true; + + // 清理测试容器 + document.body.removeChild(testContainer); + + result.status = 'success'; + result.message = '地图创建测试通过'; + + } catch (error) { + result.status = 'error'; + result.message = `地图创建测试失败: ${error.message}`; + result.details.error = error.message; + } + + this.testResults.push(result); + console.log(`✅ 测试4完成: ${result.message}`); + } + + /** + * 加载百度地图API + */ + loadBMapApi() { + return new Promise((resolve, reject) => { + if (this.isLoading) { + reject(new Error('API正在加载中')); + return; + } + + this.isLoading = true; + + const script = document.createElement('script'); + script.src = 'https://api.map.baidu.com/api?v=3.0&ak=SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo&callback=initBMapTest'; + + window.initBMapTest = () => { + this.isLoading = false; + delete window.initBMapTest; + resolve(); + }; + + script.onerror = () => { + this.isLoading = false; + delete window.initBMapTest; + reject(new Error('API脚本加载失败')); + }; + + document.head.appendChild(script); + }); + } + + /** + * 输出测试结果 + */ + outputTestResults() { + console.log('\n📊 百度地图API测试结果:'); + console.log('='.repeat(50)); + + this.testResults.forEach((result, index) => { + const statusIcon = { + success: '✅', + error: '❌', + warning: '⚠️', + info: 'ℹ️' + }[result.status] || '❓'; + + console.log(`${index + 1}. ${statusIcon} ${result.name}: ${result.message}`); + + if (result.details && Object.keys(result.details).length > 0) { + console.log(' 详细信息:', result.details); + } + }); + + console.log('='.repeat(50)); + + const successCount = this.testResults.filter(r => r.status === 'success').length; + const totalCount = this.testResults.length; + + console.log(`📈 测试总结: ${successCount}/${totalCount} 项测试通过`); + + if (successCount === totalCount) { + console.log('🎉 所有测试通过!百度地图API工作正常。'); + } else { + console.log('⚠️ 部分测试失败,请检查上述错误信息。'); + } + } + + /** + * 获取修复建议 + */ + getFixSuggestions() { + const suggestions = []; + + this.testResults.forEach(result => { + if (result.status === 'error') { + switch (result.name) { + case 'BMap状态检查': + suggestions.push('1. 检查网络连接是否正常'); + suggestions.push('2. 检查百度地图API密钥是否有效'); + suggestions.push('3. 检查域名白名单配置'); + break; + case 'API加载测试': + suggestions.push('1. 尝试刷新页面'); + suggestions.push('2. 检查浏览器控制台是否有CORS错误'); + suggestions.push('3. 尝试使用备用API密钥'); + break; + case 'BMap功能测试': + suggestions.push('1. 等待API完全加载后再使用'); + suggestions.push('2. 检查API版本兼容性'); + break; + case '地图创建测试': + suggestions.push('1. 确保容器元素存在且有尺寸'); + suggestions.push('2. 检查容器是否被隐藏'); + break; + } + } + }); + + return suggestions; + } +} + +// 导出单例实例 +export const baiduMapTester = new BaiduMapTester(); diff --git a/admin-system/frontend/src/utils/chartService.js b/admin-system/src/utils/chartService.js similarity index 100% rename from admin-system/frontend/src/utils/chartService.js rename to admin-system/src/utils/chartService.js diff --git a/admin-system/frontend/src/utils/dataService.js b/admin-system/src/utils/dataService.js similarity index 100% rename from admin-system/frontend/src/utils/dataService.js rename to admin-system/src/utils/dataService.js diff --git a/admin-system/frontend/src/utils/exportUtils.js b/admin-system/src/utils/exportUtils.js similarity index 97% rename from admin-system/frontend/src/utils/exportUtils.js rename to admin-system/src/utils/exportUtils.js index bdcf499..2593adc 100644 --- a/admin-system/frontend/src/utils/exportUtils.js +++ b/admin-system/src/utils/exportUtils.js @@ -1,447 +1,447 @@ -import * as XLSX from 'xlsx' -import { saveAs } from 'file-saver' - -/** - * 通用Excel导出工具类 - */ -export class ExportUtils { - /** - * 导出数据到Excel文件 - * @param {Array} data - 要导出的数据数组 - * @param {Array} columns - 列配置数组 - * @param {String} filename - 文件名(不包含扩展名) - * @param {String} sheetName - 工作表名称,默认为'Sheet1' - */ - static exportToExcel(data, columns, filename, sheetName = 'Sheet1') { - try { - // 验证参数 - if (!Array.isArray(data)) { - throw new Error('数据必须是数组格式') - } - if (!Array.isArray(columns)) { - throw new Error('列配置必须是数组格式') - } - if (!filename) { - throw new Error('文件名不能为空') - } - - // 准备Excel数据 - const excelData = this.prepareExcelData(data, columns) - - // 创建工作簿 - const workbook = XLSX.utils.book_new() - - // 创建工作表 - const worksheet = XLSX.utils.aoa_to_sheet(excelData) - - // 设置列宽 - const colWidths = this.calculateColumnWidths(excelData) - worksheet['!cols'] = colWidths - - // 添加工作表到工作簿 - XLSX.utils.book_append_sheet(workbook, worksheet, sheetName) - - // 生成Excel文件并下载 - const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' }) - const blob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }) - - // 添加时间戳到文件名 - const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:]/g, '').replace('T', '_') - const finalFilename = `${filename}_${timestamp}.xlsx` - - saveAs(blob, finalFilename) - - return { - success: true, - message: '导出成功', - filename: finalFilename - } - } catch (error) { - console.error('导出Excel失败:', error) - return { - success: false, - message: `导出失败: ${error.message}`, - error: error - } - } -} - -/** - * 准备Excel数据格式 - */ - static prepareExcelData(data, columns) { - // 第一行:列标题 - const headers = columns.map(col => col.title || col.dataIndex || col.key) - const excelData = [headers] - - // 数据行 - data.forEach(item => { - const row = columns.map(col => { - const fieldName = col.dataIndex || col.key - let value = item[fieldName] - - // 处理特殊数据类型 - if (value === null || value === undefined) { - return '' - } - - // 处理日期时间 - if (col.dataType === 'datetime' && value) { - return new Date(value).toLocaleString('zh-CN') - } - - // 处理布尔值 - if (typeof value === 'boolean') { - return value ? '是' : '否' - } - - // 处理数组 - if (Array.isArray(value)) { - return value.join(', ') - } - - // 处理对象 - if (typeof value === 'object') { - return JSON.stringify(value) - } - - return String(value) - }) - excelData.push(row) - }) - - return excelData - } - - /** - * 计算列宽 - */ - static calculateColumnWidths(excelData) { - if (!excelData || excelData.length === 0) return [] - - const colCount = excelData[0].length - const colWidths = [] - - for (let i = 0; i < colCount; i++) { - let maxWidth = 10 // 最小宽度 - - excelData.forEach(row => { - if (row[i]) { - const cellWidth = String(row[i]).length - maxWidth = Math.max(maxWidth, cellWidth) - } - }) - - // 限制最大宽度 - colWidths.push({ wch: Math.min(maxWidth + 2, 50) }) - } - - return colWidths - } - - /** - * 导出智能设备数据 - */ - static exportDeviceData(data, deviceType) { - const deviceTypeMap = { - collar: { name: '智能项圈', columns: this.getCollarColumns() }, - eartag: { name: '智能耳标', columns: this.getEartagColumns() }, - host: { name: '智能主机', columns: this.getHostColumns() } - } - - const config = deviceTypeMap[deviceType] - if (!config) { - throw new Error(`不支持的设备类型: ${deviceType}`) - } - - return this.exportToExcel(data, config.columns, config.name + '数据') - } - - /** - * 导出预警数据 - */ - static exportAlertData(data, alertType) { - const alertTypeMap = { - collar: { name: '智能项圈预警', columns: this.getCollarAlertColumns() }, - eartag: { name: '智能耳标预警', columns: this.getEartagAlertColumns() } - } - - const config = alertTypeMap[alertType] - if (!config) { - throw new Error(`不支持的预警类型: ${alertType}`) - } - - return this.exportToExcel(data, config.columns, config.name + '数据') - } - - /** - * 导出牛只档案数据 - */ - static exportCattleData(data) { - return this.exportToExcel(data, this.getCattleColumns(), '牛只档案数据') - } - - /** - * 导出动物数据(别名方法,与exportCattleData相同) - */ - static exportAnimalsData(data) { - return this.exportCattleData(data) - } - - /** - * 导出栏舍数据 - */ - static exportPenData(data) { - return this.exportToExcel(data, this.getPenColumns(), '栏舍数据') - } - - /** - * 导出批次数据 - */ - static exportBatchData(data) { - return this.exportToExcel(data, this.getBatchColumns(), '批次数据') - } - - /** - * 导出转栏记录数据 - */ - static exportTransferData(data) { - return this.exportToExcel(data, this.getTransferColumns(), '转栏记录数据') - } - - /** - * 导出离栏记录数据 - */ - static exportExitData(data) { - return this.exportToExcel(data, this.getExitColumns(), '离栏记录数据') - } - - /** - * 导出养殖场数据 - */ - static exportFarmData(data) { - return this.exportToExcel(data, this.getFarmColumns(), '养殖场数据') - } - - /** - * 导出用户数据 - */ - static exportUserData(data) { - return this.exportToExcel(data, this.getUserColumns(), '用户数据') - } - - // 列配置定义 - static getCollarColumns() { - return [ - { title: '设备ID', dataIndex: 'id', key: 'id' }, - { title: '设备名称', dataIndex: 'device_name', key: 'device_name' }, - { title: '设备编号', dataIndex: 'device_code', key: 'device_code' }, - { title: '设备状态', dataIndex: 'status', key: 'status' }, - { title: '电量', dataIndex: 'battery_level', key: 'battery_level' }, - { title: '信号强度', dataIndex: 'signal_strength', key: 'signal_strength' }, - { title: '最后在线时间', dataIndex: 'last_online', key: 'last_online', dataType: 'datetime' }, - { title: '创建时间', dataIndex: 'created_at', key: 'created_at', dataType: 'datetime' } - ] - } - - static getEartagColumns() { - return [ - { title: '设备ID', dataIndex: 'id', key: 'id' }, - { title: '耳标编号', dataIndex: 'eartagNumber', key: 'eartagNumber' }, - { title: '设备状态', dataIndex: 'deviceStatus', key: 'deviceStatus' }, - { title: '电量/%', dataIndex: 'battery', key: 'battery' }, - { title: '设备温度/°C', dataIndex: 'temperature', key: 'temperature' }, - { title: '被采集主机', dataIndex: 'collectedHost', key: 'collectedHost' }, - { title: '总运动量', dataIndex: 'totalMovement', key: 'totalMovement' }, - { title: '当日运动量', dataIndex: 'dailyMovement', key: 'dailyMovement' }, - { title: '定位信息', dataIndex: 'location', key: 'location' }, - { title: '数据最后更新时间', dataIndex: 'lastUpdate', key: 'lastUpdate', dataType: 'datetime' }, - { title: '绑定牲畜', dataIndex: 'bindingStatus', key: 'bindingStatus' }, - { title: 'GPS信号强度', dataIndex: 'gpsSignal', key: 'gpsSignal' }, - { title: '经度', dataIndex: 'longitude', key: 'longitude' }, - { title: '纬度', dataIndex: 'latitude', key: 'latitude' } - ] - } - - static getHostColumns() { - return [ - { title: '设备ID', dataIndex: 'id', key: 'id' }, - { title: '设备名称', dataIndex: 'device_name', key: 'device_name' }, - { title: '设备编号', dataIndex: 'device_code', key: 'device_code' }, - { title: '设备状态', dataIndex: 'status', key: 'status' }, - { title: 'IP地址', dataIndex: 'ip_address', key: 'ip_address' }, - { title: '端口', dataIndex: 'port', key: 'port' }, - { title: '最后在线时间', dataIndex: 'last_online', key: 'last_online', dataType: 'datetime' }, - { title: '创建时间', dataIndex: 'created_at', key: 'created_at', dataType: 'datetime' } - ] - } - - static getCollarAlertColumns() { - return [ - { title: '耳标编号', dataIndex: 'collarNumber', key: 'collarNumber' }, - { title: '预警类型', dataIndex: 'alertType', key: 'alertType' }, - { title: '预警级别', dataIndex: 'alertLevel', key: 'alertLevel' }, - { title: '预警时间', dataIndex: 'alertTime', key: 'alertTime', dataType: 'datetime' }, - { title: '设备电量', dataIndex: 'battery', key: 'battery' }, - { title: '设备温度', dataIndex: 'temperature', key: 'temperature' }, - { title: '当日步数', dataIndex: 'dailySteps', key: 'dailySteps' } - ] - } - - static getEartagAlertColumns() { - return [ - { title: '设备编号', dataIndex: 'device_name', key: 'device_name' }, - { title: '预警类型', dataIndex: 'alert_type', key: 'alert_type' }, - { title: '预警级别', dataIndex: 'alert_level', key: 'alert_level' }, - { title: '预警内容', dataIndex: 'alert_content', key: 'alert_content' }, - { title: '预警时间', dataIndex: 'alert_time', key: 'alert_time', dataType: 'datetime' }, - { title: '处理状态', dataIndex: 'status', key: 'status' }, - { title: '处理人', dataIndex: 'handler', key: 'handler' } - ] - } - - static getCattleColumns() { - return [ - { title: '牛只ID', dataIndex: 'id', key: 'id' }, - { title: '耳标号', dataIndex: 'ear_tag', key: 'ear_tag' }, - { title: '智能佩戴设备', dataIndex: 'device_id', key: 'device_id' }, - { title: '出生日期', dataIndex: 'birth_date', key: 'birth_date', dataType: 'datetime' }, - { title: '月龄', dataIndex: 'age_in_months', key: 'age_in_months' }, - { title: '品类', dataIndex: 'category', key: 'category' }, - { title: '品种', dataIndex: 'breed', key: 'breed' }, - { title: '生理阶段', dataIndex: 'physiological_stage', key: 'physiological_stage' }, - { title: '性别', dataIndex: 'gender', key: 'gender' }, - { title: '出生体重(KG)', dataIndex: 'birth_weight', key: 'birth_weight' }, - { title: '现估值(KG)', dataIndex: 'current_weight', key: 'current_weight' }, - { title: '代数', dataIndex: 'generation', key: 'generation' }, - { title: '血统纯度', dataIndex: 'bloodline_purity', key: 'bloodline_purity' }, - { title: '入场日期', dataIndex: 'entry_date', key: 'entry_date', dataType: 'datetime' }, - { title: '栏舍', dataIndex: 'pen_name', key: 'pen_name' }, - { title: '已产胎次', dataIndex: 'calvings', key: 'calvings' }, - { title: '来源', dataIndex: 'source', key: 'source' }, - { title: '冻精编号', dataIndex: 'frozen_semen_number', key: 'frozen_semen_number' } - ] - } - - static getPenColumns() { - return [ - { title: '栏舍ID', dataIndex: 'id', key: 'id' }, - { title: '栏舍名', dataIndex: 'name', key: 'name' }, - { title: '动物类型', dataIndex: 'animal_type', key: 'animal_type' }, - { title: '栏舍类型', dataIndex: 'pen_type', key: 'pen_type' }, - { title: '负责人', dataIndex: 'responsible', key: 'responsible' }, - { title: '容量', dataIndex: 'capacity', key: 'capacity' }, - { title: '状态', dataIndex: 'status', key: 'status' }, - { title: '创建人', dataIndex: 'creator', key: 'creator' }, - { title: '创建时间', dataIndex: 'created_at', key: 'created_at', dataType: 'datetime' } - ] - } - - static getBatchColumns() { - return [ - { title: '批次ID', dataIndex: 'id', key: 'id' }, - { title: '批次名称', dataIndex: 'batch_name', key: 'batch_name' }, - { title: '批次类型', dataIndex: 'batch_type', key: 'batch_type' }, - { title: '目标数量', dataIndex: 'target_count', key: 'target_count' }, - { title: '当前数量', dataIndex: 'current_count', key: 'current_count' }, - { title: '创建时间', dataIndex: 'created_at', key: 'created_at', dataType: 'datetime' }, - { title: '负责人', dataIndex: 'manager', key: 'manager' }, - { title: '状态', dataIndex: 'status', key: 'status' } - ] - } - - static getTransferColumns() { - return [ - { title: '记录ID', dataIndex: 'id', key: 'id' }, - { title: '牛只耳标', dataIndex: 'ear_tag', key: 'ear_tag' }, - { title: '原栏舍', dataIndex: 'from_pen', key: 'from_pen' }, - { title: '目标栏舍', dataIndex: 'to_pen', key: 'to_pen' }, - { title: '转栏时间', dataIndex: 'transfer_time', key: 'transfer_time', dataType: 'datetime' }, - { title: '转栏原因', dataIndex: 'reason', key: 'reason' }, - { title: '操作人', dataIndex: 'operator', key: 'operator' } - ] - } - - static getExitColumns() { - return [ - { title: '记录ID', dataIndex: 'id', key: 'id' }, - { title: '牛只耳标', dataIndex: 'ear_tag', key: 'ear_tag' }, - { title: '原栏舍', dataIndex: 'from_pen', key: 'from_pen' }, - { title: '离栏时间', dataIndex: 'exit_time', key: 'exit_time', dataType: 'datetime' }, - { title: '离栏原因', dataIndex: 'exit_reason', key: 'exit_reason' }, - { title: '去向', dataIndex: 'destination', key: 'destination' }, - { title: '操作人', dataIndex: 'operator', key: 'operator' } - ] - } - - static getFarmColumns() { - return [ - { title: '养殖场ID', dataIndex: 'id', key: 'id' }, - { title: '养殖场名称', dataIndex: 'name', key: 'name' }, - { title: '地址', dataIndex: 'address', key: 'address' }, - { title: '联系电话', dataIndex: 'phone', key: 'phone' }, - { title: '负责人', dataIndex: 'contact', key: 'contact' }, - { title: '面积', dataIndex: 'area', key: 'area' }, - { title: '状态', dataIndex: 'status', key: 'status' }, - { title: '创建时间', dataIndex: 'createdAt', key: 'createdAt', dataType: 'datetime' } - ] - } - - static getUserColumns() { - return [ - { title: '用户ID', dataIndex: 'id', key: 'id' }, - { title: '用户名', dataIndex: 'username', key: 'username' }, - { title: '邮箱', dataIndex: 'email', key: 'email' }, - { title: '角色', dataIndex: 'roleName', key: 'roleName' }, - { title: '状态', dataIndex: 'status', key: 'status' }, - { title: '最后登录', dataIndex: 'last_login', key: 'last_login', dataType: 'datetime' }, - { title: '创建时间', dataIndex: 'created_at', key: 'created_at', dataType: 'datetime' } - ] - } - - // 其他导出方法(为了兼容性) - static exportFarmsData(data) { - return this.exportFarmData(data) - } - - static exportAlertsData(data) { - return this.exportAlertData(data, 'collar') // 默认使用collar类型 - } - - static exportDevicesData(data) { - return this.exportDeviceData(data, 'collar') // 默认使用collar类型 - } - - static exportOrdersData(data) { - return this.exportToExcel(data, this.getOrderColumns(), '订单数据') - } - - static exportProductsData(data) { - return this.exportToExcel(data, this.getProductColumns(), '产品数据') - } - - static getOrderColumns() { - return [ - { title: '订单ID', dataIndex: 'id', key: 'id' }, - { title: '订单号', dataIndex: 'order_number', key: 'order_number' }, - { title: '客户名称', dataIndex: 'customer_name', key: 'customer_name' }, - { title: '订单金额', dataIndex: 'total_amount', key: 'total_amount' }, - { title: '订单状态', dataIndex: 'status', key: 'status' }, - { title: '创建时间', dataIndex: 'created_at', key: 'created_at', dataType: 'datetime' } - ] - } - - static getProductColumns() { - return [ - { title: '产品ID', dataIndex: 'id', key: 'id' }, - { title: '产品名称', dataIndex: 'name', key: 'name' }, - { title: '产品类型', dataIndex: 'type', key: 'type' }, - { title: '价格', dataIndex: 'price', key: 'price' }, - { title: '库存', dataIndex: 'stock', key: 'stock' }, - { title: '状态', dataIndex: 'status', key: 'status' }, - { title: '创建时间', dataIndex: 'created_at', key: 'created_at', dataType: 'datetime' } - ] - } -} - +import * as XLSX from 'xlsx' +import { saveAs } from 'file-saver' + +/** + * 通用Excel导出工具类 + */ +export class ExportUtils { + /** + * 导出数据到Excel文件 + * @param {Array} data - 要导出的数据数组 + * @param {Array} columns - 列配置数组 + * @param {String} filename - 文件名(不包含扩展名) + * @param {String} sheetName - 工作表名称,默认为'Sheet1' + */ + static exportToExcel(data, columns, filename, sheetName = 'Sheet1') { + try { + // 验证参数 + if (!Array.isArray(data)) { + throw new Error('数据必须是数组格式') + } + if (!Array.isArray(columns)) { + throw new Error('列配置必须是数组格式') + } + if (!filename) { + throw new Error('文件名不能为空') + } + + // 准备Excel数据 + const excelData = this.prepareExcelData(data, columns) + + // 创建工作簿 + const workbook = XLSX.utils.book_new() + + // 创建工作表 + const worksheet = XLSX.utils.aoa_to_sheet(excelData) + + // 设置列宽 + const colWidths = this.calculateColumnWidths(excelData) + worksheet['!cols'] = colWidths + + // 添加工作表到工作簿 + XLSX.utils.book_append_sheet(workbook, worksheet, sheetName) + + // 生成Excel文件并下载 + const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' }) + const blob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }) + + // 添加时间戳到文件名 + const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:]/g, '').replace('T', '_') + const finalFilename = `${filename}_${timestamp}.xlsx` + + saveAs(blob, finalFilename) + + return { + success: true, + message: '导出成功', + filename: finalFilename + } + } catch (error) { + console.error('导出Excel失败:', error) + return { + success: false, + message: `导出失败: ${error.message}`, + error: error + } + } +} + +/** + * 准备Excel数据格式 + */ + static prepareExcelData(data, columns) { + // 第一行:列标题 + const headers = columns.map(col => col.title || col.dataIndex || col.key) + const excelData = [headers] + + // 数据行 + data.forEach(item => { + const row = columns.map(col => { + const fieldName = col.dataIndex || col.key + let value = item[fieldName] + + // 处理特殊数据类型 + if (value === null || value === undefined) { + return '' + } + + // 处理日期时间 + if (col.dataType === 'datetime' && value) { + return new Date(value).toLocaleString('zh-CN') + } + + // 处理布尔值 + if (typeof value === 'boolean') { + return value ? '是' : '否' + } + + // 处理数组 + if (Array.isArray(value)) { + return value.join(', ') + } + + // 处理对象 + if (typeof value === 'object') { + return JSON.stringify(value) + } + + return String(value) + }) + excelData.push(row) + }) + + return excelData + } + + /** + * 计算列宽 + */ + static calculateColumnWidths(excelData) { + if (!excelData || excelData.length === 0) return [] + + const colCount = excelData[0].length + const colWidths = [] + + for (let i = 0; i < colCount; i++) { + let maxWidth = 10 // 最小宽度 + + excelData.forEach(row => { + if (row[i]) { + const cellWidth = String(row[i]).length + maxWidth = Math.max(maxWidth, cellWidth) + } + }) + + // 限制最大宽度 + colWidths.push({ wch: Math.min(maxWidth + 2, 50) }) + } + + return colWidths + } + + /** + * 导出智能设备数据 + */ + static exportDeviceData(data, deviceType) { + const deviceTypeMap = { + collar: { name: '智能项圈', columns: this.getCollarColumns() }, + eartag: { name: '智能耳标', columns: this.getEartagColumns() }, + host: { name: '智能主机', columns: this.getHostColumns() } + } + + const config = deviceTypeMap[deviceType] + if (!config) { + throw new Error(`不支持的设备类型: ${deviceType}`) + } + + return this.exportToExcel(data, config.columns, config.name + '数据') + } + + /** + * 导出预警数据 + */ + static exportAlertData(data, alertType) { + const alertTypeMap = { + collar: { name: '智能项圈预警', columns: this.getCollarAlertColumns() }, + eartag: { name: '智能耳标预警', columns: this.getEartagAlertColumns() } + } + + const config = alertTypeMap[alertType] + if (!config) { + throw new Error(`不支持的预警类型: ${alertType}`) + } + + return this.exportToExcel(data, config.columns, config.name + '数据') + } + + /** + * 导出牛只档案数据 + */ + static exportCattleData(data) { + return this.exportToExcel(data, this.getCattleColumns(), '牛只档案数据') + } + + /** + * 导出动物数据(别名方法,与exportCattleData相同) + */ + static exportAnimalsData(data) { + return this.exportCattleData(data) + } + + /** + * 导出栏舍数据 + */ + static exportPenData(data) { + return this.exportToExcel(data, this.getPenColumns(), '栏舍数据') + } + + /** + * 导出批次数据 + */ + static exportBatchData(data) { + return this.exportToExcel(data, this.getBatchColumns(), '批次数据') + } + + /** + * 导出转栏记录数据 + */ + static exportTransferData(data) { + return this.exportToExcel(data, this.getTransferColumns(), '转栏记录数据') + } + + /** + * 导出离栏记录数据 + */ + static exportExitData(data) { + return this.exportToExcel(data, this.getExitColumns(), '离栏记录数据') + } + + /** + * 导出养殖场数据 + */ + static exportFarmData(data) { + return this.exportToExcel(data, this.getFarmColumns(), '养殖场数据') + } + + /** + * 导出用户数据 + */ + static exportUserData(data) { + return this.exportToExcel(data, this.getUserColumns(), '用户数据') + } + + // 列配置定义 + static getCollarColumns() { + return [ + { title: '设备ID', dataIndex: 'id', key: 'id' }, + { title: '设备名称', dataIndex: 'device_name', key: 'device_name' }, + { title: '设备编号', dataIndex: 'device_code', key: 'device_code' }, + { title: '设备状态', dataIndex: 'status', key: 'status' }, + { title: '电量', dataIndex: 'battery_level', key: 'battery_level' }, + { title: '信号强度', dataIndex: 'signal_strength', key: 'signal_strength' }, + { title: '最后在线时间', dataIndex: 'last_online', key: 'last_online', dataType: 'datetime' }, + { title: '创建时间', dataIndex: 'created_at', key: 'created_at', dataType: 'datetime' } + ] + } + + static getEartagColumns() { + return [ + { title: '设备ID', dataIndex: 'id', key: 'id' }, + { title: '耳标编号', dataIndex: 'eartagNumber', key: 'eartagNumber' }, + { title: '设备状态', dataIndex: 'deviceStatus', key: 'deviceStatus' }, + { title: '电量/%', dataIndex: 'battery', key: 'battery' }, + { title: '设备温度/°C', dataIndex: 'temperature', key: 'temperature' }, + { title: '被采集主机', dataIndex: 'collectedHost', key: 'collectedHost' }, + { title: '总运动量', dataIndex: 'totalMovement', key: 'totalMovement' }, + { title: '当日运动量', dataIndex: 'dailyMovement', key: 'dailyMovement' }, + { title: '定位信息', dataIndex: 'location', key: 'location' }, + { title: '数据最后更新时间', dataIndex: 'lastUpdate', key: 'lastUpdate', dataType: 'datetime' }, + { title: '绑定牲畜', dataIndex: 'bindingStatus', key: 'bindingStatus' }, + { title: 'GPS信号强度', dataIndex: 'gpsSignal', key: 'gpsSignal' }, + { title: '经度', dataIndex: 'longitude', key: 'longitude' }, + { title: '纬度', dataIndex: 'latitude', key: 'latitude' } + ] + } + + static getHostColumns() { + return [ + { title: '设备ID', dataIndex: 'id', key: 'id' }, + { title: '设备名称', dataIndex: 'device_name', key: 'device_name' }, + { title: '设备编号', dataIndex: 'device_code', key: 'device_code' }, + { title: '设备状态', dataIndex: 'status', key: 'status' }, + { title: 'IP地址', dataIndex: 'ip_address', key: 'ip_address' }, + { title: '端口', dataIndex: 'port', key: 'port' }, + { title: '最后在线时间', dataIndex: 'last_online', key: 'last_online', dataType: 'datetime' }, + { title: '创建时间', dataIndex: 'created_at', key: 'created_at', dataType: 'datetime' } + ] + } + + static getCollarAlertColumns() { + return [ + { title: '耳标编号', dataIndex: 'collarNumber', key: 'collarNumber' }, + { title: '预警类型', dataIndex: 'alertType', key: 'alertType' }, + { title: '预警级别', dataIndex: 'alertLevel', key: 'alertLevel' }, + { title: '预警时间', dataIndex: 'alertTime', key: 'alertTime', dataType: 'datetime' }, + { title: '设备电量', dataIndex: 'battery', key: 'battery' }, + { title: '设备温度', dataIndex: 'temperature', key: 'temperature' }, + { title: '当日步数', dataIndex: 'dailySteps', key: 'dailySteps' } + ] + } + + static getEartagAlertColumns() { + return [ + { title: '设备编号', dataIndex: 'device_name', key: 'device_name' }, + { title: '预警类型', dataIndex: 'alert_type', key: 'alert_type' }, + { title: '预警级别', dataIndex: 'alert_level', key: 'alert_level' }, + { title: '预警内容', dataIndex: 'alert_content', key: 'alert_content' }, + { title: '预警时间', dataIndex: 'alert_time', key: 'alert_time', dataType: 'datetime' }, + { title: '处理状态', dataIndex: 'status', key: 'status' }, + { title: '处理人', dataIndex: 'handler', key: 'handler' } + ] + } + + static getCattleColumns() { + return [ + { title: '牛只ID', dataIndex: 'id', key: 'id' }, + { title: '耳标号', dataIndex: 'ear_tag', key: 'ear_tag' }, + { title: '智能佩戴设备', dataIndex: 'device_id', key: 'device_id' }, + { title: '出生日期', dataIndex: 'birth_date', key: 'birth_date', dataType: 'datetime' }, + { title: '月龄', dataIndex: 'age_in_months', key: 'age_in_months' }, + { title: '品类', dataIndex: 'category', key: 'category' }, + { title: '品种', dataIndex: 'breed', key: 'breed' }, + { title: '生理阶段', dataIndex: 'physiological_stage', key: 'physiological_stage' }, + { title: '性别', dataIndex: 'gender', key: 'gender' }, + { title: '出生体重(KG)', dataIndex: 'birth_weight', key: 'birth_weight' }, + { title: '现估值(KG)', dataIndex: 'current_weight', key: 'current_weight' }, + { title: '代数', dataIndex: 'generation', key: 'generation' }, + { title: '血统纯度', dataIndex: 'bloodline_purity', key: 'bloodline_purity' }, + { title: '入场日期', dataIndex: 'entry_date', key: 'entry_date', dataType: 'datetime' }, + { title: '栏舍', dataIndex: 'pen_name', key: 'pen_name' }, + { title: '已产胎次', dataIndex: 'calvings', key: 'calvings' }, + { title: '来源', dataIndex: 'source', key: 'source' }, + { title: '冻精编号', dataIndex: 'frozen_semen_number', key: 'frozen_semen_number' } + ] + } + + static getPenColumns() { + return [ + { title: '栏舍ID', dataIndex: 'id', key: 'id' }, + { title: '栏舍名', dataIndex: 'name', key: 'name' }, + { title: '动物类型', dataIndex: 'animal_type', key: 'animal_type' }, + { title: '栏舍类型', dataIndex: 'pen_type', key: 'pen_type' }, + { title: '负责人', dataIndex: 'responsible', key: 'responsible' }, + { title: '容量', dataIndex: 'capacity', key: 'capacity' }, + { title: '状态', dataIndex: 'status', key: 'status' }, + { title: '创建人', dataIndex: 'creator', key: 'creator' }, + { title: '创建时间', dataIndex: 'created_at', key: 'created_at', dataType: 'datetime' } + ] + } + + static getBatchColumns() { + return [ + { title: '批次ID', dataIndex: 'id', key: 'id' }, + { title: '批次名称', dataIndex: 'batch_name', key: 'batch_name' }, + { title: '批次类型', dataIndex: 'batch_type', key: 'batch_type' }, + { title: '目标数量', dataIndex: 'target_count', key: 'target_count' }, + { title: '当前数量', dataIndex: 'current_count', key: 'current_count' }, + { title: '创建时间', dataIndex: 'created_at', key: 'created_at', dataType: 'datetime' }, + { title: '负责人', dataIndex: 'manager', key: 'manager' }, + { title: '状态', dataIndex: 'status', key: 'status' } + ] + } + + static getTransferColumns() { + return [ + { title: '记录ID', dataIndex: 'id', key: 'id' }, + { title: '牛只耳标', dataIndex: 'ear_tag', key: 'ear_tag' }, + { title: '原栏舍', dataIndex: 'from_pen', key: 'from_pen' }, + { title: '目标栏舍', dataIndex: 'to_pen', key: 'to_pen' }, + { title: '转栏时间', dataIndex: 'transfer_time', key: 'transfer_time', dataType: 'datetime' }, + { title: '转栏原因', dataIndex: 'reason', key: 'reason' }, + { title: '操作人', dataIndex: 'operator', key: 'operator' } + ] + } + + static getExitColumns() { + return [ + { title: '记录ID', dataIndex: 'id', key: 'id' }, + { title: '牛只耳标', dataIndex: 'ear_tag', key: 'ear_tag' }, + { title: '原栏舍', dataIndex: 'from_pen', key: 'from_pen' }, + { title: '离栏时间', dataIndex: 'exit_time', key: 'exit_time', dataType: 'datetime' }, + { title: '离栏原因', dataIndex: 'exit_reason', key: 'exit_reason' }, + { title: '去向', dataIndex: 'destination', key: 'destination' }, + { title: '操作人', dataIndex: 'operator', key: 'operator' } + ] + } + + static getFarmColumns() { + return [ + { title: '养殖场ID', dataIndex: 'id', key: 'id' }, + { title: '养殖场名称', dataIndex: 'name', key: 'name' }, + { title: '地址', dataIndex: 'address', key: 'address' }, + { title: '联系电话', dataIndex: 'phone', key: 'phone' }, + { title: '负责人', dataIndex: 'contact', key: 'contact' }, + { title: '面积', dataIndex: 'area', key: 'area' }, + { title: '状态', dataIndex: 'status', key: 'status' }, + { title: '创建时间', dataIndex: 'createdAt', key: 'createdAt', dataType: 'datetime' } + ] + } + + static getUserColumns() { + return [ + { title: '用户ID', dataIndex: 'id', key: 'id' }, + { title: '用户名', dataIndex: 'username', key: 'username' }, + { title: '邮箱', dataIndex: 'email', key: 'email' }, + { title: '角色', dataIndex: 'roleName', key: 'roleName' }, + { title: '状态', dataIndex: 'status', key: 'status' }, + { title: '最后登录', dataIndex: 'last_login', key: 'last_login', dataType: 'datetime' }, + { title: '创建时间', dataIndex: 'created_at', key: 'created_at', dataType: 'datetime' } + ] + } + + // 其他导出方法(为了兼容性) + static exportFarmsData(data) { + return this.exportFarmData(data) + } + + static exportAlertsData(data) { + return this.exportAlertData(data, 'collar') // 默认使用collar类型 + } + + static exportDevicesData(data) { + return this.exportDeviceData(data, 'collar') // 默认使用collar类型 + } + + static exportOrdersData(data) { + return this.exportToExcel(data, this.getOrderColumns(), '订单数据') + } + + static exportProductsData(data) { + return this.exportToExcel(data, this.getProductColumns(), '产品数据') + } + + static getOrderColumns() { + return [ + { title: '订单ID', dataIndex: 'id', key: 'id' }, + { title: '订单号', dataIndex: 'order_number', key: 'order_number' }, + { title: '客户名称', dataIndex: 'customer_name', key: 'customer_name' }, + { title: '订单金额', dataIndex: 'total_amount', key: 'total_amount' }, + { title: '订单状态', dataIndex: 'status', key: 'status' }, + { title: '创建时间', dataIndex: 'created_at', key: 'created_at', dataType: 'datetime' } + ] + } + + static getProductColumns() { + return [ + { title: '产品ID', dataIndex: 'id', key: 'id' }, + { title: '产品名称', dataIndex: 'name', key: 'name' }, + { title: '产品类型', dataIndex: 'type', key: 'type' }, + { title: '价格', dataIndex: 'price', key: 'price' }, + { title: '库存', dataIndex: 'stock', key: 'stock' }, + { title: '状态', dataIndex: 'status', key: 'status' }, + { title: '创建时间', dataIndex: 'created_at', key: 'created_at', dataType: 'datetime' } + ] + } +} + export default ExportUtils \ No newline at end of file diff --git a/admin-system/frontend/src/utils/mapService.js b/admin-system/src/utils/mapService.js similarity index 100% rename from admin-system/frontend/src/utils/mapService.js rename to admin-system/src/utils/mapService.js diff --git a/admin-system/frontend/src/utils/mapServiceFixed.js b/admin-system/src/utils/mapServiceFixed.js similarity index 96% rename from admin-system/frontend/src/utils/mapServiceFixed.js rename to admin-system/src/utils/mapServiceFixed.js index 8148a52..e16f935 100644 --- a/admin-system/frontend/src/utils/mapServiceFixed.js +++ b/admin-system/src/utils/mapServiceFixed.js @@ -1,332 +1,332 @@ -/** - * 修复版百度地图服务工具 - * 专门解决 coordType 错误问题 - */ - -// 百度地图API加载状态 -let BMapLoaded = false; -let loadingPromise = null; - -/** - * 加载百度地图API - */ -export const loadBMapScript = async (retryCount = 0) => { - if (BMapLoaded) { - console.log('百度地图API已加载'); - return Promise.resolve(); - } - - if (loadingPromise) { - console.log('百度地图API正在加载中...'); - return loadingPromise; - } - - console.log(`开始加载百度地图API... (重试次数: ${retryCount})`); - - loadingPromise = new Promise(async (resolve, reject) => { - try { - const { BAIDU_MAP_CONFIG } = await import('../config/env'); - - console.log('使用API密钥:', BAIDU_MAP_CONFIG.apiKey); - - if (!BAIDU_MAP_CONFIG.apiKey || BAIDU_MAP_CONFIG.apiKey === 'YOUR_VALID_BAIDU_MAP_API_KEY') { - const error = new Error('百度地图API密钥未配置或无效'); - console.error('API密钥错误:', error); - reject(error); - return; - } - - if (typeof window.BMap !== 'undefined') { - console.log('BMap已存在,直接使用'); - BMapLoaded = true; - resolve(); - return; - } - - // 创建全局回调函数 - window.initBMap = () => { - console.log('百度地图API加载成功'); - clearTimeout(timeoutId); - - setTimeout(() => { - if (window.BMap && typeof window.BMap.Map === 'function') { - BMapLoaded = true; - resolve(); - } else { - console.error('BMap对象未正确初始化'); - reject(new Error('BMap对象未正确初始化')); - } - delete window.initBMap; - }, 100); - }; - - // 创建script标签 - const script = document.createElement('script'); - script.type = 'text/javascript'; - script.src = `https://api.map.baidu.com/api?v=3.0&ak=${BAIDU_MAP_CONFIG.apiKey}&callback=initBMap`; - - console.log('百度地图API URL:', script.src); - - script.onerror = async (error) => { - console.error('百度地图脚本加载失败:', error); - clearTimeout(timeoutId); - if (script.parentNode) { - script.parentNode.removeChild(script); - } - if (window.initBMap) { - delete window.initBMap; - } - - if (retryCount < BAIDU_MAP_CONFIG.maxRetries) { - console.log(`重试加载百度地图API (${retryCount + 1}/${BAIDU_MAP_CONFIG.maxRetries})`); - setTimeout(() => { - loadBMapScript(retryCount + 1).then(resolve).catch(reject); - }, BAIDU_MAP_CONFIG.retryDelay); - } else { - reject(new Error('百度地图API加载失败,已达到最大重试次数')); - } - }; - - // 设置超时 - const timeoutId = setTimeout(() => { - console.error('百度地图API加载超时'); - if (script.parentNode) { - script.parentNode.removeChild(script); - } - if (window.initBMap) { - delete window.initBMap; - } - reject(new Error('百度地图API加载超时')); - }, 10000); - - document.head.appendChild(script); - - } catch (error) { - console.error('加载百度地图API时发生错误:', error); - reject(error); - } - }); - - return loadingPromise; -}; - -/** - * 修复版创建地图函数 - * 专门解决 coordType 错误 - */ -export const createMap = async (container, options = {}) => { - console.log('修复版createMap函数开始执行'); - - // 确保API已加载 - await loadBMapScript(); - - // 验证容器 - if (!container) { - throw new Error('地图容器不能为空'); - } - - // 检查容器是否在DOM中 - if (!document.contains(container)) { - throw new Error('地图容器不在DOM中'); - } - - // 强制设置容器样式,确保地图能正确初始化 - const originalStyles = { - position: container.style.position, - display: container.style.display, - visibility: container.style.visibility, - width: container.style.width, - height: container.style.height, - minHeight: container.style.minHeight - }; - - // 设置临时样式 - container.style.position = 'relative'; - container.style.display = 'block'; - container.style.visibility = 'visible'; - container.style.width = container.style.width || '100%'; - container.style.height = container.style.height || '400px'; - container.style.minHeight = container.style.minHeight || '400px'; - - // 等待样式生效 - await new Promise(resolve => setTimeout(resolve, 50)); - - // 检查容器尺寸 - if (container.offsetWidth === 0 || container.offsetHeight === 0) { - console.warn('容器尺寸为0,强制设置尺寸'); - container.style.width = '100%'; - container.style.height = '400px'; - await new Promise(resolve => setTimeout(resolve, 50)); - } - - console.log('容器最终状态:', { - offsetWidth: container.offsetWidth, - offsetHeight: container.offsetHeight, - clientWidth: container.clientWidth, - clientHeight: container.clientHeight, - computedStyle: { - position: window.getComputedStyle(container).position, - display: window.getComputedStyle(container).display, - visibility: window.getComputedStyle(container).visibility - } - }); - - // 检查父级元素样式 - let parent = container.parentElement; - while (parent && parent !== document.body) { - const parentStyle = window.getComputedStyle(parent); - if (parentStyle.position === 'fixed' || parentStyle.display === 'none') { - console.warn('发现可能有问题的父级样式:', { - tagName: parent.tagName, - position: parentStyle.position, - display: parentStyle.display - }); - } - parent = parent.parentElement; - } - - // 默认配置 - const defaultOptions = { - center: { lng: 106.27, lat: 38.47 }, - zoom: 8, - enableMapClick: true, - enableScrollWheelZoom: true, - enableDragging: true, - enableDoubleClickZoom: true, - enableKeyboard: true - }; - - const mergedOptions = { ...defaultOptions, ...options }; - - console.log('地图配置选项:', mergedOptions); - - // 创建地图实例 - let map; - try { - console.log('开始创建BMap.Map实例...'); - - // 确保BMap对象存在 - if (!window.BMap || typeof window.BMap.Map !== 'function') { - throw new Error('BMap对象未正确加载'); - } - - // 创建地图实例 - map = new window.BMap.Map(container); - console.log('地图实例创建成功:', map); - - // 验证地图实例 - if (!map || typeof map.centerAndZoom !== 'function') { - throw new Error('地图实例创建失败'); - } - - } catch (error) { - console.error('创建地图实例失败:', error); - // 恢复原始样式 - Object.keys(originalStyles).forEach(key => { - container.style[key] = originalStyles[key]; - }); - throw error; - } - - // 恢复原始样式 - Object.keys(originalStyles).forEach(key => { - container.style[key] = originalStyles[key]; - }); - - // 设置中心点和缩放级别 - console.log('设置地图中心点和缩放级别:', mergedOptions.center, mergedOptions.zoom); - map.centerAndZoom(mergedOptions.center, mergedOptions.zoom); - - // 添加地图控件 - map.addControl(new window.BMap.NavigationControl()); - map.addControl(new window.BMap.ScaleControl()); - map.addControl(new window.BMap.OverviewMapControl()); - map.addControl(new window.BMap.MapTypeControl()); - - // 监听地图事件 - map.addEventListener('tilesloaded', function() { - console.log('百度地图瓦片加载完成'); - }); - - map.addEventListener('load', function() { - console.log('百度地图完全加载完成'); - }); - - // 设置地图功能 - if (mergedOptions.enableMapClick) { - map.enableMapClick(); - } - if (mergedOptions.enableScrollWheelZoom) { - map.enableScrollWheelZoom(); - } - if (mergedOptions.enableDragging) { - map.enableDragging(); - } - if (mergedOptions.enableDoubleClickZoom) { - map.enableDoubleClickZoom(); - } - if (mergedOptions.enableKeyboard) { - map.enableKeyboard(); - } - - console.log('修复版地图创建完成'); - return map; -}; - -/** - * 添加标记点 - */ -export const addMarkers = (map, markers) => { - if (!map || !markers || !Array.isArray(markers)) { - console.warn('addMarkers: 参数无效'); - return; - } - - console.log('添加标记点:', markers.length); - - markers.forEach((markerData, index) => { - try { - const point = new window.BMap.Point(markerData.lng, markerData.lat); - const marker = new window.BMap.Marker(point); - - if (markerData.title) { - const infoWindow = new window.BMap.InfoWindow(markerData.title); - marker.addEventListener('click', function() { - map.openInfoWindow(infoWindow, point); - }); - } - - map.addOverlay(marker); - console.log(`标记点 ${index + 1} 添加成功`); - } catch (error) { - console.error(`添加标记点 ${index + 1} 失败:`, error); - } - }); -}; - -/** - * 清除所有覆盖物 - */ -export const clearOverlays = (map) => { - if (!map) { - console.warn('clearOverlays: 地图实例无效'); - return; - } - - map.clearOverlays(); - console.log('已清除所有覆盖物'); -}; - -/** - * 调整视图以适应所有标记 - */ -export const setViewToFitMarkers = (map, markers) => { - if (!map || !markers || !Array.isArray(markers) || markers.length === 0) { - console.warn('setViewToFitMarkers: 参数无效'); - return; - } - - const points = markers.map(marker => new window.BMap.Point(marker.lng, marker.lat)); - map.setViewport(points); - console.log('已调整视图以适应标记点'); -}; +/** + * 修复版百度地图服务工具 + * 专门解决 coordType 错误问题 + */ + +// 百度地图API加载状态 +let BMapLoaded = false; +let loadingPromise = null; + +/** + * 加载百度地图API + */ +export const loadBMapScript = async (retryCount = 0) => { + if (BMapLoaded) { + console.log('百度地图API已加载'); + return Promise.resolve(); + } + + if (loadingPromise) { + console.log('百度地图API正在加载中...'); + return loadingPromise; + } + + console.log(`开始加载百度地图API... (重试次数: ${retryCount})`); + + loadingPromise = new Promise(async (resolve, reject) => { + try { + const { BAIDU_MAP_CONFIG } = await import('../config/env'); + + console.log('使用API密钥:', BAIDU_MAP_CONFIG.apiKey); + + if (!BAIDU_MAP_CONFIG.apiKey || BAIDU_MAP_CONFIG.apiKey === 'YOUR_VALID_BAIDU_MAP_API_KEY') { + const error = new Error('百度地图API密钥未配置或无效'); + console.error('API密钥错误:', error); + reject(error); + return; + } + + if (typeof window.BMap !== 'undefined') { + console.log('BMap已存在,直接使用'); + BMapLoaded = true; + resolve(); + return; + } + + // 创建全局回调函数 + window.initBMap = () => { + console.log('百度地图API加载成功'); + clearTimeout(timeoutId); + + setTimeout(() => { + if (window.BMap && typeof window.BMap.Map === 'function') { + BMapLoaded = true; + resolve(); + } else { + console.error('BMap对象未正确初始化'); + reject(new Error('BMap对象未正确初始化')); + } + delete window.initBMap; + }, 100); + }; + + // 创建script标签 + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = `https://api.map.baidu.com/api?v=3.0&ak=${BAIDU_MAP_CONFIG.apiKey}&callback=initBMap`; + + console.log('百度地图API URL:', script.src); + + script.onerror = async (error) => { + console.error('百度地图脚本加载失败:', error); + clearTimeout(timeoutId); + if (script.parentNode) { + script.parentNode.removeChild(script); + } + if (window.initBMap) { + delete window.initBMap; + } + + if (retryCount < BAIDU_MAP_CONFIG.maxRetries) { + console.log(`重试加载百度地图API (${retryCount + 1}/${BAIDU_MAP_CONFIG.maxRetries})`); + setTimeout(() => { + loadBMapScript(retryCount + 1).then(resolve).catch(reject); + }, BAIDU_MAP_CONFIG.retryDelay); + } else { + reject(new Error('百度地图API加载失败,已达到最大重试次数')); + } + }; + + // 设置超时 + const timeoutId = setTimeout(() => { + console.error('百度地图API加载超时'); + if (script.parentNode) { + script.parentNode.removeChild(script); + } + if (window.initBMap) { + delete window.initBMap; + } + reject(new Error('百度地图API加载超时')); + }, 10000); + + document.head.appendChild(script); + + } catch (error) { + console.error('加载百度地图API时发生错误:', error); + reject(error); + } + }); + + return loadingPromise; +}; + +/** + * 修复版创建地图函数 + * 专门解决 coordType 错误 + */ +export const createMap = async (container, options = {}) => { + console.log('修复版createMap函数开始执行'); + + // 确保API已加载 + await loadBMapScript(); + + // 验证容器 + if (!container) { + throw new Error('地图容器不能为空'); + } + + // 检查容器是否在DOM中 + if (!document.contains(container)) { + throw new Error('地图容器不在DOM中'); + } + + // 强制设置容器样式,确保地图能正确初始化 + const originalStyles = { + position: container.style.position, + display: container.style.display, + visibility: container.style.visibility, + width: container.style.width, + height: container.style.height, + minHeight: container.style.minHeight + }; + + // 设置临时样式 + container.style.position = 'relative'; + container.style.display = 'block'; + container.style.visibility = 'visible'; + container.style.width = container.style.width || '100%'; + container.style.height = container.style.height || '400px'; + container.style.minHeight = container.style.minHeight || '400px'; + + // 等待样式生效 + await new Promise(resolve => setTimeout(resolve, 50)); + + // 检查容器尺寸 + if (container.offsetWidth === 0 || container.offsetHeight === 0) { + console.warn('容器尺寸为0,强制设置尺寸'); + container.style.width = '100%'; + container.style.height = '400px'; + await new Promise(resolve => setTimeout(resolve, 50)); + } + + console.log('容器最终状态:', { + offsetWidth: container.offsetWidth, + offsetHeight: container.offsetHeight, + clientWidth: container.clientWidth, + clientHeight: container.clientHeight, + computedStyle: { + position: window.getComputedStyle(container).position, + display: window.getComputedStyle(container).display, + visibility: window.getComputedStyle(container).visibility + } + }); + + // 检查父级元素样式 + let parent = container.parentElement; + while (parent && parent !== document.body) { + const parentStyle = window.getComputedStyle(parent); + if (parentStyle.position === 'fixed' || parentStyle.display === 'none') { + console.warn('发现可能有问题的父级样式:', { + tagName: parent.tagName, + position: parentStyle.position, + display: parentStyle.display + }); + } + parent = parent.parentElement; + } + + // 默认配置 + const defaultOptions = { + center: { lng: 106.27, lat: 38.47 }, + zoom: 8, + enableMapClick: true, + enableScrollWheelZoom: true, + enableDragging: true, + enableDoubleClickZoom: true, + enableKeyboard: true + }; + + const mergedOptions = { ...defaultOptions, ...options }; + + console.log('地图配置选项:', mergedOptions); + + // 创建地图实例 + let map; + try { + console.log('开始创建BMap.Map实例...'); + + // 确保BMap对象存在 + if (!window.BMap || typeof window.BMap.Map !== 'function') { + throw new Error('BMap对象未正确加载'); + } + + // 创建地图实例 + map = new window.BMap.Map(container); + console.log('地图实例创建成功:', map); + + // 验证地图实例 + if (!map || typeof map.centerAndZoom !== 'function') { + throw new Error('地图实例创建失败'); + } + + } catch (error) { + console.error('创建地图实例失败:', error); + // 恢复原始样式 + Object.keys(originalStyles).forEach(key => { + container.style[key] = originalStyles[key]; + }); + throw error; + } + + // 恢复原始样式 + Object.keys(originalStyles).forEach(key => { + container.style[key] = originalStyles[key]; + }); + + // 设置中心点和缩放级别 + console.log('设置地图中心点和缩放级别:', mergedOptions.center, mergedOptions.zoom); + map.centerAndZoom(mergedOptions.center, mergedOptions.zoom); + + // 添加地图控件 + map.addControl(new window.BMap.NavigationControl()); + map.addControl(new window.BMap.ScaleControl()); + map.addControl(new window.BMap.OverviewMapControl()); + map.addControl(new window.BMap.MapTypeControl()); + + // 监听地图事件 + map.addEventListener('tilesloaded', function() { + console.log('百度地图瓦片加载完成'); + }); + + map.addEventListener('load', function() { + console.log('百度地图完全加载完成'); + }); + + // 设置地图功能 + if (mergedOptions.enableMapClick) { + map.enableMapClick(); + } + if (mergedOptions.enableScrollWheelZoom) { + map.enableScrollWheelZoom(); + } + if (mergedOptions.enableDragging) { + map.enableDragging(); + } + if (mergedOptions.enableDoubleClickZoom) { + map.enableDoubleClickZoom(); + } + if (mergedOptions.enableKeyboard) { + map.enableKeyboard(); + } + + console.log('修复版地图创建完成'); + return map; +}; + +/** + * 添加标记点 + */ +export const addMarkers = (map, markers) => { + if (!map || !markers || !Array.isArray(markers)) { + console.warn('addMarkers: 参数无效'); + return; + } + + console.log('添加标记点:', markers.length); + + markers.forEach((markerData, index) => { + try { + const point = new window.BMap.Point(markerData.lng, markerData.lat); + const marker = new window.BMap.Marker(point); + + if (markerData.title) { + const infoWindow = new window.BMap.InfoWindow(markerData.title); + marker.addEventListener('click', function() { + map.openInfoWindow(infoWindow, point); + }); + } + + map.addOverlay(marker); + console.log(`标记点 ${index + 1} 添加成功`); + } catch (error) { + console.error(`添加标记点 ${index + 1} 失败:`, error); + } + }); +}; + +/** + * 清除所有覆盖物 + */ +export const clearOverlays = (map) => { + if (!map) { + console.warn('clearOverlays: 地图实例无效'); + return; + } + + map.clearOverlays(); + console.log('已清除所有覆盖物'); +}; + +/** + * 调整视图以适应所有标记 + */ +export const setViewToFitMarkers = (map, markers) => { + if (!map || !markers || !Array.isArray(markers) || markers.length === 0) { + console.warn('setViewToFitMarkers: 参数无效'); + return; + } + + const points = markers.map(marker => new window.BMap.Point(marker.lng, marker.lat)); + map.setViewport(points); + console.log('已调整视图以适应标记点'); +}; diff --git a/admin-system/frontend/src/utils/menuDebugger.js b/admin-system/src/utils/menuDebugger.js similarity index 96% rename from admin-system/frontend/src/utils/menuDebugger.js rename to admin-system/src/utils/menuDebugger.js index 9d8db96..9727584 100644 --- a/admin-system/frontend/src/utils/menuDebugger.js +++ b/admin-system/src/utils/menuDebugger.js @@ -1,116 +1,116 @@ -/** - * 菜单权限调试工具 - * @file menuDebugger.js - * @description 用于调试菜单权限问题的工具函数 - */ - -/** - * 调试菜单权限问题 - * @param {Object} userData - 用户数据 - * @param {Array} routes - 路由配置 - */ -export function debugMenuPermissions(userData, routes) { - console.log('🔍 菜单权限调试开始...') - console.log('📊 用户数据:', userData) - console.log('📊 路由配置:', routes) - - if (!userData) { - console.error('❌ 用户数据为空') - return - } - - console.log('📋 用户权限:', userData.permissions || []) - console.log('📋 用户角色:', userData.role) - console.log('📋 可访问菜单:', userData.accessibleMenus || []) - - // 检查每个路由的权限 - routes.forEach(route => { - console.log(`\n🔍 检查路由: ${route.name}`) - console.log(' - 路径:', route.path) - console.log(' - 标题:', route.meta?.title) - console.log(' - 图标:', route.meta?.icon) - console.log(' - 权限要求:', route.meta?.permission) - console.log(' - 角色要求:', route.meta?.roles) - console.log(' - 菜单要求:', route.meta?.menu) - - // 检查权限 - if (route.meta?.permission) { - const hasPerm = userData.permissions?.includes(route.meta.permission) - console.log(` - 权限检查: ${route.meta.permission} -> ${hasPerm ? '✅' : '❌'}`) - } - - // 检查角色 - if (route.meta?.roles) { - const hasRole = route.meta.roles.includes(userData.role?.name) - console.log(` - 角色检查: ${route.meta.roles} -> ${hasRole ? '✅' : '❌'}`) - } - - // 检查菜单 - if (route.meta?.menu) { - const canAccess = userData.accessibleMenus?.includes(route.meta.menu) - console.log(` - 菜单检查: ${route.meta.menu} -> ${canAccess ? '✅' : '❌'}`) - } - }) - - console.log('🔍 菜单权限调试完成') -} - -/** - * 检查特定权限 - * @param {Object} userData - 用户数据 - * @param {string} permission - 权限名称 - */ -export function checkPermission(userData, permission) { - console.log(`🔍 检查权限: ${permission}`) - console.log('用户权限列表:', userData.permissions || []) - - const hasPerm = userData.permissions?.includes(permission) - console.log(`权限检查结果: ${hasPerm ? '✅' : '❌'}`) - - return hasPerm -} - -/** - * 检查特定角色 - * @param {Object} userData - 用户数据 - * @param {string} role - 角色名称 - */ -export function checkRole(userData, role) { - console.log(`🔍 检查角色: ${role}`) - console.log('用户角色:', userData.role) - - const hasRole = userData.role?.name === role - console.log(`角色检查结果: ${hasRole ? '✅' : '❌'}`) - - return hasRole -} - -/** - * 检查菜单访问 - * @param {Object} userData - 用户数据 - * @param {string} menuKey - 菜单键 - */ -export function checkMenuAccess(userData, menuKey) { - console.log(`🔍 检查菜单访问: ${menuKey}`) - console.log('可访问菜单:', userData.accessibleMenus || []) - - const canAccess = userData.accessibleMenus?.includes(menuKey) - console.log(`菜单访问检查结果: ${canAccess ? '✅' : '❌'}`) - - return canAccess -} - -/** - * 导出调试工具到全局 - */ -export function setupGlobalDebugger() { - if (typeof window !== 'undefined') { - window.menuDebugger = { - debugMenuPermissions, - checkPermission, - checkRole, - checkMenuAccess - } - console.log('🔧 菜单权限调试工具已添加到 window.menuDebugger') - } -} +/** + * 菜单权限调试工具 + * @file menuDebugger.js + * @description 用于调试菜单权限问题的工具函数 + */ + +/** + * 调试菜单权限问题 + * @param {Object} userData - 用户数据 + * @param {Array} routes - 路由配置 + */ +export function debugMenuPermissions(userData, routes) { + console.log('🔍 菜单权限调试开始...') + console.log('📊 用户数据:', userData) + console.log('📊 路由配置:', routes) + + if (!userData) { + console.error('❌ 用户数据为空') + return + } + + console.log('📋 用户权限:', userData.permissions || []) + console.log('📋 用户角色:', userData.role) + console.log('📋 可访问菜单:', userData.accessibleMenus || []) + + // 检查每个路由的权限 + routes.forEach(route => { + console.log(`\n🔍 检查路由: ${route.name}`) + console.log(' - 路径:', route.path) + console.log(' - 标题:', route.meta?.title) + console.log(' - 图标:', route.meta?.icon) + console.log(' - 权限要求:', route.meta?.permission) + console.log(' - 角色要求:', route.meta?.roles) + console.log(' - 菜单要求:', route.meta?.menu) + + // 检查权限 + if (route.meta?.permission) { + const hasPerm = userData.permissions?.includes(route.meta.permission) + console.log(` - 权限检查: ${route.meta.permission} -> ${hasPerm ? '✅' : '❌'}`) + } + + // 检查角色 + if (route.meta?.roles) { + const hasRole = route.meta.roles.includes(userData.role?.name) + console.log(` - 角色检查: ${route.meta.roles} -> ${hasRole ? '✅' : '❌'}`) + } + + // 检查菜单 + if (route.meta?.menu) { + const canAccess = userData.accessibleMenus?.includes(route.meta.menu) + console.log(` - 菜单检查: ${route.meta.menu} -> ${canAccess ? '✅' : '❌'}`) + } + }) + + console.log('🔍 菜单权限调试完成') +} + +/** + * 检查特定权限 + * @param {Object} userData - 用户数据 + * @param {string} permission - 权限名称 + */ +export function checkPermission(userData, permission) { + console.log(`🔍 检查权限: ${permission}`) + console.log('用户权限列表:', userData.permissions || []) + + const hasPerm = userData.permissions?.includes(permission) + console.log(`权限检查结果: ${hasPerm ? '✅' : '❌'}`) + + return hasPerm +} + +/** + * 检查特定角色 + * @param {Object} userData - 用户数据 + * @param {string} role - 角色名称 + */ +export function checkRole(userData, role) { + console.log(`🔍 检查角色: ${role}`) + console.log('用户角色:', userData.role) + + const hasRole = userData.role?.name === role + console.log(`角色检查结果: ${hasRole ? '✅' : '❌'}`) + + return hasRole +} + +/** + * 检查菜单访问 + * @param {Object} userData - 用户数据 + * @param {string} menuKey - 菜单键 + */ +export function checkMenuAccess(userData, menuKey) { + console.log(`🔍 检查菜单访问: ${menuKey}`) + console.log('可访问菜单:', userData.accessibleMenus || []) + + const canAccess = userData.accessibleMenus?.includes(menuKey) + console.log(`菜单访问检查结果: ${canAccess ? '✅' : '❌'}`) + + return canAccess +} + +/** + * 导出调试工具到全局 + */ +export function setupGlobalDebugger() { + if (typeof window !== 'undefined') { + window.menuDebugger = { + debugMenuPermissions, + checkPermission, + checkRole, + checkMenuAccess + } + console.log('🔧 菜单权限调试工具已添加到 window.menuDebugger') + } +} diff --git a/admin-system/frontend/src/utils/websocketService.js b/admin-system/src/utils/websocketService.js similarity index 96% rename from admin-system/frontend/src/utils/websocketService.js rename to admin-system/src/utils/websocketService.js index 8ec0293..de6c918 100644 --- a/admin-system/frontend/src/utils/websocketService.js +++ b/admin-system/src/utils/websocketService.js @@ -1,379 +1,379 @@ -/** - * WebSocket实时通信服务 - * @file websocketService.js - * @description 前端WebSocket客户端,处理实时数据接收 - */ -import { io } from 'socket.io-client'; -import { useUserStore } from '../stores/user'; -import { useDataStore } from '../stores/data'; -import { message, notification } from 'ant-design-vue'; - -class WebSocketService { - constructor() { - this.socket = null; - this.isConnected = false; - this.reconnectAttempts = 0; - this.maxReconnectAttempts = 5; - this.reconnectInterval = 3000; // 3秒重连间隔 - this.userStore = null; - this.dataStore = null; - } - - /** - * 连接WebSocket服务器 - * @param {string} token JWT认证令牌 - */ - connect(token) { - if (this.socket && this.isConnected) { - console.log('WebSocket已连接,无需重复连接'); - return; - } - - // 初始化store - this.userStore = useUserStore(); - this.dataStore = useDataStore(); - - const serverUrl = import.meta.env.VITE_API_URL || 'http://localhost:5350'; - - console.log('正在连接WebSocket服务器:', serverUrl); - - this.socket = io(serverUrl, { - auth: { - token: token - }, - transports: ['websocket', 'polling'], - timeout: 20000, - reconnection: true, - reconnectionAttempts: this.maxReconnectAttempts, - reconnectionDelay: this.reconnectInterval - }); - - this.setupEventListeners(); - } - - /** - * 设置事件监听器 - */ - setupEventListeners() { - if (!this.socket) return; - - // 连接成功 - this.socket.on('connect', () => { - this.isConnected = true; - this.reconnectAttempts = 0; - console.log('WebSocket连接成功,连接ID:', this.socket.id); - - message.success('实时数据连接已建立'); - }); - - // 连接确认 - this.socket.on('connected', (data) => { - console.log('收到服务器连接确认:', data); - }); - - // 设备状态更新 - this.socket.on('device_update', (data) => { - console.log('收到设备状态更新:', data); - this.handleDeviceUpdate(data); - }); - - // 预警更新 - this.socket.on('alert_update', (data) => { - console.log('收到预警更新:', data); - this.handleAlertUpdate(data); - }); - - // 紧急预警 - this.socket.on('urgent_alert', (data) => { - console.log('收到紧急预警:', data); - this.handleUrgentAlert(data); - }); - - // 动物健康状态更新 - this.socket.on('animal_update', (data) => { - console.log('收到动物健康状态更新:', data); - this.handleAnimalUpdate(data); - }); - - // 系统统计数据更新 - this.socket.on('stats_update', (data) => { - console.log('收到系统统计数据更新:', data); - this.handleStatsUpdate(data); - }); - - // 性能监控数据(仅管理员) - this.socket.on('performance_update', (data) => { - console.log('收到性能监控数据:', data); - this.handlePerformanceUpdate(data); - }); - - // 连接断开 - this.socket.on('disconnect', (reason) => { - this.isConnected = false; - console.log('WebSocket连接断开:', reason); - - if (reason === 'io server disconnect') { - // 服务器主动断开,需要重新连接 - this.reconnect(); - } - }); - - // 连接错误 - this.socket.on('connect_error', (error) => { - console.error('WebSocket连接错误:', error); - - if (error.message.includes('认证失败') || error.message.includes('未提供认证令牌')) { - message.error('实时连接认证失败,请重新登录'); - this.userStore.logout(); - } else { - this.handleReconnect(); - } - }); - - // 心跳响应 - this.socket.on('pong', (data) => { - console.log('收到心跳响应:', data); - }); - } - - /** - * 处理设备状态更新 - * @param {Object} data 设备数据 - */ - handleDeviceUpdate(data) { - // 更新数据存储中的设备状态 - if (this.dataStore) { - this.dataStore.updateDeviceRealtime(data.data); - } - - // 如果设备状态异常,显示通知 - if (data.data.status === 'offline') { - notification.warning({ - message: '设备状态变化', - description: `设备 ${data.data.name} 已离线`, - duration: 4.5, - }); - } else if (data.data.status === 'maintenance') { - notification.info({ - message: '设备状态变化', - description: `设备 ${data.data.name} 进入维护模式`, - duration: 4.5, - }); - } - } - - /** - * 处理预警更新 - * @param {Object} data 预警数据 - */ - handleAlertUpdate(data) { - // 更新数据存储中的预警数据 - if (this.dataStore) { - this.dataStore.addNewAlert(data.data); - } - - // 显示预警通知 - const alertLevel = data.data.level; - let notificationType = 'info'; - - if (alertLevel === 'critical') { - notificationType = 'error'; - } else if (alertLevel === 'high') { - notificationType = 'warning'; - } - - notification[notificationType]({ - message: '新预警', - description: `${data.data.farm_name}: ${data.data.message}`, - duration: 6, - }); - } - - /** - * 处理紧急预警 - * @param {Object} data 紧急预警数据 - */ - handleUrgentAlert(data) { - // 紧急预警使用模态框显示 - notification.error({ - message: '🚨 紧急预警', - description: `${data.alert.farm_name}: ${data.alert.message}`, - duration: 0, // 不自动关闭 - style: { - backgroundColor: '#fff2f0', - border: '1px solid #ffccc7' - } - }); - - // 播放警报声音(如果浏览器支持) - this.playAlertSound(); - } - - /** - * 处理动物健康状态更新 - * @param {Object} data 动物数据 - */ - handleAnimalUpdate(data) { - // 更新数据存储 - if (this.dataStore) { - this.dataStore.updateAnimalRealtime(data.data); - } - - // 如果动物健康状态异常,显示通知 - if (data.data.health_status === 'sick') { - notification.warning({ - message: '动物健康状态变化', - description: `${data.data.farm_name}的${data.data.type}出现健康问题`, - duration: 5, - }); - } else if (data.data.health_status === 'quarantined') { - notification.error({ - message: '动物健康状态变化', - description: `${data.data.farm_name}的${data.data.type}已隔离`, - duration: 6, - }); - } - } - - /** - * 处理系统统计数据更新 - * @param {Object} data 统计数据 - */ - handleStatsUpdate(data) { - // 更新数据存储中的统计信息 - if (this.dataStore) { - this.dataStore.updateStatsRealtime(data.data); - } - } - - /** - * 处理性能监控数据更新 - * @param {Object} data 性能数据 - */ - handlePerformanceUpdate(data) { - // 只有管理员才能看到性能数据 - if (this.userStore?.user?.roles?.includes('admin')) { - console.log('收到性能监控数据:', data); - // 可以通过事件总线通知性能监控组件更新 - window.dispatchEvent(new CustomEvent('performance_update', { detail: data })); - } - } - - /** - * 播放警报声音 - */ - playAlertSound() { - try { - // 创建音频上下文 - const audioContext = new (window.AudioContext || window.webkitAudioContext)(); - - // 生成警报音 - const oscillator = audioContext.createOscillator(); - const gainNode = audioContext.createGain(); - - oscillator.connect(gainNode); - gainNode.connect(audioContext.destination); - - oscillator.frequency.setValueAtTime(800, audioContext.currentTime); - gainNode.gain.setValueAtTime(0.3, audioContext.currentTime); - - oscillator.start(); - oscillator.stop(audioContext.currentTime + 0.5); - } catch (error) { - console.log('无法播放警报声音:', error); - } - } - - /** - * 订阅农场数据 - * @param {number} farmId 农场ID - */ - subscribeFarm(farmId) { - if (this.socket && this.isConnected) { - this.socket.emit('subscribe_farm', farmId); - console.log(`已订阅农场 ${farmId} 的实时数据`); - } - } - - /** - * 取消订阅农场数据 - * @param {number} farmId 农场ID - */ - unsubscribeFarm(farmId) { - if (this.socket && this.isConnected) { - this.socket.emit('unsubscribe_farm', farmId); - console.log(`已取消订阅农场 ${farmId} 的实时数据`); - } - } - - /** - * 订阅设备数据 - * @param {number} deviceId 设备ID - */ - subscribeDevice(deviceId) { - if (this.socket && this.isConnected) { - this.socket.emit('subscribe_device', deviceId); - console.log(`已订阅设备 ${deviceId} 的实时数据`); - } - } - - /** - * 发送心跳 - */ - sendHeartbeat() { - if (this.socket && this.isConnected) { - this.socket.emit('ping'); - } - } - - /** - * 处理重连 - */ - handleReconnect() { - this.reconnectAttempts++; - - if (this.reconnectAttempts <= this.maxReconnectAttempts) { - console.log(`尝试重连WebSocket (${this.reconnectAttempts}/${this.maxReconnectAttempts})`); - - setTimeout(() => { - this.reconnect(); - }, this.reconnectInterval * this.reconnectAttempts); - } else { - message.error('实时连接已断开,请刷新页面重试'); - } - } - - /** - * 重新连接 - */ - reconnect() { - if (this.userStore?.token) { - this.connect(this.userStore.token); - } - } - - /** - * 断开连接 - */ - disconnect() { - if (this.socket) { - this.socket.disconnect(); - this.socket = null; - this.isConnected = false; - console.log('WebSocket连接已断开'); - } - } - - /** - * 获取连接状态 - * @returns {boolean} 连接状态 - */ - getConnectionStatus() { - return this.isConnected; - } -} - -// 创建单例实例 -const webSocketService = new WebSocketService(); - -export default webSocketService; +/** + * WebSocket实时通信服务 + * @file websocketService.js + * @description 前端WebSocket客户端,处理实时数据接收 + */ +import { io } from 'socket.io-client'; +import { useUserStore } from '../stores/user'; +import { useDataStore } from '../stores/data'; +import { message, notification } from 'ant-design-vue'; + +class WebSocketService { + constructor() { + this.socket = null; + this.isConnected = false; + this.reconnectAttempts = 0; + this.maxReconnectAttempts = 5; + this.reconnectInterval = 3000; // 3秒重连间隔 + this.userStore = null; + this.dataStore = null; + } + + /** + * 连接WebSocket服务器 + * @param {string} token JWT认证令牌 + */ + connect(token) { + if (this.socket && this.isConnected) { + console.log('WebSocket已连接,无需重复连接'); + return; + } + + // 初始化store + this.userStore = useUserStore(); + this.dataStore = useDataStore(); + + const serverUrl = import.meta.env.VITE_API_URL || 'http://localhost:5350'; + + console.log('正在连接WebSocket服务器:', serverUrl); + + this.socket = io(serverUrl, { + auth: { + token: token + }, + transports: ['websocket', 'polling'], + timeout: 20000, + reconnection: true, + reconnectionAttempts: this.maxReconnectAttempts, + reconnectionDelay: this.reconnectInterval + }); + + this.setupEventListeners(); + } + + /** + * 设置事件监听器 + */ + setupEventListeners() { + if (!this.socket) return; + + // 连接成功 + this.socket.on('connect', () => { + this.isConnected = true; + this.reconnectAttempts = 0; + console.log('WebSocket连接成功,连接ID:', this.socket.id); + + message.success('实时数据连接已建立'); + }); + + // 连接确认 + this.socket.on('connected', (data) => { + console.log('收到服务器连接确认:', data); + }); + + // 设备状态更新 + this.socket.on('device_update', (data) => { + console.log('收到设备状态更新:', data); + this.handleDeviceUpdate(data); + }); + + // 预警更新 + this.socket.on('alert_update', (data) => { + console.log('收到预警更新:', data); + this.handleAlertUpdate(data); + }); + + // 紧急预警 + this.socket.on('urgent_alert', (data) => { + console.log('收到紧急预警:', data); + this.handleUrgentAlert(data); + }); + + // 动物健康状态更新 + this.socket.on('animal_update', (data) => { + console.log('收到动物健康状态更新:', data); + this.handleAnimalUpdate(data); + }); + + // 系统统计数据更新 + this.socket.on('stats_update', (data) => { + console.log('收到系统统计数据更新:', data); + this.handleStatsUpdate(data); + }); + + // 性能监控数据(仅管理员) + this.socket.on('performance_update', (data) => { + console.log('收到性能监控数据:', data); + this.handlePerformanceUpdate(data); + }); + + // 连接断开 + this.socket.on('disconnect', (reason) => { + this.isConnected = false; + console.log('WebSocket连接断开:', reason); + + if (reason === 'io server disconnect') { + // 服务器主动断开,需要重新连接 + this.reconnect(); + } + }); + + // 连接错误 + this.socket.on('connect_error', (error) => { + console.error('WebSocket连接错误:', error); + + if (error.message.includes('认证失败') || error.message.includes('未提供认证令牌')) { + message.error('实时连接认证失败,请重新登录'); + this.userStore.logout(); + } else { + this.handleReconnect(); + } + }); + + // 心跳响应 + this.socket.on('pong', (data) => { + console.log('收到心跳响应:', data); + }); + } + + /** + * 处理设备状态更新 + * @param {Object} data 设备数据 + */ + handleDeviceUpdate(data) { + // 更新数据存储中的设备状态 + if (this.dataStore) { + this.dataStore.updateDeviceRealtime(data.data); + } + + // 如果设备状态异常,显示通知 + if (data.data.status === 'offline') { + notification.warning({ + message: '设备状态变化', + description: `设备 ${data.data.name} 已离线`, + duration: 4.5, + }); + } else if (data.data.status === 'maintenance') { + notification.info({ + message: '设备状态变化', + description: `设备 ${data.data.name} 进入维护模式`, + duration: 4.5, + }); + } + } + + /** + * 处理预警更新 + * @param {Object} data 预警数据 + */ + handleAlertUpdate(data) { + // 更新数据存储中的预警数据 + if (this.dataStore) { + this.dataStore.addNewAlert(data.data); + } + + // 显示预警通知 + const alertLevel = data.data.level; + let notificationType = 'info'; + + if (alertLevel === 'critical') { + notificationType = 'error'; + } else if (alertLevel === 'high') { + notificationType = 'warning'; + } + + notification[notificationType]({ + message: '新预警', + description: `${data.data.farm_name}: ${data.data.message}`, + duration: 6, + }); + } + + /** + * 处理紧急预警 + * @param {Object} data 紧急预警数据 + */ + handleUrgentAlert(data) { + // 紧急预警使用模态框显示 + notification.error({ + message: '🚨 紧急预警', + description: `${data.alert.farm_name}: ${data.alert.message}`, + duration: 0, // 不自动关闭 + style: { + backgroundColor: '#fff2f0', + border: '1px solid #ffccc7' + } + }); + + // 播放警报声音(如果浏览器支持) + this.playAlertSound(); + } + + /** + * 处理动物健康状态更新 + * @param {Object} data 动物数据 + */ + handleAnimalUpdate(data) { + // 更新数据存储 + if (this.dataStore) { + this.dataStore.updateAnimalRealtime(data.data); + } + + // 如果动物健康状态异常,显示通知 + if (data.data.health_status === 'sick') { + notification.warning({ + message: '动物健康状态变化', + description: `${data.data.farm_name}的${data.data.type}出现健康问题`, + duration: 5, + }); + } else if (data.data.health_status === 'quarantined') { + notification.error({ + message: '动物健康状态变化', + description: `${data.data.farm_name}的${data.data.type}已隔离`, + duration: 6, + }); + } + } + + /** + * 处理系统统计数据更新 + * @param {Object} data 统计数据 + */ + handleStatsUpdate(data) { + // 更新数据存储中的统计信息 + if (this.dataStore) { + this.dataStore.updateStatsRealtime(data.data); + } + } + + /** + * 处理性能监控数据更新 + * @param {Object} data 性能数据 + */ + handlePerformanceUpdate(data) { + // 只有管理员才能看到性能数据 + if (this.userStore?.user?.roles?.includes('admin')) { + console.log('收到性能监控数据:', data); + // 可以通过事件总线通知性能监控组件更新 + window.dispatchEvent(new CustomEvent('performance_update', { detail: data })); + } + } + + /** + * 播放警报声音 + */ + playAlertSound() { + try { + // 创建音频上下文 + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + + // 生成警报音 + const oscillator = audioContext.createOscillator(); + const gainNode = audioContext.createGain(); + + oscillator.connect(gainNode); + gainNode.connect(audioContext.destination); + + oscillator.frequency.setValueAtTime(800, audioContext.currentTime); + gainNode.gain.setValueAtTime(0.3, audioContext.currentTime); + + oscillator.start(); + oscillator.stop(audioContext.currentTime + 0.5); + } catch (error) { + console.log('无法播放警报声音:', error); + } + } + + /** + * 订阅农场数据 + * @param {number} farmId 农场ID + */ + subscribeFarm(farmId) { + if (this.socket && this.isConnected) { + this.socket.emit('subscribe_farm', farmId); + console.log(`已订阅农场 ${farmId} 的实时数据`); + } + } + + /** + * 取消订阅农场数据 + * @param {number} farmId 农场ID + */ + unsubscribeFarm(farmId) { + if (this.socket && this.isConnected) { + this.socket.emit('unsubscribe_farm', farmId); + console.log(`已取消订阅农场 ${farmId} 的实时数据`); + } + } + + /** + * 订阅设备数据 + * @param {number} deviceId 设备ID + */ + subscribeDevice(deviceId) { + if (this.socket && this.isConnected) { + this.socket.emit('subscribe_device', deviceId); + console.log(`已订阅设备 ${deviceId} 的实时数据`); + } + } + + /** + * 发送心跳 + */ + sendHeartbeat() { + if (this.socket && this.isConnected) { + this.socket.emit('ping'); + } + } + + /** + * 处理重连 + */ + handleReconnect() { + this.reconnectAttempts++; + + if (this.reconnectAttempts <= this.maxReconnectAttempts) { + console.log(`尝试重连WebSocket (${this.reconnectAttempts}/${this.maxReconnectAttempts})`); + + setTimeout(() => { + this.reconnect(); + }, this.reconnectInterval * this.reconnectAttempts); + } else { + message.error('实时连接已断开,请刷新页面重试'); + } + } + + /** + * 重新连接 + */ + reconnect() { + if (this.userStore?.token) { + this.connect(this.userStore.token); + } + } + + /** + * 断开连接 + */ + disconnect() { + if (this.socket) { + this.socket.disconnect(); + this.socket = null; + this.isConnected = false; + console.log('WebSocket连接已断开'); + } + } + + /** + * 获取连接状态 + * @returns {boolean} 连接状态 + */ + getConnectionStatus() { + return this.isConnected; + } +} + +// 创建单例实例 +const webSocketService = new WebSocketService(); + +export default webSocketService; diff --git a/admin-system/frontend/src/views/Alerts.vue b/admin-system/src/views/Alerts.vue similarity index 100% rename from admin-system/frontend/src/views/Alerts.vue rename to admin-system/src/views/Alerts.vue diff --git a/admin-system/frontend/src/views/Analytics.vue b/admin-system/src/views/Analytics.vue similarity index 100% rename from admin-system/frontend/src/views/Analytics.vue rename to admin-system/src/views/Analytics.vue diff --git a/admin-system/frontend/src/views/Animals.vue b/admin-system/src/views/Animals.vue similarity index 100% rename from admin-system/frontend/src/views/Animals.vue rename to admin-system/src/views/Animals.vue diff --git a/admin-system/frontend/src/views/ApiTester.vue b/admin-system/src/views/ApiTester.vue similarity index 96% rename from admin-system/frontend/src/views/ApiTester.vue rename to admin-system/src/views/ApiTester.vue index dbd8b18..2bb1c61 100644 --- a/admin-system/frontend/src/views/ApiTester.vue +++ b/admin-system/src/views/ApiTester.vue @@ -1,665 +1,665 @@ - - - - - + + + + + diff --git a/admin-system/frontend/src/views/CattleArchives.vue b/admin-system/src/views/CattleArchives.vue similarity index 100% rename from admin-system/frontend/src/views/CattleArchives.vue rename to admin-system/src/views/CattleArchives.vue diff --git a/admin-system/frontend/src/views/CattleBatches.vue b/admin-system/src/views/CattleBatches.vue similarity index 100% rename from admin-system/frontend/src/views/CattleBatches.vue rename to admin-system/src/views/CattleBatches.vue diff --git a/admin-system/frontend/src/views/CattleExitRecords.vue b/admin-system/src/views/CattleExitRecords.vue similarity index 100% rename from admin-system/frontend/src/views/CattleExitRecords.vue rename to admin-system/src/views/CattleExitRecords.vue diff --git a/admin-system/frontend/src/views/CattlePens.vue b/admin-system/src/views/CattlePens.vue similarity index 100% rename from admin-system/frontend/src/views/CattlePens.vue rename to admin-system/src/views/CattlePens.vue diff --git a/admin-system/frontend/src/views/CattleTransferRecords.vue b/admin-system/src/views/CattleTransferRecords.vue similarity index 100% rename from admin-system/frontend/src/views/CattleTransferRecords.vue rename to admin-system/src/views/CattleTransferRecords.vue diff --git a/admin-system/frontend/src/views/Dashboard.vue b/admin-system/src/views/Dashboard.vue similarity index 100% rename from admin-system/frontend/src/views/Dashboard.vue rename to admin-system/src/views/Dashboard.vue diff --git a/admin-system/frontend/src/views/Devices.vue b/admin-system/src/views/Devices.vue similarity index 100% rename from admin-system/frontend/src/views/Devices.vue rename to admin-system/src/views/Devices.vue diff --git a/admin-system/frontend/src/views/ElectronicFence.vue b/admin-system/src/views/ElectronicFence.vue similarity index 100% rename from admin-system/frontend/src/views/ElectronicFence.vue rename to admin-system/src/views/ElectronicFence.vue diff --git a/admin-system/frontend/src/views/FarmInfoManagement.vue b/admin-system/src/views/FarmInfoManagement.vue similarity index 100% rename from admin-system/frontend/src/views/FarmInfoManagement.vue rename to admin-system/src/views/FarmInfoManagement.vue diff --git a/admin-system/frontend/src/views/Farms.vue b/admin-system/src/views/Farms.vue similarity index 100% rename from admin-system/frontend/src/views/Farms.vue rename to admin-system/src/views/Farms.vue diff --git a/admin-system/frontend/src/views/FormLogManagement.vue b/admin-system/src/views/FormLogManagement.vue similarity index 96% rename from admin-system/frontend/src/views/FormLogManagement.vue rename to admin-system/src/views/FormLogManagement.vue index 649b545..14ef773 100644 --- a/admin-system/frontend/src/views/FormLogManagement.vue +++ b/admin-system/src/views/FormLogManagement.vue @@ -1,599 +1,599 @@ - - - - - + + + + + diff --git a/admin-system/frontend/src/views/Home.vue b/admin-system/src/views/Home.vue similarity index 100% rename from admin-system/frontend/src/views/Home.vue rename to admin-system/src/views/Home.vue diff --git a/admin-system/frontend/src/views/Login.vue b/admin-system/src/views/Login.vue similarity index 100% rename from admin-system/frontend/src/views/Login.vue rename to admin-system/src/views/Login.vue diff --git a/admin-system/frontend/src/views/MapView.vue b/admin-system/src/views/MapView.vue similarity index 100% rename from admin-system/frontend/src/views/MapView.vue rename to admin-system/src/views/MapView.vue diff --git a/admin-system/frontend/src/views/MapZoomDemo.vue b/admin-system/src/views/MapZoomDemo.vue similarity index 100% rename from admin-system/frontend/src/views/MapZoomDemo.vue rename to admin-system/src/views/MapZoomDemo.vue diff --git a/admin-system/frontend/src/views/Monitor.vue b/admin-system/src/views/Monitor.vue similarity index 100% rename from admin-system/frontend/src/views/Monitor.vue rename to admin-system/src/views/Monitor.vue diff --git a/admin-system/frontend/src/views/NotFound.vue b/admin-system/src/views/NotFound.vue similarity index 100% rename from admin-system/frontend/src/views/NotFound.vue rename to admin-system/src/views/NotFound.vue diff --git a/admin-system/frontend/src/views/OperationLogs.vue b/admin-system/src/views/OperationLogs.vue similarity index 95% rename from admin-system/frontend/src/views/OperationLogs.vue rename to admin-system/src/views/OperationLogs.vue index 5704088..424ae4b 100644 --- a/admin-system/frontend/src/views/OperationLogs.vue +++ b/admin-system/src/views/OperationLogs.vue @@ -1,516 +1,516 @@ - - - - - + + + + + diff --git a/admin-system/frontend/src/views/Orders.vue b/admin-system/src/views/Orders.vue similarity index 100% rename from admin-system/frontend/src/views/Orders.vue rename to admin-system/src/views/Orders.vue diff --git a/admin-system/frontend/src/views/PenManagement.vue b/admin-system/src/views/PenManagement.vue similarity index 100% rename from admin-system/frontend/src/views/PenManagement.vue rename to admin-system/src/views/PenManagement.vue diff --git a/admin-system/frontend/src/views/Products.vue b/admin-system/src/views/Products.vue similarity index 100% rename from admin-system/frontend/src/views/Products.vue rename to admin-system/src/views/Products.vue diff --git a/admin-system/frontend/src/views/Reports.vue b/admin-system/src/views/Reports.vue similarity index 96% rename from admin-system/frontend/src/views/Reports.vue rename to admin-system/src/views/Reports.vue index f2521ea..9748c61 100644 --- a/admin-system/frontend/src/views/Reports.vue +++ b/admin-system/src/views/Reports.vue @@ -1,485 +1,485 @@ - - - - - + + + + + diff --git a/admin-system/frontend/src/views/RolePermissions.vue b/admin-system/src/views/RolePermissions.vue similarity index 96% rename from admin-system/frontend/src/views/RolePermissions.vue rename to admin-system/src/views/RolePermissions.vue index d70ce8f..25fc053 100644 --- a/admin-system/frontend/src/views/RolePermissions.vue +++ b/admin-system/src/views/RolePermissions.vue @@ -1,660 +1,660 @@ - - - - - - + + + + + + diff --git a/admin-system/frontend/src/views/SearchMonitor.vue b/admin-system/src/views/SearchMonitor.vue similarity index 95% rename from admin-system/frontend/src/views/SearchMonitor.vue rename to admin-system/src/views/SearchMonitor.vue index 997f7a4..0698ec5 100644 --- a/admin-system/frontend/src/views/SearchMonitor.vue +++ b/admin-system/src/views/SearchMonitor.vue @@ -1,358 +1,358 @@ - - - - - + + + + + diff --git a/admin-system/frontend/src/views/SmartAnklet.vue b/admin-system/src/views/SmartAnklet.vue similarity index 96% rename from admin-system/frontend/src/views/SmartAnklet.vue rename to admin-system/src/views/SmartAnklet.vue index 8893431..683d4ac 100644 --- a/admin-system/frontend/src/views/SmartAnklet.vue +++ b/admin-system/src/views/SmartAnklet.vue @@ -1,509 +1,509 @@ - - - - - + + + + + diff --git a/admin-system/frontend/src/views/SmartCollar.vue b/admin-system/src/views/SmartCollar.vue similarity index 96% rename from admin-system/frontend/src/views/SmartCollar.vue rename to admin-system/src/views/SmartCollar.vue index aa596e6..a861c45 100644 --- a/admin-system/frontend/src/views/SmartCollar.vue +++ b/admin-system/src/views/SmartCollar.vue @@ -1,1776 +1,1776 @@ - - - - - + + + + + diff --git a/admin-system/frontend/src/views/SmartCollarAlert.vue b/admin-system/src/views/SmartCollarAlert.vue similarity index 100% rename from admin-system/frontend/src/views/SmartCollarAlert.vue rename to admin-system/src/views/SmartCollarAlert.vue diff --git a/admin-system/frontend/src/views/SmartEartag.vue b/admin-system/src/views/SmartEartag.vue similarity index 96% rename from admin-system/frontend/src/views/SmartEartag.vue rename to admin-system/src/views/SmartEartag.vue index 28d30ea..29b495d 100644 --- a/admin-system/frontend/src/views/SmartEartag.vue +++ b/admin-system/src/views/SmartEartag.vue @@ -1,1252 +1,1252 @@ - - - - - \ No newline at end of file diff --git a/admin-system/frontend/src/views/SmartEartagAlert.vue b/admin-system/src/views/SmartEartagAlert.vue similarity index 96% rename from admin-system/frontend/src/views/SmartEartagAlert.vue rename to admin-system/src/views/SmartEartagAlert.vue index cdc0292..f14011b 100644 --- a/admin-system/frontend/src/views/SmartEartagAlert.vue +++ b/admin-system/src/views/SmartEartagAlert.vue @@ -1,935 +1,935 @@ - - - - - + + + + + diff --git a/admin-system/frontend/src/views/SmartHost.vue b/admin-system/src/views/SmartHost.vue similarity index 96% rename from admin-system/frontend/src/views/SmartHost.vue rename to admin-system/src/views/SmartHost.vue index a4987c5..b5b55b1 100644 --- a/admin-system/frontend/src/views/SmartHost.vue +++ b/admin-system/src/views/SmartHost.vue @@ -1,890 +1,890 @@ - - - - - + + + + + diff --git a/admin-system/frontend/src/views/System.vue b/admin-system/src/views/System.vue similarity index 96% rename from admin-system/frontend/src/views/System.vue rename to admin-system/src/views/System.vue index e4c31ab..04cf5c8 100644 --- a/admin-system/frontend/src/views/System.vue +++ b/admin-system/src/views/System.vue @@ -1,924 +1,924 @@ - - - - - + + + + + diff --git a/admin-system/frontend/src/views/TableStats.vue b/admin-system/src/views/TableStats.vue similarity index 100% rename from admin-system/frontend/src/views/TableStats.vue rename to admin-system/src/views/TableStats.vue diff --git a/admin-system/frontend/src/views/TestAnalytics.vue b/admin-system/src/views/TestAnalytics.vue similarity index 100% rename from admin-system/frontend/src/views/TestAnalytics.vue rename to admin-system/src/views/TestAnalytics.vue diff --git a/admin-system/frontend/src/views/TestImport.vue b/admin-system/src/views/TestImport.vue similarity index 95% rename from admin-system/frontend/src/views/TestImport.vue rename to admin-system/src/views/TestImport.vue index 1050870..c95016b 100644 --- a/admin-system/frontend/src/views/TestImport.vue +++ b/admin-system/src/views/TestImport.vue @@ -1,13 +1,13 @@ - - - + + + diff --git a/admin-system/frontend/src/views/Users.vue b/admin-system/src/views/Users.vue similarity index 100% rename from admin-system/frontend/src/views/Users.vue rename to admin-system/src/views/Users.vue diff --git a/admin-system/frontend/test-devices-frontend.js b/admin-system/test-devices-frontend.js similarity index 100% rename from admin-system/frontend/test-devices-frontend.js rename to admin-system/test-devices-frontend.js diff --git a/admin-system/frontend/test-download.js b/admin-system/test-download.js similarity index 97% rename from admin-system/frontend/test-download.js rename to admin-system/test-download.js index b62695e..9e6e44a 100644 --- a/admin-system/frontend/test-download.js +++ b/admin-system/test-download.js @@ -1,43 +1,43 @@ -// 测试下载模板功能 -async function testDownloadTemplate() { - try { - console.log('开始测试下载模板功能...'); - - // 模拟API调用 - const response = await fetch('http://localhost:5350/api/iot-cattle/public/import/template'); - - console.log('API响应状态:', response.status); - console.log('Content-Type:', response.headers.get('content-type')); - console.log('Content-Disposition:', response.headers.get('content-disposition')); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - // 获取blob - const blob = await response.blob(); - console.log('Blob类型:', blob.type); - console.log('Blob大小:', blob.size, 'bytes'); - - // 创建下载链接 - const url = window.URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = url; - link.download = '牛只档案导入模板.xlsx'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - window.URL.revokeObjectURL(url); - - console.log('✅ 下载成功!'); - - } catch (error) { - console.error('❌ 下载失败:', error); - } -} - -// 在浏览器控制台中运行 -if (typeof window !== 'undefined') { - window.testDownloadTemplate = testDownloadTemplate; - console.log('测试函数已加载,请在控制台运行: testDownloadTemplate()'); -} +// 测试下载模板功能 +async function testDownloadTemplate() { + try { + console.log('开始测试下载模板功能...'); + + // 模拟API调用 + const response = await fetch('http://localhost:5350/api/iot-cattle/public/import/template'); + + console.log('API响应状态:', response.status); + console.log('Content-Type:', response.headers.get('content-type')); + console.log('Content-Disposition:', response.headers.get('content-disposition')); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + // 获取blob + const blob = await response.blob(); + console.log('Blob类型:', blob.type); + console.log('Blob大小:', blob.size, 'bytes'); + + // 创建下载链接 + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = '牛只档案导入模板.xlsx'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + window.URL.revokeObjectURL(url); + + console.log('✅ 下载成功!'); + + } catch (error) { + console.error('❌ 下载失败:', error); + } +} + +// 在浏览器控制台中运行 +if (typeof window !== 'undefined') { + window.testDownloadTemplate = testDownloadTemplate; + console.log('测试函数已加载,请在控制台运行: testDownloadTemplate()'); +} diff --git a/admin-system/frontend/test-users-frontend.js b/admin-system/test-users-frontend.js similarity index 100% rename from admin-system/frontend/test-users-frontend.js rename to admin-system/test-users-frontend.js diff --git a/admin-system/frontend/vite.config.js b/admin-system/vite.config.js similarity index 100% rename from admin-system/frontend/vite.config.js rename to admin-system/vite.config.js diff --git a/backend/package-lock.json b/backend/package-lock.json index 93c05ae..918aa12 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -26,7 +26,6 @@ "mysql2": "^3.6.5", "node-cron": "^3.0.3", "nodemailer": "^6.9.8", - "puppeteer": "^21.6.1", "redis": "^4.6.12", "sequelize": "^6.35.2", "sharp": "^0.33.2", @@ -97,6 +96,7 @@ "version": "7.27.1", "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", @@ -310,6 +310,7 @@ "version": "7.27.1", "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1855,50 +1856,6 @@ "node": ">=14" } }, - "node_modules/@puppeteer/browsers": { - "version": "1.9.1", - "resolved": "https://registry.npmmirror.com/@puppeteer/browsers/-/browsers-1.9.1.tgz", - "integrity": "sha512-PuvK6xZzGhKPvlx3fpfdM2kYY3P/hB1URtK8wA7XUJ6prn6pp22zvJHu48th0SGcHL9SutbPHrFuQgfXTFobWA==", - "license": "Apache-2.0", - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.1", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.2" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=16.3.0" - } - }, - "node_modules/@puppeteer/browsers/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@puppeteer/browsers/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "license": "MIT" - }, "node_modules/@redis/bloom": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/@redis/bloom/-/bloom-1.2.0.tgz", @@ -2004,12 +1961,6 @@ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "license": "MIT" }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmmirror.com/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "license": "MIT" - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2180,16 +2131,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -2241,15 +2182,6 @@ "node": ">=0.8" } }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", @@ -2300,6 +2232,7 @@ "version": "5.0.1", "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2309,6 +2242,7 @@ "version": "4.3.0", "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -2324,6 +2258,7 @@ "version": "2.0.1", "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -2336,6 +2271,7 @@ "version": "1.1.4", "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/anymatch": { @@ -2572,18 +2508,6 @@ "dev": true, "license": "MIT" }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmmirror.com/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmmirror.com/async/-/async-3.2.6.tgz", @@ -2785,26 +2709,6 @@ "license": "Apache-2.0", "optional": true }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/base64id": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/base64id/-/base64id-2.0.0.tgz", @@ -2814,15 +2718,6 @@ "node": "^4.5.0 || >= 5.9" } }, - "node_modules/basic-ftp": { - "version": "5.0.5", - "resolved": "https://registry.npmmirror.com/basic-ftp/-/basic-ftp-5.0.5.tgz", - "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/bcryptjs": { "version": "2.4.3", "resolved": "https://registry.npmmirror.com/bcryptjs/-/bcryptjs-2.4.3.tgz", @@ -2928,30 +2823,6 @@ "node-int64": "^0.4.0" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -3072,6 +2943,7 @@ "version": "3.1.0", "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3195,19 +3067,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/chromium-bidi": { - "version": "0.5.8", - "resolved": "https://registry.npmmirror.com/chromium-bidi/-/chromium-bidi-0.5.8.tgz", - "integrity": "sha512-blqh+1cEQbHBKmok3rVJkBlBxt9beKBgOsxbFgs7UJcoVbbeZ+K7+6liAsjgpc8l1Xd55cQUy14fXZdGSb4zIw==", - "license": "Apache-2.0", - "dependencies": { - "mitt": "3.0.1", - "urlpattern-polyfill": "10.0.0" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-3.9.0.tgz", @@ -3235,6 +3094,7 @@ "version": "8.0.1", "resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -3524,32 +3384,6 @@ "node": ">= 0.10" } }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz", @@ -3597,15 +3431,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "license": "MIT", - "dependencies": { - "node-fetch": "^2.6.12" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3621,15 +3446,6 @@ "node": ">= 8" } }, - "node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -3760,20 +3576,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "license": "MIT", - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3827,12 +3629,6 @@ "node": ">=8" } }, - "node_modules/devtools-protocol": { - "version": "0.0.1232444", - "resolved": "https://registry.npmmirror.com/devtools-protocol/-/devtools-protocol-0.0.1232444.tgz", - "integrity": "sha512-pM27vqEfxSxRkTMnF+XCmxSEb6duO5R+t8A9DEEJgy4Wz2RVanje2mmj99B6A3zv2r/qGfYlOvYznUhuokizmg==", - "license": "BSD-3-Clause" - }, "node_modules/dezalgo": { "version": "1.0.4", "resolved": "https://registry.npmmirror.com/dezalgo/-/dezalgo-1.0.4.tgz", @@ -3954,6 +3750,7 @@ "version": "8.0.0", "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/enabled": { @@ -3970,15 +3767,6 @@ "node": ">= 0.8" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/engine.io": { "version": "6.6.4", "resolved": "https://registry.npmmirror.com/engine.io/-/engine.io-6.6.4.tgz", @@ -4061,19 +3849,11 @@ } } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -4083,6 +3863,7 @@ "version": "0.2.1", "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, "license": "MIT" }, "node_modules/es-abstract": { @@ -4231,6 +4012,7 @@ "version": "3.2.0", "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4254,27 +4036,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, "node_modules/eslint": { "version": "8.57.1", "resolved": "https://registry.npmmirror.com/eslint/-/eslint-8.57.1.tgz", @@ -4753,6 +4514,7 @@ "version": "4.0.1", "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", @@ -4792,6 +4554,7 @@ "version": "5.3.0", "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -4958,49 +4721,6 @@ "node": ">= 0.10" } }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extract-zip/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/extract-zip/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5055,15 +4775,6 @@ "bser": "2.1.1" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmmirror.com/fecha/-/fecha-4.2.3.tgz", @@ -5416,6 +5127,7 @@ "version": "2.0.5", "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -5466,21 +5178,6 @@ "node": ">= 0.4" } }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-symbol-description": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz", @@ -5513,43 +5210,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmmirror.com/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", - "license": "MIT", - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/get-uri/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/get-uri/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -5758,78 +5418,6 @@ "node": ">= 0.8" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz", @@ -5851,26 +5439,6 @@ "node": ">=0.10.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", @@ -5891,6 +5459,7 @@ "version": "3.3.1", "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -5971,15 +5540,6 @@ "node": ">= 0.4" } }, - "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmmirror.com/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -6187,6 +5747,7 @@ "version": "3.0.0", "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7219,6 +6780,7 @@ "version": "4.0.0", "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -7256,6 +6818,7 @@ "version": "2.3.1", "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { @@ -7430,6 +6993,7 @@ "version": "1.2.4", "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, "license": "MIT" }, "node_modules/locate-path": { @@ -7718,12 +7282,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "license": "MIT" - }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", @@ -7736,12 +7294,6 @@ "mkdirp": "bin/cmd.js" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmmirror.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -7841,15 +7393,6 @@ "node": ">= 0.6" } }, - "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/node-cron": { "version": "3.0.3", "resolved": "https://registry.npmmirror.com/node-cron/-/node-cron-3.0.3.tgz", @@ -7862,26 +7405,6 @@ "node": ">=6.0.0" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz", @@ -8217,61 +7740,6 @@ "node": ">=6" } }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "license": "MIT", - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/pac-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmmirror.com/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "license": "MIT", - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -8283,6 +7751,7 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -8295,6 +7764,7 @@ "version": "5.2.0", "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", @@ -8381,12 +7851,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "license": "MIT" - }, "node_modules/pg-connection-string": { "version": "2.9.1", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", @@ -8543,15 +8007,6 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmmirror.com/prompts/-/prompts-2.4.2.tgz", @@ -8578,48 +8033,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-agent": { - "version": "6.3.1", - "resolved": "https://registry.npmmirror.com/proxy-agent/-/proxy-agent-6.3.1.tgz", - "integrity": "sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.2", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.1", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.2" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -8632,16 +8045,6 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", @@ -8652,65 +8055,6 @@ "node": ">=6" } }, - "node_modules/puppeteer": { - "version": "21.11.0", - "resolved": "https://registry.npmmirror.com/puppeteer/-/puppeteer-21.11.0.tgz", - "integrity": "sha512-9jTHuYe22TD3sNxy0nEIzC7ZrlRnDgeX3xPkbS7PnbdwYjl2o/z/YuCrRBwezdKpbTDTJ4VqIggzNyeRcKq3cg==", - "deprecated": "< 24.9.0 is no longer supported", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "1.9.1", - "cosmiconfig": "9.0.0", - "puppeteer-core": "21.11.0" - }, - "bin": { - "puppeteer": "lib/esm/puppeteer/node/cli.js" - }, - "engines": { - "node": ">=16.13.2" - } - }, - "node_modules/puppeteer-core": { - "version": "21.11.0", - "resolved": "https://registry.npmmirror.com/puppeteer-core/-/puppeteer-core-21.11.0.tgz", - "integrity": "sha512-ArbnyA3U5SGHokEvkfWjW+O8hOxV1RSJxOgriX/3A4xZRqixt9ZFHD0yPgZQF05Qj0oAqi8H/7stDorjoHY90Q==", - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "1.9.1", - "chromium-bidi": "0.5.8", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1232444", - "ws": "8.16.0" - }, - "engines": { - "node": ">=16.13.2" - } - }, - "node_modules/puppeteer-core/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/puppeteer-core/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "license": "MIT" - }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmmirror.com/pure-rand/-/pure-rand-6.1.0.tgz", @@ -8926,6 +8270,7 @@ "version": "2.1.1", "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8979,6 +8324,7 @@ "version": "4.0.0", "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -9626,16 +8972,6 @@ "node": ">=8" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, "node_modules/socket.io": { "version": "4.8.1", "resolved": "https://registry.npmmirror.com/socket.io/-/socket.io-4.8.1.tgz", @@ -9767,62 +9103,11 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmmirror.com/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "license": "MIT", - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmmirror.com/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/socks-proxy-agent/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socks-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -9968,6 +9253,7 @@ "version": "4.2.3", "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -10057,6 +9343,7 @@ "version": "6.0.1", "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -10265,17 +9552,6 @@ "express": ">=4.0.0 || >=5.0.0-beta" } }, - "node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmmirror.com/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "license": "MIT", - "dependencies": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, "node_modules/tar-stream": { "version": "3.1.7", "resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-3.1.7.tgz", @@ -10324,12 +9600,6 @@ "dev": true, "license": "MIT" }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmmirror.com/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "license": "MIT" - }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmmirror.com/tmpl/-/tmpl-1.0.5.tgz", @@ -10371,12 +9641,6 @@ "nodetouch": "bin/nodetouch.js" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmmirror.com/triple-beam/-/triple-beam-1.4.1.tgz", @@ -10426,7 +9690,8 @@ "version": "2.8.1", "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "license": "0BSD", + "optional": true }, "node_modules/type-check": { "version": "0.4.0", @@ -10579,16 +9844,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/unbzip2-stream": { - "version": "1.4.3", - "resolved": "https://registry.npmmirror.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", - "license": "MIT", - "dependencies": { - "buffer": "^5.2.1", - "through": "^2.3.8" - } - }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -10649,12 +9904,6 @@ "punycode": "^2.1.0" } }, - "node_modules/urlpattern-polyfill": { - "version": "10.0.0", - "resolved": "https://registry.npmmirror.com/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", - "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", - "license": "MIT" - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -10718,22 +9967,6 @@ "makeerror": "1.0.12" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", @@ -10922,6 +10155,7 @@ "version": "7.0.0", "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -10973,27 +10207,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmmirror.com/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/xlsx": { "version": "0.18.5", "resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz", @@ -11028,6 +10241,7 @@ "version": "5.0.8", "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -11051,6 +10265,7 @@ "version": "17.7.2", "resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -11069,21 +10284,12 @@ "version": "21.1.1", "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" } }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmmirror.com/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/backend/package.json b/backend/package.json index 709d98f..9ef96b2 100644 --- a/backend/package.json +++ b/backend/package.json @@ -56,7 +56,6 @@ "mysql2": "^3.6.5", "node-cron": "^3.0.3", "nodemailer": "^6.9.8", - "puppeteer": "^21.6.1", "redis": "^4.6.12", "sequelize": "^6.35.2", "sharp": "^0.33.2",