feat: 闯关页剧场化(开场演出覆盖层+动作卡呈现+分支演出晃动过渡)
This commit is contained in:
@@ -9,6 +9,22 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 开场演出(覆盖层,播完/跳过进入决策) -->
|
||||
<SceneTheater
|
||||
v-if="showIntro && detail"
|
||||
:scene-name="detail.scene ? detail.scene.name : ''"
|
||||
:scene-emoji="scene.emoji"
|
||||
:scene-color="scene.color"
|
||||
:scene-image="detail.scene_image"
|
||||
:content="detail.scene_content"
|
||||
:content-pinyin="detail.scene_content_pinyin"
|
||||
:audio="detail.scene_audio"
|
||||
:character="introCharacter"
|
||||
:props="introProps"
|
||||
:show-pinyin="showPinyin"
|
||||
@done="showIntro = false"
|
||||
/>
|
||||
|
||||
<view v-if="detail" class="content">
|
||||
<!-- 进度点:每走一步点亮一个 -->
|
||||
<view class="progress-dots">
|
||||
@@ -16,7 +32,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 场景面板:环境 + 情境描述 + 朗读 + 漂浮装饰 -->
|
||||
<view class="scene-card card" :style="{ background: scene.color + '44' }">
|
||||
<view class="scene-card card" :class="{ 'fx-shake': sceneFx }" :style="{ background: scene.color + '44' }">
|
||||
<image v-if="sceneImg" class="scene-img" :src="sceneImg" mode="aspectFill" />
|
||||
<view class="scene-float f1">☁️</view>
|
||||
<view class="scene-float f2">⭐</view>
|
||||
@@ -48,32 +64,21 @@
|
||||
</view>
|
||||
<SpeakButton class="node-speak" :text="curNode.content" :file="curNode.audio" label="读一读" />
|
||||
|
||||
<!-- 互动组件(触控 5 种) -->
|
||||
<PropPick v-if="curNode.interaction_type === 2" :config="curNode.config" @done="interactionDone" />
|
||||
<FindSpot v-else-if="curNode.interaction_type === 7" :config="curNode.config" @done="interactionDone" />
|
||||
<DragPlace v-else-if="curNode.interaction_type === 5" :config="curNode.config" @done="interactionDone" />
|
||||
<StepSort v-else-if="curNode.interaction_type === 3" :config="curNode.config" @done="interactionDone" />
|
||||
<LinkMatch v-else-if="curNode.interaction_type === 8" :config="curNode.config" @done="interactionDone" />
|
||||
<!-- 互动组件(触控 5 种,config 互动节点) -->
|
||||
<PropPick v-if="activeInteraction === 'PropPick'" :config="curNode.config" @done="interactionDone" />
|
||||
<FindSpot v-else-if="activeInteraction === 'FindSpot'" :config="curNode.config" @done="interactionDone" />
|
||||
<DragPlace v-else-if="activeInteraction === 'DragPlace'" :config="curNode.config" @done="interactionDone" />
|
||||
<StepSort v-else-if="activeInteraction === 'StepSort'" :config="curNode.config" @done="interactionDone" />
|
||||
<LinkMatch v-else-if="activeInteraction === 'LinkMatch'" :config="curNode.config" @done="interactionDone" />
|
||||
|
||||
<!-- 选项选择(type 1) -->
|
||||
<view v-else-if="curNode.interaction_type === 1" class="options">
|
||||
<view
|
||||
v-for="(o, idx) in curNode.options"
|
||||
:key="o.option_id"
|
||||
class="option"
|
||||
:class="['c' + (idx + 1), { disabled: choosing }]"
|
||||
@click="choose(o, idx)"
|
||||
>
|
||||
<view class="option-mark">{{ ['A', 'B', 'C', 'D'][idx] }}</view>
|
||||
<view v-if="o.prop" class="option-icon">
|
||||
<image v-if="o.prop.image" class="option-icon-img" :src="o.prop.image" mode="aspectFit" />
|
||||
<text v-else class="option-icon-emoji">{{ propEmoji(o.prop.name) }}</text>
|
||||
</view>
|
||||
<view class="option-text">
|
||||
<RubyText :text="o.text" :pinyin="o.text_pinyin" :show="showPinyin" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 动作卡(普通决策节点;多分支选项语义完整保留) -->
|
||||
<ActionCard
|
||||
v-else-if="activeInteraction === 'ActionCard'"
|
||||
:node="curNode"
|
||||
:show-pinyin="showPinyin"
|
||||
:disabled="choosing"
|
||||
@choose="choose"
|
||||
/>
|
||||
<view v-else class="soon card">该互动玩法(类型 {{ curNode.interaction_type }})敬请期待</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -99,10 +104,13 @@
|
||||
<script>
|
||||
import { api } from '../../api/index.js'
|
||||
import { getChildId, getChildAge } from '../../store/user.js'
|
||||
import { sceneVisual, personVisual, propVisual } from '../../utils/visual.js'
|
||||
import { sceneVisual, personVisual } from '../../utils/visual.js'
|
||||
import { playSound, isSoundEnabled, setSoundEnabled } from '../../utils/sound.js'
|
||||
import { pickInteraction, entryProps, entryCharacter } from '../../utils/theater.js'
|
||||
import RubyText from '../../components/RubyText.vue'
|
||||
import SpeakButton from '../../components/SpeakButton.vue'
|
||||
import SceneTheater from '../../components/SceneTheater.vue'
|
||||
import ActionCard from '../../components/ActionCard.vue'
|
||||
import PropPick from '../../components/interactions/PropPick.vue'
|
||||
import FindSpot from '../../components/interactions/FindSpot.vue'
|
||||
import DragPlace from '../../components/interactions/DragPlace.vue'
|
||||
@@ -112,7 +120,7 @@ import LinkMatch from '../../components/interactions/LinkMatch.vue'
|
||||
const RESULT_KEY = '36wisdom_result'
|
||||
|
||||
export default {
|
||||
components: { RubyText, SpeakButton, PropPick, FindSpot, DragPlace, StepSort, LinkMatch },
|
||||
components: { RubyText, SpeakButton, SceneTheater, ActionCard, PropPick, FindSpot, DragPlace, StepSort, LinkMatch },
|
||||
data() {
|
||||
return {
|
||||
childId: getChildId(),
|
||||
@@ -125,7 +133,9 @@ export default {
|
||||
routeSteps: [],
|
||||
mood: '🤔',
|
||||
combo: 0,
|
||||
soundOn: isSoundEnabled()
|
||||
soundOn: isSoundEnabled(),
|
||||
showIntro: false,
|
||||
sceneFx: ''
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
@@ -144,6 +154,15 @@ export default {
|
||||
},
|
||||
stepCount() {
|
||||
return Math.min(this.routeSteps.length + 1, 6)
|
||||
},
|
||||
activeInteraction() {
|
||||
return this.curNode ? pickInteraction(this.curNode) : ''
|
||||
},
|
||||
introCharacter() {
|
||||
return this.detail ? entryCharacter(this.detail.entry) : null
|
||||
},
|
||||
introProps() {
|
||||
return this.detail ? entryProps(this.detail.entry) : []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -152,6 +171,7 @@ export default {
|
||||
const data = await api.levelDetail(this.childId, this.levelId)
|
||||
this.detail = data
|
||||
this.curNode = data.entry
|
||||
this.showIntro = true
|
||||
} catch (e) {
|
||||
setTimeout(() => this.goBack(), 800)
|
||||
}
|
||||
@@ -168,9 +188,6 @@ export default {
|
||||
const opt = success ? this.curNode.options[0] : this.curNode.options[1]
|
||||
if (opt) this.submit(opt)
|
||||
},
|
||||
propEmoji(name) {
|
||||
return propVisual(name).emoji
|
||||
},
|
||||
toggleSound() {
|
||||
this.soundOn = !this.soundOn
|
||||
setSoundEnabled(this.soundOn)
|
||||
@@ -186,6 +203,13 @@ export default {
|
||||
const data = await api.levelChoose(this.childId, this.levelId, this.curNode.node_id, o.option_id)
|
||||
this.routeSteps.push({ text: o.text, pros: o.feedback_pros || '' })
|
||||
if (data.next) {
|
||||
this.mood = o.feedback_cons ? '😢' : '😊'
|
||||
this.combo = o.feedback_cons ? 0 : Math.min(this.combo + 1, 9)
|
||||
playSound(o.feedback_cons ? 'bad' : 'good')
|
||||
// 分支演出:场景晃动 + 0.6s 后弹点评
|
||||
this.sceneFx = 'fx-shake'
|
||||
setTimeout(() => {
|
||||
this.sceneFx = ''
|
||||
this.feedback = {
|
||||
text: o.text,
|
||||
textPinyin: o.text_pinyin,
|
||||
@@ -195,9 +219,8 @@ export default {
|
||||
cons: o.feedback_cons,
|
||||
next: data.next
|
||||
}
|
||||
this.mood = o.feedback_cons ? '😢' : '😊'
|
||||
this.combo = o.feedback_cons ? 0 : Math.min(this.combo + 1, 9)
|
||||
playSound(o.feedback_cons ? 'bad' : 'good')
|
||||
this.choosing = false
|
||||
}, 600)
|
||||
} else if (data.final) {
|
||||
uni.setStorageSync(RESULT_KEY, {
|
||||
level_id: this.levelId,
|
||||
@@ -208,9 +231,10 @@ export default {
|
||||
})
|
||||
playSound('finish')
|
||||
uni.redirectTo({ url: '/pages/result/result' })
|
||||
} else {
|
||||
this.choosing = false
|
||||
}
|
||||
} catch (e) {
|
||||
} finally {
|
||||
this.choosing = false
|
||||
}
|
||||
},
|
||||
@@ -289,4 +313,6 @@ export default {
|
||||
.fb-prop-name { font-size: 30rpx; font-weight: 700; color: #7a6248; margin-bottom: 8rpx; }
|
||||
.fb-prop-desc { font-size: 26rpx; line-height: 1.7; color: #a08c74; }
|
||||
.fb-continue { margin-top: 24rpx; font-size: 30rpx; }
|
||||
.fx-shake { animation: fx-shake 0.5s ease; }
|
||||
@keyframes fx-shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-12rpx); } 50% { transform: translateX(10rpx); } 75% { transform: translateX(-6rpx); } }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user