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.5 KiB

Alarms + Media 薄卡片组件 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: 将 Alarms 列表项与 Media 网格卡抽成本地薄展示组件,页面保留数据/请求/空态,视觉与交互不变。

Architecture: 新增 AlarmItem.vueMediaCard.vue(props + emit only)。AlarmsView / MediaView 用组件替换 v-forarticle。继续使用全局 prototype.css 类名,不加 scoped 新皮肤,不引入 t-list/t-card。

Tech Stack: Vue 3 <script setup>、既有 TDesign t-buttonprototype.css

Spec: docs/superpowers/specs/2026-08-28-alarms-media-card-components-design.md


File map

文件 职责
src/components/AlarmItem.vue 告警单行展示;emit handle
src/components/MediaCard.vue 直播/视频卡;variant;emit play / action
src/views/AlarmsView/AlarmsView.vue 接入 AlarmItem;load/handle 不变
src/views/MediaView/MediaView.vue 接入 MediaCard;load/openLive/download 不变

不改: Monitor、Tasks、LivePlayer、prototype.css、后端


Task 1: AlarmItem + AlarmsView

Files:

  • Create: src/components/AlarmItem.vue

  • Modify: src/views/AlarmsView/AlarmsView.vue

  • Step 1: 创建 AlarmItem.vue

<template>
  <article class="alarm-item" :class="severityClass">
    <span class="alarm-severity">{{ severity }}</span>
    <div>
      <strong>{{ title }}</strong>
      <small>{{ device }} · {{ time }}</small>
    </div>
    <em class="status" :class="statusClass">{{ status }}</em>
    <t-button class="alarm-handle" variant="outline" size="small" @click="$emit('handle')">处理</t-button>
  </article>
</template>

<script setup>
defineProps({
  severity: { type: String, required: true },
  severityClass: { type: String, required: true },
  title: { type: String, required: true },
  device: { type: String, required: true },
  time: { type: String, required: true },
  status: { type: String, required: true },
  statusClass: { type: String, required: true }
})
defineEmits(['handle'])
</script>
  • Step 2: AlarmsView 替换循环体

将:

<article v-for="a in alarms" :key="a.id" class="alarm-item" :class="a.severityClass">
  ...
</article>

改为:

<AlarmItem
  v-for="a in alarms"
  :key="a.id"
  :severity="a.severity"
  :severity-class="a.severityClass"
  :title="a.title"
  :device="a.device"
  :time="a.time"
  :status="a.status"
  :status-class="a.statusClass"
  @handle="handle(a)"
/>

script 增加:

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

保留空态 table-emptyloadhandle、侧栏与 metrics。

  • Step 3: 静态核对

Run:

rg -n 'AlarmItem|alarm-item' src/components/AlarmItem.vue src/views/AlarmsView/AlarmsView.vue

Expected: 组件内有 alarm-item;页面循环用 AlarmItem,无内联 article.alarm-item

  • Step 4: Commit
git add src/components/AlarmItem.vue src/views/AlarmsView/AlarmsView.vue
git commit -m "refactor: 抽取 AlarmItem 告警列表项组件"

Task 2: MediaCard + MediaView

Files:

  • Create: src/components/MediaCard.vue

  • Modify: src/views/MediaView/MediaView.vue

  • Step 1: 创建 MediaCard.vue

<template>
  <article>
    <div class="media-visual" :class="visualClass">
      <span v-if="variant === 'live'" class="live-corner"><i></i>LIVE</span>
      <t-button shape="square" variant="text" @click="$emit('play')">
        <svg><use href="#i-play" /></svg>
      </t-button>
      <small v-if="variant === 'live'">{{ source }}</small>
      <small v-else class="media-duration">{{ duration }}</small>
    </div>
    <div class="media-card-copy">
      <span><strong>{{ name }}</strong><small>{{ desc }}</small></span>
      <t-button
        class="icon-button"
        shape="square"
        variant="text"
        @click="$emit('action')"
      >
        <svg>
          <use :href="variant === 'live' ? '#i-more' : '#i-download'" />
        </svg>
      </t-button>
    </div>
  </article>
</template>

<script setup>
defineProps({
  variant: { type: String, required: true }, // 'live' | 'video'
  name: { type: String, required: true },
  desc: { type: String, required: true },
  visualClass: { type: String, required: true },
  source: { type: String, default: '' },
  duration: { type: String, default: '' }
})
defineEmits(['play', 'action'])
</script>
  • Step 2: MediaView 替换两处网格 article

直播:

<MediaCard
  v-for="s in liveStreams"
  :key="s.id"
  variant="live"
  :name="s.name"
  :desc="s.desc"
  :source="s.source"
  :visual-class="s.visualClass"
  @play="openLive(s)"
  @action="ui.toast('更多操作')"
/>

视频:

<MediaCard
  v-for="v in videos"
  :key="v.id"
  variant="video"
  :name="v.name"
  :desc="v.desc"
  :duration="v.duration"
  :visual-class="v.visualClass"
  @play="ui.toast('播放:' + v.name)"
  @action="download(v)"
/>

import:

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

保留 live-media-grid / media-grid 容器、空态、tabs、load、openLive、download。

  • Step 3: 静态核对
rg -n 'MediaCard|media-visual|media-card-copy' src/components/MediaCard.vue src/views/MediaView/MediaView.vue

Expected: 视觉 class 在组件内;页面用 MediaCard,网格容器仍在页面。

  • Step 4: Commit
git add src/components/MediaCard.vue src/views/MediaView/MediaView.vue
git commit -m "refactor: 抽取 MediaCard 媒体网格卡组件"

Task 3: 浏览器冒烟

Files: 无代码改动(验收)

  • Step 1: 告警页

登录后打开 /alarms

  • 列表为 article.alarm-item(或组件根 article 带该类)

  • 有数据时可见级别/标题/状态/「处理」

  • 无 console error

  • Step 2: 媒体页

打开 /media(及 ?view=live):

  • 直播卡:LIVE 角标 + 播放 + 更多

  • 视频卡:时长 + 播放 + 下载

  • tab 切换正常

  • Step 3: 回归抽查

/monitor 地图工具条仍在;无需改 Devices。

  • Step 4: 记录结果

若失败:修组件 class/emit 后补 commit;通过则本 plan 完成。


约束(执行时)

  • 工作目录:/Users/qingyuan/CodeProject/laic-frontend
  • 不改后台;不 git add -A
  • 跳过全量单测;以静态核对 + 浏览器冒烟为准
  • 中文 STATUS / commit message