# 换装跳舞直播间 — 第六期开发任务规划(对标 PixVerse "Live Dress") 需求来源:参考产品 PixVerse `world.pixverse.video/live/dress` 的抓包/反混淆分析, 详见 `docs/reference/pixverse-dress-live/NOTES.md`(下称"参考分析",本文各处 引用其章节标题)。现状代码:`dance.html`/`dance.css`(换装跳舞直播间,下称 "我们")、`live.html`/`live.css`(AI 短剧频道,样式基底被 dance.html 复用)。 产品需求见 `docs/todo/dressup-dance-room.md`("需求文档"),技术设计见 `docs/todo/dressup-dance-room-mvcp.md`(MVCP)。第一~五期(T1-T39)均已完成 并提交。任务编号接着往下编。 工时是**粗估**,单位人天,用来排先后不用来考核。 --- ## 0. 第六期范围(2026-09-17 已与你过一遍 §2 开放问题,范围已锁定) **做**:排队可视化(顶部"正在换装"胶囊 + 排队头像条,T40)、换装选择 面板改版为底部横条 + hover 发送态(T41/T42)、**把"传衣服/传人"付费 上传入口合并进这个面板**(T42,原来是左栏独立 tab)、上传进度条体验 优化(T44)、**聊天弹幕接入 dance room**(T46,新增正式任务)、**同一 uid 未完成条目时禁止重复免费选衣服的轻量限流**(T48,新增)。 **不做**:不接入 Agora/声网,我们的"直播"本质是服务端预渲染视频片段 按时刻表轮播(M.8),不是真正的实时推流,参考分析里"Agora 数据通道推 事件"那套换成我们已有的轮询(`pollState`/新增的聊天轮询)语义对齐即 可,不需要引入新的实时通道;不做 PixVerse 的分享海报生成(已确认排 除);不把投票排序机制换成 PixVerse 那种"FIFO 单请求"(已确认保留投 票机制,只加轻量限流,见 T48);不改动登录/积分/审核这套商业化规则 本身(T42 只是挪上传入口的**位置**,付费门槛/积分换算逻辑不变);不 做整体深色视觉基调重做(已确认保留漫画风,见 §2 决策 1)。 --- ## 1. 对照表:PixVerse Live Dress vs 我们的 dance.html | 维度 | PixVerse 参考实现(见参考分析对应章节) | 我们现状(`dance.html`/`dance.css`) | 差距结论 | |---|---|---|---| | **整体视觉基调** | 深色沉浸式,接近 Instagram/TikTok Stories:无边框卡片、圆角胶囊、毛玻璃蒙层、纯白/浅灰文字 | "漫画风":白底卡片+3px 黑色描边+硬offset阴影、马卡龙撞色(`--accent:#ff90e8`)、Tahoma 粗体、Logo 描边+多层投影 | **根本性的风格冲突**,不是改几个颜色变量能解决的,是否要整体换肤是最大的开放问题(§1.2 问题 1) | | **页面布局** | 单栏居中竖版视频铺满可视区,左下弹幕/顶部信息/底部换装条都是**悬浮在视频上**的浮层,没有常驻侧栏 | 三栏布局:左列表栏(320px 常驻)/中间视频/右选衣服栏(320px 常驻),侧栏挤占视频可视宽度 | 差距大,但改动风险也大——会影响 `live.html` 共用的 `.left-col`/`.right-col`(§1.1 决策已限定只改 dance 独有部分,不动 live.html 布局) | | **"正在为谁换装"** | 顶部居中「Now Active \| @user」胶囊 + 头像,视觉焦点强 | `now-playing-box` 固定在视频左上角,纯文字(昵称+时间),无头像、不居中、不醒目 | 值得抄(T40),纯前端改造,数据已够(`requestedBy` 昵称已有,可选加个默认头像占位) | | **排队可视化** | 顶部一排头像气泡 + 每个头像旁一个迷你换装缩略图角标,直观看到"谁在排队、submit 了什么" | 左栏"待播放" tab 用卡片列表(服装缩略图+文字),无头像,纯列表滚动 | 值得抄头像+角标的横条形式(T40),但我们没有用户头像系统(只有昵称 cookie),需要决定用什么占位图(§1.2 问题 2) | | **换装选择面板** | 底部横向缩略图条,默认收起成一行,hover 缩略图淡入黑色蒙层+居中"Send"按钮;旁边有"DIY"上传入口内联在同一条里 | 右侧栏网格(`.wardrobe-grid`,2 列),点击卡片直接提交,无 hover 二次确认;自定义上传是**独立付费 tab**(传衣服/传人),不在选衣服面板里 | **已确认合并**(见 §2 决策 2):面板改版为横条+hover 发送态(T41/T42),"传衣服/传人"付费上传入口挪进同一个面板,做成一张常驻的"DIY"卡片,付费门槛/积分逻辑不变,只改入口位置 | | **每用户并发请求数** | 严格限制:每个用户同时只能有 1 条排队中的换装请求(`queuePosition===-1` 才能发送),FIFO 处理,可主动取消 | 无限制:一个 uid 可以多次 `pick`,进入队列后靠"点赞"(`up-btn`)加速排序,是投票模型不是 FIFO | **已确认不改成 FIFO**(见 §2 决策 4),保留投票机制;改加一条轻量限流——同一 uid 已有未完成的**免费**排队条目时禁止再次 `pick`(T48),付费上传(衣服/人物)不受这条限流影响 | | **聊天/弹幕** | 左下角持续聊天流(历史+实时),另有独立的"prompt-send"飘字特效叠加在视频上 | `dance.html` **完全没有聊天**——`live.html` 有聊天面板和后端(`/api/chat`),但 dance room 从未接入;右栏被"选衣服"占用 | **已确认要做**(见 §2 决策 3):接入 dance room 专属聊天(T46),不做"prompt-send"飘字特效(超出本期范围,仅做常规聊天流) | | **分享** | 右上角分享按钮 → 竖版海报预览 + Instagram/TikTok/X/Facebook/复制链接 | 无 | **已确认不做**(见 §2 决策 3),维持候选任务 T47,未排入本期 | | **观看人数** | Agora 数据通道实时推送 `user-num-change` | `/api/dance/live/state` 4s 轮询里的 `watching` 字段,展示上都是"👀 数字",机制不同但**效果对用户来说等价** | 无需改动,轮询频率够用,不是真人数敏感场景 | | **首次自动播放音频拦截** | 小弹窗「Play live audio? / Keep muted / Play sound」,不挡住画面 | 全屏点击进入蒙层(`.splash`),挡住画面直到点击 | 我们的方案信息量更大(品牌介绍+免责声明可以一起放),**不建议改成小弹窗**,全屏蒙层同时也是 5.1 节年龄确认的载体,改小会削弱年龄提示的存在感 | | **会话健康状态机** | `connecting/playing/block/end/error` 五态,Agora 事件+轮询双保险防黑屏 | 只有 `player` 的 `error` 事件监听,出错直接跳下一条 | 场景本质不同(预渲染轮播 vs 实时推流),黑屏风险类型不一样,**不建议照搬整套状态机**,现有的"出错跳下一条"已经是对的兜底策略 | | **上传进度体验** | 伪进度(按文件大小估算时长的缓动动画)+ 真实分片上传进度混合,防止大图卡在 0%/不动 | `submitGarmentUpload`/`submitCharacterUpload` 只有"上传中…"文案,**没有进度条** | 值得抄(T44),小改动、体验提升明显 | | **内容审核错误文案** | 区分"明确拒绝"(`UPLOAD_AUDIT_REJECTED`)vs"审核服务不可用"(`UPLOAD_AUDIT_FAILED`)两种提示 | `moderateGarmentImage`/`moderateFaceBlacklist` 目前是占位实现(无供应商时 `auto_pass`),还没有真正的"拒绝"分支需要文案 | 低优先级——等 T26/T32 真正接入审核供应商时再一并做文案区分(T45,标记为"供应商就绪后执行") | | **底层通信机制** | REST 建会话(一次性)+ Agora 数据通道推送所有实时事件,客户端动作走统一 `POST /live/room/message` | 纯 HTTP 轮询:`pollState` 4s 一次全量拉取 `/api/dance/live/state` | 不建议改——我们的场景更新频率低(片段级切换,不是逐帧),轮询足够且实现简单,引入 WebSocket/SSE 收益不成比例(不在本期任务里) | --- ## 2. 已确认的决策(2026-09-17) | # | 问题 | 决策 | 落地方式 | |---|---|---|---| | 1 | 要不要放弃"漫画风"整体换成 PixVerse 深色极简风? | **不换**,保留漫画风视觉基调 | T40-T42 只改 dance.html 独有部分的**布局/交互形式**(排队头像条、底部换装横条、hover 发送态),继续用现有白底黑边硬阴影+马卡龙撞色的视觉语言重做,不做像素级抄样式;`live.html`/`live.css` 共享部分不受影响 | | 2 | 排队头像用什么占位?要不要把上传入口合并进选衣服面板? | 头像用哈希色块;**上传入口合并进选衣服面板** | 头像:复用 `live.html` 已有的 `pastel(name)` 取色函数(首字+哈希色块),不新增头像存储。上传:原来左栏独立的"传衣服"/"传人" tab 撤掉,改成新换装横条里一张常驻的"DIY"卡片(同 PixVerse 截图里 DIY 卡片的位置感,样式走漫画风),点开后二级选择"传衣服"还是"传人",分别复用现有 `submitGarmentUpload`/`submitCharacterUpload` 的付费逻辑(登录+邮箱验证+扣积分规则完全不变)——**"传人"点开后仍然会跳转新房间**(`location.href = dance.html?characterId=...`),这点和"传衣服"(原地生成、留在当前房间)行为不同,DIY 卡片的二级选择要把这个差异讲清楚(比如文案"上传衣服给 TA 穿" vs "上传新角色,开一个新直播间"),不能直接照抄 PixVerse 单一 DIY 入口的样子,否则用户点了"传人"突然跳走会觉得莫名其妙 | | 3 | 要不要做聊天弹幕、要不要做分享海报? | **做聊天弹幕(T46),不做分享海报** | 聊天弹幕正式排入本期任务表;分享海报维持候选 T47,本期不排期 | | 4 | 要不要把"投票排序"换成"每人限一条+FIFO"? | **不改机制,加一条轻量限流** | 保留投票排序;新增 T48:同一 uid 已有未完成的**免费**排队条目(`pick` 产生的 `queue` 记录,状态非 `done`/`failed`)时,再次调用 `/api/dance/pick` 直接拒绝并提示"你已经有一件衣服在排队了";**付费上传(衣服/人物)不受此限流**——已经有积分门槛天然限流,且强行限制会打击"想连续买两个人物片段"这类合理的付费场景 | --- ## 3. 依赖关系 ```mermaid graph LR T40[T40 顶部换装胶囊+排队头像条] --> T42[T42 底部换装横条改版+合并上传入口] T41[T41 换装卡片 hover 发送态组件] --> T42 T42 --> T43[T43 移动端适配换装横条] T42 --> T46[T46 聊天弹幕接入 dance room] T44[T44 上传进度条体验优化] T48[T48 同 uid 免费排队轻量限流] T45[T45 审核错误文案区分 · 占位,待供应商接入] classDef ready fill:#d4f7d4; class T40,T41,T44,T48 ready ``` T40/T41/T44/T48 互不依赖,可以完全并行开工;T42 依赖 T40(复用同一套 "当前换装状态"数据结构)和 T41(复用卡片组件),是本期工作量最大的 一项(面板改版+合并上传入口两件事揉在一起);T43 是 T42 的收尾(移动 端场景下横条要收起成可横滑的一行);T46 依赖 T42——聊天面板要挂载到 T42 改版后腾出来的右侧栏空间(选衣服从右侧网格变成底部横条后,原 `.right-col` 空出来,正好给聊天用,不用另外抢布局空间);T45 依赖 T26/T32 真正接入审核供应商(当前仍是占位实现,日期未定,先记录任务不 排进本期工时)。 --- ## 4. 任务表 | # | 任务 | 依赖 | 验收标准 | 工时(人天) | |---|---|---|---|---| | T40 ✅ | **[已完成 2026-09-17] 顶部"正在换装"胶囊 + 排队头像条**:把 `now-playing-box` 改造成居中胶囊形式(`🔥 正在换装 · 昵称`),沿用漫画风视觉;胶囊下方新增横向排队条,渲染 `state.queue` 里 `status:"generating"` 的条目,每项一个昵称首字哈希色块头像 + 迷你服装缩略图角标。**实现笔记**:没有单独提取共享 `.js`——现有代码里 `live.html`/`dance.html` 本来就大量各自复制小工具函数(`escapeHtml`/`getCookie`/`ensureIdentity`…),是这个项目一直以来的既有约定,只为一个 4 行的 `pastel()` 函数新起一个共享文件与现状不一致,所以按老样子在 `dance.html` 里复制了一份,没有改 `live.html`。原 `now-playing-box` 的昵称/时间显示拿掉了(昵称并入新胶囊,时间字段 `np-time`/`fmtClock` 直接删除——只是切换时刻的时钟展示,没人在用,删了比留着"隐藏但没用"的字段干净),`now-playing-box` 现在只剩点赞按钮。已用真实房间数据(角色 `35b3de40-…`)在浏览器里验证胶囊+头像条+T48 的排队禁用态同时正确渲染;`node --test *.test.js` 141 个测试全绿 | 无 | 有人正在被换装时,视频上方居中显示昵称胶囊;排队条按 `queue` 数组顺序渲染,每人一个色块头像+缩略图角标;队列为空时横条不占位(`hidden`);移动端窄屏下横条可横向滚动不换行(CSS 层面用标准 `overflow-x:auto` 实现,未做设备级实测,见下方备注) | 1.5 | | T41 ✅ | **[已完成 2026-09-18] 换装卡片 hover"发送"态组件**:`.wardrobe-card` 增加 hover(桌面)/点一下展开+再点确认(移动端)两种交互形态,悬浮/展开时叠一层半透明黑色蒙层+居中"发送"按钮,点击才真正提交,避免像现状一样"点一下就直接提交,容易误触"。**实现笔记**:用 `matchMedia("(hover: hover)")` 一次性判断主指针是否真的支持 hover 来分流两套交互(而不是猜 UA/触屏),桌面纯 CSS `:hover` 驱动,移动端才挂 JS 点击展开逻辑;`pickGarment` 本体不变,只是触发点从"卡片 click"搬到"发送按钮 click"。真机验证:浏览器自动化的合成 hover 事件不会真正置位 CSS `:hover`(工具本身的限制,不是代码问题),改用直接点"发送"按钮验证了完整链路(真的打到 `POST /api/dance/pick`,200),`.revealed` 展开态的 CSS 路径也单独验证过。已知小限制:移动端展开态是纯前端瞬时 class,4s 一次的 `pollState()` 整块重渲染会把展开态冲掉——这是现有轮询+整块 `innerHTML` 重绘架构的通用代价,不是本任务单独引入的问题,代码里留了注释说明 | 无 | 桌面端 hover 卡片才出现"发送"按钮,不 hover 只看到图+名字;移动端用点击展开"发送"按钮再二次点击提交的两段式交互;`pickGarment` 的实际提交逻辑不变,只是触发时机从"卡片 click"改成"发送按钮 click" | 1 | | T42 ✅ | **[已完成 2026-09-18] 右侧"选衣服"面板 → 底部横条改版 + 合并上传入口**:把 `pickPanel`(现状是右侧栏 2 列网格)在桌面端改造成贴视频底部的横向可滚动一行(复用 T41 的卡片组件),右侧栏收窄/腾出来给 T46 聊天用;横条末尾新增一张常驻"DIY"卡片(虚线描边+上传图标,漫画风样式),点击展开一个二级选择:「传衣服给 TA 穿」(原地生成,复用 `submitGarmentUpload`,成功后留在当前房间)/「传新角色开新直播间」(复用 `submitCharacterUpload`,成功后仍然 `location.href` 跳转新房间,**跳转行为不变**,只是入口从左栏 tab 挪到这里,文案要明确写出"会离开当前房间");左栏 `wardrobeTabs` 撤掉"传衣服"/"传人"两个 tab,只保留"待播放"(不再需要 tab 切换 UI,`leftTab` 状态机简化为常量)。**实现笔记**:DIY 二选一弹窗+上传表单复用 `.nickname-modal/.nickname-box` 壳子,测试时发现两个真实 bug 并已修:① 上传表单原来住在深色左栏,`.upload-panel .hint`/`.diy-choice-hint` 用的是给深色底配的 `var(--muted)`(半透明白),搬进白色弹窗后几乎看不清,改成深色低透明度;② 未登录点上传会弹 `authModal`,但它和新加的 `diyModal` z-index 相同、`diyModal` 在 DOM 里更靠后,会不受控制地盖住登录框——已在 `openAuthModal()` 里加一行 `diyModal.hidden = true` 兜底关掉。`live.html`/`live.css` 零改动(`git diff --stat` 确认),浏览器手工过了一遍确认无回归 | T40, T41 | 桌面端选衣服区域变成贴视频底部的横向条,可横向滚动查看全部衣柜;"生成中"/"生成失败"两个子区仍在横条内可见;末尾 DIY 卡片点开后能二选一走完整套现有付费上传流程(登录门槛/邮箱验证门槛/积分扣费/退款逻辑完全不变,只是换了入口位置);点"传新角色"前有明显提示会离开当前房间;左栏只剩"待播放"列表,不再有 tab 按钮;`live.html` 页面布局和样式不受影响(回归检查:`live.html` 手工过一遍) | 3 | | T43 ✅ | **[已完成 2026-09-18] 移动端适配换装横条**:现状 `.mobile-subtabs` 在移动端用"待播放/选衣服"两个 tab 切换左右栏;T42 把选衣服面板改成横条后,需要重新设计移动端下这条横条跟"待播放"列表、聊天(T46)三者的切换/共存方式。**实现笔记**:`resize_window` 工具在这个环境里对已存在的 MCP 标签页不生效,改用"在当前标签页里插一个 390×844 的真 iframe 指向同一个 URL"的办法拿到货真价实的窄视口渲染(iframe 有自己独立的 layout viewport,`@media`/`vh` 都是真的,比硬凑一段 CSS 去猜靠谱)。测出并修了两个真 bug,都不是"锦上添花"而是"挤到不可用":① `.channel{height:100vh}` 是硬高度、不是页面可滚的高度上限,T13 遗留的 `.player-wrap{width:100%}`(宽度驱动 9:16,窄屏下约等于满屏高)+ T42 新增的横条,两个加起来直接把"待播放"压成 24px 一条缝——根因是 `.player-wrap` 还带着 live.css 基础规则的 `flex:1`,会无视后面写的 `height` 去抢 `.player-col` 的剩余空间;移动端改回"由高度定宽度"(`flex:none; height:40vh`)后视频让出空间,"待播放"能完整露出不用再内部滚动。② 视频变窄之后,居中的"直播中"胶囊和左上角点赞按钮撞在一起——点赞按钮移动端挪到视频左下角,与顶部几个胶囊错开;这条只影响 dance.html(`live.html` 的视频从来不限宽,不受影响,也没有改 live.css)。另外把横条卡片调小、去掉横条自己的竖向滚动上限(避免在已经要整页滚的手机页面上再嵌一层内部滚动区域)、给 DIY 弹窗加了窄屏 `max-width` 兜底。用上述 iframe 方法反复截图验证到视频/横条/"待播放"三块无重叠、无裁切 | T42 | 移动端(`max-width:768px`)下选衣服横条可用,不跟视频/待播放列表/聊天重叠或挤压到不可用的程度;触摸横滑流畅 | 1 | | T44 ✅ | **[已完成 2026-09-18] 上传进度条体验优化**:`submitGarmentUpload`/`submitCharacterUpload` 增加进度条 UI,参照参考分析 §3 的"伪进度+真进度混合"思路——按文件大小估算一个基准时长,`setInterval` 缓动推进到 ~88%,改用 `XMLHttpRequest`(`fetch` 的 FormData 上传拿不到进度事件)的 `upload.onprogress` 拿真实进度覆盖,完成后跳 100% 再消失;小文件也有 500ms 最短可见时长,不会一闪而过。**实现笔记**:写成两个共享小工具(`uploadWithProgress` 包一层 XHR 成 Promise、`createUploadProgress` 管进度条状态机),两个上传表单各自只多传一个 `progressBar` 元素,业务逻辑(登录/邮箱验证门槛、错误分支、成功后跳转/刷新)完全没动。浏览器里手动驱动过 `createUploadProgress`(大文件模拟到 88% 封顶、`finish()` 正确跳 100% 再隐藏、小文件也有最短停留时间)——真实登录态下的端到端上传没条件测(没有已登录+已验证邮箱的测试账号),但这条改动完全不碰鉴权/积分/生成那些已有逻辑,风险低 | 无 | 上传大图(≥3MB)时进度条平滑增长不卡在 0%;上传完成后进度条走到 100% 再消失;小图(<200KB)上传时给个最短展示时长,不会因为"太快看不出动画"而显得突兀 | 1 | | T45 | **审核错误文案区分**(占位,依赖 T26/T32 真正接入审核供应商后再排期):`moderateGarmentImage`/`moderateFaceBlacklist` 接入真实供应商、有了"明确拒绝"返回值之后,前端区分"内容不合规,请换一张"(`verdict: rejected`)和"审核服务暂时不可用,请稍后重试"(`verdict: service_error`)两种提示文案,不再统一用"上传失败" | T26/T32 供应商接入(当前均是 `auto_pass` 占位,未排期) | 供应商接入后:明确拒绝和服务异常两种情况前端提示文案不同 | 0.5(供应商就绪后执行,先不计入本期工时) | | T46 ✅ | **[已完成 2026-09-18] 弹幕聊天接入 dance room**:新增 dance room 专属聊天接口——`POST /api/dance/chat`(body `{uid, nickname, text, characterId}`)、`GET /api/dance/chat?characterId=...&cid=...`(3s 轮询,同 `live.html` 的 `pollChat` 套路)。**不能照抄 `/api/chat` 的实现**——查过 `server.js:3556` 才发现现有 `/api/chat` 其实是"弹幕决定下一集演什么"的文本转视频请求入口,POST 成功后会直接调 `submitPromptToQueue` 触发生成任务,配的 `isRateLimited`(每 IP 5 分钟仅 3 次)也是按"生成任务"的额度设的,照搬到 dance 聊天上会导致发几条消息就被拦;dance room 的换装请求走的是独立的 `/api/dance/pick`,聊天在这里就是纯聊天,不应该触发任何生成。落地:新增一个不挂 `submitPromptToQueue` 的 `pushDanceChatMessage(characterId, msg)`(按 `characterId` 分桶存储,仿 `pushChatMessage` 的 300 条上限截断),**复用** `moderateText`(通用违禁词表,语义上适用)过滤消息,**新增一个独立的、更宽松的 dance 聊天限流**(不复用 `isRateLimited` 那个 3/5min 的生成配额),具体阈值开工前拉一下建议值(比如每 IP 每分钟 10 条);复用 `live.css` 已有的 `.chat-list`/`.chat-msg`/`.chat-input-row`/昵称哈希取色样式,不新增一套聊天 CSS;挂载到 T42 腾出来的右侧栏(`.right-col` 又拿回来了,这次内容是聊天,不是选衣服)。**实现笔记**:限流阈值定为 10 条/60s/IP(POST 一进来就先计数,body 校验/审核不通过的请求也占额度,防止用畸形请求循环绕开限流)。落库跟着 `saveDanceState()` 走,`chatMessages` 存在每个房间对象里(`getDanceRoom` 里懒加载补字段,兼容老的 `dance-state.json`),不单开一个文件。跟 `/api/chat` 还有一处**有意**的行为差异:未通过审核的消息**不发布**到房间(`/api/chat` 会把违禁词消息也推给所有人看,只是标"未通过审核")——dance 聊天没有下游状态要展示给别人看,没理由让全房间看到被拒的文字。前端复用了 dance.html 在 T40 时已经加的 `pastel()` 取色函数,昵称身份行/改昵称走的是既有的 `ensureIdentity`/`openNicknameModal`(跟点赞共用同一个昵称 cookie 和同一个弹窗)。新写了一条后端集成测试(GET/POST 往返、跨房间隔离、审核拒绝不落库、参数校验、限流拦截),`node --test *.test.js` 142 个全绿;浏览器里端到端发送验证过——POST 立即成功、消息在 3s 轮询周期内出现在聊天列表(这是设计使然的轮询延迟,不是 bug,跟 live.html 现有聊天体验一致)。移动端没有另外做聊天/选衣服/待播放三块的收纳优化,沿用 T43 的"整页堆叠、不做 tab 切换"思路,没有超出这条任务本身的范围 | T42(视觉空间) | 用户在某个人物直播间发的消息只在该直播间聊天里可见,切换到另一个 characterId 的房间看不到;正常聊天频率(比如 1 条/5 秒)不会被限流拦截,压测式狂刷才会被拦;违禁词仍会被拒绝;聊天面板不影响换装横条的可用性;未登录也能发言(走 `ensureIdentity()` 昵称 cookie);**不会**触发任何视频生成任务 | 2.5 | | T47 | **分享海报功能**(已确认本期不做,见 §2 决策 3,保留候选记录避免遗漏):生成一张竖版海报(当前换装截图+文案),支持复制链接/下载图片;PixVerse 的"直接分享到 Instagram/TikTok/X/Facebook"依赖对方 App 的 Web Share 集成,我们大概率只能做到"复制链接"+"下载图片让用户自己分享" | 无 | 点击分享按钮生成一张包含当前换装画面的竖版图片,可下载/复制链接 | 3(候选,未排期) | | T48 ✅ | **[已完成 2026-09-17] 同 uid 免费排队轻量限流**:`/api/dance/pick` 增加校验——提交前查该 `characterId` 房间的 `danceQueue` 里是否已存在同一 `uid` 且状态未终结(非 `done`/`failed`)的条目,若有则直接拒绝(建议 400,`error: "你已经有一件衣服在排队了,等它播完再选新的"`);前端 `pickGarment`/`renderPickPanel` 对"已有排队中条目"的用户禁用其余衣柜卡片的点击态,给出同样的提示;**只限制免费 `pick`,`upload-garment`/`upload-character`(付费)不受影响** | 无(可独立于 T40-T46 先做) | 同一 uid 已有一条排队中的免费选衣服请求时,再次点击其他衣柜卡片被拒绝并看到明确提示;该请求播放完成(终结态)后能正常再选;同一 uid 在有免费请求排队时仍然能正常走付费上传(衣服/人物)不受影响;现有 `dance-channel.test.js`/`dance-mode2.test.js`/`dance-mode3.test.js` 不能红 | 0.5 | **本期总工时约 9 人天**(T40-T44 + T46 + T48,不含占位的 T45、维持 候选未排期的 T47)。T40/T41/T44/T48 可完全并行(约 2 人天内出首批可看 效果),关键路径是 T40+T41→T42(本期最重的一项,3 人天)→T43/T46, 约 7 人天。 --- ## 5. 第六期出口 - dance.html 桌面端有醒目的"正在换装"胶囊 + 排队头像条,视觉焦点从 "一个纯文字框"变成"能一眼看出谁在排队、排到谁了"。 - 选衣服从"网格点了就提交"变成"hover/二次确认再发送",减少误触,且改 版为贴视频底部的横条,视觉上贴近参考实现的横条+悬浮态。 - "传衣服"/"传人"付费上传入口合并进换装横条的 DIY 卡片,左栏不再需要 tab 切换;上传有真实可感知的进度反馈,不再是一句"上传中…"干等。 - dance room 有了自己的聊天弹幕(按 characterId 隔离),右侧栏从"选衣 服网格"变成聊天面板。 - 同一 uid 不能靠反复免费选衣服刷队列,付费上传不受影响。 - `live.html` 与 `live.css` 的共享部分不受影响(每个改动都限定在 dance.html 专属的 DOM/CSS 里)。 ## 6. 明确不在第六期范围 - 不接入 Agora/声网或任何实时推流通道。 - 不做深色视觉基调的整体重做(已确认保留漫画风)。 - 不把投票排序模型换成 FIFO 单请求限制(已确认只加 T48 轻量限流)。 - 不做分享海报(候选 T47,需另行确认后才排期);不做 PixVerse 式的 "prompt-send"飘字弹幕特效(T46 只做常规聊天流)。 - 不改动登录/积分/审核商业化流程本身(T42 只挪上传入口位置)。