From 035a115c294a3fb6d8b105ce5fed3aed47938c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=8C?= <259278618@qq.com> Date: Thu, 13 Aug 2026 16:59:49 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20v2.1=20=E5=85=A8=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=BC=94=E5=87=BA=E8=AE=BE=E8=AE=A1=20+=20=E8=AE=A1=E5=88=92?= =?UTF-8?q?=EF=BC=88=E5=BC=80=E5=9C=BA=E8=87=AA=E5=8A=A8/=E5=9B=9E?= =?UTF-8?q?=E5=A4=8D=E8=87=AA=E5=8A=A8=E7=BB=A7=E7=BB=AD/=E7=BB=88?= =?UTF-8?q?=E5=B1=80=E6=BC=94=E7=BB=8E/=E5=B7=B2=E6=92=AD=E8=A1=8C?= =?UTF-8?q?=E6=8A=98=E5=8F=A0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plans/2026-08-13-story-theater-v2.md | 257 +++++++++++++++++- .../specs/2026-08-13-story-theater-design.md | 39 +++ 2 files changed, 295 insertions(+), 1 deletion(-) diff --git a/docs/superpowers/plans/2026-08-13-story-theater-v2.md b/docs/superpowers/plans/2026-08-13-story-theater-v2.md index 847e6fc..5bd86f4 100644 --- a/docs/superpowers/plans/2026-08-13-story-theater-v2.md +++ b/docs/superpowers/plans/2026-08-13-story-theater-v2.md @@ -1126,7 +1126,262 @@ git commit -m "docs: 剧情演绎文档补充 + 全量剧本生成导入" --- -## 验收对照(spec v2) +### Task 8: 后端 FinalSettle.final_node(v2.1 终局演绎数据层) + +> v2.1:choose 到终局时,结局台词目前从未播放(前端直接跳结算页)。本任务让终局节点(含 script)随 FinalSettle 一并返回,前端 Task 9 先演绎再跳结算。 + +**Files:** +- Modify: `biz/service/level_play.go:32-43`(FinalSettle struct) +- Modify: `biz/service/level_play.go:149-166`(Choose 终局分支) +- Modify: `biz/model/dto/level_play.go:18-28` +- Modify: `biz/controller/level_play.go:25-37` + +- [ ] **Step 1: service FinalSettle 加 FinalNode** + +`biz/service/level_play.go` FinalSettle struct 末尾加: + +```go + FinalNode *Node // 终局节点(含 script,v2.1 前端演绎结局台词后跳结算) +``` + +- [ ] **Step 2: Choose 终局分支返回终局 Node** + +`biz/service/level_play.go` Choose 中 `WithLock` 调用之后、`return nil, settle, nil` 之前插入: + +```go + // v2.1:终局节点剧本随结算返回(前端先演绎结局台词再跳结算页) + finalNode, err := nodeOf(ctx, levelRec, nextRec) + if err != nil { + return nil, nil, err + } + settle.FinalNode = finalNode +``` + +(`nodeOf` 与决策分支同函数、同签名,构建选项 + 元素 + buildNode;只读查询,锁外执行安全。) + +- [ ] **Step 3: dto FinalSettle 加 FinalNode** + +`biz/model/dto/level_play.go` FinalSettle 末尾加: + +```go + FinalNode *NodeVO `json:"final_node"` +``` + +- [ ] **Step 4: controller 映射** + +`biz/controller/level_play.go` Choose 的 `res.Final = &dto.FinalSettle{...}` 之后加: + +```go + if settle.FinalNode != nil { + vo := nodeVO(settle.FinalNode) + res.Final.FinalNode = &vo + } +``` + +- [ ] **Step 5: 编译 + 单测** + +Run: `go build ./... && go test ./biz/service/ ./cmd/genasset/` +Expected: 全部 PASS(SettleFinal 纯函数不受影响) + +- [ ] **Step 6: Commit** + +```bash +git add biz/service/level_play.go biz/model/dto/level_play.go biz/controller/level_play.go +git commit -m "feat: FinalSettle 增加 final_node——终局节点剧本透出(v2.1 终局演绎数据层)" +``` + +--- + +### Task 9: 前端全自动演出(v2.1) + +> 用户走查反馈:情景执行依赖点按钮、文字太多。本任务:开场自动进入、回应气泡自动继续、终局台词先演绎再跳结算、StoryPlayer 已播行折叠。 + +**Files:** +- Modify: `ui-src/src/components/SceneTheater.vue` +- Modify: `ui-src/src/components/StoryPlayer.vue` +- Modify: `ui-src/src/pages/play/play.vue` + +- [ ] **Step 1: SceneTheater 移除「开始」按钮,朗读结束自动 done** + +`SceneTheater.vue` 三处修改: + +a) 模板删除 `theater-actions` 整块: + +```html + + 开始 › + +``` + +b) mounted 改为朗读结束自动 emit done: + +```js + mounted() { + this.speaking = true + speak(this.content, this.audio) + // v2.1:朗读结束自动进入决策幕(「跳过」仍可手动跳过) + setTimeout(() => { this.speaking = false; this.$emit('done') }, Math.min(3000, this.content.length * 300)) + }, +``` + +c) 删除样式(不再有按钮): + +```css +.theater-actions { display: flex; justify-content: center; margin-top: 32rpx; position: relative; z-index: 2; } +.theater-go { font-size: 34rpx; min-width: 240rpx; animation: t-pop 0.5s ease 1.2s backwards; } +``` + +- [ ] **Step 2: StoryPlayer 已播行折叠** + +`StoryPlayer.vue` 模板 `story-lines` 循环改为只保留当前行 + 上一行(v-show 保持 DOM 存活,fading 淡出): + +```html + + +``` + +CSS `.story-line.done` 替换为: + +```css +.story-line.fading { opacity: 0.35; } +``` + +(`done` 类不再被引用,一并删除;`active` 保持现状。) + +- [ ] **Step 3: play.vue 终局演绎 + 回复自动继续** + +`play.vue` 六处修改: + +a) data 加: + +```js + replyTimer: null, + playingFinal: false, + finalNode: null, +``` + +b) onUnload 清回复计时器: + +```js + onUnload() { + if (this.branchTimer) clearTimeout(this.branchTimer) + if (this.replyTimer) clearTimeout(this.replyTimer) + }, +``` + +c) computed 加: + +```js + finalScript() { + return this.finalNode ? parseScript(this.finalNode.script) : null + } +``` + +d) 模板在 `v-if="curNode && isStoryNode"` 之前插入终局演绎分支(v-if 优先于剧情流,避免终局节点 script 非空时走错分支): + +```html + + + + +``` + +e) submit 的 `else if (data.final)` 分支替换为: + +```js + } else if (data.final) { + uni.setStorageSync(RESULT_KEY, { + level_id: this.levelId, + title: this.detail.title, + scene_name: this.detail.scene ? this.detail.scene.name : '', + route_steps: this.routeSteps, + final: data.final + }) + this.choosing = false + // v2.1:终局节点有剧本 → 先演绎结局台词再跳结算;无剧本直接跳(回退) + if (data.final.final_node && parseScript(data.final.final_node.script)) { + this.finalNode = data.final.final_node + this.playingFinal = true + } else { + playSound('finish') + uni.redirectTo({ url: '/pages/result/result' }) + } + } +``` + +f) branchTimer 内剧情流分支(`this.isStoryNode`)加 3 秒自动继续: + +```js + if (this.isStoryNode) { + this.reply = fb + this.showOptions = false // 提交后隐藏选项区,防重复点击 + // v2.1:回应气泡约 3 秒自动继续(点击「继续」立即继续) + this.replyTimer = setTimeout(() => this.autoContinue(), 3000) + } else { + this.feedback = fb + } +``` + +g) methods 加 `autoContinue` / `gotoResult`,`continuePlay` 开头清计时器: + +```js + autoContinue() { + if (this.reply && !this.choosing) this.continuePlay() + }, + gotoResult() { + playSound('finish') + uni.redirectTo({ url: '/pages/result/result' }) + }, + continuePlay() { + if (this.replyTimer) { clearTimeout(this.replyTimer); this.replyTimer = null } + if (this.isStoryNode) { + this.curNode = this.reply.next + this.reply = null + this.showOptions = false + } else { + this.curNode = this.feedback.next + this.feedback = null + } + this.mood = '🤔' + } +``` + +- [ ] **Step 4: 构建验证** + +Run: `cd ui-src && npx vite build --mode h5 2>&1 | tail -3` +Expected: 构建成功无报错 + +- [ ] **Step 5: H5 端到端走查** + +后端与 H5 dev server 运行中,走查 `http://localhost:5173/#/pages/play/play?level_id=<第1计关卡>`(第 1 计剧本已导入): +1. 开场朗读结束自动进入剧情流(无「开始」按钮);「跳过 ›」立即进入 +2. 剧情流只显示当前行 + 上一行淡出,无 3-6 行堆叠 +3. 选择后回应气泡约 3 秒自动进入下一节点;点击「继续 ›」立即进入;连点不双跳 +4. 走到终局:结局台词先演绎(跳过可直达结算),播完自动跳结算页;结算数据正常(final 已在 RESULT_KEY) + +- [ ] **Step 6: Commit** + +```bash +git add ui-src/src/components/SceneTheater.vue ui-src/src/components/StoryPlayer.vue ui-src/src/pages/play/play.vue +git commit -m "feat: 全自动演出——开场自动进入/回复自动继续/终局台词演绎/已播行折叠(v2.1)" +``` + +--- + +## 验收对照(spec v2 + v2.1) + +| 验收标准 | 任务 | +|---|---| +| 决策节点台词流演绎(气泡/朗读/逐字高亮/表情,末句提问) | Task 3 + Task 6 | +| ActionCard 嵌入剧情流(触控 5 种同) | Task 4 | +| 角色回应气泡融入剧情流,无弹窗 | Task 4 Step 3 | +| script 空回退 v1 呈现 | Task 4 Step 2(v-else 分支) | +| 管线生成→精修→导入→前端生效;校验拒绝不合规草稿 | Task 5 + Task 6 | +| level/choose 判分与完美机制不变 | Task 1(仅加列透出);Task 6 Step 5 走查确认 | +| 开场自动进入决策幕(无「开始」按钮),跳过可用 | Task 9 Step 1 | +| 回应气泡约 3 秒自动继续,点击立即继续,无重复推进 | Task 9 Step 3 | +| 终局台词先演绎再自动跳结算;final_node.script 空直接跳 | Task 8 + Task 9 Step 3 | +| StoryPlayer 已播行折叠(当前行 + 上一行淡出) | Task 9 Step 2 | | 验收标准 | 任务 | |---|---| diff --git a/docs/superpowers/specs/2026-08-13-story-theater-design.md b/docs/superpowers/specs/2026-08-13-story-theater-design.md index a4124c6..3af2648 100644 --- a/docs/superpowers/specs/2026-08-13-story-theater-design.md +++ b/docs/superpowers/specs/2026-08-13-story-theater-design.md @@ -85,6 +85,45 @@ - **阶段 B(生成管线)**:genasset `--only=scripts` / `--import-scripts` + 第 1 计试点生成并精修 1 关 - **阶段 C(全量)**:36 关剧本后台生成 + 人工精修流程 + 走查验收 +## v2.1 修订(2026-08-13):全自动演出 + 文字精简 + +> 用户走查反馈(v2 落地后):**「现在的情景执行依赖于点击按钮,这是不对的,应该自动的,而且也不需要显示这么多文字,文字多了,孩子更不喜欢」**。v2 解决「演什么」,v2.1 解决「怎么演得像动画」——消除按钮依赖、削减屏幕文字量。数据模型零改动;接口仅 `FinalSettle` 多返回 `final_node`(复用既有 NodeVO 链路,含 script)。 + +### 核心转变 + +| 维度 | v2(现状) | v2.1(自动演出) | +|---|---|---| +| 开场 | SceneTheater 朗读后需点「开始 ›」 | 朗读完成后自动进入决策幕(保留「跳过 ›」) | +| 反馈 | 回应气泡需点「继续 ›」 | 气泡出现约 3 秒后自动继续(点击可立即继续,计时器防重复) | +| 终局 | choose 到终局直接跳结算页,结局台词从未播放 | 先由 StoryPlayer 演绎终局节点台词(`final_node.script`),播完自动跳结算 | +| 文字量 | StoryPlayer 3-6 行完整堆叠 | 已播行折叠:只保留当前行 + 上一行淡出 | + +### 数据与接口(最小改动) + +- `FinalSettle` 增加 `final_node`(NodeVO,含 script):service Choose 终局分支把终局节点记录(既有 `nextRec`)经 `nodeOf` 构建为 Node 一并返回;controller 映射到 dto +- play.vue `data.final` 分支:先存 RESULT_KEY(同现状),若 `final_node.script` 非空 → 进入终局演绎态(StoryPlayer 播 `final_node` 剧本)→ `@done` 后 `redirectTo` 结算页;script 为空 → 直接跳结算(行为同现状) + +### 自动节奏 + +- SceneTheater:移除「开始 ›」按钮;朗读结束(mounted 的 setTimeout,同现状)自动 emit done;「跳过 ›」保留 +- 回应气泡(剧情流 reply):出现后 3 秒自动 `continuePlay()`;点击「继续 ›」立即继续;自动计时触发前点击需 clearTimeout 防重复推进;v1 弹窗反馈(feedback)不加自动继续(保持人工节奏) +- 终局演绎:StoryPlayer 播完自动跳结算(3-6 句 × 8-30 字,约 10-25 秒);跳过按钮同样可跳过终局台词直接跳结算 + +### 前端回退与渐进升级 + +- `final_node.script` 为空 → 直接跳结算(现状不变) +- 终局演绎复用 StoryPlayer 与剧情流形态(`:key` 强制重建、done → 跳转),不新增组件 + +### 验收标准(v2.1) + +1. 开场自动进入决策幕(无「开始」按钮),「跳过」仍可用 +2. 回应气泡约 3 秒自动继续;点击可立即继续;无重复推进(连点/自动+点击不双跳) +3. 终局节点台词先演绎再自动跳结算;`final_node.script` 为空直接跳结算(回退) +4. StoryPlayer 已播行折叠:当前行 + 上一行淡出,无 3-6 行完整堆叠 +5. 判分与完美机制行为不变(level_play 单测全过) + +--- + ## 设计总览 每关 = 一部 2-3 分钟儿童小剧场,三幕结构: