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