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.vue、MediaCard.vue(props + emit only)。AlarmsView / MediaView 用组件替换 v-for 内 article。继续使用全局 prototype.css 类名,不加 scoped 新皮肤,不引入 t-list/t-card。
Tech Stack: Vue 3 <script setup>、既有 TDesign t-button、prototype.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-empty、load、handle、侧栏与 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