1 changed files with 259 additions and 0 deletions
@ -0,0 +1,259 @@ |
|||
# 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** |
|||
|
|||
```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 替换循环体** |
|||
|
|||
将: |
|||
|
|||
```html |
|||
<article v-for="a in alarms" :key="a.id" class="alarm-item" :class="a.severityClass"> |
|||
... |
|||
</article> |
|||
``` |
|||
|
|||
改为: |
|||
|
|||
```html |
|||
<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 增加: |
|||
|
|||
```js |
|||
import AlarmItem from '@/components/AlarmItem.vue' |
|||
``` |
|||
|
|||
保留空态 `table-empty`、`load`、`handle`、侧栏与 metrics。 |
|||
|
|||
- [ ] **Step 3: 静态核对** |
|||
|
|||
Run: |
|||
|
|||
```bash |
|||
rg -n 'AlarmItem|alarm-item' src/components/AlarmItem.vue src/views/AlarmsView/AlarmsView.vue |
|||
``` |
|||
|
|||
Expected: 组件内有 `alarm-item`;页面循环用 `AlarmItem`,无内联 `article.alarm-item`。 |
|||
|
|||
- [ ] **Step 4: Commit** |
|||
|
|||
```bash |
|||
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** |
|||
|
|||
```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** |
|||
|
|||
直播: |
|||
|
|||
```html |
|||
<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('更多操作')" |
|||
/> |
|||
``` |
|||
|
|||
视频: |
|||
|
|||
```html |
|||
<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: |
|||
|
|||
```js |
|||
import MediaCard from '@/components/MediaCard.vue' |
|||
``` |
|||
|
|||
保留 `live-media-grid` / `media-grid` 容器、空态、tabs、load、openLive、download。 |
|||
|
|||
- [ ] **Step 3: 静态核对** |
|||
|
|||
```bash |
|||
rg -n 'MediaCard|media-visual|media-card-copy' src/components/MediaCard.vue src/views/MediaView/MediaView.vue |
|||
``` |
|||
|
|||
Expected: 视觉 class 在组件内;页面用 `MediaCard`,网格容器仍在页面。 |
|||
|
|||
- [ ] **Step 4: Commit** |
|||
|
|||
```bash |
|||
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 |
|||
Loading…
Reference in new issue