From e9691882f0b7db3ec7e5a1d3f5eb602f6269d14b Mon Sep 17 00:00:00 2001 From: "mula.liu" Date: Thu, 13 Nov 2025 18:11:08 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E5=AE=9A=E4=B9=89=E4=BA=86=E5=87=A0?= =?UTF-8?q?=E4=B8=AA=E6=96=B0=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DEPLOYMENT.md | 23 + QUICKSTART.md | 155 +++++ README.md | 26 +- VOLUME_MOUNT_SOLUTION.md | 178 ++++++ docker-compose.yml | 1 + docs/DOCKER_DOCS_SETUP.md | 370 +++++++++++ docs/components/ChartPanel.md | 593 ++++++++++++++++++ docs/components/DetailDrawer.md | 86 ++- docs/components/ExtendInfoPanel.md | 499 +++++++++++++++ docs/components/InfoPanel.md | 47 ++ docs/components/ListTable.md | 18 + docs/components/PageTitleBar.md | 78 ++- docs/components/README.md | 306 +++++++-- docs/components/SplitLayout.md | 447 +++++++++++++ docs/components/StatCard.md | 298 +++++++++ docs/layouts/content-area-layout.md | 347 ++++++++++ docs/{pages => layouts}/main-layout.md | 0 package-lock.json | 26 + package.json | 2 + scripts/clean.sh | 26 + src/App.jsx | 4 +- src/components/ChartPanel/ChartPanel.css | 17 + src/components/ChartPanel/ChartPanel.jsx | 202 ++++++ src/components/DetailDrawer/DetailDrawer.css | 11 +- .../ExtendInfoPanel/ExtendInfoPanel.css | 105 ++++ .../ExtendInfoPanel/ExtendInfoPanel.jsx | 68 ++ src/components/InfoPanel/InfoPanel.css | 20 +- .../ListActionBar/ListActionBar.css | 3 +- src/components/ListTable/ListTable.css | 39 +- src/components/ListTable/ListTable.jsx | 3 +- src/components/MainLayout/AppHeader.jsx | 2 +- src/components/MainLayout/AppSider.jsx | 2 +- src/components/MainLayout/MainLayout.css | 2 +- src/components/PageTitleBar/PageTitleBar.css | 22 + src/components/PageTitleBar/PageTitleBar.jsx | 2 +- .../SideInfoPanel/SideInfoPanel.css | 88 +++ .../SideInfoPanel/SideInfoPanel.jsx | 58 ++ src/components/SplitLayout/SplitLayout.css | 72 +++ src/components/SplitLayout/SplitLayout.jsx | 69 ++ src/components/StatCard/StatCard.css | 108 ++++ src/components/StatCard/StatCard.jsx | 78 +++ src/{constants => data}/docsMenuData.json | 45 +- src/{constants => data}/headerMenuData.json | 0 src/data/imageData.json | 48 ++ src/{constants => data}/menuData.json | 2 +- src/data/vmImageData.json | 24 + src/pages/DocsPage.jsx | 78 +-- src/pages/HostListPage.css | 31 +- src/pages/HostListPage.jsx | 306 ++++----- src/pages/UserListPage.css | 31 +- src/pages/UserListPage.jsx | 284 +++++---- src/pages/VirtualMachineImagePage.css | 33 + src/pages/VirtualMachineImagePage.jsx | 319 ++++++++++ src/styles/globals.css | 4 +- yarn.lock | 20 + 55 files changed, 5226 insertions(+), 500 deletions(-) create mode 100644 QUICKSTART.md create mode 100644 VOLUME_MOUNT_SOLUTION.md create mode 100644 docs/DOCKER_DOCS_SETUP.md create mode 100644 docs/components/ChartPanel.md create mode 100644 docs/components/ExtendInfoPanel.md create mode 100644 docs/components/SplitLayout.md create mode 100644 docs/components/StatCard.md create mode 100644 docs/layouts/content-area-layout.md rename docs/{pages => layouts}/main-layout.md (100%) create mode 100755 scripts/clean.sh create mode 100644 src/components/ChartPanel/ChartPanel.css create mode 100644 src/components/ChartPanel/ChartPanel.jsx create mode 100644 src/components/ExtendInfoPanel/ExtendInfoPanel.css create mode 100644 src/components/ExtendInfoPanel/ExtendInfoPanel.jsx create mode 100644 src/components/SideInfoPanel/SideInfoPanel.css create mode 100644 src/components/SideInfoPanel/SideInfoPanel.jsx create mode 100644 src/components/SplitLayout/SplitLayout.css create mode 100644 src/components/SplitLayout/SplitLayout.jsx create mode 100644 src/components/StatCard/StatCard.css create mode 100644 src/components/StatCard/StatCard.jsx rename src/{constants => data}/docsMenuData.json (67%) rename src/{constants => data}/headerMenuData.json (100%) rename src/{constants => data}/menuData.json (98%) create mode 100644 src/data/vmImageData.json create mode 100644 src/pages/VirtualMachineImagePage.css create mode 100644 src/pages/VirtualMachineImagePage.jsx diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 42c42d2..6d7de70 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -11,6 +11,29 @@ - Nginx serve: 静态文件服务器 - Node.js 18: 运行环境 +## 项目构建说明 + +### 文档目录处理 + +本项目的 `/design` 路由会加载项目根目录下的 `docs/` 文件夹中的 Markdown 文档。通过 Docker 卷挂载方式,将宿主机的 `docs/` 目录映射到容器内的 `/app/dist/docs/`。 + +**优势:** +- ✅ 实时更新:修改 MD 文件后无需重新构建镜像 +- ✅ 方便维护:在宿主机直接编辑文档 +- ✅ 轻量镜像:Docker 镜像不包含文档,体积更小 +- ✅ 灵活部署:可以独立管理文档版本 + +**配置方式:** +```yaml +# docker-compose.yml +volumes: + - ./docs:/app/dist/docs:ro # :ro 表示只读挂载,提高安全性 +``` + +**注意事项:** +- 部署时需要确保 `docs/` 目录存在于项目根目录 +- 如需制作完全自包含的镜像,可以在 Dockerfile 中 COPY docs 目录 + ## 文件说明 ### 1. ecosystem.config.js diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..bc2a6f6 --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,155 @@ +# Docker + PM2 部署快速参考 + +## 🚀 快速开始 + +```bash +# 本地构建测试 +yarn build +yarn preview + +# Docker 部署 +docker-compose up -d --build + +# 查看日志 +docker-compose logs -f +``` + +## 📝 常用命令 + +### 本地开发 +```bash +yarn dev # 启动开发服务器 +yarn build # 构建生产版本 +yarn preview # 预览构建结果 +yarn clean # 清理构建产物 +``` + +### Docker 操作 +```bash +# 基础操作 +docker-compose up -d # 启动服务 +docker-compose down # 停止并删除容器 +docker-compose restart # 重启服务 +docker-compose logs -f nex-design # 查看日志 + +# 重新构建 +docker-compose up -d --build # 重新构建并启动 +docker-compose build --no-cache # 不使用缓存重新构建 + +# 容器管理 +docker exec -it nex-design-app sh # 进入容器 +docker ps # 查看运行中的容器 +docker stats nex-design-app # 查看资源使用 +``` + +### PM2 管理(容器内) +```bash +pm2 list # 查看进程列表 +pm2 logs # 查看日志 +pm2 restart nex-design # 重启应用 +pm2 monit # 实时监控 +``` + +## 🔧 故障排查 + +### 文档无法加载 +```bash +# 检查 docs 目录是否正确挂载 +docker exec nex-design-app ls -la /app/dist/docs/ + +# 检查挂载配置 +docker inspect nex-design-app | grep -A 10 Mounts + +# 如果挂载失败,重启容器 +docker-compose down +docker-compose up -d +``` + +### 文档修改后未生效 +```bash +# 卷挂载方案下,修改应立即生效 +# 如果未生效,检查浏览器缓存 +# 使用 Ctrl+Shift+R 强制刷新 + +# 验证容器内文件已更新 +docker exec nex-design-app cat /app/dist/docs/DESIGN_COOKBOOK.md +``` + +### 容器启动失败 +```bash +# 查看详细日志 +docker-compose logs nex-design + +# 检查端口占用 +lsof -i :3000 +``` + +### 内存不足 +```bash +# 查看资源使用 +docker stats nex-design-app + +# 调整 ecosystem.config.js 中的 max_memory_restart +``` + +## 📁 重要文件 + +| 文件 | 说明 | +|------|------| +| `ecosystem.config.js` | PM2 配置 | +| `Dockerfile` | Docker 镜像配置 | +| `docker-compose.yml` | Docker Compose 编排 | +| `.dockerignore` | Docker 构建忽略 | +| `DEPLOYMENT.md` | 完整部署文档 | +| `docs/DOCKER_DOCS_SETUP.md` | 文档目录问题说明 | + +## 🌐 访问地址 + +- **本地开发**: http://localhost:5173 +- **本地预览**: http://localhost:4173 +- **Docker 容器**: http://localhost:3000 + +## ⚙️ 环境要求 + +- Node.js >= 16.0.0 +- Yarn >= 1.22.0 +- Docker >= 20.10 +- Docker Compose >= 2.0 + +## 📦 构建流程 + +``` +源代码 + ↓ +vite build → dist/* + ↓ +Docker 镜像: dist → /app/dist + ↓ +卷挂载: ./docs → /app/dist/docs (实时同步) + ↓ +PM2 serve: /app/dist @ :3000 +``` + +**文档更新流程:** +``` +编辑 docs/*.md → 立即生效(无需重启) +``` + +## 🔐 生产环境 + +建议配置 Nginx 反向代理和 SSL: + +```nginx +server { + listen 80; + server_name your-domain.com; + + location / { + proxy_pass http://localhost:3000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } +} +``` + +详见 `DEPLOYMENT.md` 完整配置。 diff --git a/README.md b/README.md index 686f158..845633b 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,28 @@ yarn build yarn preview ``` +## 生产部署 + +### 使用 Docker + PM2 部署 + +```bash +# 构建并启动容器 +docker-compose up -d --build + +# 查看运行状态 +docker-compose ps + +# 查看日志 +docker-compose logs -f +``` + +访问 http://localhost:3000 + +**部署文档:** +- [快速开始](./QUICKSTART.md) - 快速参考和常用命令 +- [完整部署文档](./DEPLOYMENT.md) - 详细的部署配置和优化 +- [文档目录配置](./docs/DOCKER_DOCS_SETUP.md) - docs 目录问题说明 + ## 项目结构 ``` @@ -127,10 +149,12 @@ Nex Design/ ## 版本历史 -- **v1.0.0** (2024-11-04) +- **v1.0.0** (2025-11-05) - 初始化项目 - 创建基础设计规范文档 - 建立项目结构 + - 添加 Docker + PM2 部署支持 + - 配置完整的部署文档 ## 许可证 diff --git a/VOLUME_MOUNT_SOLUTION.md b/VOLUME_MOUNT_SOLUTION.md new file mode 100644 index 0000000..24dc87c --- /dev/null +++ b/VOLUME_MOUNT_SOLUTION.md @@ -0,0 +1,178 @@ +# Docker 卷挂载方案 - 实施总结 + +## ✅ 方案优势 + +相比构建时复制方案,卷挂载方案具有以下优势: + +### 🚀 实时更新 +- 修改 MD 文档后**立即生效** +- **无需**重新构建 Docker 镜像 +- **无需**重启容器 +- 开发体验极佳 + +### 💡 简化流程 +- 构建流程更简单,无需 prebuild 脚本 +- 镜像更轻量,不包含文档内容 +- 文档和代码分离,职责清晰 + +### 🔄 灵活部署 +- 可以独立管理文档版本 +- 支持多环境使用不同文档 +- 易于回滚和更新 + +## 📝 实施内容 + +### 1. 核心配置(docker-compose.yml:16) + +```yaml +volumes: + - ./docs:/app/dist/docs:ro +``` + +**说明:** +- 将宿主机 `./docs` 挂载到容器 `/app/dist/docs` +- `:ro` 只读挂载,提高安全性 + +### 2. 清理之前的方案 + +**移除文件/配置:** +- ❌ `package.json` 中的 `prebuild` 脚本 +- ❌ `Dockerfile` 中的 `cp -r docs public/` 命令 +- ❌ `.gitignore` 中的 `public/docs/` 规则 + +**保留文件:** +- ✅ `ecosystem.config.js` - PM2 配置 +- ✅ `Dockerfile` - Docker 镜像配置(已简化) +- ✅ `docker-compose.yml` - 添加了卷挂载 +- ✅ `.dockerignore` - 优化构建 +- ✅ `scripts/clean.sh` - 清理脚本(已更新) + +### 3. 文档更新 + +- ✅ `DEPLOYMENT.md` - 更新为卷挂载方案说明 +- ✅ `docs/DOCKER_DOCS_SETUP.md` - 详细的卷挂载方案文档 +- ✅ `QUICKSTART.md` - 更新快速参考 +- ✅ `README.md` - 保持部署文档链接 + +## 🎯 使用方法 + +### 本地开发 +```bash +yarn dev # 开发服务器 +``` + +### 构建和预览 +```bash +yarn build # 构建(不包含 docs) +yarn preview # 预览 +``` + +### Docker 部署 +```bash +# 首次部署 +docker-compose up -d --build + +# 后续启动 +docker-compose up -d + +# 查看日志 +docker-compose logs -f +``` + +### 更新文档 +```bash +# 直接编辑即可,立即生效 +vim docs/DESIGN_COOKBOOK.md + +# 或使用编辑器 +code docs/components/PageTitleBar.md + +# 浏览器刷新即可看到更新 +``` + +## 🔍 验证方法 + +### 1. 检查挂载 +```bash +# 查看容器挂载情况 +docker inspect nex-design-app | grep -A 10 Mounts + +# 检查容器内文件 +docker exec nex-design-app ls -la /app/dist/docs/ +``` + +### 2. 验证实时同步 +```bash +# 在宿主机添加测试文件 +echo "# Test" > docs/test.md + +# 立即在容器内查看 +docker exec nex-design-app cat /app/dist/docs/test.md + +# 清理测试文件 +rm docs/test.md +``` + +### 3. 浏览器访问 +```bash +# 启动服务 +docker-compose up -d + +# 访问文档 +curl http://localhost:3000/docs/DESIGN_COOKBOOK.md + +# 或在浏览器打开 +open http://localhost:3000 +``` + +## 📊 方案对比 + +| 特性 | 卷挂载方案 ✅ | 构建时复制方案 | +|------|--------------|----------------| +| 文档实时更新 | ✅ 立即生效 | ❌ 需要重新构建 | +| 镜像体积 | ✅ 更小 | ❌ 更大 | +| 构建速度 | ✅ 更快 | ❌ 更慢 | +| 维护便利性 | ✅ 直接编辑 | ❌ 需要构建 | +| 部署灵活性 | ✅ 高 | ⚠️ 中 | +| 镜像自包含 | ⚠️ 需要 docs 目录 | ✅ 完全自包含 | + +## 🚨 注意事项 + +### 1. 部署要求 +- 部署时需要确保 `docs/` 目录存在 +- 使用 `git clone` 或 `scp -r` 时包含 docs 目录 + +### 2. 权限管理 +```bash +# 确保 docs 目录权限正确 +chmod -R 755 docs/ +``` + +### 3. 生产环境 +可以将 docs 部署到专门的目录: +```yaml +# docker-compose.prod.yml +volumes: + - /var/www/nex-design-docs:/app/dist/docs:ro +``` + +## 📚 相关文档 + +- **QUICKSTART.md** - 快速参考和常用命令 +- **DEPLOYMENT.md** - 完整部署文档 +- **docs/DOCKER_DOCS_SETUP.md** - 卷挂载方案详细说明 + +## 🎉 总结 + +**核心优势:** +- 修改文档 → 立即生效 +- 简化构建流程 +- 提升开发体验 + +**一行配置搞定:** +```yaml +volumes: + - ./docs:/app/dist/docs:ro +``` + +这就是你想要的方案!🎯 diff --git a/docker-compose.yml b/docker-compose.yml index 303aaee..00d9ca9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,7 @@ services: - NODE_ENV=production volumes: - ./logs:/app/logs + - ./docs:/app/dist/docs networks: - nex-network healthcheck: diff --git a/docs/DOCKER_DOCS_SETUP.md b/docs/DOCKER_DOCS_SETUP.md new file mode 100644 index 0000000..bc6f660 --- /dev/null +++ b/docs/DOCKER_DOCS_SETUP.md @@ -0,0 +1,370 @@ +# 文档目录 Docker 部署方案 + +## 问题描述 + +应用中 `/design` 路由通过 `fetch('/docs/...')` 加载项目根目录下 `docs/` 文件夹中的 Markdown 文档。在 Docker 容器中运行时,需要确保这些文档能够被访问。 + +## 解决方案:Docker 卷挂载 + +采用 Docker 卷挂载方式,将宿主机的 `docs/` 目录直接映射到容器内,实现文档的实时更新和灵活管理。 + +### 配置方式 + +#### docker-compose.yml 配置 + +```yaml +version: '3.8' + +services: + nex-design: + build: + context: . + dockerfile: Dockerfile + volumes: + - ./logs:/app/logs # 日志目录挂载 + - ./docs:/app/dist/docs:ro # 文档目录挂载(只读) +``` + +**说明:** +- `./docs:/app/dist/docs` - 将宿主机当前目录的 `docs` 映射到容器的 `/app/dist/docs` +- `:ro` - 只读挂载,提高安全性,防止容器内进程修改文档 + +### 方案优势 + +✅ **实时更新** +- 修改 MD 文件后立即生效 +- 无需重新构建 Docker 镜像 +- 无需重启容器 + +✅ **方便维护** +- 在宿主机直接编辑文档 +- 使用熟悉的编辑器和工具 +- 支持版本控制 + +✅ **轻量镜像** +- Docker 镜像不包含文档内容 +- 镜像体积更小 +- 构建速度更快 + +✅ **灵活部署** +- 可以独立管理文档版本 +- 支持多环境部署(开发、测试、生产使用不同文档) +- 易于更新和回滚 + +### 目录结构 + +**宿主机:** +``` +nex-design/ +├── docs/ # 文档源文件(Git 管理) +│ ├── DESIGN_COOKBOOK.md +│ ├── components/ +│ │ ├── PageTitleBar.md +│ │ ├── ListTable.md +│ │ └── ... +│ └── pages/ +├── dist/ # 构建产物(容器内) +│ ├── index.html +│ └── assets/ +└── docker-compose.yml +``` + +**容器内:** +``` +/app/ +├── dist/ # 应用构建产物 +│ ├── index.html +│ ├── assets/ +│ └── docs/ # 挂载点 → 宿主机 docs/ +├── logs/ # 日志目录(挂载) +└── ecosystem.config.js # PM2 配置 +``` + +### 使用流程 + +#### 1. 启动服务 + +```bash +# 第一次启动(构建镜像) +docker-compose up -d --build + +# 后续启动(使用已有镜像) +docker-compose up -d +``` + +#### 2. 修改文档 + +```bash +# 在宿主机直接编辑文档 +vim docs/DESIGN_COOKBOOK.md + +# 或使用 VS Code 等编辑器 +code docs/components/PageTitleBar.md +``` + +#### 3. 验证更新 + +```bash +# 文档修改后立即生效,无需任何操作 +# 浏览器刷新即可看到最新内容 + +# 或使用 curl 验证 +curl http://localhost:3000/docs/DESIGN_COOKBOOK.md +``` + +### 验证挂载 + +```bash +# 检查容器内的挂载情况 +docker exec nex-design-app ls -la /app/dist/docs/ + +# 查看某个文档内容 +docker exec nex-design-app cat /app/dist/docs/DESIGN_COOKBOOK.md + +# 验证文件同步 +# 在宿主机修改文件 +echo "# Test" >> docs/test.md + +# 立即在容器内查看 +docker exec nex-design-app cat /app/dist/docs/test.md +``` + +### 部署注意事项 + +#### 1. 生产环境部署 + +**方式一:携带 docs 目录** +```bash +# 使用 git clone 或 scp 上传整个项目 +git clone /path/to/deploy +cd /path/to/deploy +docker-compose up -d +``` + +**方式二:单独管理文档** +```bash +# 文档单独部署在某个目录 +mkdir -p /var/www/nex-design-docs +# 上传文档到该目录 + +# 修改 docker-compose.yml +volumes: + - /var/www/nex-design-docs:/app/dist/docs:ro +``` + +#### 2. 权限管理 + +```bash +# 确保 docs 目录有正确的权限 +chmod -R 755 docs/ + +# 只读挂载可防止容器内修改,但宿主机权限仍需控制 +``` + +#### 3. 多环境配置 + +可以为不同环境创建不同的 docker-compose 文件: + +```bash +# docker-compose.dev.yml - 开发环境 +volumes: + - ./docs:/app/dist/docs:ro + +# docker-compose.prod.yml - 生产环境 +volumes: + - /var/www/docs:/app/dist/docs:ro +``` + +使用时指定配置文件: +```bash +docker-compose -f docker-compose.prod.yml up -d +``` + +### 故障排查 + +#### 问题 1:文档无法加载 + +```bash +# 检查挂载是否成功 +docker inspect nex-design-app | grep -A 10 Mounts + +# 检查容器内文件 +docker exec nex-design-app ls -la /app/dist/docs/ + +# 检查文件权限 +ls -la docs/ +``` + +#### 问题 2:修改后未生效 + +```bash +# 确认使用的是卷挂载而不是 COPY +docker exec nex-design-app cat /app/dist/docs/DESIGN_COOKBOOK.md + +# 检查浏览器缓存 +# 使用 Ctrl+Shift+R 强制刷新 + +# 检查 serve 是否缓存了静态文件 +docker-compose restart +``` + +#### 问题 3:Windows 路径问题 + +Windows 下需要注意路径格式: +```yaml +# 错误 +volumes: + - .\docs:/app/dist/docs:ro + +# 正确 +volumes: + - ./docs:/app/dist/docs:ro +``` + +### 性能考虑 + +#### 1. 卷挂载性能 + +- **Linux/macOS**: 性能很好,几乎无损耗 +- **Windows/macOS + Docker Desktop**: 可能有轻微性能损耗 +- **生产环境**: 使用 Linux 主机,性能最佳 + +#### 2. 优化建议 + +如果文档很多且访问频繁,可考虑: + +1. **使用命名卷**: +```yaml +volumes: + docs-data: + driver: local + driver_opts: + type: none + o: bind + device: /path/to/docs + +services: + nex-design: + volumes: + - docs-data:/app/dist/docs:ro +``` + +2. **缓存策略**: +在 Nginx 反向代理中添加缓存: +```nginx +location /docs/ { + proxy_pass http://nex_design; + proxy_cache_valid 200 10m; + add_header X-Cache-Status $upstream_cache_status; +} +``` + +## 替代方案对比 + +### 方案 A: 构建时复制(未采用) + +```dockerfile +# Dockerfile +COPY docs /app/dist/docs +``` + +❌ 缺点: +- 修改文档需要重新构建镜像 +- 镜像体积更大 +- 更新流程复杂 + +✅ 优点: +- 镜像自包含 +- 适合不常修改的场景 + +### 方案 B: prebuild 脚本(未采用) + +```json +{ + "scripts": { + "prebuild": "cp -r docs public/" + } +} +``` + +❌ 缺点: +- 需要重新构建才能更新 +- 增加构建时间 +- 文档和代码耦合 + +### 方案 C: 卷挂载(✅ 当前采用) + +```yaml +volumes: + - ./docs:/app/dist/docs:ro +``` + +✅ 优点: +- 实时更新 +- 灵活管理 +- 镜像轻量 + +⚠️ 注意: +- 需要保持 docs 目录结构 +- 部署时需要文档文件 + +## 最佳实践 + +### 1. 文档版本管理 + +```bash +# 使用 Git 管理文档版本 +cd docs +git log DESIGN_COOKBOOK.md + +# 回滚到特定版本 +git checkout DESIGN_COOKBOOK.md +``` + +### 2. 文档自动化部署 + +```bash +#!/bin/bash +# scripts/update-docs.sh + +echo "更新文档..." +cd /path/to/nex-design + +# 拉取最新文档 +git pull origin main -- docs/ + +# 无需重启容器,文档即时生效 +echo "文档已更新!" +``` + +### 3. 监控文档访问 + +可以在 Nginx 中记录文档访问日志: +```nginx +location /docs/ { + access_log /var/log/nginx/docs-access.log; + proxy_pass http://nex_design; +} +``` + +## 总结 + +使用 Docker 卷挂载方案是最灵活、最适合本项目的解决方案: + +✅ **实时更新** - 修改即生效 +✅ **简单维护** - 直接编辑文件 +✅ **轻量镜像** - 更快的构建和部署 +✅ **灵活部署** - 支持多种场景 + +**核心配置只需一行:** +```yaml +volumes: + - ./docs:/app/dist/docs:ro +``` + +## 相关文件 + +- `docker-compose.yml:16` - 卷挂载配置 +- `DEPLOYMENT.md:14-35` - 部署文档说明 +- `QUICKSTART.md` - 快速参考 + diff --git a/docs/components/ChartPanel.md b/docs/components/ChartPanel.md new file mode 100644 index 0000000..39320b4 --- /dev/null +++ b/docs/components/ChartPanel.md @@ -0,0 +1,593 @@ +# ChartPanel 组件 + +## 组件说明 + +图表面板组件,基于 ECharts 封装,用于展示数据可视化图表。支持折线图、柱状图、饼图、环形图等多种图表类型,适合在仪表盘、监控面板、数据分析页面中使用。 + +## 组件位置 + +``` +src/components/ChartPanel/ChartPanel.jsx +src/components/ChartPanel/ChartPanel.css +``` + +## 参数说明 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| type | string | 否 | 'line' | 图表类型:line/bar/pie/ring | +| title | string | 否 | - | 图表标题 | +| data | ChartData | 是 | - | 图表数据 | +| height | number | 否 | 200 | 图表高度(像素) | +| option | object | 否 | {} | 自定义 ECharts 配置项 | +| className | string | 否 | '' | 自定义类名 | + +### ChartData 数据格式 + +#### 折线图 / 柱状图数据格式 + +```typescript +{ + xAxis: string[] // X 轴数据(时间、类别等) + series: Array<{ + name: string // 系列名称 + data: number[] // Y 轴数据 + color?: string // 自定义颜色(可选) + }> +} +``` + +#### 饼图 / 环形图数据格式 + +```typescript +{ + data: Array<{ + name: string // 数据项名称 + value: number // 数据值 + color?: string // 自定义颜色(可选) + }> +} +``` + +### 图表类型说明 + +| 类型 | 适用场景 | 数据维度 | +|------|---------|---------| +| line | 趋势展示、时序数据、性能监控 | 多系列、时间轴 | +| bar | 对比分析、排名展示、统计数据 | 多系列、分类轴 | +| pie | 占比分析、分布展示 | 单维度、百分比 | +| ring | 占比分析(带中心文字) | 单维度、百分比 | + +## 使用示例 + +### 折线图 + +```jsx +import ChartPanel from '../components/ChartPanel/ChartPanel' + +const lineData = { + xAxis: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00'], + series: [ + { + name: 'CPU 使用率', + data: [30, 45, 65, 50, 70, 55], + color: '#1677ff', + }, + { + name: '内存使用率', + data: [20, 35, 55, 45, 60, 50], + color: '#52c41a', + }, + ], +} + + +``` + +### 柱状图 + +```jsx +const barData = { + xAxis: ['周一', '周二', '周三', '周四', '周五'], + series: [ + { + name: '新增用户', + data: [120, 200, 150, 180, 220], + color: '#1677ff', + }, + { + name: '活跃用户', + data: [80, 150, 120, 140, 180], + color: '#52c41a', + }, + ], +} + + +``` + +### 饼图 + +```jsx +const pieData = { + data: [ + { name: '运行中', value: 45, color: '#52c41a' }, + { name: '已停止', value: 20, color: '#8c8c8c' }, + { name: '错误', value: 5, color: '#ff4d4f' }, + { name: '待部署', value: 30, color: '#faad14' }, + ], +} + + +``` + +### 环形图 + +```jsx +const ringData = { + data: [ + { name: '在线', value: 85 }, + { name: '离线', value: 15 }, + ], +} + + +``` + +### 自定义 ECharts 配置 + +```jsx + +``` + +### 配合 SideInfoPanel 使用 + +```jsx +import SideInfoPanel from '../components/SideInfoPanel/SideInfoPanel' +import ChartPanel from '../components/ChartPanel/ChartPanel' + +, + content: ( + <> + + + + ), + }, + { + key: 'distribution', + title: '状态分布', + icon: , + content: ( + <> + + + + ), + }, + ]} +/> +``` + +## DOM 结构层级 + +```html +
+ + + {title && ( +
+ {title} +
+ )} + + +
+ +
+ +
+``` + +## ECharts 配置说明 + +### 折线图默认配置 + +```javascript +{ + tooltip: { + trigger: 'axis', + backgroundColor: 'rgba(0, 0, 0, 0.7)', + }, + legend: { + bottom: 0, + left: 'center', + }, + grid: { + left: '3%', + right: '4%', + bottom: '15%', + containLabel: true, + }, + xAxis: { + type: 'category', + data: data.xAxis, + boundaryGap: false, + }, + yAxis: { + type: 'value', + }, + series: data.series.map(s => ({ + name: s.name, + type: 'line', + data: s.data, + smooth: true, + itemStyle: { color: s.color }, + })), +} +``` + +### 柱状图默认配置 + +```javascript +{ + tooltip: { + trigger: 'axis', + axisPointer: { type: 'shadow' }, + }, + legend: { + bottom: 0, + left: 'center', + }, + grid: { + left: '3%', + right: '4%', + bottom: '15%', + containLabel: true, + }, + xAxis: { + type: 'category', + data: data.xAxis, + }, + yAxis: { + type: 'value', + }, + series: data.series.map(s => ({ + name: s.name, + type: 'bar', + data: s.data, + itemStyle: { color: s.color }, + })), +} +``` + +### 饼图默认配置 + +```javascript +{ + tooltip: { + trigger: 'item', + formatter: '{b}: {c} ({d}%)', + }, + legend: { + bottom: 0, + left: 'center', + }, + series: [ + { + type: 'pie', + radius: '70%', + center: ['50%', '45%'], + data: data.data.map(item => ({ + name: item.name, + value: item.value, + itemStyle: { color: item.color }, + })), + emphasis: { + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: 'rgba(0, 0, 0, 0.5)', + }, + }, + }, + ], +} +``` + +### 环形图默认配置 + +```javascript +{ + tooltip: { + trigger: 'item', + formatter: '{b}: {c} ({d}%)', + }, + legend: { + bottom: 0, + left: 'center', + }, + series: [ + { + type: 'pie', + radius: ['50%', '70%'], // 环形图特征:内外半径 + center: ['50%', '45%'], + data: data.data.map(item => ({ + name: item.name, + value: item.value, + itemStyle: { color: item.color }, + })), + label: { + show: false, // 环形图默认不显示标签 + }, + emphasis: { + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: 'rgba(0, 0, 0, 0.5)', + }, + }, + }, + ], +} +``` + +## 组件生命周期 + +### 初始化流程 + +1. 组件挂载时,通过 `useRef` 获取 DOM 容器 +2. 使用 `echarts.init()` 初始化 ECharts 实例 +3. 根据 `type` 和 `data` 生成配置项 +4. 调用 `setOption()` 渲染图表 + +### 更新流程 + +1. 当 `type`、`data`、`option` 变化时触发 `useEffect` +2. 复用已有的 ECharts 实例 +3. 重新生成配置项并调用 `setOption()` 更新图表 + +### 响应式处理 + +1. 监听 `window.resize` 事件 +2. 调用 `chartInstance.resize()` 自动调整图表尺寸 +3. 组件卸载时移除事件监听并销毁 ECharts 实例 + +```javascript +useEffect(() => { + const handleResize = () => { + if (chartInstance.current) { + chartInstance.current.resize() + } + } + + window.addEventListener('resize', handleResize) + + return () => { + window.removeEventListener('resize', handleResize) + if (chartInstance.current) { + chartInstance.current.dispose() + } + } +}, []) +``` + +## 样式定制 + +组件提供以下 CSS 类名供自定义样式: + +- `.chart-panel` - 面板容器 +- `.chart-panel-title` - 图表标题 +- `.chart-panel-chart` - ECharts 容器 + +### 自定义样式示例 + +```css +/* 修改标题样式 */ +.chart-panel-title { + font-size: 16px; + font-weight: 600; + color: #1677ff; +} + +/* 修改面板背景 */ +.chart-panel { + background: #f5f5f5; + border-radius: 8px; + padding: 16px; +} + +/* 修改图表容器 */ +.chart-panel-chart { + background: #ffffff; +} +``` + +## 使用场景 + +1. **性能监控面板** - 展示 CPU、内存、网络等实时性能数据 +2. **数据分析仪表盘** - 展示业务指标、用户统计、销售数据 +3. **状态分布展示** - 展示系统状态、设备状态、任务状态的占比 +4. **趋势对比分析** - 展示多个指标的时序变化和对比 + +## 注意事项 + +1. **数据格式**: + - 确保数据格式与图表类型匹配 + - 折线图/柱状图使用 xAxis + series 格式 + - 饼图/环形图使用 data 数组格式 + - 数据更新时会自动重新渲染 + +2. **图表高度**: + - 默认高度 200px,建议根据内容调整 + - 折线图/柱状图:200-400px + - 饼图/环形图:200-300px + - 避免高度过小导致显示不清 + +3. **颜色使用**: + - 可在数据中指定 color 属性自定义颜色 + - 不指定时使用 ECharts 默认配色 + - 建议使用语义化颜色(绿色=正常,红色=错误) + - 同一页面保持配色风格一致 + +4. **性能优化**: + - 大数据量时考虑数据采样 + - 避免频繁更新图表(建议间隔 > 1s) + - 组件卸载时会自动销毁 ECharts 实例 + - 窗口 resize 时会自动调整图表尺寸 + +5. **响应式**: + - 图表会自动适应容器宽度 + - 窗口大小变化时自动 resize + - 小屏幕时建议减小图表高度 + +6. **自定义配置**: + - 使用 option 参数传入自定义 ECharts 配置 + - 自定义配置会与默认配置深度合并 + - 可完全覆盖默认配置实现高度定制 + +7. **标题使用**: + - title 为可选参数 + - 在 SideInfoPanel 中使用时,section 已有标题,可省略 ChartPanel 标题 + - 独立使用时建议添加标题增强可读性 + +## 配合使用的组件 + +- **SideInfoPanel** - 侧边信息面板(推荐) +- **StatCard** - 统计卡片(配合使用) +- **SplitLayout** - 分栏布局 +- **PageTitleBar** - 页面标题栏 + +## ECharts 依赖 + +本组件依赖 ECharts 库,确保已安装: + +```bash +npm install echarts +# 或 +yarn add echarts +``` + +导入方式: + +```javascript +import * as echarts from 'echarts' +``` + +## 示例数据结构参考 + +### 性能监控数据 + +```javascript +const performanceData = { + xAxis: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00'], + series: [ + { + name: 'CPU', + data: [30, 45, 65, 50, 70, 55], + color: '#1677ff', + }, + { + name: '内存', + data: [20, 35, 55, 45, 60, 50], + color: '#52c41a', + }, + ], +} +``` + +### 状态分布数据 + +```javascript +const statusData = { + data: [ + { name: '运行中', value: 45, color: '#52c41a' }, + { name: '已停止', value: 20, color: '#8c8c8c' }, + { name: '错误', value: 5, color: '#ff4d4f' }, + { name: '待部署', value: 30, color: '#faad14' }, + ], +} +``` + +### 区域分布数据 + +```javascript +const regionData = { + xAxis: ['华东', '华南', '华北', '西南', '西北'], + series: [ + { + name: '服务器数量', + data: [120, 80, 95, 65, 45], + color: '#1677ff', + }, + ], +} +``` diff --git a/docs/components/DetailDrawer.md b/docs/components/DetailDrawer.md index 7663666..c3420d6 100644 --- a/docs/components/DetailDrawer.md +++ b/docs/components/DetailDrawer.md @@ -419,6 +419,8 @@ function UserListPage() { ## 布局结构 +### 整体布局 + 抽屉采用固定头部、可滚动内容的布局: ``` @@ -427,13 +429,81 @@ function UserListPage() { │ [关闭] [标题] [徽标] [操作按钮] │ ├─────────────────────────────────────┤ │ │ -│ 内容区域(可滚动) │ +│ 内容区域(可滚动,padding: 24px) │ │ - children 主要内容 │ │ - tabs 标签页(可选) │ │ │ └─────────────────────────────────────┘ ``` +### DOM 结构层级 + +```html + +
+ + +
+
+
+
+ {headerActions} +
+
+ + +
+ + + {children} + + +
+ + +
+ {tab.content} +
+
+
+
+ +
+
+
+``` + +### Padding 说明 + +**重要:为避免内容贴边,组件已经统一管理 padding** + +- ✅ **Drawer body**: `padding: 0`(由组件设置) +- ✅ **detail-drawer-scrollable-content**: `padding: 24px`(统一的外边距) +- ❌ **children 内容**: 不需要额外添加 padding +- ❌ **tab content**: 不需要额外添加 padding + +**正确用法:** +```jsx + + {/* ✅ 不需要额外 padding */} + +``` + +**错误用法:** +```jsx + +
{/* ❌ 不要添加额外的 padding */} + +
+
+``` + ## 样式定制 组件提供以下 CSS 类名供自定义样式: @@ -460,10 +530,10 @@ function UserListPage() { ## 注意事项 -1. 抽屉宽度默认 1080px,可根据内容调整,建议取值范围:720-1200px -2. 标题栏固定在顶部,不随内容滚动,确保操作按钮始终可见 -3. `children` 内容区域会自动应用内边距,`tabs` 内容需要自行控制样式 -4. 操作按钮数量不宜过多,建议不超过 3 个 -5. 使用 `tabs` 时,第一个标签页默认激活 -6. 关闭抽屉时建议清空选中状态,避免下次打开时显示旧数据 -7. 配合 InfoPanel 使用时,InfoPanel 会自动处理内边距 +1. **宽度选择**:抽屉宽度默认 1080px,可根据内容调整,建议取值范围:720-1200px +2. **固定头部**:标题栏固定在顶部,不随内容滚动,确保操作按钮始终可见 +3. **内容 padding**:`detail-drawer-scrollable-content` 已经统一设置了 24px padding,children 内容不需要额外添加 padding +4. **操作按钮**:操作按钮数量不宜过多,建议不超过 3 个 +5. **标签页**:使用 `tabs` 时,第一个标签页默认激活 +6. **状态清理**:关闭抽屉时建议清空选中状态,避免下次打开时显示旧数据 +7. **InfoPanel 集成**:配合 InfoPanel 使用时,InfoPanel 会自动处理内部样式,不需要额外的容器包裹 diff --git a/docs/components/ExtendInfoPanel.md b/docs/components/ExtendInfoPanel.md new file mode 100644 index 0000000..445e12e --- /dev/null +++ b/docs/components/ExtendInfoPanel.md @@ -0,0 +1,499 @@ +# ExtendInfoPanel 组件 + +## 组件说明 + +扩展信息面板组件,用于展示多个可折叠的信息区块。支持横向和纵向两种布局模式,每个区块独立管理展开/收起状态,支持自定义图标和内容。适合在页面的扩展信息区(右侧或顶部)使用。 + +> **注意**:该组件由 SideInfoPanel 重命名而来,功能完全兼容。 + +## 组件位置 + +``` +src/components/ExtendInfoPanel/ExtendInfoPanel.jsx +src/components/ExtendInfoPanel/ExtendInfoPanel.css +``` + +## 参数说明 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| sections | Array | 否 | [] | 信息区块配置数组 | +| layout | string | 否 | 'vertical' | 布局方式:'vertical'(垂直堆叠)\| 'horizontal'(水平排列) | +| className | string | 否 | '' | 自定义类名 | + +### SectionConfig 配置项 + +| 属性名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| key | string | 是 | 区块唯一标识 | +| title | string | 是 | 区块标题 | +| icon | ReactNode | 否 | 标题图标 | +| content | ReactNode | 是 | 区块内容 | +| defaultCollapsed | boolean | 否 | 是否默认折叠,默认 false | +| hideTitleBar | boolean | 否 | 是否隐藏标题栏,默认 false(隐藏后区块内容始终显示) | + +## 布局模式 + +### 垂直布局(Vertical) + +区块垂直堆叠排列,适合在右侧信息面板中使用。 + +``` +┌────────────────┐ +│ Section 1 │ +├────────────────┤ +│ Section 2 │ +├────────────────┤ +│ Section 3 │ +└────────────────┘ +``` + +### 水平布局(Horizontal) + +区块水平排列,适合在顶部扩展面板中使用。 + +``` +┌──────┬──────┬──────┐ +│ Sec1 │ Sec2 │ Sec3 │ +└──────┴──────┴──────┘ +``` + +## 使用示例 + +### 基础用法 - 垂直布局 + +```jsx +import ExtendInfoPanel from '../components/ExtendInfoPanel/ExtendInfoPanel' +import { DashboardOutlined } from '@ant-design/icons' + +function MyPage() { + return ( + , + content:
概览内容
, + }, + ]} + /> + ) +} +``` + +### 水平布局 - 用于顶部扩展区(隐藏标题栏) + +```jsx + + + + + + + ), + }, + ]} +/> +``` + +### 水平布局 - 多个区块 + +```jsx + + + + + ), + }, + ]} +/> +``` + +### 多个区块 + +```jsx +, + content:
基本信息内容
, + }, + { + key: 'stats', + title: '统计数据', + icon: , + content:
统计数据内容
, + }, + { + key: 'logs', + title: '操作日志', + icon: , + defaultCollapsed: true, // 默认折叠 + content:
日志内容
, + }, + ]} +/> +``` + +### 配合 StatCard 使用 + +```jsx +import StatCard from '../components/StatCard/StatCard' + +, + content: ( +
+ } + color="blue" + gridColumn="1 / -1" + /> + + +
+ ), + }, + ]} +/> +``` + +### 配合 ChartPanel 使用 + +```jsx +import ChartPanel from '../components/ChartPanel/ChartPanel' + +, + content: ( + <> + + + + ), + }, + ]} +/> +``` + +### 完整示例 - 配合 SplitLayout + +#### 横向布局(右侧扩展区) + +```jsx +import SplitLayout from '../components/SplitLayout/SplitLayout' +import ExtendInfoPanel from '../components/ExtendInfoPanel/ExtendInfoPanel' + + + + + + } + extendContent={ + , + content: + }, + { + key: 'monitor', + title: '性能监控', + icon: , + content: + } + ]} + /> + } +/> +``` + +#### 纵向布局(顶部扩展区) + +```jsx +import { useState } from 'react' +import PageTitleBar from '../components/PageTitleBar/PageTitleBar' +import SplitLayout from '../components/SplitLayout/SplitLayout' +import ExtendInfoPanel from '../components/ExtendInfoPanel/ExtendInfoPanel' + +function UserListPage() { + const [showStats, setShowStats] = useState(false) + + return ( + <> + + + + + + + } + extendContent={ + + + + + + + ) + } + ]} + /> + } + showExtend={showStats} + /> + + ) +} +``` + +## 布局结构 + +### DOM 结构层级 + +#### 垂直布局 + +```html +
+ + +
+ +
+
+ {icon} + {title} +
+ +
+ + + {!isCollapsed && ( +
+ {content} +
+ )} +
+ + + +
+``` + +#### 水平布局 + +```html +
+
...
+
...
+
...
+
+``` + +## 样式定制 + +组件提供以下 CSS 类名供自定义样式: + +- `.extend-info-panel` - 面板容器 +- `.extend-info-panel-vertical` - 垂直布局模式 +- `.extend-info-panel-horizontal` - 水平布局模式 +- `.extend-info-section` - 单个信息区块 +- `.extend-info-section-header` - 区块头部 +- `.extend-info-section-title` - 区块标题 +- `.extend-info-section-icon` - 标题图标 +- `.extend-info-section-toggle` - 折叠按钮 +- `.extend-info-section-content` - 区块内容 + +### 自定义样式示例 + +```css +/* 修改区块间距 */ +.extend-info-panel-vertical { + gap: 20px; +} + +/* 自定义区块头部背景 */ +.extend-info-section-header { + background: linear-gradient(135deg, #f0f7ff 0%, #e8f4ff 100%); +} + +/* 修改水平布局的区块宽度 */ +.extend-info-panel-horizontal .extend-info-section { + flex: 1; + min-width: 300px; +} +``` + +## 使用场景 + +### 1. 右侧信息面板(垂直布局) + +- **系统监控面板**:展示系统状态、性能指标、告警信息 +- **数据分析侧边栏**:展示统计数据、图表、筛选器 +- **详情页辅助信息**:展示相关数据、操作历史、关联信息 + +### 2. 顶部扩展面板(水平布局) + +- **统计数据面板**:展示多个统计卡片 +- **快捷操作区**:展示常用操作和快捷入口 +- **筛选条件区**:展示可展开的筛选条件 + +## 注意事项 + +### 1. 布局选择 + +```jsx +// ✅ 右侧扩展区使用垂直布局 + + + + +// ✅ 顶部扩展区使用水平布局 + + + + +// ❌ 避免:右侧扩展区使用水平布局(宽度不够) + + + +``` + +### 2. 区块数量 + +- **垂直布局**:建议 2-4 个区块,过多影响用户体验 +- **水平布局**:建议 1-4 个区块,根据容器宽度调整 + +### 3. 折叠状态 + +- 区块的折叠状态由组件内部管理,外部无法直接控制 +- 可通过 `defaultCollapsed` 设置初始状态 +- 建议将不常用的区块设为默认折叠 + +### 4. 内容高度 + +- **垂直布局**:区块内容高度不限,但建议单个区块不要过长(建议 < 500px) +- **水平布局**:建议控制区块内容高度一致,保持视觉整齐 + +### 5. 图标使用 + +- 建议为每个区块添加图标,提升视觉识别度 +- 图标应与区块内容相关 +- 使用 Ant Design 图标库保持风格统一 + +### 6. 宽度适配 + +- **垂直布局**:组件自适应父容器宽度,建议在 320-400px 容器中使用 +- **水平布局**:组件占满父容器宽度,区块平均分配或根据内容自适应 + +## 迁移指南 + +### 从 SideInfoPanel 迁移 + +组件功能完全兼容,只需更改导入路径和组件名: + +**旧代码**: +```jsx +import SideInfoPanel from '../components/SideInfoPanel/SideInfoPanel' + + +``` + +**新代码**: +```jsx +import ExtendInfoPanel from '../components/ExtendInfoPanel/ExtendInfoPanel' + + +``` + +**新增参数**: +- `layout` - 布局模式(新增),默认 'vertical' + +## 配合使用的组件 + +- **SplitLayout** - 布局容器(必需) +- **StatCard** - 统计卡片(推荐) +- **ChartPanel** - 图表面板(推荐) +- **InfoPanel** - 信息展示面板 +- **PageTitleBar** - 页面标题栏(配合纵向布局) + +## 相关文档 + +- [主内容区布局](../layouts/content-area-layout.md) - 详细的布局使用指南 +- [SplitLayout](./SplitLayout.md) - 布局容器组件 +- [StatCard](./StatCard.md) - 统计卡片组件 +- [ChartPanel](./ChartPanel.md) - 图表面板组件 diff --git a/docs/components/InfoPanel.md b/docs/components/InfoPanel.md index 9c928d6..fe7051a 100644 --- a/docs/components/InfoPanel.md +++ b/docs/components/InfoPanel.md @@ -256,6 +256,8 @@ const fields = [ ## 布局说明 +### 栅格布局 + 组件使用 Ant Design 的 24 栅格系统: ``` @@ -276,6 +278,51 @@ const fields = [ - `span=12` - 一行 2 列 - `span=24` - 占满整行(适合描述、备注等长文本字段) +### DOM 结构层级 + +```html +
+ + + + +
+
用户名
+
admin
+
+ + +
+
姓名
+
系统管理员
+
+ + +
+ + +
+ + + + +
+ +
+``` + +### Padding 说明 + +**当在 DetailDrawer 中使用时:** + +- ✅ **DetailDrawer scrollable-content**: `padding: 24px`(外层统一边距) +- ✅ **info-panel**: `padding: 0`(无额外 padding) +- ✅ **info-panel Row**: `padding: 24px`(内容区内边距) +- ✅ **info-panel-actions**: `padding: 24px 32px`(操作区内边距) + +**独立使用时:** +组件会自动处理内部 padding,无需额外设置。 + ## 样式定制 组件提供以下 CSS 类名供自定义样式: diff --git a/docs/components/ListTable.md b/docs/components/ListTable.md index 6903e5a..0ebf2a5 100644 --- a/docs/components/ListTable.md +++ b/docs/components/ListTable.md @@ -367,6 +367,24 @@ function UserListPage() { - `.list-table-container` - 表格容器 - `.row-selected` - 选中行的类名 +### 固定高度设计 + +ListTable 组件采用固定表格体高度设计,确保页面布局的稳定性: + +- **尺寸**:使用 Ant Design `size="middle"` 属性 +- **行高**:47px(Ant Design middle 尺寸默认值) +- **表格体高度**:470px(固定高度,内部滚动) +- **显示行数**:10 行(470px ÷ 47px = 10) +- **分页器高度**:56px(Ant Design 默认) +- **容器内边距**:16px × 2 + +**设计说明**: +- 组件使用 Ant Design 的标准 `size="middle"` 属性,不自定义行高 +- 表格体固定 470px 高度,恰好显示 10 行数据 +- 超过 10 行的内容通过表格体内部滚动查看 +- 当数据不足 10 行时,表格体仍保持 470px 高度,确保布局稳定 +- 分页器上边距 16px,与表格体保持适当间距 + ## 使用场景 1. **用户列表** - 显示和管理用户数据 diff --git a/docs/components/PageTitleBar.md b/docs/components/PageTitleBar.md index 9e2585d..dfcaa59 100644 --- a/docs/components/PageTitleBar.md +++ b/docs/components/PageTitleBar.md @@ -20,8 +20,8 @@ src/components/PageTitleBar/PageTitleBar.css | description | string | 否 | - | 页面描述文本,显示在标题下方 | | actions | ReactNode | 否 | - | 右侧操作按钮区域内容 | | showToggle | boolean | 否 | false | 是否显示展开/收起按钮 | -| onToggle | function(expanded: boolean) | 否 | - | 展开/收起状态变化时的回调函数 | -| defaultExpanded | boolean | 否 | true | 默认展开状态 | +| onToggle | function(expanded: boolean) | 否 | - | 展开/收起状态变化时的回调函数,接收当前展开状态 | +| defaultExpanded | boolean | 否 | false | 默认展开状态 | ## 使用示例 @@ -59,10 +59,12 @@ import { Tag } from 'antd' ```jsx import { useState } from 'react' +import { Card, Row, Col, Statistic } from 'antd' +import { DesktopOutlined, CheckCircleOutlined, CloseCircleOutlined, SearchOutlined } from '@ant-design/icons' import PageTitleBar from '../components/PageTitleBar/PageTitleBar' function MyPage() { - const [showStatsPanel, setShowStatsPanel] = useState(true) + const [showStatsPanel, setShowStatsPanel] = useState(false) return (
@@ -70,13 +72,55 @@ function MyPage() { title="主机列表" description="查看和管理所有接入的主机终端" showToggle={true} + defaultExpanded={false} onToggle={(expanded) => setShowStatsPanel(expanded)} /> - {/* 可展开/收起的内容区域 */} + {/* 可展开/收起的统计面板 */} {showStatsPanel && (
- {/* 统计面板内容 */} + + + + } + valueStyle={{ color: '#1677ff' }} + /> + + + + + } + valueStyle={{ color: '#52c41a' }} + /> + + + + + } + valueStyle={{ color: '#8c8c8c' }} + /> + + + + + } + valueStyle={{ color: '#faad14' }} + /> + + +
)}
@@ -106,7 +150,7 @@ import { PlusOutlined } from '@ant-design/icons' 组件提供以下 CSS 类名供自定义样式: - `.page-title-bar` - 组件根容器 -- `.title-bar-content` - 内容容器 +- `.title-bar-content` - 标题栏内容容器 - `.title-bar-left` - 左侧内容区域 - `.title-bar-right` - 右侧内容区域 - `.title-group` - 标题和徽章组合 @@ -120,12 +164,26 @@ import { PlusOutlined } from '@ant-design/icons' 1. **列表页面** - 显示列表页面的标题和描述 2. **详情页面** - 显示详情页的标题和状态标签 -3. **带统计面板的页面** - 配合展开/收起功能控制统计信息的显示 +3. **带统计面板的页面** - 通过展开/收起按钮控制外部统计面板的显示,保持页面简洁 4. **需要快捷操作的页面** - 通过 actions 参数在标题栏添加常用操作按钮 +## 设计理念 + +PageTitleBar 采用**分体式设计**,组件本身只负责标题栏的展示和展开/收起状态管理,不包含扩展内容。这种设计具有以下优势: + +1. **职责单一** - 组件专注于标题栏功能,代码更清晰 +2. **灵活性高** - 扩展内容由父组件管理,可以自由定制样式和布局 +3. **易于维护** - 标题栏和扩展内容相互独立,修改互不影响 +4. **复用性强** - 同一个 PageTitleBar 可以控制不同类型的扩展内容 + ## 注意事项 1. `title` 参数为必填项,建议简洁明了 -2. 当使用 `showToggle` 时,建议同时提供 `onToggle` 回调以响应状态变化 -3. `badge` 参数支持任何 React 节点,常用的如 Ant Design 的 Tag、Badge 组件 -4. `actions` 区域不建议放置过多按钮,以保持界面简洁 +2. 当使用 `showToggle` 时: + - 必须提供 `onToggle` 回调函数来响应状态变化 + - 在父组件中管理扩展内容的显示/隐藏状态 + - 扩展内容应该独立于 PageTitleBar 组件之外渲染 +3. `defaultExpanded` 默认值为 `false`,扩展面板默认收起,推荐保持此默认值以保持页面简洁 +4. `badge` 参数支持任何 React 节点,常用的如 Ant Design 的 Tag、Badge 组件 +5. `actions` 区域不建议放置过多按钮,以保持界面简洁 +6. 扩展内容(如统计面板)应该在父组件中独立管理,不要嵌入到 PageTitleBar 内部 diff --git a/docs/components/README.md b/docs/components/README.md index 9854f05..29b2af3 100644 --- a/docs/components/README.md +++ b/docs/components/README.md @@ -8,95 +8,214 @@ ### 页面布局组件 -1. **[PageTitleBar](./PageTitleBar.md)** - 页面标题栏组件 +1. **PageTitleBar** - 页面标题栏组件 - 显示页面标题、描述和操作按钮 - 支持展开/收起功能 - 适用于所有页面的顶部区域 +### 布局容器组件 + +2. **SplitLayout** - 主内容区布局容器 + - 支持横向(左右)和纵向(上下)分栏 + - 主内容区 + 扩展信息区 + - 响应式设计 + +3. **ExtendInfoPanel** - 扩展信息面板 + - 多个可折叠的信息区块 + - 支持垂直堆叠和水平排列 + - 配合 SplitLayout 使用 + ### 列表相关组件 -2. **[ListActionBar](./ListActionBar.md)** - 列表操作栏组件 +4. **ListActionBar** - 列表操作栏组件 - 提供操作按钮、搜索、筛选功能 - 适用于列表页面的顶部操作区 -3. **[TreeFilterPanel](./TreeFilterPanel.md)** - 树形筛选面板组件 +5. **TreeFilterPanel** - 树形筛选面板组件 - 树形结构的数据筛选 - 支持搜索和多级展开 - 配合 ListActionBar 使用 -4. **[ListTable](./ListTable.md)** - 列表表格组件 +6. **ListTable** - 列表表格组件 - 统一的表格样式和交互 - 支持行选择、分页、排序 - 适用于所有列表页面 ### 详情展示组件 -5. **[DetailDrawer](./DetailDrawer.md)** - 详情抽屉组件 +7. **DetailDrawer** - 详情抽屉组件 - 从右侧滑出的详情面板 - 支持标签页和操作按钮 - 固定头部,内容可滚动 -6. **[InfoPanel](./InfoPanel.md)** - 信息展示面板组件 +8. **InfoPanel** - 信息展示面板组件 - 网格布局展示结构化数据 - 支持自定义字段渲染 - 配合 DetailDrawer 使用 +### 数据展示组件 + +9. **StatCard** - 统计卡片组件 + - 展示数值型统计数据 + - 支持图标、颜色主题、趋势指示器 + - 支持网格布局(单列/双列) + - 配合 ExtendInfoPanel 使用 + +10. **ChartPanel** - 图表面板组件 + - 基于 ECharts 的图表展示 + - 支持折线图、柱状图、饼图、环形图 + - 自适应容器尺寸 + - 配合 ExtendInfoPanel 使用 + ### 交互反馈组件 -7. **[ConfirmDialog](./ConfirmDialog.md)** - 确认对话框组件 - - 提供统一的确认对话框样式 - - 支持删除、警告、通用确认等场景 - - 支持异步操作 +11. **ConfirmDialog** - 确认对话框组件 + - 提供统一的确认对话框样式 + - 支持删除、警告、通用确认等场景 + - 支持异步操作 -8. **[Toast](./Toast.md)** - 通知反馈组件 - - 操作完成后的提示信息 - - 支持成功、错误、警告、信息四种类型 - - 从右上角滑出,自动消失 +12. **Toast** - 通知反馈组件 + - 操作完成后的提示信息 + - 支持成功、错误、警告、信息四种类型 + - 从右上角滑出,自动消失 ## 组件关系图 +### 页面布局结构 + +#### 横向布局(左右分栏) + +适用场景:需要持续展示扩展信息的页面(如监控页面、数据分析页面) + +``` +┌─────────────────────────────────────────────────────────┐ +│ PageTitleBar (页面标题栏) │ +├─────────────────────────────────┬───────────────────────┤ +│ │ │ +│ 主内容区 (Main Content) │ 扩展信息区 │ +│ │ (Extend Info) │ +│ ┌───────────────────────────┐ │ ┌─────────────────┐ │ +│ │ ListActionBar (操作栏) │ │ │ ExtendInfoPanel │ │ +│ │ ├─ 操作按钮 │ │ │ │ │ +│ │ ├─ 搜索框 │ │ │ - 概览区块 │ │ +│ │ └─ TreeFilterPanel │ │ │ - 图表区块 │ │ +│ ├───────────────────────────┤ │ │ - 监控区块 │ │ +│ │ ListTable (数据表格) │ │ │ │ │ +│ │ └─ 点击行 → DetailDrawer │ │ │ (StatCard + │ │ +│ └───────────────────────────┘ │ │ ChartPanel) │ │ +│ │ └─────────────────┘ │ +│ SplitLayout (direction="horizontal") │ +└─────────────────────────────────┴───────────────────────┘ ``` -页面结构层次: -┌─────────────────────────────────────────┐ -│ PageTitleBar (页面标题栏) │ -├─────────────────────────────────────────┤ -│ ListActionBar (操作栏) │ -│ ├─ 操作按钮 │ -│ ├─ 搜索框 │ -│ └─ TreeFilterPanel (筛选面板) │ -├─────────────────────────────────────────┤ -│ ListTable (数据表格) │ -│ └─ 点击行 → DetailDrawer │ -├─────────────────────────────────────────┤ -│ DetailDrawer (详情抽屉) │ -│ ├─ InfoPanel (基本信息) │ -│ └─ Tabs (关联数据标签页) │ -└─────────────────────────────────────────┘ +#### 纵向布局(上下分栏) -交互反馈: +适用场景:需要可展开/收起的统计面板(如用户列表、主机列表) + +``` +┌─────────────────────────────────────────────────────────┐ +│ PageTitleBar (页面标题栏 + Toggle 控制) │ +├─────────────────────────────────────────────────────────┤ +│ │ +│ 扩展信息区 (Extend Info - 可展开/收起) │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ ExtendInfoPanel (layout="horizontal") │ │ +│ │ │ │ +│ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ │ +│ │ │ 总数 │ │ 在线 │ │ 离线 │ │ 筛选 │ │ │ +│ │ │ Card │ │ Card │ │ Card │ │ Card │ │ │ +│ │ └──────┘ └──────┘ └──────┘ └──────┘ │ │ +│ │ (StatCard 组件,水平排列) │ │ +│ └─────────────────────────────────────────────────┘ │ +│ │ +├─────────────────────────────────────────────────────────┤ +│ │ +│ 主内容区 (Main Content) │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ ListActionBar (操作栏) │ │ +│ │ ├─ 操作按钮 │ │ +│ │ ├─ 搜索框 │ │ +│ │ └─ TreeFilterPanel (高级筛选) │ │ +│ ├─────────────────────────────────────────────────┤ │ +│ │ ListTable (数据表格) │ │ +│ │ └─ 点击行 → DetailDrawer │ │ +│ └─────────────────────────────────────────────────┘ │ +│ │ +│ SplitLayout (direction="vertical", extendPosition="top") │ +└─────────────────────────────────────────────────────────┘ +``` + +详情抽屉 (从右侧滑出): +``` +┌─────────────────────────────────┐ +│ DetailDrawer │ +│ ├─ InfoPanel (基本信息) │ +│ └─ Tabs (关联数据标签页) │ +└─────────────────────────────────┘ + +交互反馈流程: 操作 → ConfirmDialog (确认) → Toast (结果反馈) ``` -## 组件组合示例 +### 组件依赖关系 -### 标准列表页面 +``` +PageTitleBar (独立使用) + +SplitLayout (布局容器) + ├─ mainContent + │ ├─ ListActionBar + │ │ └─ TreeFilterPanel + │ └─ ListTable + │ └─ DetailDrawer + │ └─ InfoPanel + └─ extendContent + └─ ExtendInfoPanel + ├─ StatCard + └─ ChartPanel + +ConfirmDialog (全局调用) +Toast (全局调用) +``` + +## 典型页面组合 + +### 1. 标准列表页面(横向布局) ```jsx - + - - }} -/> - - + + + + } + extendContent={ + + }, + { + key: 'monitor', + title: '性能监控', + content: + } + ]} + /> + } /> @@ -104,7 +223,49 @@ ``` -### 删除操作流程 +### 2. 带统计面板的列表页面(纵向布局) + +```jsx + + + + + }} + /> + + + } + extendContent={ + + } + ]} + /> + } + showExtend={showStats} +/> +``` + +### 3. 删除操作流程 ```jsx // 1. 点击删除按钮 @@ -122,13 +283,42 @@ ConfirmDialog.delete({ }) ``` +## 组件选择指南 + +### 布局组件 + +| 场景 | 推荐组件 | 说明 | +|------|---------|------| +| 需要右侧信息面板 | SplitLayout (horizontal) + ExtendInfoPanel | 监控页面、数据分析页面 | +| 需要顶部统计面板 | SplitLayout (vertical) + ExtendInfoPanel | 可展开的统计信息 | +| 简单列表页 | ListActionBar + ListTable | 无扩展信息需求 | + +### 数据展示组件 + +| 数据类型 | 推荐组件 | 说明 | +|---------|---------|------| +| 统计数值 | StatCard | 简洁的数值展示 | +| 趋势图表 | ChartPanel (line) | 时间序列数据 | +| 分布数据 | ChartPanel (pie/ring) | 占比分析 | +| 对比数据 | ChartPanel (bar) | 类别对比 | +| 结构化信息 | InfoPanel | 对象详细属性 | + +### 交互组件 + +| 场景 | 推荐组件 | 说明 | +|------|---------|------| +| 危险操作确认 | ConfirmDialog.delete | 删除确认 | +| 一般操作确认 | ConfirmDialog.confirm | 普通确认 | +| 操作结果反馈 | Toast | 成功/失败提示 | +| 筛选数据 | TreeFilterPanel | 树形结构筛选 | + ## 使用指南 ### 开始使用 -1. 查看对应组件的详细文档 -2. 了解组件的参数配置 -3. 参考示例代码 +1. 从左侧菜单选择组件查看详细文档 +2. 了解组件的参数配置和使用场景 +3. 参考示例代码进行开发 4. 根据实际需求调整参数 ### 设计原则 @@ -137,13 +327,29 @@ ConfirmDialog.delete({ - **可复用** - 组件高度封装,易于复用 - **可配置** - 提供丰富的配置选项 - **易用性** - API 设计简洁直观 +- **响应式** - 自适应不同屏幕尺寸 ### 技术栈 - React 18 - Ant Design 5.x +- ECharts 5.x - CSS Modules +### 命名规范 + +- **组件名**:PascalCase(如 PageTitleBar) +- **参数名**:camelCase(如 showToggle) +- **CSS 类名**:kebab-case(如 page-title-bar) +- **文件名**:与组件名一致(如 PageTitleBar.jsx) + ## 更新记录 +- 2025-11-13: 新增布局系统(SplitLayout + ExtendInfoPanel) +- 2025-11-13: 新增数据展示组件(StatCard + ChartPanel) - 2025-11-04: 初始版本,包含 8 个核心组件文档 + +## 相关文档 + +- **主内容区布局** - 详细的布局使用指南 +- **设计手册** - 设计规范和最佳实践 diff --git a/docs/components/SplitLayout.md b/docs/components/SplitLayout.md new file mode 100644 index 0000000..006055c --- /dev/null +++ b/docs/components/SplitLayout.md @@ -0,0 +1,447 @@ +# SplitLayout 组件 + +## 组件说明 + +主内容区布局组件,支持横向(左右)和纵向(上下)两种分栏模式。用于将页面划分为主内容区和扩展信息区,支持响应式设计和灵活的布局配置。 + +## 组件位置 + +``` +src/components/SplitLayout/SplitLayout.jsx +src/components/SplitLayout/SplitLayout.css +``` + +## 参数说明 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| direction | string | 否 | 'horizontal' | 布局方向:'horizontal'(左右分栏)\| 'vertical'(上下分栏) | +| mainContent | ReactNode | 是 | - | 主内容区 | +| extendContent | ReactNode | 否 | - | 扩展内容区 | +| extendSize | number | 否 | 360 | 扩展区尺寸(horizontal 模式下为宽度,px) | +| gap | number | 否 | 16 | 主内容与扩展区间距(px) | +| showExtend | boolean | 否 | true | 是否显示扩展区 | +| extendPosition | string | 否 | 根据 direction 自动设置 | 扩展区位置:horizontal 模式默认 'right',vertical 模式默认 'top' | +| className | string | 否 | '' | 自定义类名 | + +## 布局模式 + +### 横向布局(Horizontal) + +左右分栏,主内容在左,扩展信息在右。 + +**布局结构**: +``` +┌──────────────────────────┬──────────────────┐ +│ │ │ +│ Main Content │ Extend Info │ +│ (flex: 1) │ (固定宽度) │ +│ │ (可独立滚动) │ +│ │ │ +└──────────────────────────┴──────────────────┘ +``` + +**适用场景**: +- 数据列表 + 统计信息面板 +- 监控页面 + 实时数据面板 +- 表单编辑 + 帮助文档 + +### 纵向布局(Vertical) + +上下分栏,扩展信息在上,主内容在下。 + +**布局结构**: +``` +┌─────────────────────────────────────────┐ +│ Extend Info (高度自适应, 可收起) │ +├─────────────────────────────────────────┤ +│ │ +│ Main Content │ +│ │ +│ │ +└─────────────────────────────────────────┘ +``` + +**适用场景**: +- 带统计面板的列表页 +- 可展开/收起的筛选条件区 +- 概览信息 + 详细列表 + +## 使用示例 + +### 基础用法 - 横向布局 + +```jsx +import SplitLayout from '../components/SplitLayout/SplitLayout' +import ListTable from '../components/ListTable/ListTable' +import ExtendInfoPanel from '../components/ExtendInfoPanel/ExtendInfoPanel' + +function MyPage() { + return ( + + } + extendContent={ + + } + /> + ) +} +``` + +### 纵向布局 + 可折叠 + +```jsx +import { useState } from 'react' +import PageTitleBar from '../components/PageTitleBar/PageTitleBar' +import SplitLayout from '../components/SplitLayout/SplitLayout' +import ExtendInfoPanel from '../components/ExtendInfoPanel/ExtendInfoPanel' + +function MyPage() { + const [showStats, setShowStats] = useState(false) + + return ( + <> + + + + + + + } + extendContent={ + + } + ]} + /> + } + showExtend={showStats} + /> + + ) +} +``` + +### 自定义尺寸和间距 + +```jsx +主内容} + extendContent={
扩展信息
} + extendSize={400} + gap={24} +/> +``` + +### 完整示例 - 虚拟机镜像页面 + +```jsx +import { useState } from 'react' +import SplitLayout from '../components/SplitLayout/SplitLayout' +import ListActionBar from '../components/ListActionBar/ListActionBar' +import ListTable from '../components/ListTable/ListTable' +import ExtendInfoPanel from '../components/ExtendInfoPanel/ExtendInfoPanel' +import StatCard from '../components/StatCard/StatCard' +import ChartPanel from '../components/ChartPanel/ChartPanel' + +function VirtualMachineImagePage() { + const [selectedRowKeys, setSelectedRowKeys] = useState([]) + + return ( + + + + + } + extendContent={ + + } + color="blue" + gridColumn="1 / -1" + /> + + + + ), + }, + { + key: 'charts', + title: '性能监控', + content: ( + <> + + + + ), + }, + ]} + /> + } + extendSize={360} + extendPosition="right" + /> + ) +} +``` + +## DOM 结构 + +### 横向布局 + +```html +
+ +
+ {mainContent} +
+ + +
+ {extendContent} +
+
+``` + +### 纵向布局 + +```html +
+ +
+ {extendContent} +
+ + +
+ {mainContent} +
+
+``` + +## 响应式设计 + +### 横向布局响应式 + +| 屏幕宽度 | 布局行为 | +|---------|---------| +| ≥ 1200px | 显示扩展信息区 | +| < 1200px | 自动隐藏扩展信息区 | + +```css +@media (max-width: 1200px) { + .split-layout-extend-right { + display: none; + } +} +``` + +### 纵向布局响应式 + +- 扩展区始终占满宽度 +- 高度由内容自适应 +- 通过 `showExtend` 参数控制显示/隐藏 + +## 样式定制 + +组件提供以下 CSS 类名供自定义样式: + +- `.split-layout` - 布局容器 +- `.split-layout-horizontal` - 横向布局模式 +- `.split-layout-vertical` - 纵向布局模式 +- `.split-layout-main` - 主内容区 +- `.split-layout-extend` - 扩展信息区 +- `.split-layout-extend-right` - 右侧扩展区(横向布局) +- `.split-layout-extend-top` - 顶部扩展区(纵向布局) + +### 自定义样式示例 + +```css +/* 修改扩展区背景色 */ +.split-layout-extend { + background: #f5f5f5; +} + +/* 自定义滚动条样式(横向布局) */ +.split-layout-extend-right::-webkit-scrollbar { + width: 8px; +} + +.split-layout-extend-right::-webkit-scrollbar-thumb { + background: #1677ff; + border-radius: 4px; +} +``` + +## 使用场景 + +### 1. 横向布局场景 + +- **数据列表 + 信息面板**:左侧显示数据表格,右侧显示统计信息和图表 +- **监控页面**:左侧显示设备列表,右侧显示实时监控数据 +- **内容编辑 + 预览**:左侧编辑器,右侧实时预览 + +### 2. 纵向布局场景 + +- **带统计面板的列表页**:顶部显示统计卡片,下方显示数据列表 +- **可展开的筛选区**:顶部显示筛选条件,下方显示筛选结果 +- **概览信息页**:顶部显示关键指标,下方显示详细数据 + +## 注意事项 + +### 1. 横向布局 + +- **扩展区宽度建议**:320-400px +- **主内容最小宽度**:确保至少 800px +- **总宽度建议**:≥ 1200px +- **扩展区滚动**:自动 sticky 定位,独立滚动 + +### 2. 纵向布局 + +- **扩展区高度**:由内容自适应,不需要设置固定高度 +- **配合 PageTitleBar**:使用 toggle 功能控制显示/隐藏 +- **内容组织**:避免扩展区内容过多,建议不超过 300px 高度 + +### 3. 内容组织 + +- **主内容区**:放置主要内容(列表、表格、表单等) +- **扩展区**:放置辅助信息(统计、图表、说明等) +- **避免**:扩展区放置过多交互元素 + +### 4. 性能考虑 + +- 扩展区内容会始终渲染(即使隐藏) +- 如需完全卸载,使用 `showExtend={false}` +- 大量图表建议使用懒加载 + +### 5. 布局选择 + +```jsx +// ✅ 适合横向布局 +- 需要持续展示的监控信息 +- 辅助信息较多且重要 +- 页面宽度充足(> 1200px) + +// ✅ 适合纵向布局 +- 统计信息可按需展开 +- 移动端友好的布局 +- 扩展内容简洁明了 + +// ❌ 不需要 SplitLayout +- 简单的列表页面 +- 无扩展信息需求 +- 直接使用 ListActionBar + ListTable +``` + +## 迁移指南 + +### 从旧版 API 迁移 + +**旧版代码**: +```jsx +} + rightContent={} + rightWidth={360} + showRight={true} +/> +``` + +**新版代码**: +```jsx +} + extendContent={} + extendSize={360} + showExtend={true} + extendPosition="right" +/> +``` + +**变更对照表**: + +| 旧参数 | 新参数 | 说明 | +|--------|--------|------| +| leftContent | mainContent | 主内容区 | +| rightContent | extendContent | 扩展内容区 | +| rightWidth | extendSize | 扩展区尺寸 | +| showRight | showExtend | 显示扩展区 | +| - | direction | 新增:布局方向 | +| - | extendPosition | 新增:扩展区位置 | + +## 配合使用的组件 + +- **ExtendInfoPanel** - 扩展信息面板容器(推荐) +- **StatCard** - 统计卡片 +- **ChartPanel** - 图表展示 +- **ListTable** - 列表表格 +- **ListActionBar** - 列表操作栏 +- **PageTitleBar** - 页面标题栏(配合纵向布局) + +## 相关文档 + +- [主内容区布局](../layouts/content-area-layout.md) - 详细的布局使用指南 +- [ExtendInfoPanel](./ExtendInfoPanel.md) - 扩展信息面板组件 +- [StatCard](./StatCard.md) - 统计卡片组件 +- [ChartPanel](./ChartPanel.md) - 图表面板组件 diff --git a/docs/components/StatCard.md b/docs/components/StatCard.md new file mode 100644 index 0000000..0de8c1d --- /dev/null +++ b/docs/components/StatCard.md @@ -0,0 +1,298 @@ +# StatCard 组件 + +## 组件说明 + +统计卡片组件,用于展示数值型统计数据。支持图标、颜色主题、趋势指示器等功能,适合在仪表盘或信息面板中使用。 + +## 组件位置 + +``` +src/components/StatCard/StatCard.jsx +src/components/StatCard/StatCard.css +``` + +## 参数说明 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| title | string | 是 | - | 卡片标题 | +| value | number \| string | 是 | - | 统计值 | +| icon | ReactNode | 否 | - | 图标 | +| color | string | 否 | 'blue' | 主题颜色:blue/green/orange/red/purple/gray 或自定义颜色值 | +| trend | TrendConfig | 否 | - | 趋势信息 | +| suffix | string | 否 | '' | 后缀单位 | +| layout | string | 否 | 'column' | 布局模式:'column'(一列)\| 'row'(两列) | +| className | string | 否 | '' | 自定义类名 | + +### TrendConfig 配置项 + +| 属性名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| value | number | 是 | 趋势值(百分比,自动取绝对值) | +| direction | 'up' \| 'down' | 是 | 趋势方向 | + +### 颜色预设 + +| 颜色名 | 色值 | 适用场景 | +|--------|------|---------| +| blue | #1677ff | 默认、总数、常规数据 | +| green | #52c41a | 成功、在线、正常状态 | +| orange | #faad14 | 警告、待处理 | +| red | #ff4d4f | 错误、危险、异常 | +| purple | #722ed1 | 特殊、高级功能 | +| gray | #8c8c8c | 禁用、离线、未激活 | + +## 使用示例 + +### 基础用法 + +```jsx +import StatCard from '../components/StatCard/StatCard' +import { DatabaseOutlined } from '@ant-design/icons' + +} +/> +``` + +### 带颜色主题 + +```jsx +} + color="green" +/> + +} + color="red" +/> +``` + +### 带趋势指示 + +```jsx +} + trend={{ value: 12.5, direction: 'up' }} +/> + + +``` + +### 自定义颜色 + +```jsx + +``` + +### 布局模式 + +StatCard 支持两种布局模式,通过 `layout` 参数配置: + +#### 一列布局(默认) + +标题和图标在上方,数值和趋势在下方。适合垂直排列的卡片网格。 + +```jsx +} + color="blue" + trend={{ value: 5, direction: 'up' }} +/> +``` + +#### 两列布局 + +左侧显示标题和图标,右侧显示数值和趋势。适合在较宽的容器中横向展示数据。 + +```jsx +} + color="orange" + trend={{ value: 3, direction: 'down' }} +/> +``` + +#### 布局模式选择建议 + +- **一列布局(column)**: + - 适用于窄宽度容器(如侧边信息面板) + - 适用于网格布局中垂直排列 + - 数值作为视觉焦点,强调数据本身 + +- **两列布局(row)**: + - 适用于较宽的容器(宽度 > 300px) + - 适用于列表或卡片中横向展示 + - 平衡展示标题和数值,节省垂直空间 + +### 网格布局 + +```jsx +
+ } + color="blue" + /> + } + color="green" + trend={{ value: 12, direction: 'up' }} + /> + } + color="gray" + /> +
+``` + +### 配合 SideInfoPanel 使用 + +```jsx +import SideInfoPanel from '../components/SideInfoPanel/SideInfoPanel' +import StatCard from '../components/StatCard/StatCard' + + + } + trend={{ value: 5, direction: 'up' }} + /> + + + + ), + }, + ]} +/> +``` + +## 样式定制 + +组件提供以下 CSS 类名供自定义样式: + +- `.stat-card` - 卡片容器 +- `.stat-card-column` - 一列布局模式 +- `.stat-card-row` - 两列布局模式 +- `.stat-card-header` - 卡片头部 +- `.stat-card-title` - 标题文本 +- `.stat-card-icon` - 图标 +- `.stat-card-body` - 卡片内容 +- `.stat-card-value` - 统计值 +- `.stat-card-suffix` - 后缀单位 +- `.stat-card-trend` - 趋势指示器 +- `.trend-up` - 上升趋势 +- `.trend-down` - 下降趋势 + +### 自定义样式示例 + +```css +/* 修改卡片圆角 */ +.stat-card { + border-radius: 12px; +} + +/* 自定义标题样式 */ +.stat-card-title { + font-size: 14px; + font-weight: 600; +} + +/* 自定义数值大小 */ +.stat-card-value { + font-size: 28px; +} +``` + +## 使用场景 + +1. **仪表盘概览** - 展示关键业务指标 +2. **系统监控** - 展示CPU、内存、磁盘使用率 +3. **数据统计** - 展示用户数、订单数等统计数据 +4. **性能指标** - 展示响应时间、吞吐量等性能数据 + +## 注意事项 + +1. **数值格式化**: + - 大数值建议使用千分位格式(如 1,250) + - 小数建议保留 1-2 位 + - 使用 suffix 添加单位,而不是直接拼接在 value 中 + +2. **颜色使用**: + - 保持颜色语义一致(绿色=正常,红色=错误) + - 避免过多颜色,建议同一页面不超过 4 种 + - 优先使用预设颜色,保持风格统一 + +3. **趋势指示**: + - 趋势值会自动取绝对值显示 + - 上升趋势不一定是好事(如错误率上升) + - 根据业务含义选择合适的颜色 + +4. **布局建议**: + - 一列布局(column):适合侧边信息面板和网格垂直排列 + - 两列布局(row):适合较宽容器和横向展示 + - 网格布局推荐使用:`display: grid; gap: 12px;` + - 避免卡片过宽,建议最大宽度 400px + - 根据容器宽度选择合适的 layout 模式 + +5. **图标选择**: + - 使用与数据相关的图标 + - 保持图标风格统一 + - 避免过于复杂的图标 + +## 配合使用的组件 + +- **SideInfoPanel** - 侧边信息面板(推荐) +- **SplitLayout** - 分栏布局 +- **ChartPanel** - 图表面板(配合使用) diff --git a/docs/layouts/content-area-layout.md b/docs/layouts/content-area-layout.md new file mode 100644 index 0000000..e4fadad --- /dev/null +++ b/docs/layouts/content-area-layout.md @@ -0,0 +1,347 @@ +# 主内容区布局 + +## 概述 + +主内容区布局是页面中除了导航栏外的核心内容区域,由 **SplitLayout** 和 **ExtendInfoPanel** 两个核心组件组成,提供灵活的横向(左右)和纵向(上下)分栏布局方案。 + +## 布局模式 + +### 1. 横向布局(Horizontal) + +**适用场景**:需要在主内容区右侧展示扩展信息的页面 + +**布局结构**: +``` +┌─────────────────────────────────────────┐ +│ PageTitleBar │ +├──────────────────────────┬──────────────┤ +│ │ │ +│ Main Content │ Extend │ +│ - ListActionBar │ Info │ +│ - ListTable │ Panel │ +│ │ │ +└──────────────────────────┴──────────────┘ +``` + +**代码示例**: +```jsx +import SplitLayout from '../components/SplitLayout/SplitLayout' +import ExtendInfoPanel from '../components/ExtendInfoPanel/ExtendInfoPanel' + + + + + + } + extendContent={ + , + content: + }, + { + key: 'charts', + title: '图表', + content: + } + ]} + /> + } + extendSize={360} + extendPosition="right" +/> +``` + +### 2. 纵向布局(Vertical) + +**适用场景**:需要在主内容区顶部展示统计信息或扩展面板的页面 + +**布局结构**: +``` +┌─────────────────────────────────────────┐ +│ PageTitleBar (带展开/收起按钮) │ +├─────────────────────────────────────────┤ +│ Extend Info Panel (可收起) │ +│ - 统计卡片 / 图表 / 其他扩展信息 │ +├─────────────────────────────────────────┤ +│ Main Content │ +│ - ListActionBar │ +│ - ListTable │ +│ │ +└─────────────────────────────────────────┘ +``` + +**代码示例**: +```jsx +import { useState } from 'react' +import SplitLayout from '../components/SplitLayout/SplitLayout' +import ExtendInfoPanel from '../components/ExtendInfoPanel/ExtendInfoPanel' + +function MyPage() { + const [showExtend, setShowExtend] = useState(false) + + return ( + <> + + + + + + + } + extendContent={ + + + + + ) + } + ]} + /> + } + showExtend={showExtend} + extendPosition="top" + /> + + ) +} +``` + +## 核心组件 + +### SplitLayout + +**职责**:主内容区的布局容器,支持横向和纵向分栏 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| direction | string | 否 | 'horizontal' | 布局方向:'horizontal'(左右)\| 'vertical'(上下) | +| mainContent | ReactNode | 是 | - | 主内容区 | +| extendContent | ReactNode | 否 | - | 扩展内容区 | +| extendSize | number | 否 | 360 | 扩展区尺寸(horizontal 模式下为宽度,px) | +| gap | number | 否 | 16 | 主内容与扩展区间距(px) | +| showExtend | boolean | 否 | true | 是否显示扩展区 | +| extendPosition | string | 否 | 'right'/'top' | 扩展区位置:horizontal 模式下为 'right',vertical 模式下为 'top' | +| className | string | 否 | '' | 自定义类名 | + +**详细文档**:[SplitLayout.md](../components/SplitLayout.md) + +### ExtendInfoPanel + +**职责**:扩展信息面板容器,支持多个可折叠区块 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| sections | Array | 是 | [] | 区块配置数组 | +| layout | string | 否 | 'vertical' | 布局方式:'vertical'(垂直堆叠)\| 'horizontal'(水平排列) | +| className | string | 否 | '' | 自定义类名 | + +**Section 配置项**: + +| 属性名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| key | string | 是 | 区块唯一标识 | +| title | string | 是 | 区块标题 | +| icon | ReactNode | 否 | 标题图标 | +| content | ReactNode | 是 | 区块内容 | +| defaultCollapsed | boolean | 否 | 默认是否折叠 | + +**详细文档**:[ExtendInfoPanel.md](../components/ExtendInfoPanel.md) + +## 使用场景 + +### 场景 1:带右侧信息面板的列表页 + +**示例页面**:虚拟机镜像管理页(VirtualMachineImagePage) + +**特点**: +- 左侧:操作栏 + 数据表格 +- 右侧:概览统计 + 图表监控 + +**布局选择**:横向布局(horizontal) + +### 场景 2:带顶部统计面板的列表页 + +**示例页面**:用户列表页(UserListPage) + +**特点**: +- 顶部:可展开/收起的统计面板 +- 下方:操作栏 + 数据表格 + +**布局选择**:纵向布局(vertical) + +### 场景 3:纯表格列表页 + +**示例页面**:简单的数据列表页 + +**特点**: +- 只有操作栏和数据表格 +- 无扩展信息区 + +**布局选择**:直接使用 ListActionBar + ListTable,不使用 SplitLayout + +```jsx +<> + + + + +``` + +## 布局对比 + +| 特性 | 横向布局 | 纵向布局 | 无布局 | +|------|---------|---------|--------| +| 扩展区位置 | 右侧 | 顶部 | 无 | +| 扩展区尺寸 | 固定宽度 | 高度自适应 | - | +| 主内容宽度 | 自适应 | 100% | 100% | +| 展开/收起 | 响应式隐藏 | PageTitleBar 控制 | - | +| 适用场景 | 仪表盘、监控页 | 统计分析页 | 简单列表页 | + +## 响应式设计 + +### 横向布局响应式 + +- **宽屏(> 1200px)**:显示扩展信息区 +- **中等屏幕(≤ 1200px)**:自动隐藏扩展信息区,主内容占满 + +```css +@media (max-width: 1200px) { + .split-layout-extend-right { + display: none; + } +} +``` + +### 纵向布局响应式 + +- 扩展区始终占满宽度 +- 通过 `showExtend` 参数控制显示/隐藏 +- 建议配合 PageTitleBar 的 toggle 功能使用 + +## 最佳实践 + +### 1. 选择合适的布局模式 + +```jsx +// ✅ 好的做法:监控页面使用横向布局 +} + extendContent={} +/> + +// ✅ 好的做法:统计页面使用纵向布局 +} + extendContent={} + showExtend={showStats} +/> + +// ❌ 避免:简单列表页使用复杂布局 +// 直接使用 ListActionBar + ListTable 即可 +``` + +### 2. ExtendInfoPanel 的 layout 选择 + +```jsx +// horizontal 方向的 SplitLayout 配合 vertical layout 的 ExtendInfoPanel + + } +/> + +// vertical 方向的 SplitLayout 配合 horizontal layout 的 ExtendInfoPanel + + } +/> +``` + +### 3. 合理设置扩展区尺寸 + +```jsx +// ✅ 横向布局:扩展区宽度推荐 320-400px + + +// ✅ 纵向布局:高度自适应,由内容决定 + +``` + +### 4. 统一命名规范 + +```jsx +// ✅ 使用新的参数命名 + + +// ❌ 避免使用旧的命名(已废弃) + +``` + +## 相关组件 + +- [PageTitleBar](../components/PageTitleBar.md) - 页面标题栏 +- [ListActionBar](../components/ListActionBar.md) - 列表操作栏 +- [ListTable](../components/ListTable.md) - 列表表格 +- [StatCard](../components/StatCard.md) - 统计卡片 +- [ChartPanel](../components/ChartPanel.md) - 图表面板 +- [InfoPanel](../components/InfoPanel.md) - 信息展示面板 + +## 示例页面 + +- **横向布局示例**:`src/pages/VirtualMachineImagePage.jsx` +- **纵向布局示例**:`src/pages/UserListPage.jsx` diff --git a/docs/pages/main-layout.md b/docs/layouts/main-layout.md similarity index 100% rename from docs/pages/main-layout.md rename to docs/layouts/main-layout.md diff --git a/package-lock.json b/package-lock.json index e5cc53c..cd189e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@ant-design/icons": "^5.2.6", "antd": "^5.12.0", + "echarts": "^6.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^10.1.0", @@ -2003,6 +2004,16 @@ "dev": true, "license": "MIT" }, + "node_modules/echarts": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/echarts/-/echarts-6.0.0.tgz", + "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "6.0.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.244", "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz", @@ -7379,6 +7390,12 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", @@ -7999,6 +8016,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zrender": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/zrender/-/zrender-6.0.0.tgz", + "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index 08132e0..d5ec381 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,13 @@ "dev": "vite", "build": "vite build", "preview": "vite preview", + "clean": "bash scripts/clean.sh", "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0" }, "dependencies": { "@ant-design/icons": "^5.2.6", "antd": "^5.12.0", + "echarts": "^6.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^10.1.0", diff --git a/scripts/clean.sh b/scripts/clean.sh new file mode 100755 index 0000000..80d8159 --- /dev/null +++ b/scripts/clean.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# 清理构建产物和临时文件 + +echo "清理构建产物..." + +# 清理 dist 目录 +if [ -d "dist" ]; then + rm -rf dist + echo "✓ 已清理 dist 目录" +fi + +# 清理日志 +if [ -d "logs" ]; then + rm -rf logs + echo "✓ 已清理 logs 目录" +fi + +# 清理 node_modules(可选,取消注释使用) +# if [ -d "node_modules" ]; then +# rm -rf node_modules +# echo "✓ 已清理 node_modules 目录" +# fi + +echo "" +echo "清理完成!" diff --git a/src/App.jsx b/src/App.jsx index e67c2a0..ef4dc3f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -4,6 +4,7 @@ import OverviewPage from './pages/OverviewPage' import HostListPage from './pages/HostListPage' import UserListPage from './pages/UserListPage' import ImageListPage from './pages/ImageListPage' +import VirtualMachineImagePage from './pages/VirtualMachineImagePage' import DocsPage from './pages/DocsPage' function App() { @@ -14,7 +15,8 @@ function App() { } /> } /> } /> - } /> + } /> + } /> } /> {/* 其他路由将在后续添加 */} diff --git a/src/components/ChartPanel/ChartPanel.css b/src/components/ChartPanel/ChartPanel.css new file mode 100644 index 0000000..69a0cc2 --- /dev/null +++ b/src/components/ChartPanel/ChartPanel.css @@ -0,0 +1,17 @@ +/* 图表面板 */ +.chart-panel { + margin-bottom: 16px; +} + +.chart-panel:last-child { + margin-bottom: 0; +} + +.chart-panel-title { + font-size: 13px; + font-weight: 600; + color: rgba(0, 0, 0, 0.88); + margin-bottom: 12px; + padding-left: 8px; + border-left: 3px solid #1677ff; +} diff --git a/src/components/ChartPanel/ChartPanel.jsx b/src/components/ChartPanel/ChartPanel.jsx new file mode 100644 index 0000000..5f26c8f --- /dev/null +++ b/src/components/ChartPanel/ChartPanel.jsx @@ -0,0 +1,202 @@ +import { useEffect, useRef } from 'react' +import * as echarts from 'echarts' +import './ChartPanel.css' + +/** + * 图表面板组件 + * @param {Object} props + * @param {string} props.type - 图表类型: 'line' | 'bar' | 'pie' | 'ring' + * @param {string} props.title - 图表标题 + * @param {Object} props.data - 图表数据 + * @param {number} props.height - 图表高度,默认 200px + * @param {Object} props.option - 自定义 ECharts 配置 + * @param {string} props.className - 自定义类名 + */ +function ChartPanel({ type = 'line', title, data, height = 200, option = {}, className = '' }) { + const chartRef = useRef(null) + const chartInstance = useRef(null) + + useEffect(() => { + if (!chartRef.current || !data) return + + // 使用 setTimeout 确保 DOM 完全渲染 + const timer = setTimeout(() => { + // 初始化图表 + if (!chartInstance.current) { + chartInstance.current = echarts.init(chartRef.current) + } + + // 根据类型生成配置 + const chartOption = getChartOption(type, data, option) + chartInstance.current.setOption(chartOption, true) + }, 0) + + // 窗口大小改变时重绘(使用 passive 选项) + const handleResize = () => { + if (chartInstance.current) { + chartInstance.current.resize() + } + } + + // 添加被动事件监听器 + window.addEventListener('resize', handleResize, { passive: true }) + + return () => { + clearTimeout(timer) + window.removeEventListener('resize', handleResize) + } + }, [type, data, option]) + + // 组件卸载时销毁图表 + useEffect(() => { + return () => { + chartInstance.current?.dispose() + } + }, []) + + return ( +
+ {title &&
{title}
} +
+
+ ) +} + +/** + * 根据图表类型生成 ECharts 配置 + */ +function getChartOption(type, data, customOption) { + const baseOption = { + grid: { + left: '10%', + right: '5%', + top: '15%', + bottom: '15%', + }, + tooltip: { + trigger: type === 'pie' || type === 'ring' ? 'item' : 'axis', + backgroundColor: 'rgba(255, 255, 255, 0.95)', + borderColor: '#e8e8e8', + borderWidth: 1, + textStyle: { + color: '#333', + }, + }, + } + + switch (type) { + case 'line': + return { + ...baseOption, + xAxis: { + type: 'category', + data: data.xAxis || [], + boundaryGap: false, + axisLine: { lineStyle: { color: '#e8e8e8' } }, + axisLabel: { color: '#8c8c8c', fontSize: 11 }, + }, + yAxis: { + type: 'value', + axisLine: { lineStyle: { color: '#e8e8e8' } }, + axisLabel: { color: '#8c8c8c', fontSize: 11 }, + splitLine: { lineStyle: { color: '#f0f0f0' } }, + }, + series: [ + { + type: 'line', + data: data.series || [], + smooth: true, + lineStyle: { width: 2, color: '#1677ff' }, + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: 'rgba(22, 119, 255, 0.3)' }, + { offset: 1, color: 'rgba(22, 119, 255, 0.05)' }, + ]), + }, + symbol: 'circle', + symbolSize: 6, + itemStyle: { color: '#1677ff' }, + }, + ], + ...customOption, + } + + case 'bar': + return { + ...baseOption, + xAxis: { + type: 'category', + data: data.xAxis || [], + axisLine: { lineStyle: { color: '#e8e8e8' } }, + axisLabel: { color: '#8c8c8c', fontSize: 11 }, + }, + yAxis: { + type: 'value', + axisLine: { lineStyle: { color: '#e8e8e8' } }, + axisLabel: { color: '#8c8c8c', fontSize: 11 }, + splitLine: { lineStyle: { color: '#f0f0f0' } }, + }, + series: [ + { + type: 'bar', + data: data.series || [], + itemStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: '#4096ff' }, + { offset: 1, color: '#1677ff' }, + ]), + borderRadius: [4, 4, 0, 0], + }, + barWidth: '50%', + }, + ], + ...customOption, + } + + case 'pie': + case 'ring': + return { + ...baseOption, + grid: undefined, + legend: { + orient: 'vertical', + right: '10%', + top: 'center', + textStyle: { color: '#8c8c8c', fontSize: 12 }, + }, + series: [ + { + type: 'pie', + radius: type === 'ring' ? ['40%', '65%'] : '65%', + center: ['40%', '50%'], + data: data.series || [], + label: { + fontSize: 11, + color: '#8c8c8c', + }, + labelLine: { + lineStyle: { color: '#d9d9d9' }, + }, + itemStyle: { + borderRadius: 4, + borderColor: '#fff', + borderWidth: 2, + }, + emphasis: { + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: 'rgba(0, 0, 0, 0.3)', + }, + }, + }, + ], + ...customOption, + } + + default: + return { ...baseOption, ...customOption } + } +} + +export default ChartPanel diff --git a/src/components/DetailDrawer/DetailDrawer.css b/src/components/DetailDrawer/DetailDrawer.css index 32bf54d..c5db2f2 100644 --- a/src/components/DetailDrawer/DetailDrawer.css +++ b/src/components/DetailDrawer/DetailDrawer.css @@ -10,7 +10,7 @@ display: flex; justify-content: space-between; align-items: center; - padding: 16px 24px; + padding: 16px; background: #fafafa; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; @@ -65,13 +65,13 @@ flex: 1; overflow-y: auto; overflow-x: hidden; + padding: 24px; } /* 标签页区域 */ .detail-drawer-tabs { background: #ffffff; - padding-top: 16px; - padding-left: 12px; + padding: 0; min-height: 400px; } @@ -85,8 +85,7 @@ .detail-drawer-tabs :global(.ant-tabs-nav) { padding: 0; - margin: 0 24px; - margin-bottom: 0; + margin: 0 0 16px 0; background: transparent; } @@ -115,6 +114,6 @@ } .detail-drawer-tab-content { - padding: 24px; + padding: 0; background: #ffffff; } diff --git a/src/components/ExtendInfoPanel/ExtendInfoPanel.css b/src/components/ExtendInfoPanel/ExtendInfoPanel.css new file mode 100644 index 0000000..8ea059b --- /dev/null +++ b/src/components/ExtendInfoPanel/ExtendInfoPanel.css @@ -0,0 +1,105 @@ +/* 扩展信息面板容器 */ +.extend-info-panel { + display: flex; + gap: 16px; + width: 100%; +} + +/* 垂直布局(默认) */ +.extend-info-panel-vertical { + flex-direction: column; +} + +/* 水平布局 */ +.extend-info-panel-horizontal { + flex-direction: row; + flex-wrap: wrap; +} + +/* 信息区块 */ +.extend-info-section { + background: #ffffff; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + overflow: hidden; + transition: all 0.3s ease; +} + +/* 水平布局时区块自适应宽度 */ +.extend-info-panel-horizontal .extend-info-section { + flex: 1; + min-width: 0; +} + +.extend-info-section:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +/* 区块头部 */ +.extend-info-section-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 20px; + background: linear-gradient(135deg, #f8f9ff 0%, #f0f4ff 100%); + border-bottom: 1px solid #e8e8e8; + cursor: pointer; + user-select: none; + transition: background 0.2s ease; +} + +.extend-info-section-header:hover { + background: linear-gradient(135deg, #f0f4ff 0%, #e8f0ff 100%); +} + +.extend-info-section-title { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + font-weight: 600; + color: rgba(0, 0, 0, 0.88); +} + +.extend-info-section-icon { + display: flex; + align-items: center; + font-size: 16px; + color: #1677ff; +} + +.extend-info-section-toggle { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border: none; + background: transparent; + color: #8c8c8c; + cursor: pointer; + transition: all 0.2s ease; + border-radius: 4px; +} + +.extend-info-section-toggle:hover { + background: rgba(0, 0, 0, 0.06); + color: #1677ff; +} + +/* 区块内容 */ +.extend-info-section-content { + padding: 16px 20px; + animation: expandContent 0.3s ease-out; +} + +@keyframes expandContent { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} diff --git a/src/components/ExtendInfoPanel/ExtendInfoPanel.jsx b/src/components/ExtendInfoPanel/ExtendInfoPanel.jsx new file mode 100644 index 0000000..03e5166 --- /dev/null +++ b/src/components/ExtendInfoPanel/ExtendInfoPanel.jsx @@ -0,0 +1,68 @@ +import { useState } from 'react' +import { UpOutlined, DownOutlined } from '@ant-design/icons' +import './ExtendInfoPanel.css' + +/** + * 扩展信息面板组件 + * @param {Object} props + * @param {Array} props.sections - 信息区块配置数组 + * @param {string} props.sections[].key - 区块唯一键 + * @param {string} props.sections[].title - 区块标题 + * @param {ReactNode} props.sections[].icon - 标题图标 + * @param {ReactNode} props.sections[].content - 区块内容 + * @param {boolean} props.sections[].defaultCollapsed - 默认是否折叠 + * @param {boolean} props.sections[].hideTitleBar - 是否隐藏该区块的标题栏(默认 false) + * @param {string} props.layout - 布局方式:'vertical'(垂直堆叠)| 'horizontal'(水平排列) + * @param {string} props.className - 自定义类名 + */ +function ExtendInfoPanel({ sections = [], layout = 'vertical', className = '' }) { + const [collapsedSections, setCollapsedSections] = useState(() => { + const initial = {} + sections.forEach((section) => { + if (section.defaultCollapsed) { + initial[section.key] = true + } + }) + return initial + }) + + const toggleSection = (key) => { + setCollapsedSections((prev) => ({ + ...prev, + [key]: !prev[key], + })) + } + + return ( +
+ {sections.map((section) => { + const isCollapsed = collapsedSections[section.key] + const hideTitleBar = section.hideTitleBar === true + + return ( +
+ {/* 区块头部 - 可配置隐藏 */} + {!hideTitleBar && ( +
toggleSection(section.key)}> +
+ {section.icon && {section.icon}} + {section.title} +
+ +
+ )} + + {/* 区块内容 - 如果隐藏标题栏则总是显示,否则根据折叠状态 */} + {(hideTitleBar || !isCollapsed) && ( +
{section.content}
+ )} +
+ ) + })} +
+ ) +} + +export default ExtendInfoPanel diff --git a/src/components/InfoPanel/InfoPanel.css b/src/components/InfoPanel/InfoPanel.css index 0593f3c..463adb6 100644 --- a/src/components/InfoPanel/InfoPanel.css +++ b/src/components/InfoPanel/InfoPanel.css @@ -1,12 +1,12 @@ /* 信息面板 */ .info-panel { - padding: 6px 8px; + padding: 0; background: #ffffff; } /* 信息区域容器 */ .info-panel > :global(.ant-row) { - padding: 32px; + padding: 24px; background: #ffffff; border-bottom: 1px solid #f0f0f0; } @@ -25,17 +25,18 @@ border-bottom: none; } -/* 添加底部装饰条 */ +/* 添加左侧装饰条 */ .info-panel-item::before { content: ''; position: absolute; - left: 3px; - bottom: -1px; + left: 0; + top: 50%; + transform: translateY(-50%); width: 0; - height: 3px; - background: linear-gradient(90deg, #1677ff 0%, #4096ff 100%); + height: 0; + background: linear-gradient(180deg, #1677ff 0%, #4096ff 100%); border-radius: 2px; - transition: width 0.3s ease; + transition: all 0.3s ease; } .info-panel-item:hover { @@ -49,7 +50,8 @@ } .info-panel-item:hover::before { - width: 60px; + width: 3px; + height: 60%; } .info-panel-label { diff --git a/src/components/ListActionBar/ListActionBar.css b/src/components/ListActionBar/ListActionBar.css index b1b6fb6..0b60116 100644 --- a/src/components/ListActionBar/ListActionBar.css +++ b/src/components/ListActionBar/ListActionBar.css @@ -5,11 +5,12 @@ display: flex; justify-content: space-between; align-items: center; - margin-bottom: 16px; + margin-bottom: 4px; padding: 16px; background: #ffffff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + width: 100%; } .list-action-bar-left, diff --git a/src/components/ListTable/ListTable.css b/src/components/ListTable/ListTable.css index 7d96709..43e43f1 100644 --- a/src/components/ListTable/ListTable.css +++ b/src/components/ListTable/ListTable.css @@ -4,40 +4,7 @@ border-radius: 8px; padding: 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); -} - -/* 表格行样式 */ -.list-table-container :global(.ant-table-row) { - cursor: pointer; - transition: all 0.3s; -} - -.list-table-container :global(.ant-table-row:hover) { - background: #f5f5f5; -} - -.list-table-container :global(.ant-table-row.row-selected) { - background: #e6f4ff; -} - -/* 操作列样式 - 重新设计 */ -.list-table-container :global(.ant-table-thead > tr > th:last-child) { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; - color: #ffffff !important; - font-weight: 600; - border-left: 2px solid #e8e8e8; -} - -.list-table-container :global(.ant-table-tbody > tr > td:last-child) { - background: #f8f9ff !important; - border-left: 2px solid #e8e8e8; - box-shadow: -2px 0 4px rgba(0, 0, 0, 0.02); -} - -.list-table-container :global(.ant-table-tbody > tr:hover > td:last-child) { - background: #eef0ff !important; -} - -.list-table-container :global(.ant-table-tbody > tr.row-selected > td:last-child) { - background: #e1e6ff !important; + height: 626px; + overflow-y: auto; + width: 100%; } diff --git a/src/components/ListTable/ListTable.jsx b/src/components/ListTable/ListTable.jsx index 49bc09f..69b90e5 100644 --- a/src/components/ListTable/ListTable.jsx +++ b/src/components/ListTable/ListTable.jsx @@ -28,7 +28,7 @@ function ListTable({ showQuickJumper: true, showTotal: (total) => `共 ${total} 条`, }, - scroll = { x: 1200 }, + scroll = { x: 1200}, onRowClick, selectedRow, loading = false, @@ -45,6 +45,7 @@ function ListTable({ return (
{ + const initial = {} + sections.forEach((section) => { + if (section.defaultCollapsed) { + initial[section.key] = true + } + }) + return initial + }) + + const toggleSection = (key) => { + setCollapsedSections((prev) => ({ + ...prev, + [key]: !prev[key], + })) + } + + return ( +
+ {sections.map((section) => { + const isCollapsed = collapsedSections[section.key] + + return ( +
+ {/* 区块头部 */} +
toggleSection(section.key)}> +
+ {section.icon && {section.icon}} + {section.title} +
+ +
+ + {/* 区块内容 */} + {!isCollapsed && ( +
{section.content}
+ )} +
+ ) + })} +
+ ) +} + +export default SideInfoPanel diff --git a/src/components/SplitLayout/SplitLayout.css b/src/components/SplitLayout/SplitLayout.css new file mode 100644 index 0000000..097fa77 --- /dev/null +++ b/src/components/SplitLayout/SplitLayout.css @@ -0,0 +1,72 @@ +/* 分栏布局容器 */ +.split-layout { + display: flex; + width: 100%; + align-items: flex-start; +} + +/* 横向布局(左右分栏) */ +.split-layout-horizontal { + flex-direction: row; +} + +/* 纵向布局(上下分栏) */ +.split-layout-vertical { + flex-direction: column; +} + +/* 主内容区 */ +.split-layout-main { + flex: 1; + min-width: 0; + width: 100%; + display: flex; + flex-direction: column; +} + +/* 扩展信息区 */ +.split-layout-extend { + flex-shrink: 0; + background: #ffffff; +} + +/* 右侧扩展区(横向布局) */ +.split-layout-extend-right { + height: 693px; + overflow-y: auto; + overflow-x: hidden; + position: sticky; + top: 16px; + padding-right: 4px; +} + +/* 顶部扩展区(纵向布局) */ +.split-layout-extend-top { + width: 100%; +} + +/* 滚动条样式(横向布局右侧扩展区) */ +.split-layout-extend-right::-webkit-scrollbar { + width: 6px; +} + +.split-layout-extend-right::-webkit-scrollbar-track { + background: #f5f5f5; + border-radius: 3px; +} + +.split-layout-extend-right::-webkit-scrollbar-thumb { + background: #d9d9d9; + border-radius: 3px; +} + +.split-layout-extend-right::-webkit-scrollbar-thumb:hover { + background: #bfbfbf; +} + +/* 响应式:小屏幕时隐藏右侧扩展区 */ +@media (max-width: 1200px) { + .split-layout-extend-right { + display: none; + } +} diff --git a/src/components/SplitLayout/SplitLayout.jsx b/src/components/SplitLayout/SplitLayout.jsx new file mode 100644 index 0000000..b54a2f7 --- /dev/null +++ b/src/components/SplitLayout/SplitLayout.jsx @@ -0,0 +1,69 @@ +import './SplitLayout.css' + +/** + * 主内容区布局组件 + * @param {Object} props + * @param {string} props.direction - 布局方向:'horizontal'(左右)| 'vertical'(上下) + * @param {ReactNode} props.mainContent - 主内容区 + * @param {ReactNode} props.extendContent - 扩展内容区 + * @param {number} props.extendSize - 扩展区尺寸(horizontal 模式下为宽度,px) + * @param {number} props.gap - 主内容与扩展区间距(px) + * @param {boolean} props.showExtend - 是否显示扩展区 + * @param {string} props.extendPosition - 扩展区位置(horizontal: 'right', vertical: 'top') + * @param {string} props.className - 自定义类名 + * + * @deprecated 旧参数(向后兼容):leftContent, rightContent, rightWidth, showRight + */ +function SplitLayout({ + // 新 API + direction = 'horizontal', + mainContent, + extendContent, + extendSize = 360, + gap = 16, + showExtend = true, + extendPosition, + className = '', + // 旧 API(向后兼容) + leftContent, + rightContent, + rightWidth, + showRight, +}) { + // 向后兼容:如果使用旧 API,转换为新 API + const actualMainContent = mainContent || leftContent + const actualExtendContent = extendContent || rightContent + const actualExtendSize = extendSize !== 360 ? extendSize : (rightWidth || 360) + const actualShowExtend = showExtend !== undefined ? showExtend : (showRight !== undefined ? showRight : true) + const actualDirection = direction + const actualExtendPosition = extendPosition || (actualDirection === 'horizontal' ? 'right' : 'top') + + return ( +
+ {/* 纵向布局且扩展区在顶部时,先渲染扩展区 */} + {actualDirection === 'vertical' && actualExtendPosition === 'top' && actualShowExtend && actualExtendContent && ( +
+ {actualExtendContent} +
+ )} + + {/* 主内容区 */} +
{actualMainContent}
+ + {/* 横向布局时,扩展区在右侧 */} + {actualDirection === 'horizontal' && actualShowExtend && actualExtendContent && ( +
+ {actualExtendContent} +
+ )} +
+ ) +} + +export default SplitLayout diff --git a/src/components/StatCard/StatCard.css b/src/components/StatCard/StatCard.css new file mode 100644 index 0000000..509d577 --- /dev/null +++ b/src/components/StatCard/StatCard.css @@ -0,0 +1,108 @@ +/* 统计卡片 */ +.stat-card { + padding: 16px; + background: #ffffff; + border-radius: 8px; + border: 1px solid #f0f0f0; + transition: all 0.3s ease; +} + +.stat-card:hover { + border-color: #d9d9d9; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} + +/* 一列布局(默认) */ +.stat-card-column { + /* 继承默认样式 */ +} + +/* 两列布局 */ +.stat-card.stat-card-row { + display: flex; + align-items: center; + gap: 16px; +} + +.stat-card.stat-card-row .stat-card-header { + flex: 1; + margin-bottom: 0; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; +} + +.stat-card.stat-card-row .stat-card-body { + flex-shrink: 0; + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 4px; +} + +/* 卡片头部 */ +.stat-card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} + +.stat-card-title { + font-size: 13px; + color: rgba(0, 0, 0, 0.65); + font-weight: 500; +} + +.stat-card-icon { + font-size: 18px; + display: flex; + align-items: center; +} + +/* 卡片内容 */ +.stat-card-body { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 8px; +} + +.stat-card-value { + font-size: 24px; + font-weight: 600; + line-height: 1; +} + +.stat-card-suffix { + font-size: 14px; + font-weight: 400; + margin-left: 4px; + color: rgba(0, 0, 0, 0.45); +} + +/* 趋势指示器 */ +.stat-card-trend { + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + font-weight: 500; + padding: 2px 6px; + border-radius: 4px; +} + +.stat-card-trend.trend-up { + color: #52c41a; + background: #f6ffed; +} + +.stat-card-trend.trend-down { + color: #ff4d4f; + background: #fff1f0; +} + +.stat-card-trend svg { + font-size: 10px; +} diff --git a/src/components/StatCard/StatCard.jsx b/src/components/StatCard/StatCard.jsx new file mode 100644 index 0000000..e333c5c --- /dev/null +++ b/src/components/StatCard/StatCard.jsx @@ -0,0 +1,78 @@ +import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons' +import './StatCard.css' + +/** + * 统计卡片组件 + * @param {Object} props + * @param {string} props.title - 卡片标题 + * @param {number|string} props.value - 统计值 + * @param {ReactNode} props.icon - 图标 + * @param {string} props.color - 主题颜色,默认 'blue' + * @param {Object} props.trend - 趋势信息 { value: number, direction: 'up' | 'down' } + * @param {string} props.suffix - 后缀单位 + * @param {string} props.layout - 布局模式: 'column' | 'row',默认 'column'(一列) + * @param {string} props.gridColumn - 网格列跨度,如 '1 / -1' 表示占满整行 + * @param {string} props.className - 自定义类名 + * @param {Function} props.onClick - 点击事件处理函数 + * @param {Object} props.style - 自定义样式对象 + */ +function StatCard({ + title, + value, + icon, + color = 'blue', + trend, + suffix = '', + layout = 'column', + gridColumn, + className = '', + onClick, + style: customStyle = {}, +}) { + const colorMap = { + blue: '#1677ff', + green: '#52c41a', + orange: '#faad14', + red: '#ff4d4f', + purple: '#722ed1', + gray: '#8c8c8c', + } + + const themeColor = colorMap[color] || color + + const style = { + ...(gridColumn ? { gridColumn } : {}), + ...customStyle, + } + + return ( +
+
+ {title} + {icon && ( + + {icon} + + )} +
+ +
+
+ {value} + {suffix && {suffix}} +
+ + {trend && ( +
+ {trend.direction === 'up' ? : } + {Math.abs(trend.value)}% +
+ )} +
+
+ ) +} + +export default StatCard diff --git a/src/constants/docsMenuData.json b/src/data/docsMenuData.json similarity index 67% rename from src/constants/docsMenuData.json rename to src/data/docsMenuData.json index 8afad95..99ca1f5 100644 --- a/src/constants/docsMenuData.json +++ b/src/data/docsMenuData.json @@ -10,6 +10,22 @@ } ] }, + { + "key": "layouts", + "label": "布局文档", + "children": [ + { + "key": "main-layout", + "label": "主布局", + "path": "/docs/layouts/main-layout.md" + }, + { + "key": "content-area-layout", + "label": "主内容区布局", + "path": "/docs/layouts/content-area-layout.md" + } + ] + }, { "key": "components", "label": "组件文档", @@ -58,17 +74,26 @@ "key": "toast", "label": "Toast", "path": "/docs/components/Toast.md" - } - ] - }, - { - "key": "pages", - "label": "页面文档", - "children": [ + }, { - "key": "main-layout", - "label": "主布局", - "path": "/docs/pages/main-layout.md" + "key": "split-layout", + "label": "SplitLayout", + "path": "/docs/components/SplitLayout.md" + }, + { + "key": "extend-info-panel", + "label": "ExtendInfoPanel", + "path": "/docs/components/ExtendInfoPanel.md" + }, + { + "key": "stat-card", + "label": "StatCard", + "path": "/docs/components/StatCard.md" + }, + { + "key": "chart-panel", + "label": "ChartPanel", + "path": "/docs/components/ChartPanel.md" } ] } diff --git a/src/constants/headerMenuData.json b/src/data/headerMenuData.json similarity index 100% rename from src/constants/headerMenuData.json rename to src/data/headerMenuData.json diff --git a/src/data/imageData.json b/src/data/imageData.json index afaaa8e..e1a3b1d 100644 --- a/src/data/imageData.json +++ b/src/data/imageData.json @@ -89,6 +89,54 @@ "filePath": "/vms/iso/Kylin-Desktop-V10-SP1-2503-HWE-Release-2503-X86_64.iso", "btPath": "--", "description": "--" + }, + { + "id": 8, + "name": "Kylin-Desktop-V10-SP1-HWE-Release-2303-X86_64", + "os": "linux", + "version": "2303", + "status": "成功", + "uploadTime": "2025-10-09 09:55:41", + "fileName": "Kylin-Desktop-V10-SP1-HWE-Release-2303-X86_64.iso", + "filePath": "/vms/iso/Kylin-Desktop-V10-SP1-HWE-Release-2303-X86_64.iso", + "btPath": "--", + "description": "--" + }, + { + "id": 9, + "name": "ky2203", + "os": "linux", + "version": "2203", + "status": "成功", + "uploadTime": "2025-10-13 09:41:50", + "fileName": "ky2203.iso", + "filePath": "/vms/iso/Kylin-Desktop-V10-SP1-General-Release-2203-X86_64.iso", + "btPath": "--", + "description": "--" + }, + { + "id": 10, + "name": "windows-10", + "os": "windows", + "version": "1", + "status": "成功", + "uploadTime": "2025-10-14 16:54:38", + "fileName": "windows-10.iso", + "filePath": "/vms/iso/cn_windows_10_business.iso", + "btPath": "--", + "description": "--" + }, + { + "id": 11, + "name": "windows-11", + "os": "windows", + "version": "1", + "status": "成功", + "uploadTime": "2025-10-14 16:54:49", + "fileName": "windows-11.iso", + "filePath": "/vms/iso/Windows11.iso", + "btPath": "--", + "description": "--" } ] } diff --git a/src/constants/menuData.json b/src/data/menuData.json similarity index 98% rename from src/constants/menuData.json rename to src/data/menuData.json index 847a67c..161cf4d 100644 --- a/src/constants/menuData.json +++ b/src/data/menuData.json @@ -74,7 +74,7 @@ { "key": "image-system", "label": "系统镜像", - "path": "/image/list" + "path": "/image/system" }, { "key": "image-vm", diff --git a/src/data/vmImageData.json b/src/data/vmImageData.json new file mode 100644 index 0000000..336d72c --- /dev/null +++ b/src/data/vmImageData.json @@ -0,0 +1,24 @@ +{ + "images": [ + { + "id": 1, + "name": "Windows Server 2022 Standard", + "os": "Windows", + "version": "21H2", + "status": "running", + "cpuUsage": 45, + "memoryUsage": 62, + "createTime": "2024-01-15 10:30:00" + }, + { + "id": 2, + "name": "Ubuntu Server 22.04 LTS", + "os": "Linux", + "version": "22.04", + "status": "running", + "cpuUsage": 28, + "memoryUsage": 45, + "createTime": "2024-02-10 14:20:00" + } + ] +} diff --git a/src/pages/DocsPage.jsx b/src/pages/DocsPage.jsx index f93ee7a..5a1ccac 100644 --- a/src/pages/DocsPage.jsx +++ b/src/pages/DocsPage.jsx @@ -6,87 +6,11 @@ import remarkGfm from 'remark-gfm' import rehypeRaw from 'rehype-raw' import rehypeHighlight from 'rehype-highlight' import 'highlight.js/styles/github.css' +import docsMenuData from '../data/docsMenuData.json' import './DocsPage.css' const { Sider, Content } = Layout -// 文档目录数据 -const docsMenuData = [ - { - key: 'design', - label: '设计规范', - children: [ - { - key: 'design-cookbook', - label: '设计手册', - path: '/docs/DESIGN_COOKBOOK.md', - }, - ], - }, - { - key: 'components', - label: '组件文档', - children: [ - { - key: 'components-overview', - label: '组件概览', - path: '/docs/components/README.md', - }, - { - key: 'page-title-bar', - label: 'PageTitleBar', - path: '/docs/components/PageTitleBar.md', - }, - { - key: 'list-action-bar', - label: 'ListActionBar', - path: '/docs/components/ListActionBar.md', - }, - { - key: 'tree-filter-panel', - label: 'TreeFilterPanel', - path: '/docs/components/TreeFilterPanel.md', - }, - { - key: 'list-table', - label: 'ListTable', - path: '/docs/components/ListTable.md', - }, - { - key: 'detail-drawer', - label: 'DetailDrawer', - path: '/docs/components/DetailDrawer.md', - }, - { - key: 'info-panel', - label: 'InfoPanel', - path: '/docs/components/InfoPanel.md', - }, - { - key: 'confirm-dialog', - label: 'ConfirmDialog', - path: '/docs/components/ConfirmDialog.md', - }, - { - key: 'toast', - label: 'Toast', - path: '/docs/components/Toast.md', - }, - ], - }, - { - key: 'pages', - label: '页面文档', - children: [ - { - key: 'main-layout', - label: '主布局', - path: '/docs/pages/main-layout.md', - }, - ], - }, -] - function DocsPage() { const [selectedKey, setSelectedKey] = useState('design-cookbook') const [markdownContent, setMarkdownContent] = useState('') diff --git a/src/pages/HostListPage.css b/src/pages/HostListPage.css index 97821e3..4ee6815 100644 --- a/src/pages/HostListPage.css +++ b/src/pages/HostListPage.css @@ -2,6 +2,25 @@ width: 100%; } +/* 统计卡片网格 */ +.stat-cards-grid { + display: grid; + gap: 12px; +} + +/* 不同列数的网格布局 */ +.stat-cards-grid-2 { + grid-template-columns: repeat(2, 1fr); +} + +.stat-cards-grid-3 { + grid-template-columns: repeat(3, 1fr); +} + +.stat-cards-grid-4 { + grid-template-columns: repeat(4, 1fr); +} + /* 统计面板 - HostListPage 特有 */ .stats-panel { margin-bottom: 16px; @@ -179,14 +198,20 @@ /* 响应式 */ @media (max-width: 1200px) { - .stats-panel :global(.ant-row) { - display: grid; + .stat-cards-grid-4 { + grid-template-columns: repeat(2, 1fr); + } + + .stat-cards-grid-3 { grid-template-columns: repeat(2, 1fr); } } @media (max-width: 768px) { - .stats-panel :global(.ant-row) { + .stat-cards-grid, + .stat-cards-grid-2, + .stat-cards-grid-3, + .stat-cards-grid-4 { grid-template-columns: 1fr; } } diff --git a/src/pages/HostListPage.jsx b/src/pages/HostListPage.jsx index c628241..e806e27 100644 --- a/src/pages/HostListPage.jsx +++ b/src/pages/HostListPage.jsx @@ -14,7 +14,6 @@ import { Card, Row, Col, - Statistic, TreeSelect, } from 'antd' import { @@ -30,6 +29,7 @@ import { DesktopOutlined, DatabaseOutlined, UserOutlined, + DashboardOutlined, } from '@ant-design/icons' import PageTitleBar from '../components/PageTitleBar/PageTitleBar' import ListActionBar from '../components/ListActionBar/ListActionBar' @@ -37,6 +37,9 @@ import TreeFilterPanel from '../components/TreeFilterPanel/TreeFilterPanel' import ListTable from '../components/ListTable/ListTable' import DetailDrawer from '../components/DetailDrawer/DetailDrawer' import InfoPanel from '../components/InfoPanel/InfoPanel' +import SplitLayout from '../components/SplitLayout/SplitLayout' +import ExtendInfoPanel from '../components/ExtendInfoPanel/ExtendInfoPanel' +import StatCard from '../components/StatCard/StatCard' import ConfirmDialog from '../components/ConfirmDialog/ConfirmDialog' import Toast from '../components/Toast/Toast' import hostData from '../data/hostData.json' @@ -54,7 +57,7 @@ function HostListPage() { const [selectedGroupName, setSelectedGroupName] = useState('') const [tempSelectedGroup, setTempSelectedGroup] = useState(null) const [filteredHosts, setFilteredHosts] = useState(hostData.hosts) - const [showStatsPanel, setShowStatsPanel] = useState(true) + const [showStatsPanel, setShowStatsPanel] = useState(false) const [statusFilter, setStatusFilter] = useState(null) // 表格列定义 @@ -613,153 +616,168 @@ function HostListPage() { title="主机列表" description="查看和管理所有接入的主机终端,包括服务器和办公设备" showToggle={true} + defaultExpanded={false} onToggle={(expanded) => setShowStatsPanel(expanded)} /> - {/* 数据统计面板 */} - {showStatsPanel && ( -
- -
- - } - valueStyle={{ color: '#1677ff' }} - /> - - - - handleStatusFilterClick('online')} - style={{ cursor: 'pointer' }} - > - h.status === 'online').length} - prefix={} - valueStyle={{ color: '#52c41a' }} - /> - - - - handleStatusFilterClick('offline')} - style={{ cursor: 'pointer' }} - > - h.status === 'offline').length} - prefix={} - valueStyle={{ color: '#8c8c8c' }} - /> - - - - - } - valueStyle={{ color: '#faad14' }} - /> - - - - - )} - - {/* 操作栏 - 使用新组件 */} - , - type: 'primary', - onClick: () => { - setEditMode('add') - setSelectedHost(null) - setShowDetailDrawer(false) - setShowEditDrawer(true) - }, - }, - { - key: 'batchPowerOn', - label: '批量开机', - icon: , - disabled: selectedRowKeys.length === 0, - onClick: () => console.log('批量开机'), - }, - { - key: 'batchPowerOff', - label: '批量关机', - icon: , - disabled: selectedRowKeys.length === 0, - onClick: () => console.log('批量关机'), - }, - { - key: 'batchDelete', - label: '批量删除', - icon: , - danger: true, - disabled: selectedRowKeys.length === 0, - onClick: handleBatchDelete, - }, - ]} - search={{ - placeholder: '搜索主机名、IP或MAC地址', - value: searchKeyword, - onSearch: handleSearch, - onChange: handleSearch, - }} - filter={{ - content: ( - + {/* 操作栏 - 使用新组件 */} + , + type: 'primary', + onClick: () => { + setEditMode('add') + setSelectedHost(null) + setShowDetailDrawer(false) + setShowEditDrawer(true) + }, + }, + { + key: 'batchPowerOn', + label: '批量开机', + icon: , + disabled: selectedRowKeys.length === 0, + onClick: () => console.log('批量开机'), + }, + { + key: 'batchPowerOff', + label: '批量关机', + icon: , + disabled: selectedRowKeys.length === 0, + onClick: () => console.log('批量关机'), + }, + { + key: 'batchDelete', + label: '批量删除', + icon: , + danger: true, + disabled: selectedRowKeys.length === 0, + onClick: handleBatchDelete, + }, + ]} + search={{ + placeholder: '搜索主机名、IP或MAC地址', + value: searchKeyword, + onSearch: handleSearch, + onChange: handleSearch, + }} + filter={{ + content: ( + + ), + title: '高级筛选', + visible: showFilterPopover, + onVisibleChange: (visible) => { + setShowFilterPopover(visible) + if (visible) { + setTempSelectedGroup(selectedGroup) + } + }, + selectedLabel: selectedGroupName, + isActive: !!selectedGroup, + }} + showRefresh + onRefresh={() => console.log('刷新')} /> - ), - title: '高级筛选', - visible: showFilterPopover, - onVisibleChange: (visible) => { - setShowFilterPopover(visible) - if (visible) { - setTempSelectedGroup(selectedGroup) - } - }, - selectedLabel: selectedGroupName, - isActive: !!selectedGroup, - }} - showRefresh - onRefresh={() => console.log('刷新')} - /> - {/* 数据表格 - 使用新组件 */} - + + } + extendContent={ + , + defaultCollapsed: false, + hideTitleBar: true, + content: ( +
+ } + color="blue" + className={statusFilter === null ? '' : 'stat-card-dimmed'} + onClick={handleTotalClick} + style={{ cursor: 'pointer' }} + /> + h.status === 'online').length} + icon={} + color="green" + className={ + statusFilter === 'online' + ? 'stat-card-active' + : statusFilter !== null + ? 'stat-card-dimmed' + : '' + } + onClick={() => handleStatusFilterClick('online')} + style={{ cursor: 'pointer' }} + /> + h.status === 'offline').length} + icon={} + color="gray" + className={ + statusFilter === 'offline' + ? 'stat-card-active' + : statusFilter !== null + ? 'stat-card-dimmed' + : '' + } + onClick={() => handleStatusFilterClick('offline')} + style={{ cursor: 'pointer' }} + /> + } + color="orange" + /> +
+ ), + }, + ]} + /> + } + showExtend={showStatsPanel} + extendPosition="top" /> {/* 详情抽屉 - 使用新组件 */} diff --git a/src/pages/UserListPage.css b/src/pages/UserListPage.css index 37dff6c..8e29319 100644 --- a/src/pages/UserListPage.css +++ b/src/pages/UserListPage.css @@ -2,6 +2,25 @@ width: 100%; } +/* 统计卡片网格 */ +.stat-cards-grid { + display: grid; + gap: 12px; +} + +/* 不同列数的网格布局 */ +.stat-cards-grid-2 { + grid-template-columns: repeat(2, 1fr); +} + +.stat-cards-grid-3 { + grid-template-columns: repeat(3, 1fr); +} + +.stat-cards-grid-4 { + grid-template-columns: repeat(4, 1fr); +} + /* 统计面板 - UserListPage 特有 */ .stats-panel { margin-bottom: 16px; @@ -143,14 +162,20 @@ /* 响应式 */ @media (max-width: 1200px) { - .stats-panel :global(.ant-row) { - display: grid; + .stat-cards-grid-4 { + grid-template-columns: repeat(2, 1fr); + } + + .stat-cards-grid-3 { grid-template-columns: repeat(2, 1fr); } } @media (max-width: 768px) { - .stats-panel :global(.ant-row) { + .stat-cards-grid, + .stat-cards-grid-2, + .stat-cards-grid-3, + .stat-cards-grid-4 { grid-template-columns: 1fr; } } diff --git a/src/pages/UserListPage.jsx b/src/pages/UserListPage.jsx index cc0313a..46ea7cb 100644 --- a/src/pages/UserListPage.jsx +++ b/src/pages/UserListPage.jsx @@ -7,14 +7,13 @@ import { Select, Input, Divider, - Card, - Row, - Col, - Statistic, Switch, Badge, Drawer, TreeSelect, + Card, + Row, + Col, } from 'antd' import { UserOutlined, @@ -28,6 +27,7 @@ import { CloseCircleOutlined, DesktopOutlined, DatabaseOutlined, + DashboardOutlined, } from '@ant-design/icons' import PageTitleBar from '../components/PageTitleBar/PageTitleBar' import ListActionBar from '../components/ListActionBar/ListActionBar' @@ -35,6 +35,9 @@ import TreeFilterPanel from '../components/TreeFilterPanel/TreeFilterPanel' import ListTable from '../components/ListTable/ListTable' import DetailDrawer from '../components/DetailDrawer/DetailDrawer' import InfoPanel from '../components/InfoPanel/InfoPanel' +import SplitLayout from '../components/SplitLayout/SplitLayout' +import ExtendInfoPanel from '../components/ExtendInfoPanel/ExtendInfoPanel' +import StatCard from '../components/StatCard/StatCard' import ConfirmDialog from '../components/ConfirmDialog/ConfirmDialog' import Toast from '../components/Toast/Toast' import userData from '../data/userData.json' @@ -52,7 +55,7 @@ function UserListPage() { const [selectedGroupName, setSelectedGroupName] = useState('') const [tempSelectedGroup, setTempSelectedGroup] = useState(null) const [filteredUsers, setFilteredUsers] = useState(userData.users) - const [showStatsPanel, setShowStatsPanel] = useState(true) + const [showStatsPanel, setShowStatsPanel] = useState(false) const [statusFilter, setStatusFilter] = useState(null) // 表格列定义 @@ -477,139 +480,154 @@ function UserListPage() { title="用户列表" description="管理系统用户,包括用户信息、权限和授权管理" showToggle={true} + defaultExpanded={false} onToggle={(expanded) => setShowStatsPanel(expanded)} /> - {/* 数据统计面板 */} - {showStatsPanel && ( -
- -
- - } - valueStyle={{ color: '#1677ff' }} - /> - - - - handleStatusFilterClick('enabled')} - style={{ cursor: 'pointer' }} - > - u.status === 'enabled').length} - prefix={} - valueStyle={{ color: '#52c41a' }} - /> - - - - handleStatusFilterClick('disabled')} - style={{ cursor: 'pointer' }} - > - u.status === 'disabled').length} - prefix={} - valueStyle={{ color: '#8c8c8c' }} - /> - - - - - } - valueStyle={{ color: '#faad14' }} - /> - - - - - )} - - {/* 操作栏 - 使用新组件 */} - , - type: 'primary', - onClick: () => { - setEditMode('add') - setSelectedUser(null) - setShowDetailDrawer(false) - setShowEditDrawer(true) - }, - }, - { - key: 'batchDelete', - label: '批量删除', - icon: , - danger: true, - disabled: selectedRowKeys.length === 0, - onClick: handleBatchDelete, - }, - ]} - search={{ - placeholder: '搜索用户名或姓名', - value: searchKeyword, - onSearch: handleSearch, - onChange: handleSearch, - }} - filter={{ - content: ( - + {/* 操作栏 - 使用新组件 */} + , + type: 'primary', + onClick: () => { + setEditMode('add') + setSelectedUser(null) + setShowDetailDrawer(false) + setShowEditDrawer(true) + }, + }, + { + key: 'batchDelete', + label: '批量删除', + icon: , + danger: true, + disabled: selectedRowKeys.length === 0, + onClick: handleBatchDelete, + }, + ]} + search={{ + placeholder: '搜索用户名或姓名', + value: searchKeyword, + onSearch: handleSearch, + onChange: handleSearch, + }} + filter={{ + content: ( + + ), + title: '高级筛选', + visible: showFilterPopover, + onVisibleChange: (visible) => { + setShowFilterPopover(visible) + if (visible) { + setTempSelectedGroup(selectedGroup) + } + }, + selectedLabel: selectedGroupName, + isActive: !!selectedGroup, + }} + showRefresh + onRefresh={() => console.log('刷新')} /> - ), - title: '高级筛选', - visible: showFilterPopover, - onVisibleChange: (visible) => { - setShowFilterPopover(visible) - if (visible) { - setTempSelectedGroup(selectedGroup) - } - }, - selectedLabel: selectedGroupName, - isActive: !!selectedGroup, - }} - showRefresh - onRefresh={() => console.log('刷新')} - /> - {/* 数据表格 - 使用新组件 */} - + + } + extendContent={ + , + defaultCollapsed: false, + hideTitleBar: true, + content: ( +
+ } + color="blue" + className={statusFilter === null ? '' : 'stat-card-dimmed'} + onClick={handleTotalClick} + style={{ cursor: 'pointer' }} + /> + u.status === 'enabled').length} + icon={} + color="green" + className={ + statusFilter === 'enabled' + ? 'stat-card-active' + : statusFilter !== null + ? 'stat-card-dimmed' + : '' + } + onClick={() => handleStatusFilterClick('enabled')} + style={{ cursor: 'pointer' }} + /> + u.status === 'disabled').length} + icon={} + color="gray" + className={ + statusFilter === 'disabled' + ? 'stat-card-active' + : statusFilter !== null + ? 'stat-card-dimmed' + : '' + } + onClick={() => handleStatusFilterClick('disabled')} + style={{ cursor: 'pointer' }} + /> + } + color="orange" + /> +
+ ), + }, + ]} + /> + } + showExtend={showStatsPanel} + extendPosition="top" /> {/* 详情抽屉 - 使用新组件 */} diff --git a/src/pages/VirtualMachineImagePage.css b/src/pages/VirtualMachineImagePage.css new file mode 100644 index 0000000..77ed4b4 --- /dev/null +++ b/src/pages/VirtualMachineImagePage.css @@ -0,0 +1,33 @@ +/* 虚拟机镜像页面 */ +.vm-image-page { + width: 100%; +} + +/* 统计卡片网格 */ +.stat-cards-grid { + display: grid; + gap: 12px; +} + +/* 不同列数的网格布局 */ +.stat-cards-grid-2 { + grid-template-columns: repeat(2, 1fr); +} + +.stat-cards-grid-3 { + grid-template-columns: repeat(3, 1fr); +} + +.stat-cards-grid-4 { + grid-template-columns: repeat(4, 1fr); +} + +/* 响应式 */ +@media (max-width: 768px) { + .stat-cards-grid, + .stat-cards-grid-2, + .stat-cards-grid-3, + .stat-cards-grid-4 { + grid-template-columns: 1fr; + } +} diff --git a/src/pages/VirtualMachineImagePage.jsx b/src/pages/VirtualMachineImagePage.jsx new file mode 100644 index 0000000..955a1eb --- /dev/null +++ b/src/pages/VirtualMachineImagePage.jsx @@ -0,0 +1,319 @@ +import { useState } from 'react' +import { Button, Tag, Space } from 'antd' +import { + PlusOutlined, + DeleteOutlined, + DatabaseOutlined, + CheckCircleOutlined, + CloseCircleOutlined, + LineChartOutlined, + PieChartOutlined, + DashboardOutlined, +} from '@ant-design/icons' +import PageTitleBar from '../components/PageTitleBar/PageTitleBar' +import ListActionBar from '../components/ListActionBar/ListActionBar' +import ListTable from '../components/ListTable/ListTable' +import SplitLayout from '../components/SplitLayout/SplitLayout' +import ExtendInfoPanel from '../components/ExtendInfoPanel/ExtendInfoPanel' +import StatCard from '../components/StatCard/StatCard' +import ChartPanel from '../components/ChartPanel/ChartPanel' +import vmImageData from '../data/vmImageData.json' +import './VirtualMachineImagePage.css' + +function VirtualMachineImagePage() { + const [selectedRowKeys, setSelectedRowKeys] = useState([]) + const [searchKeyword, setSearchKeyword] = useState('') + const [filteredImages, setFilteredImages] = useState(vmImageData.images) + const [showExtendPanel, setShowExtendPanel] = useState(true) + + // 表格列定义 + const columns = [ + { + title: '序号', + dataIndex: 'id', + key: 'id', + width: 80, + align: 'center', + }, + { + title: '镜像名称', + dataIndex: 'name', + key: 'name', + width: 250, + ellipsis: true, + }, + { + title: '操作系统', + dataIndex: 'os', + key: 'os', + width: 120, + }, + { + title: '版本', + dataIndex: 'version', + key: 'version', + width: 120, + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + width: 100, + align: 'center', + render: (status) => { + const colorMap = { + running: 'green', + stopped: 'default', + error: 'red', + } + const textMap = { + running: '运行中', + stopped: '已停止', + error: '错误', + } + return {textMap[status]} + }, + }, + { + title: 'CPU使用率', + dataIndex: 'cpuUsage', + key: 'cpuUsage', + width: 110, + align: 'center', + render: (value) => `${value}%`, + }, + { + title: '内存使用率', + dataIndex: 'memoryUsage', + key: 'memoryUsage', + width: 110, + align: 'center', + render: (value) => `${value}%`, + }, + { + title: '创建时间', + dataIndex: 'createTime', + key: 'createTime', + width: 180, + }, + { + title: '操作', + key: 'action', + width: 180, + fixed: 'right', + render: (_, record) => ( + e.stopPropagation()}> + + + + + ), + }, + ] + + // 处理搜索 + const handleSearch = (value) => { + setSearchKeyword(value) + if (value) { + const filtered = vmImageData.images.filter( + (image) => + image.name.toLowerCase().includes(value.toLowerCase()) || + image.os.toLowerCase().includes(value.toLowerCase()) + ) + setFilteredImages(filtered) + } else { + setFilteredImages(vmImageData.images) + } + } + + // 批量删除 + const handleBatchDelete = () => { + console.log('批量删除', selectedRowKeys) + } + + // 统计数据 + const totalCount = vmImageData.images.length + const runningCount = vmImageData.images.filter((img) => img.status === 'running').length + const stoppedCount = vmImageData.images.filter((img) => img.status === 'stopped').length + const errorCount = vmImageData.images.filter((img) => img.status === 'error').length + + // CPU使用率趋势数据 + const cpuTrendData = { + xAxis: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00'], + series: [25, 32, 45, 58, 42, 38], + } + + // 内存使用率趋势数据 + const memoryTrendData = { + xAxis: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00'], + series: [40, 48, 55, 62, 58, 52], + } + + // 状态分布数据 + const statusDistData = { + series: [ + { value: runningCount, name: '运行中', itemStyle: { color: '#52c41a' } }, + { value: stoppedCount, name: '已停止', itemStyle: { color: '#8c8c8c' } }, + { value: errorCount, name: '错误', itemStyle: { color: '#ff4d4f' } }, + ], + } + + // 操作系统分布数据 + const osDistData = { + xAxis: ['Windows', 'Linux', 'Ubuntu', 'CentOS'], + series: [12, 8, 7, 5], + } + + return ( +
+ setShowExtendPanel(expanded)} + /> + + + , + type: 'primary', + onClick: () => console.log('新建镜像'), + }, + { + key: 'batchDelete', + label: '批量删除', + icon: , + danger: true, + disabled: selectedRowKeys.length === 0, + onClick: handleBatchDelete, + }, + ]} + search={{ + placeholder: '搜索镜像名称或操作系统', + value: searchKeyword, + onSearch: handleSearch, + onChange: handleSearch, + }} + showRefresh + onRefresh={() => console.log('刷新')} + /> + + + + } + extendContent={ + , + content: ( +
+ } + color="blue" + gridColumn="1 / -1" + /> + } + color="green" + trend={{ value: 12, direction: 'up' }} + /> + } + color="gray" + /> + } + color="red" + trend={{ value: 5, direction: 'down' }} + gridColumn="1 / -1" + /> +
+ ), + }, + { + key: 'status', + title: '状态统计', + icon: , + content: ( + <> + + + + ), + }, + { + key: 'monitor', + title: '性能监控', + icon: , + defaultCollapsed: false, + content: ( + <> + + + + ), + }, + ]} + /> + } + extendSize={360} + showExtend={showExtendPanel} + /> +
+ ) +} + +export default VirtualMachineImagePage diff --git a/src/styles/globals.css b/src/styles/globals.css index 0508072..654352c 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -66,11 +66,11 @@ body { .content-container { max-width: 1200px; margin: 0 auto; - padding: 0 24px; + padding: 16px; } .page-header { - margin-bottom: 24px; + margin-bottom: 8px; } .card-shadow { diff --git a/yarn.lock b/yarn.lock index a070c66..6fbbda8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1134,6 +1134,14 @@ eastasianwidth@^0.2.0: resolved "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +echarts@^6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/echarts/-/echarts-6.0.0.tgz" + integrity sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ== + dependencies: + tslib "2.3.0" + zrender "6.0.0" + electron-to-chromium@^1.5.238: version "1.5.244" resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz" @@ -4087,6 +4095,11 @@ ts-interface-checker@^0.1.9: resolved "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== +tslib@2.3.0: + version "2.3.0" + resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz" + integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz" @@ -4371,6 +4384,13 @@ yocto-queue@^0.1.0: resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +zrender@6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/zrender/-/zrender-6.0.0.tgz" + integrity sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg== + dependencies: + tslib "2.3.0" + zwitch@^2.0.0: version "2.0.4" resolved "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz"