You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

6.3 KiB

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-checkboxprototype.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

<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-listtemplate v-for + 整段 article.device-card 替换为:

<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:

import DockDeviceCard from '@/components/DockDeviceCard.vue'

function droneAltitude(id) {
  return devices.asset(id)?.altitude
}

删除页面内的 droneSummarydroneTag 函数(已迁入组件)。

保留:groupedDocksfilteredDocksfilterCountsselectedDockIdsisDockCardActivesetDockSelectionselectAllDocksclearDockSelection、搜索、filter-row、multi-select-bar、地图与控制。

确认 select / setDockSelection 可直接作为事件处理器(select(id)setDockSelection(id, checked))。

  • Step 3: 静态核对
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

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
  • 跳过全量单测;静态核对 + 浏览器冒烟