|
@@ -1,1183 +1,232 @@
|
|
|
<template>
|
|
|
- <view class="play-room-page">
|
|
|
- <!-- 游戏头部信息 -->
|
|
|
- <view class="game-header" :class="isHost ? 'host-header' : 'player-header'">
|
|
|
- <view class="game-title">
|
|
|
- {{ gameTitle }}
|
|
|
- <view class="tag" :class="isHost ? 'host-tag' : 'player-tag'">
|
|
|
- {{ isHost ? '主持人' : '玩家' }}
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- <view class="game-info">
|
|
|
- <view class="timer">
|
|
|
- <IconFont name="clock" size="16"></IconFont>
|
|
|
- 已进行: {{ gameDuration }}分钟
|
|
|
- </view>
|
|
|
+ <view class="turtlesoup-play-room">
|
|
|
+ <!-- 顶部栏 -->
|
|
|
+ <view class="header-bar" :class="isHost ? 'host' : 'player'">
|
|
|
+ <view class="title">{{ gameTitle }}</view>
|
|
|
+ <view class="role-tag">{{ isHost ? '主持人' : '玩家' }}</view>
|
|
|
+ <view class="status-row">
|
|
|
<view class="status">{{ statusText }}</view>
|
|
|
+ <view class="timer">已进行:{{ gameDuration }}分钟</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 游戏内容区域 -->
|
|
|
- <view class="game-content">
|
|
|
- <!-- 故事展示区 -->
|
|
|
- <view class="scenario-card">
|
|
|
- <view class="scenario-title">故事</view>
|
|
|
- <view class="scenario-text">{{ currentView?.description || '故事加载中...' }}</view>
|
|
|
+ <!-- 主持人视角 -->
|
|
|
+ <template v-if="isHost">
|
|
|
+ <!-- 故事区 -->
|
|
|
+ <view class="card scenario-card">
|
|
|
+ <view class="card-title">故事</view>
|
|
|
+ <view class="card-content">{{ scenario }}</view>
|
|
|
</view>
|
|
|
-
|
|
|
- <!-- 进度条 -->
|
|
|
- <view class="progress-section">
|
|
|
- <view class="progress-label">解谜进度: {{ gameProgress }}%</view>
|
|
|
- <nut-progress
|
|
|
- :percentage="gameProgress"
|
|
|
- stroke-color="#3C92FB"
|
|
|
- :text-inside="true"
|
|
|
- stroke-width="14"
|
|
|
- ></nut-progress>
|
|
|
+ <!-- 汤底 -->
|
|
|
+ <view class="card solution-card">
|
|
|
+ <view class="card-title">真相(仅主持人可见)</view>
|
|
|
+ <view class="card-content">{{ solution }}</view>
|
|
|
</view>
|
|
|
-
|
|
|
- <!-- 主持人视图 -->
|
|
|
- <template v-if="isHost">
|
|
|
- <view class="host-solution">
|
|
|
- <view class="solution-title">真相(仅主持人可见)</view>
|
|
|
- <view class="solution-text">{{ hostView?.solution || '真相加载中...' }}</view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <view class="hints-manager">
|
|
|
- <view class="hints-title">线索管理</view>
|
|
|
- <view class="hints-list">
|
|
|
- <view
|
|
|
- v-for="hint in hostView?.hints"
|
|
|
- :key="hint.id"
|
|
|
- class="hint-item"
|
|
|
- :class="{ 'revealed': hint.revealed }"
|
|
|
- >
|
|
|
- <view class="hint-content">{{ hint.content }}</view>
|
|
|
- <nut-button
|
|
|
- v-if="!hint.revealed"
|
|
|
- size="small"
|
|
|
- type="primary"
|
|
|
- @click="revealHint(hint.id)"
|
|
|
- >
|
|
|
- 公开
|
|
|
- </nut-button>
|
|
|
- <view v-else class="revealed-time">
|
|
|
- {{ formatTime(hint.revealedAt) }}已公开
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <view class="pending-questions">
|
|
|
- <view class="questions-title">待回答问题</view>
|
|
|
- <view v-if="pendingQuestions.length === 0" class="no-questions">
|
|
|
- 暂无待回答问题
|
|
|
- </view>
|
|
|
- <view v-else class="questions-list">
|
|
|
- <view
|
|
|
- v-for="question in pendingQuestions"
|
|
|
- :key="question.id"
|
|
|
- class="question-item"
|
|
|
- >
|
|
|
- <view class="question-header">
|
|
|
- <view class="asker-name">{{ question.askedByName }}</view>
|
|
|
- <view class="ask-time">{{ formatTime(question.timestamp) }}</view>
|
|
|
- </view>
|
|
|
- <view class="question-content">{{ question.content }}</view>
|
|
|
- <view class="answer-buttons">
|
|
|
- <nut-button
|
|
|
- size="small"
|
|
|
- type="success"
|
|
|
- @click="answerQuestion(question.id, TurtleSoupAnswerType.YES)"
|
|
|
- >
|
|
|
- 是
|
|
|
- </nut-button>
|
|
|
- <nut-button
|
|
|
- size="small"
|
|
|
- type="danger"
|
|
|
- @click="answerQuestion(question.id, TurtleSoupAnswerType.NO)"
|
|
|
- >
|
|
|
- 否
|
|
|
- </nut-button>
|
|
|
- <nut-button
|
|
|
- size="small"
|
|
|
- @click="answerQuestion(question.id, TurtleSoupAnswerType.IRRELEVANT)"
|
|
|
- >
|
|
|
- 不重要
|
|
|
- </nut-button>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <view class="host-controls">
|
|
|
- <nut-button
|
|
|
- block
|
|
|
- type="primary"
|
|
|
- size="large"
|
|
|
- @click="updateGameProgress"
|
|
|
+ <!-- 关键线索管理 -->
|
|
|
+ <view class="card clue-card">
|
|
|
+ <view class="card-title">关键线索 <span class="clue-count">{{ revealedClues.length }}/{{ clues.length }}已显示</span></view>
|
|
|
+ <view class="clue-list">
|
|
|
+ <view
|
|
|
+ v-for="(clue, idx) in clues"
|
|
|
+ :key="idx"
|
|
|
+ class="clue-item"
|
|
|
+ :class="{ revealed: revealedClues.includes(idx) }"
|
|
|
>
|
|
|
- 更新进度
|
|
|
- </nut-button>
|
|
|
- <nut-button
|
|
|
- block
|
|
|
- type="success"
|
|
|
- size="large"
|
|
|
- @click="showSolvedDialog = true"
|
|
|
- >
|
|
|
- 有人解出了!
|
|
|
- </nut-button>
|
|
|
- </view>
|
|
|
- </template>
|
|
|
-
|
|
|
- <!-- 玩家视图 -->
|
|
|
- <template v-else>
|
|
|
- <view class="revealed-hints">
|
|
|
- <view class="hints-title">已公开线索</view>
|
|
|
- <view v-if="revealedHints.length === 0" class="no-hints">
|
|
|
- 主持人尚未公开任何线索
|
|
|
- </view>
|
|
|
- <view v-else class="hints-list">
|
|
|
- <view
|
|
|
- v-for="hint in revealedHints"
|
|
|
- :key="hint.id"
|
|
|
- class="hint-item"
|
|
|
- >
|
|
|
- <view class="hint-content">{{ hint.content }}</view>
|
|
|
- <view class="revealed-time">{{ formatTime(hint.revealedAt) }}</view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <view class="question-section">
|
|
|
- <view class="question-title">我的提问</view>
|
|
|
- <view class="question-input">
|
|
|
- <nut-textarea
|
|
|
- v-model="questionDraft"
|
|
|
- placeholder="请输入您的问题,主持人会以是/否/不重要进行回答"
|
|
|
- max-length="100"
|
|
|
- ></nut-textarea>
|
|
|
- <nut-button
|
|
|
- block
|
|
|
- type="primary"
|
|
|
- size="large"
|
|
|
- :disabled="!questionDraft.trim()"
|
|
|
- @click="submitQuestion"
|
|
|
- >
|
|
|
- 提交问题
|
|
|
- </nut-button>
|
|
|
- </view>
|
|
|
-
|
|
|
- <view class="my-questions">
|
|
|
- <view class="questions-subtitle">我的提问历史</view>
|
|
|
- <view v-if="myQuestions.length === 0" class="no-questions">
|
|
|
- 您还没有提出任何问题
|
|
|
- </view>
|
|
|
- <view v-else class="questions-list">
|
|
|
- <view
|
|
|
- v-for="question in myQuestions"
|
|
|
- :key="question.id"
|
|
|
- class="question-item"
|
|
|
- >
|
|
|
- <view class="question-header">
|
|
|
- <view class="ask-time">{{ formatTime(question.timestamp) }}</view>
|
|
|
- </view>
|
|
|
- <view class="question-content">{{ question.content }}</view>
|
|
|
- <view v-if="question.answered" class="question-answer" :class="getAnswerClass(question.answer)">
|
|
|
- {{ formatAnswer(question.answer) }}
|
|
|
- </view>
|
|
|
- <view v-else class="question-pending">
|
|
|
- 等待回答...
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
+ <span class="clue-label">线索{{ idx + 1 }}:</span>
|
|
|
+ <span class="clue-text">{{ clue }}</span>
|
|
|
+ <nut-button
|
|
|
+ v-if="!revealedClues.includes(idx)"
|
|
|
+ size="mini"
|
|
|
+ type="primary"
|
|
|
+ @click="revealClue(idx)"
|
|
|
+ >显示</nut-button>
|
|
|
+ <span v-else class="clue-revealed-time">已显示</span>
|
|
|
</view>
|
|
|
</view>
|
|
|
-
|
|
|
- <view class="all-questions">
|
|
|
- <view class="all-questions-title">所有问题</view>
|
|
|
- <view v-if="sortedQuestions.length === 0" class="no-questions">
|
|
|
- 暂无已回答问题
|
|
|
- </view>
|
|
|
- <view v-else class="questions-list">
|
|
|
- <view
|
|
|
- v-for="question in sortedQuestions"
|
|
|
- :key="question.id"
|
|
|
- class="question-item"
|
|
|
- >
|
|
|
- <view class="question-header">
|
|
|
- <view class="asker-name">{{ question.askedByName }}</view>
|
|
|
- <view class="ask-time">{{ formatTime(question.timestamp) }}</view>
|
|
|
- </view>
|
|
|
- <view class="question-content">{{ question.content }}</view>
|
|
|
- <view class="question-answer" :class="getAnswerClass(question.answer)">
|
|
|
- {{ formatAnswer(question.answer) }}
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <view class="player-controls">
|
|
|
- <nut-button
|
|
|
- block
|
|
|
- type="primary"
|
|
|
- size="large"
|
|
|
- @click="showSolutionDialog = true"
|
|
|
- >
|
|
|
- 提交解答
|
|
|
- </nut-button>
|
|
|
- </view>
|
|
|
- </template>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 弹窗区域 -->
|
|
|
- <!-- 更新进度弹窗 -->
|
|
|
- <nut-dialog
|
|
|
- v-model:visible="showProgressDialog"
|
|
|
- title="更新解谜进度"
|
|
|
- content-align="center"
|
|
|
- >
|
|
|
- <view class="progress-dialog">
|
|
|
- <view class="progress-label">当前进度: {{ progressValue }}%</view>
|
|
|
- <nut-range
|
|
|
- v-model="progressValue"
|
|
|
- :min="0"
|
|
|
- :max="100"
|
|
|
- inactive-color="#E5E5E5"
|
|
|
- button-color="#3C92FB"
|
|
|
- active-color="#3C92FB"
|
|
|
- ></nut-range>
|
|
|
- <view class="dialog-buttons">
|
|
|
- <nut-button type="primary" block @click="confirmProgress">确认</nut-button>
|
|
|
- </view>
|
|
|
</view>
|
|
|
- </nut-dialog>
|
|
|
-
|
|
|
- <!-- 提交解答弹窗 -->
|
|
|
- <nut-dialog
|
|
|
- v-model:visible="showSolutionDialog"
|
|
|
- title="提交解答"
|
|
|
- content-align="center"
|
|
|
- >
|
|
|
- <view class="solution-dialog">
|
|
|
- <nut-textarea
|
|
|
- v-model="solutionDraft"
|
|
|
- placeholder="请输入你的解答"
|
|
|
- max-length="300"
|
|
|
- ></nut-textarea>
|
|
|
- <view class="dialog-buttons">
|
|
|
- <nut-button type="primary" block @click="submitSolution">提交</nut-button>
|
|
|
- </view>
|
|
|
+ <!-- 更换主题按钮 -->
|
|
|
+ <view class="footer-bar">
|
|
|
+ <nut-button block type="primary" @click="showSettings = true">更换主题</nut-button>
|
|
|
</view>
|
|
|
- </nut-dialog>
|
|
|
-
|
|
|
- <!-- 解答正确弹窗 -->
|
|
|
- <nut-dialog
|
|
|
- v-model:visible="showSolvedDialog"
|
|
|
- title="有人解出了谜题"
|
|
|
- content-align="center"
|
|
|
- >
|
|
|
- <view class="solved-dialog">
|
|
|
- <view class="solver-input">
|
|
|
- <nut-input v-model="solverName" placeholder="解答者昵称" />
|
|
|
- </view>
|
|
|
- <nut-textarea
|
|
|
- v-model="solverSolution"
|
|
|
- placeholder="解答者的答案"
|
|
|
- max-length="300"
|
|
|
- ></nut-textarea>
|
|
|
- <view class="dialog-buttons">
|
|
|
- <nut-button type="primary" block @click="endGameWithSolvedStatus">确认</nut-button>
|
|
|
+ <!-- 设置弹窗 -->
|
|
|
+ <nut-dialog v-model:visible="showSettings" title="更换主题">
|
|
|
+ <TurtleSoupSettings @close="showSettings = false"/>
|
|
|
+ </nut-dialog>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- 玩家视角 -->
|
|
|
+ <template v-else>
|
|
|
+ <!-- 故事区 -->
|
|
|
+ <view class="card scenario-card player">
|
|
|
+ <view class="card-title">{{ puzzleTitle }}</view>
|
|
|
+ <view class="card-content">{{ scenario }}</view>
|
|
|
+ </view>
|
|
|
+ <!-- 进度条 -->
|
|
|
+ <view class="progress-bar">
|
|
|
+ <view class="progress-label">解谜进度:{{ gameProgress }}%</view>
|
|
|
+ <nut-progress :percentage="gameProgress" stroke-color="#3C92FB" text-inside stroke-width="12"/>
|
|
|
+ </view>
|
|
|
+ <!-- 已公开线索 -->
|
|
|
+ <view class="card clue-card player">
|
|
|
+ <view class="card-title">已公开线索</view>
|
|
|
+ <view class="clue-list">
|
|
|
+ <view
|
|
|
+ v-for="(clue, idx) in revealedCluesList"
|
|
|
+ :key="idx"
|
|
|
+ class="clue-item revealed"
|
|
|
+ >
|
|
|
+ <span class="clue-label">线索{{ idx + 1 }}:</span>
|
|
|
+ <span class="clue-text">{{ clue }}</span>
|
|
|
+ </view>
|
|
|
+ <view v-if="revealedCluesList.length === 0" class="no-clues">暂无线索</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
- </nut-dialog>
|
|
|
+ <!-- 关键词道具解锁(占位) -->
|
|
|
+ <view class="card tool-card">
|
|
|
+ <view class="card-title">关键词道具</view>
|
|
|
+ <nut-button block type="primary" @click="showUnlockTools = true">解锁关键词提示</nut-button>
|
|
|
+ </view>
|
|
|
+ <!-- 道具解锁弹窗(占位) -->
|
|
|
+ <nut-dialog v-model:visible="showUnlockTools" title="解锁道具">
|
|
|
+ <view style="padding:24px;text-align:center;color:#888;">功能开发中,敬请期待!</view>
|
|
|
+ </nut-dialog>
|
|
|
+ </template>
|
|
|
</view>
|
|
|
- <Tabbar></Tabbar>
|
|
|
</template>
|
|
|
|
|
|
-<script lang="ts">
|
|
|
-import Taro from '@tarojs/taro'
|
|
|
-import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
|
-import { useRoomStore } from '@/stores/room'
|
|
|
-import { useUserStore } from '@/stores/user'
|
|
|
+<script lang="ts" setup>
|
|
|
+import { ref, computed } from 'vue'
|
|
|
import { useTurtleSoupStore } from '@/stores/games/turtlesoup'
|
|
|
-import Tabbar from '@/components/Tabbar.vue'
|
|
|
-import { TurtleSoupAnswerType, TurtleSoupGameStatus } from '@/types/games/turtlesoup'
|
|
|
-import { IconFont } from '@nutui/icons-vue-taro'
|
|
|
-
|
|
|
-export default {
|
|
|
- components: {
|
|
|
- Tabbar,
|
|
|
- IconFont
|
|
|
- },
|
|
|
-
|
|
|
- // 生命周期钩子 - 页面显示
|
|
|
- onShow() {
|
|
|
- // 隐藏返回首页按钮
|
|
|
- Taro.hideHomeButton()
|
|
|
- console.log('已隐藏返回首页按钮')
|
|
|
- },
|
|
|
-
|
|
|
- // Composition API
|
|
|
- setup() {
|
|
|
- // 初始化store
|
|
|
- const roomStore = useRoomStore()
|
|
|
- const userStore = useUserStore()
|
|
|
- const turtleSoupStore = useTurtleSoupStore()
|
|
|
-
|
|
|
- // 游戏和房间ID
|
|
|
- const roomId = ref('')
|
|
|
- const gameId = ref('')
|
|
|
-
|
|
|
- // 问题输入
|
|
|
- const questionDraft = ref('')
|
|
|
-
|
|
|
- // 解答输入
|
|
|
- const solutionDraft = ref('')
|
|
|
-
|
|
|
- // 主持人提交解答者
|
|
|
- const solverName = ref('')
|
|
|
- const solverSolution = ref('')
|
|
|
-
|
|
|
- // 进度值
|
|
|
- const progressValue = ref(0)
|
|
|
-
|
|
|
- // 弹窗控制
|
|
|
- const showProgressDialog = ref(false)
|
|
|
- const showSolutionDialog = ref(false)
|
|
|
- const showSolvedDialog = ref(false)
|
|
|
-
|
|
|
- // 从store获取视图数据
|
|
|
- const currentView = computed(() => turtleSoupStore.currentView)
|
|
|
- const hostView = computed(() => turtleSoupStore.hostView)
|
|
|
- const playerView = computed(() => turtleSoupStore.playerView)
|
|
|
- const isHost = computed(() => turtleSoupStore.isHost)
|
|
|
- const isGameActive = computed(() => turtleSoupStore.isGameActive)
|
|
|
- const gameProgress = computed(() => turtleSoupStore.gameProgress)
|
|
|
- const gameDuration = computed(() => turtleSoupStore.gameDuration)
|
|
|
- const pendingQuestions = computed(() => turtleSoupStore.pendingQuestions)
|
|
|
- const myQuestions = computed(() => turtleSoupStore.myQuestions)
|
|
|
- const revealedHints = computed(() => turtleSoupStore.revealedHints)
|
|
|
- const sortedQuestions = computed(() => turtleSoupStore.sortedQuestions)
|
|
|
-
|
|
|
- // 游戏标题
|
|
|
- const gameTitle = computed(() => {
|
|
|
- return currentView.value?.title || '海龟汤游戏'
|
|
|
- })
|
|
|
-
|
|
|
- // 状态文本
|
|
|
- const statusText = computed(() => {
|
|
|
- if (!currentView.value) return '游戏状态未知'
|
|
|
-
|
|
|
- switch(currentView.value.status) {
|
|
|
- case TurtleSoupGameStatus.CREATED:
|
|
|
- return '等待开始'
|
|
|
- case TurtleSoupGameStatus.WAITING:
|
|
|
- return '准备中'
|
|
|
- case TurtleSoupGameStatus.ACTIVE:
|
|
|
- return '游戏进行中'
|
|
|
- case TurtleSoupGameStatus.COMPLETED:
|
|
|
- return '已完成'
|
|
|
- case TurtleSoupGameStatus.ABANDONED:
|
|
|
- return '已放弃'
|
|
|
- default:
|
|
|
- return '游戏状态未知'
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- // 初始化页面
|
|
|
- const initPage = async () => {
|
|
|
- try {
|
|
|
- // 获取路由参数
|
|
|
- const pages = Taro.getCurrentPages()
|
|
|
- const currentPage = pages[pages.length - 1]
|
|
|
- const routeParams = currentPage.$taroParams
|
|
|
-
|
|
|
- if (routeParams) {
|
|
|
- if (routeParams.roomId) {
|
|
|
- roomId.value = routeParams.roomId
|
|
|
- }
|
|
|
-
|
|
|
- if (routeParams.gameId) {
|
|
|
- gameId.value = routeParams.gameId
|
|
|
- } else if (roomId.value) {
|
|
|
- // 如果没有gameId但有roomId,尝试从房间信息获取游戏ID
|
|
|
- await roomStore.loadRoomInfo(roomId.value)
|
|
|
- if (roomStore.currentRoom?.gameId) {
|
|
|
- gameId.value = roomStore.currentRoom.gameId
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (gameId.value) {
|
|
|
- // 判断当前用户角色并加载相应视图
|
|
|
- const isCurrentUserHost = roomStore.getUserRole(userStore.openid) === 'hoster'
|
|
|
- turtleSoupStore.setViewType(isCurrentUserHost ? 'host' : 'player')
|
|
|
-
|
|
|
- await loadGameData()
|
|
|
-
|
|
|
- // 更新进度值
|
|
|
- progressValue.value = gameProgress.value
|
|
|
-
|
|
|
- // 启动定时刷新
|
|
|
- startGameListener()
|
|
|
- } else {
|
|
|
- Taro.showToast({
|
|
|
- title: '游戏ID不存在',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
-
|
|
|
- setTimeout(() => {
|
|
|
- Taro.navigateBack()
|
|
|
- }, 1500)
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('初始化页面失败:', error)
|
|
|
- Taro.showToast({
|
|
|
- title: '加载游戏失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 加载游戏数据
|
|
|
- const loadGameData = async () => {
|
|
|
- if (!gameId.value) return false
|
|
|
-
|
|
|
- try {
|
|
|
- Taro.showLoading({ title: '加载游戏数据...' })
|
|
|
-
|
|
|
- let result
|
|
|
- if (isHost.value) {
|
|
|
- result = await turtleSoupStore.loadHostGame(gameId.value)
|
|
|
- } else {
|
|
|
- result = await turtleSoupStore.loadPlayerGame(gameId.value)
|
|
|
- }
|
|
|
-
|
|
|
- Taro.hideLoading()
|
|
|
- return result
|
|
|
- } catch (error) {
|
|
|
- console.error('加载游戏数据失败:', error)
|
|
|
- Taro.hideLoading()
|
|
|
- Taro.showToast({
|
|
|
- title: '加载游戏数据失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- return false
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 提交问题 (玩家)
|
|
|
- const submitQuestion = async () => {
|
|
|
- if (!questionDraft.value.trim()) {
|
|
|
- Taro.showToast({
|
|
|
- title: '问题不能为空',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- Taro.showLoading({ title: '提交问题中...' })
|
|
|
-
|
|
|
- const result = await turtleSoupStore.submitQuestion(questionDraft.value)
|
|
|
-
|
|
|
- Taro.hideLoading()
|
|
|
-
|
|
|
- if (result) {
|
|
|
- Taro.showToast({
|
|
|
- title: '问题已提交',
|
|
|
- icon: 'success'
|
|
|
- })
|
|
|
- questionDraft.value = '' // 清空输入
|
|
|
- } else {
|
|
|
- Taro.showToast({
|
|
|
- title: turtleSoupStore.error || '提交失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('提交问题失败:', error)
|
|
|
- Taro.hideLoading()
|
|
|
- Taro.showToast({
|
|
|
- title: '提交问题失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 回答问题 (主持人)
|
|
|
- const answerQuestion = async (questionId: string, answer: TurtleSoupAnswerType) => {
|
|
|
- try {
|
|
|
- Taro.showLoading({ title: '回答问题中...' })
|
|
|
-
|
|
|
- const result = await turtleSoupStore.answerQuestion(questionId, answer)
|
|
|
-
|
|
|
- Taro.hideLoading()
|
|
|
-
|
|
|
- if (result) {
|
|
|
- Taro.showToast({
|
|
|
- title: '已回答',
|
|
|
- icon: 'success'
|
|
|
- })
|
|
|
- } else {
|
|
|
- Taro.showToast({
|
|
|
- title: turtleSoupStore.error || '回答失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('回答问题失败:', error)
|
|
|
- Taro.hideLoading()
|
|
|
- Taro.showToast({
|
|
|
- title: '回答问题失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 公开提示 (主持人)
|
|
|
- const revealHint = async (hintId: string) => {
|
|
|
- try {
|
|
|
- Taro.showLoading({ title: '公开提示中...' })
|
|
|
-
|
|
|
- const result = await turtleSoupStore.revealHint(hintId)
|
|
|
-
|
|
|
- Taro.hideLoading()
|
|
|
-
|
|
|
- if (result) {
|
|
|
- Taro.showToast({
|
|
|
- title: '提示已公开',
|
|
|
- icon: 'success'
|
|
|
- })
|
|
|
- } else {
|
|
|
- Taro.showToast({
|
|
|
- title: turtleSoupStore.error || '公开提示失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('公开提示失败:', error)
|
|
|
- Taro.hideLoading()
|
|
|
- Taro.showToast({
|
|
|
- title: '公开提示失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 更新游戏进度对话框
|
|
|
- const updateGameProgress = () => {
|
|
|
- // 确保进度值与当前存储进度一致
|
|
|
- progressValue.value = gameProgress.value
|
|
|
- showProgressDialog.value = true
|
|
|
- }
|
|
|
-
|
|
|
- // 确认更新进度 (主持人)
|
|
|
- const confirmProgress = async () => {
|
|
|
- try {
|
|
|
- Taro.showLoading({ title: '更新进度中...' })
|
|
|
-
|
|
|
- const result = await turtleSoupStore.updateProgress(progressValue.value)
|
|
|
-
|
|
|
- Taro.hideLoading()
|
|
|
-
|
|
|
- if (result) {
|
|
|
- showProgressDialog.value = false
|
|
|
- Taro.showToast({
|
|
|
- title: '进度已更新',
|
|
|
- icon: 'success'
|
|
|
- })
|
|
|
- } else {
|
|
|
- Taro.showToast({
|
|
|
- title: turtleSoupStore.error || '更新进度失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('更新进度失败:', error)
|
|
|
- Taro.hideLoading()
|
|
|
- Taro.showToast({
|
|
|
- title: '更新进度失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 提交解答 (玩家)
|
|
|
- const submitSolution = async () => {
|
|
|
- if (!solutionDraft.value.trim()) {
|
|
|
- Taro.showToast({
|
|
|
- title: '解答不能为空',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- Taro.showLoading({ title: '提交解答中...' })
|
|
|
-
|
|
|
- const result = await turtleSoupStore.submitSolution(solutionDraft.value)
|
|
|
-
|
|
|
- Taro.hideLoading()
|
|
|
- showSolutionDialog.value = false
|
|
|
-
|
|
|
- if (result.success) {
|
|
|
- if (result.correct) {
|
|
|
- Taro.showModal({
|
|
|
- title: '恭喜你!',
|
|
|
- content: '你的解答正确!',
|
|
|
- confirmText: '确定',
|
|
|
- showCancel: false
|
|
|
- })
|
|
|
-
|
|
|
- // 主持人会处理游戏结束
|
|
|
- solutionDraft.value = '' // 清空输入
|
|
|
- } else {
|
|
|
- Taro.showToast({
|
|
|
- title: '解答不正确,再试试吧!',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
- } else {
|
|
|
- Taro.showToast({
|
|
|
- title: turtleSoupStore.error || '提交解答失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('提交解答失败:', error)
|
|
|
- Taro.hideLoading()
|
|
|
- Taro.showToast({
|
|
|
- title: '提交解答失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 结束游戏 (主持人提交解答者)
|
|
|
- const endGameWithSolvedStatus = async () => {
|
|
|
- if (!solverName.value.trim() || !solverSolution.value.trim()) {
|
|
|
- Taro.showToast({
|
|
|
- title: '请填写解答者和解答内容',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- Taro.showLoading({ title: '结束游戏中...' })
|
|
|
-
|
|
|
- const result = await turtleSoupStore.endGame({
|
|
|
- gameId: gameId.value,
|
|
|
- solved: true,
|
|
|
- solvedBy: 'player', // 实际应用中应该是玩家ID
|
|
|
- solvedByName: solverName.value,
|
|
|
- solution: solverSolution.value
|
|
|
- })
|
|
|
-
|
|
|
- Taro.hideLoading()
|
|
|
- showSolvedDialog.value = false
|
|
|
-
|
|
|
- if (result) {
|
|
|
- Taro.showModal({
|
|
|
- title: '游戏已结束',
|
|
|
- content: `恭喜玩家 ${solverName.value} 成功解谜!`,
|
|
|
- confirmText: '返回房间',
|
|
|
- showCancel: false,
|
|
|
- success: () => {
|
|
|
- // 跳转到结果页或回到房间页
|
|
|
- Taro.redirectTo({
|
|
|
- url: `/pages/room/result/index?roomId=${roomId.value}&gameId=${gameId.value}`
|
|
|
- })
|
|
|
- }
|
|
|
- })
|
|
|
- } else {
|
|
|
- Taro.showToast({
|
|
|
- title: turtleSoupStore.error || '结束游戏失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('结束游戏失败:', error)
|
|
|
- Taro.hideLoading()
|
|
|
- Taro.showToast({
|
|
|
- title: '结束游戏失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 格式化时间
|
|
|
- const formatTime = (timestamp?: number) => {
|
|
|
- if (!timestamp) return ''
|
|
|
-
|
|
|
- const now = Date.now()
|
|
|
- const diff = now - timestamp
|
|
|
-
|
|
|
- if (diff < 60000) {
|
|
|
- // 不到1分钟
|
|
|
- return '刚刚'
|
|
|
- } else if (diff < 3600000) {
|
|
|
- // 不到1小时
|
|
|
- return `${Math.floor(diff / 60000)}分钟前`
|
|
|
- } else if (diff < 86400000) {
|
|
|
- // 不到1天
|
|
|
- return `${Math.floor(diff / 3600000)}小时前`
|
|
|
- } else {
|
|
|
- // 超过1天
|
|
|
- const date = new Date(timestamp)
|
|
|
- return `${date.getMonth() + 1}月${date.getDate()}日 ${date.getHours()}:${String(date.getMinutes()).padStart(2, '0')}`
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 格式化回答
|
|
|
- const formatAnswer = (answer?: TurtleSoupAnswerType) => {
|
|
|
- if (!answer) return ''
|
|
|
-
|
|
|
- switch (answer) {
|
|
|
- case TurtleSoupAnswerType.YES:
|
|
|
- return '是'
|
|
|
- case TurtleSoupAnswerType.NO:
|
|
|
- return '否'
|
|
|
- case TurtleSoupAnswerType.IRRELEVANT:
|
|
|
- return '不重要'
|
|
|
- default:
|
|
|
- return ''
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 获取回答样式类
|
|
|
- const getAnswerClass = (answer?: TurtleSoupAnswerType) => {
|
|
|
- if (!answer) return ''
|
|
|
-
|
|
|
- switch (answer) {
|
|
|
- case TurtleSoupAnswerType.YES:
|
|
|
- return 'answer-yes'
|
|
|
- case TurtleSoupAnswerType.NO:
|
|
|
- return 'answer-no'
|
|
|
- case TurtleSoupAnswerType.IRRELEVANT:
|
|
|
- return 'answer-irrelevant'
|
|
|
- default:
|
|
|
- return ''
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 游戏刷新定时器
|
|
|
- let gameRefreshInterval: NodeJS.Timeout | null = null
|
|
|
-
|
|
|
- // 开始游戏监听
|
|
|
- const startGameListener = () => {
|
|
|
- // 每10秒刷新一次游戏数据
|
|
|
- gameRefreshInterval = setInterval(async () => {
|
|
|
- if (gameId.value) {
|
|
|
- await turtleSoupStore.refreshGameData(gameId.value)
|
|
|
-
|
|
|
- // 如果游戏已结束,跳转到结果页
|
|
|
- if (turtleSoupStore.isGameEnded) {
|
|
|
- stopGameListener()
|
|
|
- Taro.redirectTo({
|
|
|
- url: `/pages/room/result/index?roomId=${roomId.value}&gameId=${gameId.value}`
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
- }, 10000)
|
|
|
- }
|
|
|
-
|
|
|
- // 停止游戏监听
|
|
|
- const stopGameListener = () => {
|
|
|
- if (gameRefreshInterval) {
|
|
|
- clearInterval(gameRefreshInterval)
|
|
|
- gameRefreshInterval = null
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 页面加载时初始化
|
|
|
- onMounted(() => {
|
|
|
- initPage()
|
|
|
- })
|
|
|
-
|
|
|
- // 页面卸载时清理
|
|
|
- onUnmounted(() => {
|
|
|
- stopGameListener()
|
|
|
- })
|
|
|
-
|
|
|
- return {
|
|
|
- // 数据
|
|
|
- gameTitle,
|
|
|
- statusText,
|
|
|
- currentView,
|
|
|
- hostView,
|
|
|
- playerView,
|
|
|
- isHost,
|
|
|
- isGameActive,
|
|
|
- gameProgress,
|
|
|
- gameDuration,
|
|
|
- pendingQuestions,
|
|
|
- myQuestions,
|
|
|
- revealedHints,
|
|
|
- sortedQuestions,
|
|
|
- questionDraft,
|
|
|
- solutionDraft,
|
|
|
- progressValue,
|
|
|
- solverName,
|
|
|
- solverSolution,
|
|
|
- showProgressDialog,
|
|
|
- showSolutionDialog,
|
|
|
- showSolvedDialog,
|
|
|
-
|
|
|
- // 方法
|
|
|
- submitQuestion,
|
|
|
- answerQuestion,
|
|
|
- revealHint,
|
|
|
- updateGameProgress,
|
|
|
- confirmProgress,
|
|
|
- submitSolution,
|
|
|
- endGameWithSolvedStatus,
|
|
|
- formatTime,
|
|
|
- formatAnswer,
|
|
|
- getAnswerClass,
|
|
|
-
|
|
|
- // 枚举
|
|
|
- TurtleSoupAnswerType
|
|
|
- }
|
|
|
+import TurtleSoupSettings from '@/components/room/host/gamesettings/TurtleSoupSettings.vue'
|
|
|
+
|
|
|
+const turtleSoupStore = useTurtleSoupStore()
|
|
|
+const isHost = computed(() => turtleSoupStore.isHost)
|
|
|
+const currentView = computed(() => turtleSoupStore.currentView)
|
|
|
+const gameTitle = computed(() => currentView.value?.title || '海龟汤')
|
|
|
+const puzzleTitle = computed(() => currentView.value?.puzzle?.title || '谜题')
|
|
|
+const scenario = computed(() => currentView.value?.puzzle?.scenario || '')
|
|
|
+const solution = computed(() => isHost.value ? currentView.value?.puzzle?.truth : '')
|
|
|
+const gameProgress = computed(() => turtleSoupStore.gameProgress)
|
|
|
+const gameDuration = computed(() => turtleSoupStore.gameDuration)
|
|
|
+const statusText = computed(() => {
|
|
|
+ switch(currentView.value?.status) {
|
|
|
+ case 'active': return '游戏进行中'
|
|
|
+ case 'waiting': return '准备中'
|
|
|
+ case 'completed': return '已完成'
|
|
|
+ default: return ''
|
|
|
}
|
|
|
+})
|
|
|
+
|
|
|
+// 关键线索(直接取puzzle.keyClues)
|
|
|
+const clues = computed(() => currentView.value?.puzzle?.keyClues || [])
|
|
|
+// 主持人已显示线索索引(假设用本地state控制,真实用store/后端同步)
|
|
|
+const revealedClues = ref<number[]>([0, 1]) // 示例,已显示前两条
|
|
|
+function revealClue(idx: number) {
|
|
|
+ if (!revealedClues.value.includes(idx)) revealedClues.value.push(idx)
|
|
|
}
|
|
|
+
|
|
|
+// 玩家端公开线索(直接取keyClues中已公开的)
|
|
|
+const revealedCluesList = computed(() => clues.value.filter((_, idx) => revealedClues.value.includes(idx)))
|
|
|
+
|
|
|
+// 弹窗控制
|
|
|
+const showSettings = ref(false)
|
|
|
+const showUnlockTools = ref(false)
|
|
|
</script>
|
|
|
|
|
|
-<style lang="scss">
|
|
|
-.play-room-page {
|
|
|
- padding: $spacing-base;
|
|
|
- background-color: $background-color-base;
|
|
|
+<style lang="scss" scoped>
|
|
|
+.turtlesoup-play-room {
|
|
|
+ padding: 0 0 80px 0;
|
|
|
+ background: #f7f8fa;
|
|
|
min-height: 100vh;
|
|
|
- padding-bottom: $spacing-large * 4; // 为底部tabbar留出空间
|
|
|
-
|
|
|
- .game-header {
|
|
|
- padding: $spacing-base;
|
|
|
- border-radius: $border-radius-base;
|
|
|
- margin-bottom: $spacing-base;
|
|
|
-
|
|
|
- &.host-header {
|
|
|
- background-color: $background-color-orange; // 浅橙色背景
|
|
|
- border-left: 4px solid $orange-color;
|
|
|
- }
|
|
|
-
|
|
|
- &.player-header {
|
|
|
- background-color: $background-color-blue; // 浅蓝色背景
|
|
|
- border-left: 4px solid $blue-light-color;
|
|
|
- }
|
|
|
-
|
|
|
- .game-title {
|
|
|
- font-size: $font-size-large;
|
|
|
- font-weight: $font-weight-bold;
|
|
|
- color: $text-color-primary;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: $spacing-small;
|
|
|
-
|
|
|
- .tag {
|
|
|
- margin-left: $spacing-base;
|
|
|
- padding: $spacing-mini $spacing-base;
|
|
|
- border-radius: $border-radius-mini;
|
|
|
- font-size: $font-size-small;
|
|
|
-
|
|
|
- &.host-tag {
|
|
|
- background-color: $orange-color;
|
|
|
- color: $text-color-light;
|
|
|
- }
|
|
|
-
|
|
|
- &.player-tag {
|
|
|
- background-color: $blue-light-color;
|
|
|
- color: $text-color-light;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .game-info {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
-
|
|
|
- .timer, .status {
|
|
|
- font-size: $font-size-base;
|
|
|
- color: $text-color-secondary;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
-
|
|
|
- .nut-icon {
|
|
|
- margin-right: $spacing-mini;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+}
|
|
|
+
|
|
|
+.header-bar {
|
|
|
+ padding: 18px 16px 12px 16px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ border-radius: 0 0 12px 12px;
|
|
|
+ .title {
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #222;
|
|
|
}
|
|
|
-
|
|
|
- .game-content {
|
|
|
- .scenario-card {
|
|
|
- background-color: $background-color-light;
|
|
|
- border-radius: $border-radius-base;
|
|
|
- padding: $spacing-large;
|
|
|
- margin-bottom: $spacing-large;
|
|
|
- box-shadow: $shadow-light;
|
|
|
-
|
|
|
- .scenario-title {
|
|
|
- font-size: $font-size-medium;
|
|
|
- font-weight: $font-weight-bold;
|
|
|
- color: $text-color-primary;
|
|
|
- margin-bottom: $spacing-base;
|
|
|
- border-bottom: 1px solid $border-color-light;
|
|
|
- padding-bottom: $spacing-small;
|
|
|
- }
|
|
|
-
|
|
|
- .scenario-text {
|
|
|
- font-size: $font-size-base;
|
|
|
- line-height: $line-height-loose;
|
|
|
- color: $text-color-primary;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .progress-section {
|
|
|
- margin-bottom: $spacing-large;
|
|
|
-
|
|
|
- .progress-label {
|
|
|
- font-size: $font-size-small;
|
|
|
- color: $text-color-secondary;
|
|
|
- margin-bottom: $spacing-small;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 主持人视图样式
|
|
|
- .host-solution {
|
|
|
- background-color: $background-color-orange;
|
|
|
- border-radius: $border-radius-base;
|
|
|
- padding: $spacing-large;
|
|
|
- margin-bottom: $spacing-large;
|
|
|
- box-shadow: $shadow-light;
|
|
|
-
|
|
|
- .solution-title {
|
|
|
- font-size: $font-size-medium;
|
|
|
- font-weight: $font-weight-bold;
|
|
|
- color: $orange-color;
|
|
|
- margin-bottom: $spacing-base;
|
|
|
- border-bottom: 1px solid rgba($orange-color, 0.2);
|
|
|
- padding-bottom: $spacing-small;
|
|
|
- }
|
|
|
-
|
|
|
- .solution-text {
|
|
|
- font-size: $font-size-base;
|
|
|
- line-height: $line-height-loose;
|
|
|
- color: $text-color-primary;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .hints-manager, .revealed-hints {
|
|
|
- background-color: $background-color-light;
|
|
|
- border-radius: $border-radius-base;
|
|
|
- padding: $spacing-large;
|
|
|
- margin-bottom: $spacing-large;
|
|
|
- box-shadow: $shadow-light;
|
|
|
-
|
|
|
- .hints-title {
|
|
|
- font-size: $font-size-medium;
|
|
|
- font-weight: $font-weight-bold;
|
|
|
- color: $text-color-primary;
|
|
|
- margin-bottom: $spacing-base;
|
|
|
- border-bottom: 1px solid $border-color-light;
|
|
|
- padding-bottom: $spacing-small;
|
|
|
- }
|
|
|
-
|
|
|
- .no-hints {
|
|
|
- color: $text-color-secondary;
|
|
|
- text-align: center;
|
|
|
- padding: $spacing-large 0;
|
|
|
- }
|
|
|
-
|
|
|
- .hints-list {
|
|
|
- .hint-item {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- padding: $spacing-base 0;
|
|
|
- border-bottom: 1px solid $border-color-light;
|
|
|
-
|
|
|
- &:last-child {
|
|
|
- border-bottom: none;
|
|
|
- }
|
|
|
-
|
|
|
- &.revealed {
|
|
|
- background-color: rgba($green-color, 0.05);
|
|
|
- }
|
|
|
-
|
|
|
- .hint-content {
|
|
|
- flex: 1;
|
|
|
- font-size: $font-size-base;
|
|
|
- line-height: $line-height-loose;
|
|
|
- color: $text-color-primary;
|
|
|
- margin-right: $spacing-base;
|
|
|
- }
|
|
|
-
|
|
|
- .revealed-time {
|
|
|
- font-size: $font-size-small;
|
|
|
- color: $green-color;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .pending-questions, .all-questions, .my-questions {
|
|
|
- background-color: $background-color-light;
|
|
|
- border-radius: $border-radius-base;
|
|
|
- padding: $spacing-large;
|
|
|
- margin-bottom: $spacing-large;
|
|
|
- box-shadow: $shadow-light;
|
|
|
-
|
|
|
- .questions-title, .all-questions-title, .questions-subtitle {
|
|
|
- font-size: $font-size-medium;
|
|
|
- font-weight: $font-weight-bold;
|
|
|
- color: $text-color-primary;
|
|
|
- margin-bottom: $spacing-base;
|
|
|
- border-bottom: 1px solid $border-color-light;
|
|
|
- padding-bottom: $spacing-small;
|
|
|
- }
|
|
|
-
|
|
|
- .no-questions {
|
|
|
- color: $text-color-secondary;
|
|
|
- text-align: center;
|
|
|
- padding: $spacing-large 0;
|
|
|
- }
|
|
|
-
|
|
|
- .questions-list {
|
|
|
- .question-item {
|
|
|
- padding: $spacing-base;
|
|
|
- border-bottom: 1px solid $border-color-light;
|
|
|
- border-radius: $border-radius-small;
|
|
|
- margin-bottom: $spacing-base;
|
|
|
-
|
|
|
- &:last-child {
|
|
|
- margin-bottom: 0;
|
|
|
- border-bottom: none;
|
|
|
- }
|
|
|
-
|
|
|
- .question-header {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: $spacing-small;
|
|
|
-
|
|
|
- .asker-name {
|
|
|
- font-size: $font-size-small;
|
|
|
- font-weight: $font-weight-medium;
|
|
|
- color: $text-color-primary;
|
|
|
- }
|
|
|
-
|
|
|
- .ask-time {
|
|
|
- font-size: $font-size-small;
|
|
|
- color: $text-color-secondary;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .question-content {
|
|
|
- font-size: $font-size-base;
|
|
|
- color: $text-color-primary;
|
|
|
- margin-bottom: $spacing-base;
|
|
|
- line-height: $line-height-loose;
|
|
|
- }
|
|
|
-
|
|
|
- .answer-buttons {
|
|
|
- display: flex;
|
|
|
- gap: $spacing-base;
|
|
|
- }
|
|
|
-
|
|
|
- .question-answer {
|
|
|
- font-size: $font-size-base;
|
|
|
- font-weight: $font-weight-medium;
|
|
|
- padding: $spacing-mini $spacing-base;
|
|
|
- border-radius: $border-radius-mini;
|
|
|
- display: inline-block;
|
|
|
-
|
|
|
- &.answer-yes {
|
|
|
- background-color: rgba($success-color, 0.1);
|
|
|
- color: $success-color;
|
|
|
- }
|
|
|
-
|
|
|
- &.answer-no {
|
|
|
- background-color: rgba($danger-color, 0.1);
|
|
|
- color: $danger-color;
|
|
|
- }
|
|
|
-
|
|
|
- &.answer-irrelevant {
|
|
|
- background-color: rgba($text-color-secondary, 0.1);
|
|
|
- color: $text-color-secondary;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .question-pending {
|
|
|
- font-size: $font-size-small;
|
|
|
- color: $text-color-secondary;
|
|
|
- font-style: italic;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .question-section {
|
|
|
- background-color: $background-color-light;
|
|
|
- border-radius: $border-radius-base;
|
|
|
- padding: $spacing-large;
|
|
|
- margin-bottom: $spacing-large;
|
|
|
- box-shadow: $shadow-light;
|
|
|
-
|
|
|
- .question-title {
|
|
|
- font-size: $font-size-medium;
|
|
|
- font-weight: $font-weight-bold;
|
|
|
- color: $text-color-primary;
|
|
|
- margin-bottom: $spacing-base;
|
|
|
- border-bottom: 1px solid $border-color-light;
|
|
|
- padding-bottom: $spacing-small;
|
|
|
- }
|
|
|
-
|
|
|
- .question-input {
|
|
|
- margin-bottom: $spacing-large;
|
|
|
-
|
|
|
- .nut-textarea {
|
|
|
- margin-bottom: $spacing-base;
|
|
|
- }
|
|
|
- }
|
|
|
+ .role-tag {
|
|
|
+ font-size: 14px;
|
|
|
+ color: white;
|
|
|
+ display: inline-block;
|
|
|
+ background: #ffae3d;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 2px 12px;
|
|
|
+ float: right;
|
|
|
+ margin-left: auto;
|
|
|
+ }
|
|
|
+ &.host { background: #fff6e3; .role-tag { background: #ffae3d; } }
|
|
|
+ &.player { background: #e6f1ff; .role-tag { background: #3c92fb; } }
|
|
|
+ .status-row {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-top: 8px;
|
|
|
+ .status { font-size: 14px; color: #888; }
|
|
|
+ .timer { font-size: 14px; color: #888; }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.card {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 12px;
|
|
|
+ margin: 16px 12px 0 12px;
|
|
|
+ padding: 16px;
|
|
|
+ box-shadow: 0 2px 10px 0 rgb(0 0 0 / 5%);
|
|
|
+ &.scenario-card { }
|
|
|
+ &.solution-card { background: #fff6e3; }
|
|
|
+ &.clue-card { }
|
|
|
+ &.tool-card { background: #f4faff; }
|
|
|
+ &.player { background: #f4faff; }
|
|
|
+ .card-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ color: #333;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ .clue-count {
|
|
|
+ margin-left: auto;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #ffae3d;
|
|
|
}
|
|
|
-
|
|
|
- .host-controls, .player-controls {
|
|
|
+ }
|
|
|
+ .card-content {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #666;
|
|
|
+ line-height: 1.7;
|
|
|
+ }
|
|
|
+ .clue-list {
|
|
|
+ .clue-item {
|
|
|
display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: $spacing-base;
|
|
|
- margin-bottom: $spacing-large;
|
|
|
- }
|
|
|
+ align-items: center;
|
|
|
+ margin: 8px 0;
|
|
|
+ padding: 6px 0;
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+ &:last-child { border-bottom: none; }
|
|
|
+ .clue-label { color: #3c92fb; font-size: 14px; }
|
|
|
+ .clue-text { margin: 0 10px 0 6px; color: #333; flex: 1; }
|
|
|
+ .clue-revealed-time { color: #aaa; font-size: 13px; margin-left: 8px; }
|
|
|
+ &.revealed { background: #fffbe4; }
|
|
|
+ }
|
|
|
+ .no-clues { color: #aaa; font-size: 14px; padding: 24px 0; text-align: center;}
|
|
|
}
|
|
|
-
|
|
|
- // 弹窗样式
|
|
|
- .progress-dialog, .solution-dialog, .solved-dialog {
|
|
|
- padding: $spacing-base;
|
|
|
-
|
|
|
- .progress-label {
|
|
|
- margin-bottom: $spacing-base;
|
|
|
- font-size: $font-size-small;
|
|
|
- color: $text-color-secondary;
|
|
|
- text-align: center;
|
|
|
- }
|
|
|
-
|
|
|
- .nut-range {
|
|
|
- margin-bottom: $spacing-large;
|
|
|
- }
|
|
|
-
|
|
|
- .nut-textarea {
|
|
|
- margin-bottom: $spacing-large;
|
|
|
- }
|
|
|
-
|
|
|
- .solver-input {
|
|
|
- margin-bottom: $spacing-base;
|
|
|
- }
|
|
|
-
|
|
|
- .dialog-buttons {
|
|
|
- margin-top: $spacing-base;
|
|
|
- }
|
|
|
+}
|
|
|
+
|
|
|
+.progress-bar {
|
|
|
+ margin: 8px 18px 0 18px;
|
|
|
+ .progress-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #888;
|
|
|
+ margin-bottom: 4px;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+.footer-bar {
|
|
|
+ margin: 20px 12px 0 12px;
|
|
|
+}
|
|
|
</style>
|