# Phase 4 · Extras 挂件 · Implementation Plan (技术大纲)

**Goal:** 实装 4 个独立直播挂件（Profile Border / Voting Tool / Lucky Game / Lucky Fans），每个独立 URL 输出到 OBS。

**Architecture:** 每挂件 = 一个 Vite 入口 → 一个独立 HTML。配置在主应用 Extras Tab，通过 IPC 持久化，挂件页面 query string 取参数 + WS 订阅实时数据。

**Tech Stack:** 复用 Phase 1-3 全栈 + canvas-confetti（lucky-fans 中奖庆祝动画）

---

## File Structure

```
packages/db/src/migrations/
  └── 004-extras.sql              (挂件实例表 + 模板表)

packages/db/src/repositories/
  ├── widget-instances.ts         (Profile Border 多实例 / Lucky Game 10 套 config)
  ├── voting-tool.ts              (My Votes 列表)
  └── lucky-fans-records.ts       (中奖记录)

packages/ipc-contract/src/
  └── extras.ts                   (4 挂件的 RPC schema)

apps/widgets/src/                 (4 个新增挂件入口)
  ├── profile-border/
  ├── voting-tool/
  ├── lucky-game/
  └── lucky-fans/

apps/desktop/src/pages/Extras/
  ├── index.tsx                   (4 Tab：Profile / Voting / Lucky Game / Lucky Fans)
  ├── ProfileBorder/
  │   ├── Console.tsx             (多实例管理)
  │   └── SkinGallery.tsx         (20+ skins)
  ├── VotingTool/
  │   ├── List.tsx                (My Votes)
  │   └── Editor.tsx              (规则 + 选项)
  ├── LuckyGame/
  │   ├── Wheel.tsx               (转盘配置)
  │   └── ConfigSwitch.tsx        (10 套切换)
  └── LuckyFans/
      ├── Operation.tsx           (Start Draw / 中奖名单)
      └── ListSettings.tsx        (名单管理 + 黑名单)
```

---

## Task 列表

| # | 主题 | 关键代码 sketch |
|---|---|---|
| 1 | DB schema: widget_instances / voting_tool / lucky_fans_records | 见下方 SQL |
| 2 | Profile Border 配置 + 20 skins SVG（用 spec 设计稿里的） | `apps/widgets/src/profile-border/index.html` |
| 3 | Profile Border 挂件页：query string 取 skin / label / avatar | `?skin=7&label=TOP1&avatar=https://...` |
| 4 | Voting Tool: 评论投票引擎（订阅 chat_event 关键词匹配） | 复用 `interactive-vote.ts` 部分逻辑 |
| 5 | Voting Tool 挂件 UI：实时进度条 + 倒计时 | shimmer 动画 + bar growth |
| 6 | Lucky Game 转盘：4 skins + SVG 转动动画 | `transform: rotate(${5*360 + targetAngle}deg)` |
| 7 | Lucky Game 配置：10 套 config 切换 + textarea 多行选项 | 持久化 `widget_instances` |
| 8 | Lucky Fans 名单管理：自动从 chat/follow/gift events 聚合 | DB query: SELECT DISTINCT user_uid FROM live_events WHERE ... |
| 9 | Lucky Fans 抽奖动画：滚动名字 + ease-out 停在中奖人 | requestAnimationFrame |
| 10 | Lucky Fans 中奖记录持久化 + xlsx 导出 | `lucky_fans_records` 表 |

## DB Schema

```sql
-- 004-extras.sql
CREATE TABLE IF NOT EXISTS widget_instances (
  id          INTEGER PRIMARY KEY AUTOINCREMENT,
  widget_type TEXT NOT NULL,            -- profile_border / voting_tool / lucky_game / lucky_fans
  slot        INTEGER NOT NULL,         -- 实例编号
  config_json TEXT NOT NULL,
  updated_at  INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_widget_type ON widget_instances(widget_type);

CREATE TABLE IF NOT EXISTS voting_tool_polls (
  id            INTEGER PRIMARY KEY AUTOINCREMENT,
  title         TEXT NOT NULL,
  options_json  TEXT NOT NULL,
  rule_json     TEXT NOT NULL,
  status        TEXT NOT NULL DEFAULT 'pending',  -- pending/running/ended
  results_json  TEXT,
  started_at    INTEGER,
  ended_at      INTEGER,
  created_at    INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS lucky_fans_records (
  id              INTEGER PRIMARY KEY AUTOINCREMENT,
  draw_at         INTEGER NOT NULL,
  participants    TEXT NOT NULL,        -- JSON
  winners         TEXT NOT NULL,        -- JSON
  prize_label     TEXT,
  notes           TEXT
);
```

## 准入 / 准出

**准入**：Phase 3 已 merge（共用 widget-server pubsub）

**准出**：
- ✅ 4 挂件都能 Copy URL → OBS 加载 → 直播画面看到
- ✅ Voting Tool 接到评论实时计票
- ✅ Lucky Fans 抽完导出 .xlsx
- ✅ 配置 Backup / Restore JSON 可工作

## Self-Review

- ✅ 涵盖 spec §extras 全部 4 挂件
- ✅ design extras.html 描述的 16 屏全部映射到 task
- ✅ DRY: 复用 widget-server pubsub + voting 评论引擎
