1 changed files with 196 additions and 0 deletions
@ -0,0 +1,196 @@ |
|||||
|
# Monitor DockDeviceCard Implementation Plan |
||||
|
|
||||
|
> **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:** 将 Monitor 侧栏机巢设备卡抽成 `DockDeviceCard` 薄展示组件;选中/多选/过滤仍由页面持有。 |
||||
|
|
||||
|
**Architecture:** 新增 `DockDeviceCard.vue`(dock + 选中态 props,emit `select` / `toggle-select`)。`droneSummary` / `droneTag` 迁入组件;飞行高度通过可选 `altitudeOf(id)` 回调。继续使用 `prototype.css`。 |
||||
|
|
||||
|
**Tech Stack:** Vue 3 `<script setup>`、TDesign `t-checkbox`、`prototype.css` |
||||
|
|
||||
|
**Spec:** `docs/superpowers/specs/2026-08-28-monitor-dock-device-card-design.md` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## File map |
||||
|
|
||||
|
| 文件 | 职责 | |
||||
|
|------|------| |
||||
|
| `src/components/DockDeviceCard.vue` | 单张机巢卡展示 | |
||||
|
| `src/views/MonitorView/MonitorView.vue` | 接入组件;删除内联卡模板与 summary/tag | |
||||
|
|
||||
|
**不改:** 地图、控制条、LivePlayer、filter-row、multi-select-bar、devicesStore、prototype.css |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Task 1: DockDeviceCard + MonitorView 接入 |
||||
|
|
||||
|
**Files:** |
||||
|
- Create: `src/components/DockDeviceCard.vue` |
||||
|
- Modify: `src/views/MonitorView/MonitorView.vue` |
||||
|
|
||||
|
- [ ] **Step 1: 创建 DockDeviceCard.vue** |
||||
|
|
||||
|
```vue |
||||
|
<template> |
||||
|
<article class="device-card" :class="{ active, 'multi-selected': multiSelected }"> |
||||
|
<div class="device-select-checkbox" :title="`选择${dock.name}`" @click.stop> |
||||
|
<t-checkbox |
||||
|
:checked="multiSelected" |
||||
|
@change="(checked) => $emit('toggle-select', dock.id, checked)" |
||||
|
/> |
||||
|
</div> |
||||
|
<button class="device-card-main" @click="$emit('select', dock.id)"> |
||||
|
<span class="dock-glyph" :class="{ alarm: dock.statusClass === 'alarm', offline: dock.statusClass === 'offline' }"> |
||||
|
<svg><use href="#i-home" /></svg> |
||||
|
</span> |
||||
|
<span class="device-copy"> |
||||
|
<strong>{{ dock.name }}</strong> |
||||
|
<small>{{ dock.code }}</small> |
||||
|
</span> |
||||
|
<i class="status" :class="dock.statusClass">{{ dock.status }}</i> |
||||
|
</button> |
||||
|
|
||||
|
<button |
||||
|
v-for="drone in dock.children || []" |
||||
|
:key="drone.id" |
||||
|
class="bound-drone" |
||||
|
:class="{ |
||||
|
active: selectedDroneId === drone.id, |
||||
|
flying: drone.statusClass === 'mission', |
||||
|
offline: drone.statusClass === 'offline' |
||||
|
}" |
||||
|
@click="$emit('select', drone.id)" |
||||
|
> |
||||
|
<span class="tree-line"></span> |
||||
|
<svg><use href="#i-plane" /></svg> |
||||
|
<span><strong>{{ drone.name }}</strong><small>{{ droneSummary(drone) }}</small></span> |
||||
|
<i>{{ droneTag(drone) }}</i> |
||||
|
</button> |
||||
|
|
||||
|
<div v-if="dock.statusClass === 'alarm'" class="device-alert"> |
||||
|
<svg><use href="#i-alert" /></svg>检测到设备告警<span>{{ dock.updated }}</span> |
||||
|
</div> |
||||
|
<div v-else-if="dock.statusClass === 'offline'" class="device-alert muted"> |
||||
|
最后在线 {{ dock.updated }}<span>网络中断</span> |
||||
|
</div> |
||||
|
</article> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
const props = defineProps({ |
||||
|
dock: { type: Object, required: true }, |
||||
|
active: { type: Boolean, default: false }, |
||||
|
multiSelected: { type: Boolean, default: false }, |
||||
|
selectedDroneId: { type: String, default: null }, |
||||
|
altitudeOf: { type: Function, default: null } |
||||
|
}) |
||||
|
|
||||
|
defineEmits(['select', 'toggle-select']) |
||||
|
|
||||
|
function droneSummary(drone) { |
||||
|
if (drone.statusClass === 'mission') { |
||||
|
const alt = props.altitudeOf?.(drone.id) |
||||
|
if (alt != null) return `飞行中 · ${alt} m` |
||||
|
return '飞行中' |
||||
|
} |
||||
|
if (drone.statusClass === 'offline') return '设备离线' |
||||
|
return drone.battery && drone.battery !== '--' ? `已入巢 · 充电 ${drone.battery}` : '已入巢' |
||||
|
} |
||||
|
|
||||
|
function droneTag(drone) { |
||||
|
if (drone.statusClass === 'mission') return '飞行中' |
||||
|
if (drone.statusClass === 'offline') return '离线' |
||||
|
return '在巢' |
||||
|
} |
||||
|
</script> |
||||
|
``` |
||||
|
|
||||
|
- [ ] **Step 2: MonitorView 替换 device-list 循环** |
||||
|
|
||||
|
将 `device-list` 内 `template v-for` + 整段 `article.device-card` 替换为: |
||||
|
|
||||
|
```html |
||||
|
<div class="device-list"> |
||||
|
<DockDeviceCard |
||||
|
v-for="dock in filteredDocks" |
||||
|
:key="dock.id" |
||||
|
:dock="dock" |
||||
|
:active="isDockCardActive(dock)" |
||||
|
:multi-selected="selectedDockIds.has(dock.id)" |
||||
|
:selected-drone-id="selectedId" |
||||
|
:altitude-of="droneAltitude" |
||||
|
@select="select" |
||||
|
@toggle-select="setDockSelection" |
||||
|
/> |
||||
|
<div v-if="!filteredDocks.length" class="table-empty" style="height:120px;display:grid;place-items:center;color:var(--muted);font-size:10px">暂无匹配设备</div> |
||||
|
</div> |
||||
|
``` |
||||
|
|
||||
|
script: |
||||
|
|
||||
|
```js |
||||
|
import DockDeviceCard from '@/components/DockDeviceCard.vue' |
||||
|
|
||||
|
function droneAltitude(id) { |
||||
|
return devices.asset(id)?.altitude |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
删除页面内的 `droneSummary`、`droneTag` 函数(已迁入组件)。 |
||||
|
|
||||
|
保留:`groupedDocks`、`filteredDocks`、`filterCounts`、`selectedDockIds`、`isDockCardActive`、`setDockSelection`、`selectAllDocks`、`clearDockSelection`、搜索、filter-row、multi-select-bar、地图与控制。 |
||||
|
|
||||
|
确认 `select` / `setDockSelection` 可直接作为事件处理器(`select(id)`、`setDockSelection(id, checked)`)。 |
||||
|
|
||||
|
- [ ] **Step 3: 静态核对** |
||||
|
|
||||
|
```bash |
||||
|
rg -n 'DockDeviceCard|device-card|droneSummary|droneTag' src/components/DockDeviceCard.vue src/views/MonitorView/MonitorView.vue |
||||
|
``` |
||||
|
|
||||
|
Expected: |
||||
|
- 组件内有 `device-card` / `droneSummary` / `droneTag` |
||||
|
- MonitorView 使用 `DockDeviceCard`,无内联 `article.device-card`,无页面级 `function droneSummary` |
||||
|
|
||||
|
- [ ] **Step 4: Commit** |
||||
|
|
||||
|
```bash |
||||
|
git add src/components/DockDeviceCard.vue src/views/MonitorView/MonitorView.vue |
||||
|
git commit -m "refactor: 抽取 Monitor DockDeviceCard 侧栏设备卡" |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Task 2: 浏览器冒烟 |
||||
|
|
||||
|
**Files:** 无代码改动(验收) |
||||
|
|
||||
|
- [ ] **Step 1: 打开 /monitor** |
||||
|
|
||||
|
- 侧栏有 `article.device-card` |
||||
|
- filter-row(全部/在线/告警)仍在 |
||||
|
- multi-select-bar 仍在 |
||||
|
|
||||
|
- [ ] **Step 2: 交互** |
||||
|
|
||||
|
- 点击机巢主行:选中态 / 右侧聚焦仍工作 |
||||
|
- 点击绑定无人机行:选中无人机 |
||||
|
- checkbox 勾选:`multi-selected` 样式;全选/取消选择可用 |
||||
|
|
||||
|
- [ ] **Step 3: 回归** |
||||
|
|
||||
|
- map-type 卫星/地图仍在 |
||||
|
- 无 console error |
||||
|
|
||||
|
- [ ] **Step 4: 记录结果** |
||||
|
|
||||
|
失败则修组件事件/class 后补 commit;通过则完成。 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 约束 |
||||
|
|
||||
|
- 工作目录:`/Users/qingyuan/CodeProject/laic-frontend` |
||||
|
- 不改后台;精确 git add;中文 commit |
||||
|
- 跳过全量单测;静态核对 + 浏览器冒烟 |
||||
Loading…
Reference in new issue