From 3ea47d471e25bb133a5d8a9ffb4eb9dbd384eaa8 Mon Sep 17 00:00:00 2001 From: walkpan Date: Mon, 30 Mar 2026 19:28:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=A7=84=E5=88=99=E7=AE=A1=E7=90=86):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=BB=E9=A2=98=E8=A7=84=E5=88=99=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8F=91=E7=8E=B0=E8=AE=BE=E5=A4=87=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现设备自动发现规则管理系统,包含以下主要功能: 1. 新增规则管理页面和API,支持创建、编辑和删除主题匹配规则 2. 添加规则匹配引擎,支持+和#通配符匹配设备主题 3. 实现Broker设备注册表,自动发现并管理符合规则的设备 4. 扩展仪表盘显示Broker信息和活跃主题 5. 修改设备卡片和详情页以区分规则发现的设备 6. 添加相关测试用例确保功能稳定性 --- .../console-2026-03-29T15-40-07-523Z.log | 1 + dashboard-final.png | Bin 0 -> 68451 bytes .../plans/2026-03-29-mqtt-home-frontend.md | 1367 +++++++++++++++++ frontend/device-detail-screenshot.png | Bin 0 -> 42878 bytes frontend/src/api/index.js | 6 + frontend/src/components/DeviceCard.vue | 2 +- frontend/src/components/RuleCard.vue | 80 + frontend/src/components/RuleModal.vue | 146 ++ frontend/src/components/Sidebar.vue | 3 +- frontend/src/router/index.js | 5 + frontend/src/stores/devices.js | 33 +- frontend/src/views/DashboardView.vue | 115 +- frontend/src/views/DeviceDetailView.vue | 13 +- frontend/src/views/DevicesView.vue | 2 +- frontend/src/views/RulesView.vue | 69 + src/mqtt_home/api/__init__.py | 2 + src/mqtt_home/api/dashboard.py | 36 +- src/mqtt_home/api/devices.py | 51 +- src/mqtt_home/api/rules.py | 65 + src/mqtt_home/broker_devices.py | 107 ++ src/mqtt_home/main.py | 49 + src/mqtt_home/models.py | 15 +- src/mqtt_home/rule_registry.py | 57 + src/mqtt_home/schemas.py | 47 + src/mqtt_home/topic_matcher.py | 131 ++ tests/test_broker_devices.py | 86 ++ tests/test_rule_registry.py | 66 + tests/test_topic_matcher.py | 74 + 28 files changed, 2606 insertions(+), 22 deletions(-) create mode 100644 .playwright-mcp/console-2026-03-29T15-40-07-523Z.log create mode 100644 dashboard-final.png create mode 100644 docs/superpowers/plans/2026-03-29-mqtt-home-frontend.md create mode 100644 frontend/device-detail-screenshot.png create mode 100644 frontend/src/components/RuleCard.vue create mode 100644 frontend/src/components/RuleModal.vue create mode 100644 frontend/src/views/RulesView.vue create mode 100644 src/mqtt_home/api/rules.py create mode 100644 src/mqtt_home/broker_devices.py create mode 100644 src/mqtt_home/rule_registry.py create mode 100644 src/mqtt_home/topic_matcher.py create mode 100644 tests/test_broker_devices.py create mode 100644 tests/test_rule_registry.py create mode 100644 tests/test_topic_matcher.py diff --git a/.playwright-mcp/console-2026-03-29T15-40-07-523Z.log b/.playwright-mcp/console-2026-03-29T15-40-07-523Z.log new file mode 100644 index 0000000..e217284 --- /dev/null +++ b/.playwright-mcp/console-2026-03-29T15-40-07-523Z.log @@ -0,0 +1 @@ +[ 71057ms] [ERROR] WebSocket connection to 'ws://localhost:8000/ws/devices' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:8000/assets/devices-D8vTPJoB.js:0 diff --git a/dashboard-final.png b/dashboard-final.png new file mode 100644 index 0000000000000000000000000000000000000000..70b3809529794b9b113318b954cdd9db89b1c316 GIT binary patch literal 68451 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 literal 0 HcmV?d00001 diff --git a/docs/superpowers/plans/2026-03-29-mqtt-home-frontend.md b/docs/superpowers/plans/2026-03-29-mqtt-home-frontend.md new file mode 100644 index 0000000..1578d1c --- /dev/null +++ b/docs/superpowers/plans/2026-03-29-mqtt-home-frontend.md @@ -0,0 +1,1367 @@ +# MQTT 智能家居管理系统 - 前端实现计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 使用 Vue 3 + Tailwind CSS 构建响应式 SPA 前端,连接后端 REST API 和 WebSocket,实现设备管理、仪表盘、Broker 管理三大页面。 + +**Architecture:** Vue 3 Composition API + Vite 构建工具,Pinia 状态管理(WebSocket 实时更新设备状态),通过 API 模块与后端 FastAPI 通信,Vite 配置代理解决开发跨域。 + +**Tech Stack:** Vue 3, Vite, Tailwind CSS 3, Pinia, Vue Router 4, @heroicons/vue + +--- + +## 后端 API 接口参考 + +前端需要对接的后端 API(已在后端实现中就绪): + +| 方法 | 路径 | 返回类型 | +|------|------|----------| +| GET | `/api/devices` | `DeviceResponse[]` | +| POST | `/api/devices` | `DeviceResponse` (body: `{name, type, mqtt_topic, command_topic?}`) | +| GET | `/api/devices/{id}` | `DeviceResponse` | +| PUT | `/api/devices/{id}` | `DeviceResponse` (body: `{name?, type?, command_topic?}`) | +| DELETE | `/api/devices/{id}` | 204 | +| POST | `/api/devices/{id}/command` | `DeviceLogResponse` (body: `{payload: string}`) | +| GET | `/api/devices/{id}/logs` | `DeviceLogResponse[]` | +| GET | `/api/broker/status` | `{status, metrics}` | +| GET | `/api/broker/clients` | `BrokerClient[]` | +| GET | `/api/broker/topics` | `BrokerTopic[]` | +| GET | `/api/dashboard` | `DashboardStats` | +| GET | `/health` | `{status, mqtt_connected}` | +| WS | `/ws/devices` | `{type:"device_update", device_id, state, is_online, last_seen}` | + +--- + +## 文件结构总览 + +``` +frontend/ +├── index.html +├── package.json +├── vite.config.js +├── tailwind.config.js +├── postcss.config.js +├── src/ +│ ├── main.js # 入口:创建 app、router、pinia +│ ├── App.vue # 根组件:布局 + 侧边栏 +│ ├── style.css # Tailwind 导入 +│ ├── api/ +│ │ └── index.js # HTTP 请求封装(fetch) +│ ├── composables/ +│ │ └── useWebSocket.js # WebSocket 连接与自动重连 +│ ├── stores/ +│ │ └── devices.js # Pinia store:设备列表 + 实时更新 +│ ├── router/ +│ │ └── index.js # Vue Router 路由配置 +│ ├── components/ +│ │ ├── DeviceCard.vue # 设备卡片(列表页用) +│ │ ├── DeviceControl.vue # 设备控制面板(详情页用,按类型自适应) +│ │ ├── DeviceLogList.vue # 消息日志列表 +│ │ ├── StatsCard.vue # 统计数字卡片 +│ │ ├── AddDeviceModal.vue # 添加设备弹窗 +│ │ └── Sidebar.vue # 侧边导航栏 +│ └── views/ +│ ├── DashboardView.vue # 仪表盘页面 +│ ├── DevicesView.vue # 设备列表页面 +│ ├── DeviceDetailView.vue # 设备详情页面 +│ └── BrokerView.vue # Broker 管理页面 +``` + +--- + +### Task 1: Vue 项目初始化与配置 + +**Files:** +- Create: `frontend/package.json` +- Create: `frontend/vite.config.js` +- Create: `frontend/tailwind.config.js` +- Create: `frontend/postcss.config.js` +- Create: `frontend/index.html` +- Create: `frontend/src/main.js` +- Create: `frontend/src/App.vue` +- Create: `frontend/src/style.css` + +- [ ] **Step 1: 创建 `frontend/package.json`** + +```json +{ + "name": "mqtt-home-frontend", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.5.0", + "vue-router": "^4.4.0", + "pinia": "^2.2.0", + "@heroicons/vue": "^2.2.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.1.0", + "vite": "^5.4.0", + "tailwindcss": "^3.4.0", + "postcss": "^8.4.0", + "autoprefixer": "^10.4.0" + } +} +``` + +- [ ] **Step 2: 创建 `frontend/vite.config.js`** + +```javascript +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], + server: { + port: 3000, + proxy: { + '/api': 'http://localhost:8000', + '/ws': { + target: 'http://localhost:8000', + ws: true, + }, + '/health': 'http://localhost:8000', + }, + }, + build: { + outDir: 'dist', + }, +}) +``` + +- [ ] **Step 3: 创建 `frontend/tailwind.config.js`** + +```javascript +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./index.html', './src/**/*.{vue,js,ts}'], + theme: { + extend: { + colors: { + primary: { + 50: '#eff6ff', + 100: '#dbeafe', + 500: '#3b82f6', + 600: '#2563eb', + 700: '#1d4ed8', + }, + }, + }, + }, + plugins: [], +} +``` + +- [ ] **Step 4: 创建 `frontend/postcss.config.js`** + +```javascript +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} +``` + +- [ ] **Step 5: 创建 `frontend/index.html`** + +```html + + + + + + MQTT Home + + +
+ + + +``` + +- [ ] **Step 6: 创建 `frontend/src/style.css`** + +```css +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +} +``` + +- [ ] **Step 7: 创建 `frontend/src/main.js`** + +```javascript +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import App from './App.vue' +import router from './router' +import './style.css' + +const app = createApp(App) +app.use(createPinia()) +app.use(router) +app.mount('#app') +``` + +- [ ] **Step 8: 创建 `frontend/src/App.vue`** + +```vue + + + +``` + +- [ ] **Step 9: 创建占位 `frontend/src/router/index.js`** + +```javascript +import { createRouter, createWebHistory } from 'vue-router' + +const router = createRouter({ + history: createWebHistory(), + routes: [ + { path: '/', redirect: '/dashboard' }, + { + path: '/dashboard', + name: 'Dashboard', + component: () => import('../views/DashboardView.vue'), + }, + { + path: '/devices', + name: 'Devices', + component: () => import('../views/DevicesView.vue'), + }, + { + path: '/devices/:id', + name: 'DeviceDetail', + component: () => import('../views/DeviceDetailView.vue'), + }, + { + path: '/broker', + name: 'Broker', + component: () => import('../views/BrokerView.vue'), + }, + ], +}) + +export default router +``` + +- [ ] **Step 10: 创建占位页面文件** + +创建以下 4 个最小占位文件: + +`frontend/src/views/DashboardView.vue`: +```vue + +``` + +`frontend/src/views/DevicesView.vue`: +```vue + +``` + +`frontend/src/views/DeviceDetailView.vue`: +```vue + +``` + +`frontend/src/views/BrokerView.vue`: +```vue + +``` + +创建 `frontend/src/components/Sidebar.vue`: +```vue + + + +``` + +- [ ] **Step 11: 安装依赖并验证构建** + +Run: `cd frontend && npm install` +Run: `cd frontend && npm run dev` (后台启动验证无报错) +Expected: Vite 启动成功,浏览器访问 localhost:3000 显示侧边栏和路由页面 + +- [ ] **Step 12: 提交** + +```bash +git add frontend/ +git commit -m "feat: Vue 3 project scaffolding with Tailwind, Router, Pinia, and Sidebar" +``` + +--- + +### Task 2: API 客户端模块 + +**Files:** +- Create: `frontend/src/api/index.js` + +- [ ] **Step 1: 创建 `frontend/src/api/index.js`** + +```javascript +const BASE_URL = '/api' + +async function request(path, options = {}) { + const url = `${BASE_URL}${path}` + const config = { + headers: { + 'Content-Type': 'application/json', + ...options.headers, + }, + ...options, + } + if (config.body && typeof config.body === 'object') { + config.body = JSON.stringify(config.body) + } + const res = await fetch(url, config) + if (!res.ok) { + const error = new Error(`API error: ${res.status}`) + error.status = res.status + try { + error.data = await res.json() + } catch { + error.data = await res.text() + } + throw error + } + if (res.status === 204) return null + return res.json() +} + +// Devices +export const getDevices = () => request('/devices') +export const createDevice = (data) => request('/devices', { method: 'POST', body: data }) +export const getDevice = (id) => request(`/devices/${id}`) +export const updateDevice = (id, data) => request(`/devices/${id}`, { method: 'PUT', body: data }) +export const deleteDevice = (id) => request(`/devices/${id}`, { method: 'DELETE' }) +export const sendCommand = (id, payload) => + request(`/devices/${id}/command`, { method: 'POST', body: { payload } }) +export const getDeviceLogs = (id, limit = 20) => request(`/devices/${id}/logs?limit=${limit}`) + +// Broker +export const getBrokerStatus = () => request('/broker/status') +export const getBrokerClients = () => request('/broker/clients') +export const getBrokerTopics = () => request('/broker/topics') + +// Dashboard +export const getDashboardStats = () => request('/dashboard') +``` + +- [ ] **Step 2: 提交** + +```bash +git add frontend/src/api/ +git commit -m "feat: API client module for all backend endpoints" +``` + +--- + +### Task 3: WebSocket 组合式函数 + +**Files:** +- Create: `frontend/src/composables/useWebSocket.js` + +- [ ] **Step 1: 创建 `frontend/src/composables/useWebSocket.js`** + +```javascript +import { ref, onMounted, onUnmounted } from 'vue' + +export function useWebSocket(url, onMessage) { + const connected = ref(false) + let ws = null + let reconnectTimer = null + let retryDelay = 1000 + + function connect() { + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' + const fullUrl = `${protocol}//${window.location.host}${url}` + + ws = new WebSocket(fullUrl) + + ws.onopen = () => { + connected.value = true + retryDelay = 1000 + } + + ws.onmessage = (event) => { + try { + const data = JSON.parse(event.data) + onMessage(data) + } catch { + // ignore non-JSON messages + } + } + + ws.onclose = () => { + connected.value = false + reconnectTimer = setTimeout(connect, retryDelay) + retryDelay = Math.min(retryDelay * 2, 30000) + } + + ws.onerror = () => { + ws.close() + } + } + + onMounted(() => connect()) + + onUnmounted(() => { + clearTimeout(reconnectTimer) + if (ws) ws.close() + }) + + return { connected } +} +``` + +- [ ] **Step 2: 提交** + +```bash +git add frontend/src/composables/ +git commit -m "feat: WebSocket composable with auto-reconnect" +``` + +--- + +### Task 4: Pinia 设备 Store + +**Files:** +- Create: `frontend/src/stores/devices.js` + +- [ ] **Step 1: 创建 `frontend/src/stores/devices.js`** + +```javascript +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { getDevices, createDevice, deleteDevice, sendCommand as apiSendCommand } from '../api' +import { useWebSocket } from '../composables/useWebSocket' + +export const useDeviceStore = defineStore('devices', () => { + const devices = ref([]) + const loading = ref(false) + const wsConnected = ref(false) + + async function fetchDevices() { + loading.value = true + try { + devices.value = await getDevices() + } finally { + loading.value = false + } + } + + async function addDevice(data) { + const device = await createDevice(data) + devices.value.unshift(device) + return device + } + + async function removeDevice(id) { + await deleteDevice(id) + devices.value = devices.value.filter((d) => d.id !== id) + } + + async function sendCommand(id, payload) { + return await apiSendCommand(id, payload) + } + + function handleWsMessage(data) { + if (data.type !== 'device_update') return + const idx = devices.value.findIndex((d) => d.id === data.device_id) + if (idx === -1) return + const device = devices.value[idx] + devices.value.splice(idx, 1, { + ...device, + state: data.state, + is_online: data.is_online, + last_seen: data.last_seen, + }) + } + + // Initialize WebSocket + const { connected } = useWebSocket('/ws/devices', handleWsMessage) + wsConnected.value = connected + + return { + devices, + loading, + wsConnected, + fetchDevices, + addDevice, + removeDevice, + sendCommand, + } +}) +``` + +- [ ] **Step 2: 提交** + +```bash +git add frontend/src/stores/ +git commit -m "feat: Pinia device store with WebSocket real-time updates" +``` + +--- + +### Task 5: 仪表盘页面 + +**Files:** +- Create: `frontend/src/components/StatsCard.vue` +- Modify: `frontend/src/views/DashboardView.vue` + +- [ ] **Step 1: 创建 `frontend/src/components/StatsCard.vue`** + +```vue + + + +``` + +- [ ] **Step 2: 替换 `frontend/src/views/DashboardView.vue`** + +```vue + + + +``` + +- [ ] **Step 3: 验证** + +Run: `cd frontend && npm run dev` +Expected: 访问 `/dashboard` 显示三个统计卡片和最近活动列表 + +- [ ] **Step 4: 提交** + +```bash +git add frontend/ +git commit -m "feat: dashboard page with stats cards and recent activity timeline" +``` + +--- + +### Task 6: 设备列表页面 + +**Files:** +- Create: `frontend/src/components/DeviceCard.vue` +- Create: `frontend/src/components/AddDeviceModal.vue` +- Modify: `frontend/src/views/DevicesView.vue` + +- [ ] **Step 1: 创建 `frontend/src/components/DeviceCard.vue`** + +```vue + + + +``` + +- [ ] **Step 2: 创建 `frontend/src/components/AddDeviceModal.vue`** + +```vue + + + +``` + +- [ ] **Step 3: 替换 `frontend/src/views/DevicesView.vue`** + +```vue + + + +``` + +- [ ] **Step 4: 提交** + +```bash +git add frontend/ +git commit -m "feat: devices list page with cards, quick toggle, and add device modal" +``` + +--- + +### Task 7: 设备详情页面 + +**Files:** +- Create: `frontend/src/components/DeviceControl.vue` +- Create: `frontend/src/components/DeviceLogList.vue` +- Modify: `frontend/src/views/DeviceDetailView.vue` + +- [ ] **Step 1: 创建 `frontend/src/components/DeviceControl.vue`** + +```vue + + + +``` + +- [ ] **Step 2: 创建 `frontend/src/components/DeviceLogList.vue`** + +```vue + + + +``` + +- [ ] **Step 3: 替换 `frontend/src/views/DeviceDetailView.vue`** + +```vue + + + +``` + +- [ ] **Step 4: 提交** + +```bash +git add frontend/ +git commit -m "feat: device detail page with control panel, custom commands, and message log" +``` + +--- + +### Task 8: Broker 管理页面 + +**Files:** +- Modify: `frontend/src/views/BrokerView.vue` + +- [ ] **Step 1: 替换 `frontend/src/views/BrokerView.vue`** + +```vue + + + +``` + +- [ ] **Step 2: 提交** + +```bash +git add frontend/ +git commit -m "feat: broker management page with status, clients, and topics" +``` + +--- + +### Task 9: 生产构建与后端静态文件服务 + +**Files:** +- Modify: `src/mqtt_home/main.py` + +- [ ] **Step 1: 构建前端** + +Run: `cd frontend && npm run build` +Expected: `frontend/dist/` 目录生成,包含 index.html 和 JS/CSS 资源 + +- [ ] **Step 2: 修改 `src/mqtt_home/main.py`,添加静态文件服务** + +在文件顶部 import 区域添加: +```python +from fastapi.staticfiles import StaticFiles +from fastapi.responses import FileResponse +import pathlib +``` + +在 `app.include_router(api_router)` 之前添加: +```python +# Serve frontend static files in production +frontend_dist = pathlib.Path(__file__).parent.parent.parent / "frontend" / "dist" +if frontend_dist.exists(): + app.mount("/assets", StaticFiles(directory=str(frontend_dist / "assets")), name="assets") + + @app.get("/{full_path:path}") + async def serve_spa(full_path: str): + file = frontend_dist / full_path + if file.exists() and file.is_file(): + return FileResponse(str(file)) + return FileResponse(str(frontend_dist / "index.html")) +``` + +- [ ] **Step 3: 验证生产模式** + +Run: `cd D:\home\mqtt && python -m mqtt_home serve` +Expected: 访问 `http://localhost:8000/` 显示前端页面,API 正常工作 + +- [ ] **Step 4: 提交** + +```bash +git add frontend/ src/ +git commit -m "feat: production build with FastAPI static file serving for SPA" +``` + +--- + +### Task 10: 端到端验证与最终提交 + +- [ ] **Step 1: 启动服务** + +Run: `cd D:\home\mqtt && python -m mqtt_home serve` + +- [ ] **Step 2: 浏览器验证以下功能** + +1. `http://localhost:8000/` — 仪表盘显示设备统计 +2. `http://localhost:8000/devices` — 设备列表(网格布局) +3. 点击"添加设备"弹窗 — 填写表单创建设备 +4. 点击设备卡片 — 进入详情页,显示控制面板和日志 +5. `http://localhost:8000/broker` — Broker 状态、客户端、主题 +6. 侧边栏导航切换正常 +7. MQTT 连接状态指示灯正确 + +- [ ] **Step 3: 提交** + +```bash +git add -A +git commit -m "feat: frontend complete - dashboard, devices, device detail, broker pages" +``` + +--- + +## 自检结果 + +| 设计文档章节 | 对应 Task | +|---|---| +| 仪表盘 — 设备数量、在线/离线、最近活动 | Task 5 | +| 设备列表 — 网格视图、类型图标、状态指示、快捷切换 | Task 6 | +| 设备详情 — 状态展示、控制面板、消息日志 | Task 7 | +| Broker 管理 — 客户端列表、活跃主题、健康状态 | Task 8 | +| WebSocket 实时推送 | Task 3 + Task 4 | +| 响应式布局 | Task 1 (Tailwind responsive classes) | +| 控制面板按类型自适应 | Task 7 (DeviceControl.vue) | +| 添加设备弹窗 | Task 6 (AddDeviceModal.vue) | diff --git a/frontend/device-detail-screenshot.png b/frontend/device-detail-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..a82009f8dd085276f234548a6f0cb80b69fe7826 GIT binary patch literal 42878 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 request('/broker/topics') // Dashboard export const getDashboardStats = () => request('/dashboard') + +// Rules +export const getRules = () => request('/rules') +export const createRule = (data) => request('/rules', { method: 'POST', body: data }) +export const updateRule = (id, data) => request(`/rules/${id}`, { method: 'PUT', body: data }) +export const deleteRule = (id) => request(`/rules/${id}`, { method: 'DELETE' }) diff --git a/frontend/src/components/DeviceCard.vue b/frontend/src/components/DeviceCard.vue index 6c5ef0e..d898f31 100644 --- a/frontend/src/components/DeviceCard.vue +++ b/frontend/src/components/DeviceCard.vue @@ -22,7 +22,7 @@
协议 - {{ device.protocol === 'ha_discovery' ? 'HA' : '自定义' }} + {{ device.protocol === 'ha_discovery' ? 'HA' : device.protocol === 'topic_rule' ? '规则' : '手动' }}
+ + + + + + + diff --git a/frontend/src/components/RuleModal.vue b/frontend/src/components/RuleModal.vue new file mode 100644 index 0000000..7369728 --- /dev/null +++ b/frontend/src/components/RuleModal.vue @@ -0,0 +1,146 @@ + + + diff --git a/frontend/src/components/Sidebar.vue b/frontend/src/components/Sidebar.vue index 6217a69..046f50f 100644 --- a/frontend/src/components/Sidebar.vue +++ b/frontend/src/components/Sidebar.vue @@ -27,7 +27,7 @@ diff --git a/frontend/src/views/DeviceDetailView.vue b/frontend/src/views/DeviceDetailView.vue index 663765a..890556d 100644 --- a/frontend/src/views/DeviceDetailView.vue +++ b/frontend/src/views/DeviceDetailView.vue @@ -19,11 +19,12 @@
{{ device.type }} - {{ device.protocol === 'ha_discovery' ? 'HA Discovery' : '自定义' }} + {{ device.protocol === 'ha_discovery' ? 'HA Discovery' : device.protocol === 'topic_rule' ? '主题规则' : '自定义' }} ID: {{ device.id.slice(0, 8) }}