diff --git a/.gitignore b/.gitignore index 351a4836d..1005c5eda 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,7 @@ vite.config.ts.* .claude/ CLAUDE.md # AGENTS.md 允许追踪(跨 agent 通用规范,方案 C) + +# Diagnostic documents - DO NOT COMMIT +docs/问题诊断/*.md +!docs/问题诊断/README.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..faacd14b6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,297 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## 项目概述 + +IoT 设备管理前端,基于 Vue 3 + Vben Admin 框架构建的 Turborepo monorepo。包含 4 个 UI 框架变体(Ant Design、Element Plus、Naive UI、TDesign),共享核心代码库。 + +**生产部署信息:** +- **容器名称:** `aiot-web-antd`(生产使用 Ant Design 版本) +- **端口映射:** 9090:80 +- **部署位置:** 腾讯云服务器 `/opt/vsp-platform` +- **后端 API:** http://服务器IP:48080(芋道微服务网关) +- **访问地址:** http://服务器IP:9090 +- **默认账号:** admin / admin123 + +## 常用命令 + +### 安装依赖 + +```bash +# 需要 pnpm 10.0.0+ 和 Node.js 20.19.0+ +pnpm install +``` + +### 本地开发 + +```bash +# 运行 Ant Design 版本(生产使用) +pnpm dev:antd + +# 运行其他版本 +pnpm dev:ele # Element Plus +pnpm dev:naive # Naive UI +pnpm dev:tdesign # TDesign + +# 开发服务器默认端口:5666 +# 访问:http://localhost:5666 +``` + +### 构建 + +```bash +# 构建 Ant Design 版本 +pnpm build:antd + +# 构建所有版本 +pnpm build + +# 构建其他版本 +pnpm build:ele +pnpm build:naive +pnpm build:tdesign +``` + +### 代码质量 + +```bash +# 代码检查 +pnpm lint + +# 代码格式化 +pnpm format + +# TypeScript 类型检查 +pnpm check:type + +# 检查循环依赖 +pnpm check:circular +``` + +### 测试 + +```bash +# 单元测试(Vitest) +pnpm test:unit + +# E2E 测试(Playwright) +pnpm test:e2e +``` + +### 清理 + +```bash +# 清理构建产物 +pnpm clean + +# 清理并重新安装依赖 +pnpm reinstall +``` + +### Docker 部署(生产环境) + +```bash +# 构建 Ant Design 版本镜像 +cd apps/web-antd +docker build -t aiot-web-antd:latest . + +# 运行容器 +docker run -d \ + --name aiot-web-antd \ + -p 9090:80 \ + -e VITE_BASE_URL=http://服务器IP:48080 \ + aiot-web-antd:latest + +# 查看日志 +docker logs -f aiot-web-antd + +# 重启容器 +docker restart aiot-web-antd +``` + +## 项目结构 + +### 目录组织 + +``` +iot-device-management-frontend/ +├── apps/ # 应用层(4 个 UI 框架变体) +│ ├── web-antd/ # Ant Design 版本(生产使用) +│ ├── web-ele/ # Element Plus 版本 +│ ├── web-naive/ # Naive UI 版本 +│ └── web-tdesign/ # TDesign 版本 +├── packages/ # 共享包 +│ ├── @core/ # 核心库(框架无关) +│ │ ├── base/ # 基础库(design tokens, icons, shared) +│ │ ├── composables/ # Vue composables +│ │ ├── preferences/ # 设置管理 +│ │ └── ui-kit/ # UI 组件(form, layout, menu, popup, tabs) +│ ├── effects/ # 功能包 +│ │ ├── access/ # 访问控制 +│ │ ├── hooks/ # React-like hooks +│ │ ├── layouts/ # 布局组件 +│ │ └── plugins/ # 插件(echarts, vxe-table等) +│ └── constants/ # 共享常量 +├── internal/ # 内部工具 +│ ├── lint-configs/ # ESLint、Prettier、Stylelint 配置 +│ ├── tsconfig/ # TypeScript 配置 +│ ├── vite-config/ # Vite 配置 +│ └── tailwind-config/ # Tailwind CSS 配置 +└── docs/ # 文档站点 +``` + +### 核心概念 + +**Workspace 架构:** +- 使用 pnpm workspaces 管理 monorepo +- 使用 Turborepo 进行构建优化 +- 所有 apps 共享 `@core` 和 `effects` 包 +- 修改 `@core` 包自动反映到所有 apps(Vite HMR) + +**包命名规范:** +- `@vben/` — Vben Admin 框架包 +- `@core/` — 项目核心包 +- `workspace:*` — workspace 内部依赖 + +## 关键页面路由 + +### AIoT 告警管理 +- `/aiot/alarm/event` — 告警事件列表 + - 分页、筛选、统计 + - 实时 WebSocket 推送 + - 告警处理(确认、忽略、派单) + +### 边缘设备管理 +- `/aiot/edge/device` — 边缘设备列表 + - 设备状态监控 + - 告警数统计 + +### 摄像头与 ROI 管理 +- `/aiot/camera` — 摄像头管理 + - 摄像头列表、添加、编辑 + - 与 WVP 平台同步 +- `/aiot/roi` — ROI 区域配置 + - ROI 绘制(polygon) + - 算法绑定配置 + +## 环境配置 + +### 开发环境(apps/web-antd/.env.development) + +```bash +VITE_PORT=5666 # 开发服务器端口 +VITE_BASE_URL=http://127.0.0.1:48080 # 后端 API 地址 +VITE_GLOB_API_URL=/admin-api # API 路径前缀 +VITE_DEVTOOLS=false # Vue DevTools +VITE_APP_DEFAULT_USERNAME=admin # 默认用户名 +VITE_APP_DEFAULT_PASSWORD=admin123 # 默认密码 +``` + +### 生产环境(apps/web-antd/.env.production) + +```bash +VITE_BASE_URL=http://服务器IP:48080 +VITE_GLOB_API_URL=/admin-api +``` + +## API 集成 + +### 后端接口(通过 aiot-gateway 48080) + +**告警事件:** +- `GET /admin-api/aiot/alarm/event/page` — 分页查询 +- `GET /admin-api/aiot/alarm/event/get` — 获取详情 +- `PUT /admin-api/aiot/alarm/event/handle` — 处理告警 +- `GET /admin-api/aiot/alarm/event/statistics` — 统计 + +**边缘设备:** +- `GET /admin-api/aiot/edge/device/page` — 分页查询 +- `GET /admin-api/aiot/edge/device/statistics` — 统计 + +**WebSocket(直连 vsp-service 8000):** +- `ws://服务器IP:8000/ws/alerts` — 实时告警推送 + +### API 调用示例 + +```typescript +// 使用 @vben/request 统一请求工具 +import { requestClient } from '@vben/request'; + +// 查询告警列表 +const response = await requestClient.get('/admin-api/aiot/alarm/event/page', { + params: { pageNo: 1, pageSize: 20 } +}); + +// 处理告警 +await requestClient.put('/admin-api/aiot/alarm/event/handle', { + id: alarmId, + handleStatus: 'CONFIRMED', + handleRemark: '已确认' +}); +``` + +## 开发工作流 + +### 添加新页面 +1. 在 `apps/web-antd/src/views/` 下创建页面组件 +2. 在 `apps/web-antd/src/router/routes/modules/` 添加路由配置 +3. 如果是共享组件,放到 `packages/@core/ui-kit/` 或 `packages/effects/common-ui/` + +### 修改共享组件 +1. 修改 `packages/@core/ui-kit/` 下的组件 +2. Vite HMR 自动刷新所有使用该组件的 apps +3. 无需重新构建 + +### 添加 API 接口 +1. 在 `apps/web-antd/src/api/` 创建 API 文件 +2. 使用 `requestClient` 发起请求 +3. 定义 TypeScript 类型 + +### 国际化(i18n) +- 语言文件:`apps/web-antd/src/locales/langs/` +- 中文:`zh-CN/` +- 英文:`en-US/` + +## 常见问题 + +### pnpm install 失败 +```bash +# 清理并重装 +pnpm clean +rm -rf node_modules pnpm-lock.yaml +pnpm install +``` + +### 构建失败:循环依赖 +```bash +# 检查循环依赖 +pnpm check:circular +``` + +### 类型错误 +```bash +# 运行类型检查 +pnpm check:type +``` + +### WebSocket 连接失败 +检查 vsp-service 是否运行: +```bash +docker logs vsp-service +``` + +## Git 提交规范 + +在修改代码后,使用中文提交信息: + +```bash +git add . +git commit -m "功能:添加XXX功能 + +详细说明... + +Co-Authored-By: Claude Sonnet 4.5 " +``` + +**不要立即 push**,等待用户指示再推送到远程。 diff --git a/apps/web-antd/.env b/apps/web-antd/.env index a0bfde11e..135300319 100644 --- a/apps/web-antd/.env +++ b/apps/web-antd/.env @@ -1,5 +1,8 @@ # 应用标题 -VITE_APP_TITLE=芋道管理系统 +VITE_APP_TITLE=物联运维平台 + +# 前端身份(OAuth2 client_id):后端按此生成 token 并按 platform 过滤菜单 +VITE_APP_CLIENT_ID=iot # 应用命名空间,用于缓存、store等功能的前缀,确保隔离 VITE_APP_NAMESPACE=yudao-vben-antd diff --git a/apps/web-antd/.env.development b/apps/web-antd/.env.development index cff255764..26b2ef4f2 100644 --- a/apps/web-antd/.env.development +++ b/apps/web-antd/.env.development @@ -1,5 +1,5 @@ -# 端口号 -VITE_PORT=5666 +# 端口号(IoT 平台,与业务平台 5666 错开) +VITE_PORT=5667 VITE_BASE=/ @@ -19,3 +19,10 @@ VITE_INJECT_APP_LOADING=true VITE_APP_DEFAULT_USERNAME=admin # 默认登录密码 VITE_APP_DEFAULT_PASSWORD=admin123 + +# 业务平台前端地址(用于 IoT -> 业务平台 SSO 跳转) +VITE_BIZ_BASE_URL=http://127.0.0.1:5666 + +# WVP 视频平台内置账号(仅本地开发默认值;生产必须通过 .env.production 覆盖) +VITE_WVP_USERNAME=admin +VITE_WVP_PASSWORD_MD5=21232f297a57a5a743894a0e4a801fc3 diff --git a/apps/web-antd/.env.production b/apps/web-antd/.env.production index a8f3d29a9..13efa0ac1 100644 --- a/apps/web-antd/.env.production +++ b/apps/web-antd/.env.production @@ -23,4 +23,11 @@ VITE_INJECT_APP_LOADING=true VITE_ARCHIVER=true # 验证码的开关 -VITE_APP_CAPTCHA_ENABLE=true \ No newline at end of file +VITE_APP_CAPTCHA_ENABLE=true + +# 业务平台前端地址(SSO 跳转目标),生产部署时按实际域名修改 +VITE_BIZ_BASE_URL=https://biz.example.com + +# WVP 视频平台内置账号(生产必须按实际配置覆盖,不要沿用示例值) +VITE_WVP_USERNAME=admin +VITE_WVP_PASSWORD_MD5=21232f297a57a5a743894a0e4a801fc3 \ No newline at end of file diff --git a/apps/web-antd/.gitignore b/apps/web-antd/.gitignore new file mode 100644 index 000000000..024a956ab --- /dev/null +++ b/apps/web-antd/.gitignore @@ -0,0 +1,3 @@ +# 由 vite.config.mts 的 copyLottiePlayer 插件在 dev/build 时自动从 +# node_modules/lottie-web 拷贝,不需要进版本控制 +public/lottie_light.min.js diff --git a/apps/web-antd/index.html b/apps/web-antd/index.html index dffc1503a..3451fec2d 100644 --- a/apps/web-antd/index.html +++ b/apps/web-antd/index.html @@ -13,7 +13,7 @@ /> <%= VITE_APP_TITLE %> - + + + diff --git a/apps/web-antd/package.json b/apps/web-antd/package.json index 847d65725..8027ed3df 100644 --- a/apps/web-antd/package.json +++ b/apps/web-antd/package.json @@ -57,6 +57,7 @@ "diagram-js": "catalog:", "fast-xml-parser": "catalog:", "highlight.js": "catalog:", + "lottie-web": "^5.13.0", "pinia": "catalog:", "steady-xml": "catalog:", "tinymce": "catalog:", diff --git a/apps/web-antd/public/loading.json b/apps/web-antd/public/loading.json new file mode 100644 index 000000000..89edde9ea --- /dev/null +++ b/apps/web-antd/public/loading.json @@ -0,0 +1 @@ +{"v":"5.9.6","fr":25,"ip":0,"op":101,"w":600,"h":600,"nm":"huakuai","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"five","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":21,"s":[10]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":26,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":51,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":57,"s":[-10]},{"t":62,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.351,"y":1},"o":{"x":0.276,"y":0},"t":15,"s":[520,360,0],"to":[-4.667,-15,0],"ti":[22.917,0,0]},{"i":{"x":0.351,"y":1},"o":{"x":0.276,"y":0},"t":21,"s":[465,250,0],"to":[-22.917,0,0],"ti":[0,0,0]},{"i":{"x":0.416,"y":0.416},"o":{"x":0.317,"y":0.317},"t":26,"s":[410,360,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.467,"y":0.739},"o":{"x":0.225,"y":0},"t":51,"s":[410,360,0],"to":[0,0,0],"ti":[-18.333,0,0]},{"i":{"x":0.579,"y":1},"o":{"x":0.256,"y":0.125},"t":56,"s":[464.291,250,0],"to":[18.333,0,0],"ti":[0,0,0]},{"t":61,"s":[520,360,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-162,-58,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":15,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":21,"s":[90,110,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":25,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":27,"s":[120,70,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":29,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":51,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":57,"s":[90,110,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":61,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":63,"s":[120,70,100]},{"t":65,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,80],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":10,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[0.623529434204,0.917647063732,0.494117647409,1]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":26,"s":[0.443137258291,0.737254917622,0.078431375325,1]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":51,"s":[0.443137258291,0.737254917622,0.078431375325,1]},{"t":61,"s":[0.623529434204,0.917647063732,0.494117647409,1]}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-162,-98],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":101,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"four","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":11,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":16,"s":[10]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":21,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":56,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":61,"s":[-10]},{"t":66,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.316,"y":1},"o":{"x":0.24,"y":0},"t":11,"s":[410,360,0],"to":[-4.667,-15,0],"ti":[22.917,0,0]},{"i":{"x":0.316,"y":1},"o":{"x":0.24,"y":0},"t":16,"s":[355,250,0],"to":[-22.917,0,0],"ti":[6.307,-19.302,0]},{"i":{"x":0.316,"y":1},"o":{"x":0.167,"y":0},"t":21,"s":[300,360,0],"to":[-0.116,0.355,0],"ti":[0,0,0]},{"i":{"x":0.583,"y":0.775},"o":{"x":0.167,"y":0},"t":56,"s":[300,360,0],"to":[0,0,0],"ti":[-27.317,0.053,0]},{"i":{"x":0.833,"y":1},"o":{"x":0.417,"y":0.224},"t":61,"s":[354.435,250,0],"to":[28.392,-0.055,0],"ti":[-2.765,-8.21,0]},{"t":66,"s":[410,360,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-162,-58,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":11,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":16,"s":[90,110,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":20,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":22,"s":[120,70,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":24,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":56,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":61,"s":[90,110,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":65,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":67,"s":[120,70,100]},{"t":69,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,80],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":10,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":11,"s":[0.443137258291,0.737254917622,0.078431375325,1]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":21,"s":[0.298039227724,0.623529434204,0.043137256056,1]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":56,"s":[0.298039227724,0.623529434204,0.043137256056,1]},{"t":66,"s":[0.443137258291,0.737254917622,0.078431375325,1]}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-162,-98],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":101,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"three","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":6,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":11,"s":[10]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":16,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":61,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":66,"s":[-10]},{"t":71,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.369,"y":1},"o":{"x":0.258,"y":0},"t":6,"s":[300,360,0],"to":[-5.167,-18.5,0],"ti":[22.917,0,0]},{"i":{"x":0.369,"y":1},"o":{"x":0.258,"y":0},"t":11,"s":[245,250,0],"to":[-22.917,0,0],"ti":[2.434,-10.892,0]},{"i":{"x":0.369,"y":1},"o":{"x":0.167,"y":0},"t":16,"s":[190,360,0],"to":[-0.385,1.723,0],"ti":[0,0,0]},{"i":{"x":0.583,"y":0.776},"o":{"x":0.167,"y":0},"t":61,"s":[190,360,0],"to":[0,0,0],"ti":[-29.055,-0.462,0]},{"i":{"x":0.833,"y":1},"o":{"x":0.417,"y":0.224},"t":66,"s":[245.406,250,0],"to":[28.268,0.45,0],"ti":[-3.029,-9.151,0]},{"t":71,"s":[300,360,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-162,-58,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":6,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":11,"s":[90,110,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":15,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":17,"s":[120,70,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":19,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":61,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":66,"s":[90,110,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":70,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":72,"s":[120,70,100]},{"t":74,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,80],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":10,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":6,"s":[0.298039227724,0.623529434204,0.043137256056,1]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":16,"s":[0.176470592618,0.474509805441,0.023529412225,1]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":61,"s":[0.176470592618,0.474509805441,0.023529412225,1]},{"t":71,"s":[0.298039227724,0.623529434204,0.043137256056,1]}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-162,-98],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":101,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"two","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":1,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":6,"s":[10]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":11,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":67,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":73,"s":[-10]},{"t":79,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.458,"y":1},"o":{"x":0.222,"y":0},"t":1,"s":[190,360,0],"to":[-2.417,-11.5,0],"ti":[22.917,0,0]},{"i":{"x":0.458,"y":1},"o":{"x":0.222,"y":0},"t":6,"s":[135,250,0],"to":[-22.917,0,0],"ti":[2.778,-8.535,0]},{"i":{"x":0.458,"y":1},"o":{"x":0.167,"y":0},"t":11,"s":[80,360,0],"to":[-0.664,2.04,0],"ti":[0,0,0]},{"i":{"x":0.583,"y":0.731},"o":{"x":0.167,"y":0},"t":67,"s":[80,360,0],"to":[0,0,0],"ti":[-28.172,-0.208,0]},{"i":{"x":0.833,"y":1},"o":{"x":0.417,"y":0.269},"t":73,"s":[135.986,250,0],"to":[26.782,0.198,0],"ti":[3.225,8.657,0]},{"t":79,"s":[190,360,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-162,-58,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":1,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":6,"s":[90,110,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":12,"s":[120,70,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":14,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":67,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":73,"s":[90,110,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":78,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":80,"s":[120,70,100]},{"t":82,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,80],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":10,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":1,"s":[0.176470592618,0.474509805441,0.023529412225,1]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":11,"s":[0.141176477075,0.388235300779,0.019607843831,1]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":67,"s":[0.141176477075,0.388235300779,0.019607843831,1]},{"t":79,"s":[0.176470592618,0.474509805441,0.023529412225,1]}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-162,-98],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":101,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"one","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.111,"y":1},"o":{"x":0.26,"y":0},"t":0,"s":[80,320,0],"to":[73.333,0,0],"ti":[-73.333,0,0]},{"i":{"x":0.082,"y":0.082},"o":{"x":0.213,"y":0.213},"t":39.967,"s":[520,320,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.112,"y":1},"o":{"x":0.26,"y":0},"t":50,"s":[520,320,0],"to":[-73.333,0,0],"ti":[73.333,0,0]},{"t":90,"s":[80,320,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-162,-98,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,80],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":10,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.444],"y":[1]},"o":{"x":[0.249],"y":[0]},"t":0,"s":[0.141176477075,0.388235300779,0.019607843831,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":40,"s":[0.623529434204,0.917647063732,0.494117647409,1]},{"i":{"x":[0.444],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":50,"s":[0.623529434204,0.917647063732,0.494117647409,1]},{"t":90,"s":[0.141176477075,0.388235300779,0.019607843831,1]}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-162,-98],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":101,"st":0,"ct":1,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/apps/web-antd/public/login-illustration.svg b/apps/web-antd/public/login-illustration.svg new file mode 100644 index 000000000..44b46c3cb --- /dev/null +++ b/apps/web-antd/public/login-illustration.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web-antd/public/logo.svg b/apps/web-antd/public/logo.svg new file mode 100644 index 000000000..308ca02b1 --- /dev/null +++ b/apps/web-antd/public/logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web-antd/public/lottie-tint.js b/apps/web-antd/public/lottie-tint.js new file mode 100644 index 000000000..3613a0d7a --- /dev/null +++ b/apps/web-antd/public/lottie-tint.js @@ -0,0 +1,158 @@ +/** + * Lottie 主题色适配器 + * + * 把 public/loading.json 里硬编码的 5 档绿色替换为当前主题色的 5 档亮度, + * 供 loading.html 的初始白屏动画和 Vue 内的 LottieLoading 组件共用。 + * + * 暴露为 window.__lottieTint,兼容 ES5,可被 + + diff --git a/apps/web-antd/src/components/lottie-loading/index.ts b/apps/web-antd/src/components/lottie-loading/index.ts new file mode 100644 index 000000000..51954f9df --- /dev/null +++ b/apps/web-antd/src/components/lottie-loading/index.ts @@ -0,0 +1 @@ +export { default as LottieLoading } from './LottieLoading.vue'; diff --git a/apps/web-antd/src/constants/sso.ts b/apps/web-antd/src/constants/sso.ts new file mode 100644 index 000000000..fe14e2130 --- /dev/null +++ b/apps/web-antd/src/constants/sso.ts @@ -0,0 +1,34 @@ +/** + * SSO / OAuth2 相关常量 + * + * 集中维护 client_id、state 存储 key 等跨文件复用的字符串,避免魔法字符串散落。 + */ + +/** 本平台(物联运维)在芋道后端登记的 OAuth2 客户端编号 */ +export const IOT_CLIENT_ID = 'iot'; + +/** 业务平台在芋道后端登记的 OAuth2 客户端编号 */ +export const BIZ_CLIENT_ID = 'biz'; + +/** 跳业务平台发起 OAuth2 授权时,state 写入 sessionStorage 的 key */ +export const SSO_STATE_STORAGE_KEY = 'sso:biz:state'; + +/** 本平台自身 SSO 回调页面路径 */ +export const SSO_CALLBACK_PATH = '/sso-callback'; + +/** + * 生成一个密码学安全的随机 state。 + * 首选 crypto.randomUUID;低版本浏览器回退 getRandomValues。 + */ +export function generateOauthState(): string { + if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { + return crypto.randomUUID(); + } + if (typeof crypto !== 'undefined' && crypto.getRandomValues) { + const arr = new Uint8Array(16); + crypto.getRandomValues(arr); + return Array.from(arr, (b) => b.toString(16).padStart(2, '0')).join(''); + } + // 极端降级:不会加密但比 Math.random 多一层时间戳 + return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`; +} diff --git a/apps/web-antd/src/layouts/auth.vue b/apps/web-antd/src/layouts/auth.vue index 8ba66e85a..4cfe1eff1 100644 --- a/apps/web-antd/src/layouts/auth.vue +++ b/apps/web-antd/src/layouts/auth.vue @@ -1,25 +1,175 @@ + + diff --git a/apps/web-antd/src/layouts/basic.vue b/apps/web-antd/src/layouts/basic.vue index e8715a895..5eaadc18d 100644 --- a/apps/web-antd/src/layouts/basic.vue +++ b/apps/web-antd/src/layouts/basic.vue @@ -1,14 +1,13 @@ + + diff --git a/apps/web-antd/src/views/_core/authentication/sso-callback.vue b/apps/web-antd/src/views/_core/authentication/sso-callback.vue new file mode 100644 index 000000000..f9a802cd3 --- /dev/null +++ b/apps/web-antd/src/views/_core/authentication/sso-callback.vue @@ -0,0 +1,91 @@ + + + diff --git a/apps/web-antd/src/views/iot/device/device/detail/index.vue b/apps/web-antd/src/views/iot/device/device/detail/index.vue index 4b549a363..cbe9319e7 100644 --- a/apps/web-antd/src/views/iot/device/device/detail/index.vue +++ b/apps/web-antd/src/views/iot/device/device/detail/index.vue @@ -12,13 +12,14 @@ import { DeviceTypeEnum } from '@vben/constants'; import { message, Tabs } from 'ant-design-vue'; import { getDevice } from '#/api/iot/device/device'; -import { getProduct } from '#/api/iot/product/product'; +import { getProduct, ProtocolTypeEnum } from '#/api/iot/product/product'; import { getThingModelListByProductId } from '#/api/iot/thingmodel'; import DeviceDetailConfig from './modules/config.vue'; import DeviceDetailsHeader from './modules/header.vue'; import DeviceDetailsInfo from './modules/info.vue'; import DeviceDetailsMessage from './modules/message.vue'; +import DeviceModbusConfig from './modules/modbus-config.vue'; import DeviceDetailsSimulator from './modules/simulator.vue'; import DeviceDetailsSubDevice from './modules/sub-device.vue'; import DeviceDetailsThingModel from './modules/thing-model.vue'; @@ -141,6 +142,23 @@ onMounted(async () => { @success="() => getDeviceData(id)" /> + + + diff --git a/apps/web-antd/src/views/iot/device/device/detail/modules/modbus-config-form.vue b/apps/web-antd/src/views/iot/device/device/detail/modules/modbus-config-form.vue new file mode 100644 index 000000000..fb792d4e7 --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/detail/modules/modbus-config-form.vue @@ -0,0 +1,222 @@ + + + + diff --git a/apps/web-antd/src/views/iot/device/device/detail/modules/modbus-config.vue b/apps/web-antd/src/views/iot/device/device/detail/modules/modbus-config.vue new file mode 100644 index 000000000..74f07130f --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/detail/modules/modbus-config.vue @@ -0,0 +1,359 @@ + + + + diff --git a/apps/web-antd/src/views/iot/device/device/detail/modules/modbus-point-form.vue b/apps/web-antd/src/views/iot/device/device/detail/modules/modbus-point-form.vue new file mode 100644 index 000000000..db5fc0674 --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/detail/modules/modbus-point-form.vue @@ -0,0 +1,314 @@ + + + + diff --git a/apps/web-antd/src/views/iot/device/device/detail/modules/sub-device.vue b/apps/web-antd/src/views/iot/device/device/detail/modules/sub-device.vue index 67bb69daa..3c1da17c6 100644 --- a/apps/web-antd/src/views/iot/device/device/detail/modules/sub-device.vue +++ b/apps/web-antd/src/views/iot/device/device/detail/modules/sub-device.vue @@ -1,5 +1,6 @@ + +