Przeglądaj źródła

梳理数据流,减轻代码量

wuzj 2 dni temu
rodzic
commit
4ddbf9aa80

+ 0 - 1
components.d.ts

@@ -31,7 +31,6 @@ declare module 'vue' {
     NutTabPane: typeof import('@nutui/nutui-taro')['TabPane']
     NutTabs: typeof import('@nutui/nutui-taro')['Tabs']
     NutTag: typeof import('@nutui/nutui-taro')['Tag']
-    NutTextarea: typeof import('@nutui/nutui-taro')['Textarea']
     PlayerList: typeof import('./src/components/room/PlayerList.vue')['default']
     PlayerView: typeof import('./src/components/room/player/PlayerView.vue')['default']
     RoomCode: typeof import('./src/components/room/RoomCode.vue')['default']

+ 1 - 0
src/components/room/host/gamesettings/TurtleSoupSettings.vue

@@ -624,6 +624,7 @@ watch(() => [selectedSettings.themeId, selectedSettings.difficulty], async ([the
 .settings-button {
   width: 100%;
   margin-top: $spacing-small;
+  align-items: center;
 }
 
 // 主题选择器样式

+ 1 - 1
src/pages/game-detail/index.vue

@@ -224,7 +224,7 @@ export default {
         console.log('game.value:', game.value)
         tabbarStore.switchToRoomTab('/pages/room/create/index', {
           params: {
-            gameId: game.value.id
+            gameId: game.value.gameId
           }
         })
       }

+ 26 - 26
src/pages/index/index.vue

@@ -13,8 +13,8 @@
           <nut-tab-pane title="推荐" pane-key="0">
             <game-card
               v-for="game in filteredGames"
-              :key="game.id"
-              :id="game.id"
+              :key="game.gameId"
+              :id="game.gameId"
               :title="game.title"
               :image="game.image"
               :players="game.minPlayers + '-' + game.maxPlayers"
@@ -23,7 +23,7 @@
               :is-new="game.isNew"
               :is-hot="game.isHot"
               :description="game.description"
-              @click="() => navigateToGameDetail(game.id)"
+              @click="() => navigateToGameDetail(game.gameId)"
             ></game-card>
             <nut-empty v-if="filteredGames.length === 0" description="没有找到相关游戏" image="empty" />
           </nut-tab-pane>
@@ -31,8 +31,8 @@
           <nut-tab-pane title="热门" pane-key="1">
             <game-card
               v-for="game in hotGames"
-              :key="game.id"
-              :id="game.id"
+              :key="game.gameId"
+              :id="game.gameId"
               :title="game.title"
               :image="game.image"
                 :players="game.minPlayers + '-' + game.maxPlayers"
@@ -41,7 +41,7 @@
               :is-new="game.isNew"
               :is-hot="game.isHot"
               :description="game.description"
-              @click="() => navigateToGameDetail(game.id)"
+              @click="() => navigateToGameDetail(game.gameId)"
             ></game-card>
             <nut-empty v-if="hotGames.length === 0" description="暂无热门游戏" image="empty" />
           </nut-tab-pane>
@@ -49,8 +49,8 @@
           <nut-tab-pane title="社交" pane-key="2">
             <game-card
               v-for="game in socialGames"
-              :key="game.id"
-              :id="game.id"
+              :key="game.gameId"
+              :id="game.gameId"
               :title="game.title"
               :image="game.image"
               :players="game.minPlayers + '-' + game.maxPlayers"
@@ -59,7 +59,7 @@
               :is-new="game.isNew"
               :is-hot="game.isHot"
               :description="game.description"
-              @click="() => navigateToGameDetail(game.id)"
+              @click="() => navigateToGameDetail(game.gameId)"
             ></game-card>
             <nut-empty v-if="socialGames.length === 0" description="暂无社交游戏" image="empty" />
           </nut-tab-pane>
@@ -67,8 +67,8 @@
           <nut-tab-pane title="音乐" pane-key="3">
             <game-card
               v-for="game in musicGames"
-              :key="game.id"
-              :id="game.id"
+              :key="game.gameId"
+              :id="game.gameId"
               :title="game.title"
               :image="game.image"
               :players="game.minPlayers + '-' + game.maxPlayers"
@@ -77,7 +77,7 @@
               :is-new="game.isNew"
               :is-hot="game.isHot"
               :description="game.description"
-              @click="() => navigateToGameDetail(game.id)"
+              @click="() => navigateToGameDetail(game.gameId)"
             ></game-card>
             <nut-empty v-if="musicGames.length === 0" description="暂无音乐游戏" image="empty" />
           </nut-tab-pane>
@@ -97,8 +97,8 @@
           <template v-if="activeTab === '0'">
             <game-card
               v-for="game in filteredGames"
-              :key="game.id"
-              :id="game.id"
+              :key="game.gameId"
+              :id="game.gameId"
               :title="game.title"
               :image="game.image"
               :players="game.minPlayers + '-' + game.maxPlayers"
@@ -107,7 +107,7 @@
               :is-new="game.isNew"
               :is-hot="game.isHot"
               :description="game.description"
-              @click="() => navigateToGameDetail(game.id)"
+              @click="() => navigateToGameDetail(game.gameId)"
             ></game-card>
             <nut-empty v-if="filteredGames.length === 0" description="没有找到相关游戏" image="empty" />
           </template>
@@ -116,8 +116,8 @@
           <template v-else-if="activeTab === '1'">
             <game-card
               v-for="game in hotGames"
-              :key="game.id"
-              :id="game.id"
+              :key="game.gameId"
+              :id="game.gameId"
               :title="game.title"
               :image="game.image"
               :players="game.minPlayers + '-' + game.maxPlayers"
@@ -126,7 +126,7 @@
               :is-new="game.isNew"
               :is-hot="game.isHot"
               :description="game.description"
-              @click="() => navigateToGameDetail(game.id)"
+              @click="() => navigateToGameDetail(game.gameId)"
             ></game-card>
             <nut-empty v-if="hotGames.length === 0" description="暂无热门游戏" image="empty" />
           </template>
@@ -135,8 +135,8 @@
           <template v-else-if="activeTab === '2'">
             <game-card
               v-for="game in socialGames"
-              :key="game.id"
-              :id="game.id"
+              :key="game.gameId"
+              :id="game.gameId"
               :title="game.title"
               :image="game.image"
               :players="game.minPlayers + '-' + game.maxPlayers"
@@ -145,7 +145,7 @@
               :is-new="game.isNew"
               :is-hot="game.isHot"
               :description="game.description"
-              @click="() => navigateToGameDetail(game.id)"
+              @click="() => navigateToGameDetail(game.gameId)"
             ></game-card>
             <nut-empty v-if="socialGames.length === 0" description="暂无社交游戏" image="empty" />
           </template>
@@ -154,8 +154,8 @@
           <template v-else-if="activeTab === '3'">
             <game-card
               v-for="game in musicGames"
-              :key="game.id"
-              :id="game.id"
+              :key="game.gameId"
+              :id="game.gameId"
               :title="game.title"
               :image="game.image"
               :players="game.minPlayers + '-' + game.maxPlayers"
@@ -164,7 +164,7 @@
               :is-new="game.isNew"
               :is-hot="game.isHot"
               :description="game.description"
-              @click="() => navigateToGameDetail(game.id)"
+              @click="() => navigateToGameDetail(game.gameId)"
             ></game-card>
             <nut-empty v-if="musicGames.length === 0" description="暂无音乐游戏" image="empty" />
           </template>
@@ -327,8 +327,8 @@ export default {
 
     // 跳转到游戏详情页
     const navigateToGameDetail = (gameId: string) => {
-      const game = gameStore.games.find(g => g.id === gameId)
-      if (game && (game.id === "1" || game.title.includes('海龟汤'))) {
+      const game = gameStore.games.find(g => g.gameId === gameId)
+      if (game) {
         tabbarStore.navigate('/pages/game-detail/index', {
           params: {
             gameId: gameId

+ 10 - 5
src/pages/room/create/index.vue

@@ -74,6 +74,7 @@ import { useGameStore } from '@/stores/game'
 import { type Game } from '@/types/game'  
 import { useUserStore } from '@/stores/user'
 import { useRoomStore } from '@/stores/room'
+import { useTabBarStore } from '@/stores/tabbar'
 import { RoomRole, RoomStatus, RoomVisibility } from '@/types/room'
 import Tabbar from '@/components/Tabbar.vue'
 
@@ -99,7 +100,7 @@ export default {
     const gameStore = useGameStore()
     const userStore = useUserStore()
     const roomStore = useRoomStore()
-
+    const tabbarStore = useTabBarStore()
     // 房间设置
     const roomName = ref('')
     const maxPlayers = ref(0) // 初始值会在游戏加载后设置
@@ -182,9 +183,9 @@ export default {
         
         // 构建房间数据 - 使用滑块选择的最大人数
         const roomData = {
-          id: `Room_${Math.floor(Math.random() * 1000)}`,
+          roomId: `Room_${Math.floor(Math.random() * 1000)}`,
           name: roomName.value,
-          gameId: gameInfo.value.id || '',
+          gameId: gameInfo.value.gameId || '',
           gameTitle: gameInfo.value.title || '',
           maxPlayers: maxPlayers.value, // 使用滑块选择的人数
           visibility: roomVisibility.value as RoomVisibility,
@@ -198,8 +199,12 @@ export default {
         const result = await roomStore.createRoom(roomData)
         
         if (result.success && result.roomId) {
-          // 导航到房间页面
-          roomStore.navigateToRoomPage(result.roomId)
+          tabbarStore.switchToRoomTab('/pages/room/waiting/index', {
+          params: {
+            roomId: roomData.roomId,
+            gameId: roomData.gameId
+          }
+        })
         } else {
           throw new Error(result.message || '创建房间失败')
         }

+ 202 - 1153
src/pages/room/play/index.vue

@@ -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>

+ 5 - 110
src/services/game.ts

@@ -7,7 +7,7 @@ import { USE_MOCK } from '@/services'
 // Mock游戏数据
 const mockGames: Game[] = [
   {
-    id: '1',
+    gameId: '1',  
     title: '海龟汤',
     type: GameType.TURTLE_SOUP,
     image: 'https://images.unsplash.com/photo-1582845512747-e42001c95638?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&h=100&q=80',
@@ -35,7 +35,7 @@ const mockGames: Game[] = [
     ]
   },
   {
-    id: '2',
+    gameId: '2',
     title: '剧本杀',
     type: GameType.WORD_GAME,
     image: 'https://images.unsplash.com/photo-1529156069898-49953e39b3ac?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&h=100&q=80',
@@ -63,7 +63,7 @@ const mockGames: Game[] = [
     ]
   },
   {
-    id: '3',
+    gameId: '3',
     title: '狼人杀',
     type: GameType.WORD_GAME,
     image: 'https://images.unsplash.com/photo-1529156069898-49953e39b3ac?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&h=100&q=80',
@@ -135,7 +135,7 @@ export const gameService = {
     if (USE_MOCK || process.env.TARO_ENV !== 'weapp') {
       return new Promise(resolve => {
         setTimeout(() => {
-          const game = mockGames.find(g => g.id === id);
+          const game = mockGames.find(g => g.gameId === id);
           
           if (game) {
             resolve(createSuccess(game));
@@ -151,7 +151,7 @@ export const gameService = {
       .then(result => {
         // 如果没有返回数据,尝试从mock找
         if (result.success && !result.data) {
-          const mockGame = mockGames.find(g => g.id === id);
+          const mockGame = mockGames.find(g => g.gameId === id);
           if (mockGame) {
             return createSuccess(mockGame);
           }
@@ -163,110 +163,5 @@ export const gameService = {
         console.error('获取游戏详情失败:', error);
         return createError(error.message || '获取游戏详情失败');
       });
-  },
-  
-  // 获取热门游戏
-  async getHotGames(limit: number = 5): Promise<Result<Game[]>> {
-    const result = await this.getGames();
-    
-    if (!result.success) {
-      return result;
-    }
-    
-    const hotGames = result.data
-      ?.filter(game => game.isHot)
-      .sort((a, b) => b.rating - a.rating)
-      .slice(0, limit);
-      
-    return createSuccess(hotGames || []);
-  },
-  
-  // 获取新游戏
-  async getNewGames(limit: number = 5): Promise<Result<Game[]>> {
-    const result = await this.getGames();
-    
-    if (!result.success) {
-      return result;
-    }
-    
-    const newGames = result.data
-      ?.filter(game => game.isNew)
-      .slice(0, limit);
-      
-    return createSuccess(newGames || []);
-  },
-  
-  // 搜索游戏
-  async searchGames(keyword: string): Promise<Result<Game[]>> {
-    // 如果在开发环境或非小程序环境,使用Mock数据
-    if (USE_MOCK || process.env.TARO_ENV !== 'weapp') {
-      return new Promise(resolve => {
-        setTimeout(() => {
-          if (!keyword.trim()) {
-            resolve(createSuccess(mockGames));
-            return;
-          }
-          
-          const searchKey = keyword.toLowerCase();
-          const results = mockGames.filter(game => 
-            game.title.toLowerCase().includes(searchKey) || 
-            game.description.toLowerCase().includes(searchKey) ||
-            game.category.toLowerCase().includes(searchKey)
-          );
-          
-          resolve(createSuccess(results));
-        }, 300);
-      });
-    }
-    
-    // 使用cloudApi调用云函数
-    return cloudApi.call<Game[]>('searchGames', { keyword })
-      .then(result => {
-        // 如果云函数搜索失败,降级到前端搜索mock数据
-        if (!result.success || !result.data || result.data.length === 0) {
-          const searchKey = keyword.toLowerCase();
-          const results = mockGames.filter(game => 
-            game.title.toLowerCase().includes(searchKey) || 
-            game.description.toLowerCase().includes(searchKey) ||
-            game.category.toLowerCase().includes(searchKey)
-          );
-          
-          return createSuccess(results);
-        }
-        return result;
-      })
-      .catch(error => {
-        console.error('搜索游戏失败:', error);
-        return createError(error.message || '搜索游戏失败');
-      });
-  },
-  
-  // 获取游戏分类
-  async getCategories(): Promise<Result<string[]>> {
-    // 如果在开发环境或非小程序环境,使用Mock数据
-    if (USE_MOCK || process.env.TARO_ENV !== 'weapp') {
-      return new Promise(resolve => {
-        setTimeout(() => {
-          // 从mock数据中提取唯一分类
-          const categories = [...new Set(mockGames.map(game => game.category))];
-          resolve(createSuccess(categories));
-        }, 200);
-      });
-    }
-    
-    // 使用cloudApi调用云函数
-    return cloudApi.call<string[]>('getGameCategories')
-      .then(result => {
-        // 如果没有返回数据,从mock数据提取
-        if (result.success && (!result.data || result.data.length === 0)) {
-          const categories = [...new Set(mockGames.map(game => game.category))];
-          return createSuccess(categories);
-        }
-        return result;
-      })
-      .catch(error => {
-        console.error('获取游戏分类失败:', error);
-        return createError(error.message || '获取游戏分类失败');
-      });
   }
 }

+ 5 - 5
src/services/games/turtlesoup.ts

@@ -51,7 +51,7 @@ const mockPuzzles: TurtleSoupPuzzle[] = [
 
 // Mock 主持人视图
 const mockHostView: TurtleSoupGameHostView = {
-  id: 'game-1',
+  gameId: 'game-1',
   roomId: 'room-1',
   title: '欢乐海龟汤',
   theme: mockThemes[0],
@@ -64,7 +64,7 @@ const mockHostView: TurtleSoupGameHostView = {
   hostName: '小明 (我)',
   players: [
     {
-      id: 'user-1',
+      userId: 'user-1',
       name: '小红',
       avatar: '',
       joinedAt: Date.now() - 3000000,
@@ -83,7 +83,7 @@ const mockHostView: TurtleSoupGameHostView = {
 
 // Mock 玩家视图
 const mockPlayerView: TurtleSoupGamePlayerView = {
-  id: 'game-1',
+  gameId: 'game-1',
   roomId: 'room-1',
   title: '欢乐海龟汤',
   theme: mockThemes[0],
@@ -103,7 +103,7 @@ const mockPlayerView: TurtleSoupGamePlayerView = {
     maxPlayers: 10
   },
   myPlayerInfo: {
-    id: 'user-1',
+    userId: 'user-1',
     name: '小红',
     avatar: '',
     joinedAt: Date.now() - 3000000,
@@ -111,7 +111,7 @@ const mockPlayerView: TurtleSoupGamePlayerView = {
   },
   otherPlayers: [
     {
-      id: 'user-2',
+      userId: 'user-2',
       name: '小李',
       avatar: ''
     }

+ 13 - 74
src/services/room.ts

@@ -13,7 +13,7 @@ import { type UserInfo } from '@/types/user'
 // Mock房间数据(测试用)
 const mockRooms: Record<string, Room> = {
   'room_001': {
-    id: 'room_001',
+    roomId: 'room_001',
     name: '海龟汤房间',
     gameId: '1',
     gameTitle: '海龟汤',
@@ -34,7 +34,7 @@ const mockRooms: Record<string, Room> = {
     ]
   },
   'room_002': {
-    id: 'room_002',
+    roomId: 'room_002',
     name: '狼人杀小队',
     gameId: '2',
     gameTitle: '狼人杀',
@@ -71,7 +71,7 @@ export const roomService = {
       // 返回默认mock数据
       return [
         { 
-          id: 'room_001', 
+          roomId: 'room_001', 
           name: '海龟汤房间', 
           gameTitle: '海龟汤', 
           lastVisitTime: Date.now() - 3600000, 
@@ -80,7 +80,7 @@ export const roomService = {
           playerCount: 1
         },
         { 
-          id: 'room_002', 
+          roomId: 'room_002', 
           name: '狼人杀小队', 
           gameTitle: '狼人杀', 
           lastVisitTime: Date.now() - 86400000, 
@@ -102,7 +102,7 @@ export const roomService = {
       const recentRooms = Taro.getStorageSync('recentRooms')
       if (recentRooms) {
         const rooms = JSON.parse(recentRooms)
-        const room = rooms.find((r: RecentRoom) => r.id === roomId)
+        const room = rooms.find((r: RecentRoom) => r.roomId === roomId)
         if (room) {
           return { exists: true, needPassword: room.hasPassword }
         }
@@ -153,7 +153,7 @@ export const roomService = {
       
       // 添加到最近房间
       const roomInfo: RecentRoom = {
-        id: roomData.id,
+        roomId: roomData.roomId,
         name: roomData.name,
         gameTitle: roomData.gameTitle,
         lastVisitTime: roomData.createTime,
@@ -173,10 +173,10 @@ export const roomService = {
       // 简化实现:仅存储在本地缓存
       const allRooms = Taro.getStorageSync('allRooms') || '{}'
       const roomsObj = JSON.parse(allRooms)
-      roomsObj[roomData.id] = roomData
+      roomsObj[roomData.roomId] = roomData
       Taro.setStorageSync('allRooms', JSON.stringify(roomsObj))
       
-      return { roomId: roomData.id, success: true }
+      return { roomId: roomData.roomId, success: true }
     } catch (error) {
       console.error('创建房间失败:', error)
       return { roomId: null, success: false, message: '创建房间失败' }
@@ -222,7 +222,7 @@ export const roomService = {
       
       // 保存到最近加入的房间
       const roomInfo: RecentRoom = {
-        id: room.id,
+        roomId: room.roomId,
         name: room.name,
         gameTitle: room.gameTitle,
         lastVisitTime: Date.now(),
@@ -248,7 +248,7 @@ export const roomService = {
       const rooms = JSON.parse(recentRooms)
       
       // 检查是否已存在
-      const existingIndex = rooms.findIndex((r: RecentRoom) => r.id === roomInfo.id)
+      const existingIndex = rooms.findIndex((r: RecentRoom) => r.roomId === roomInfo.roomId)
       if (existingIndex !== -1) {
         // 移除已存在的
         rooms.splice(existingIndex, 1)
@@ -337,7 +337,7 @@ export const roomService = {
       if (userActiveRooms.length === 0) {
         return [
           { 
-            id: 'room_001', 
+            roomId: 'room_001', 
             name: '欢乐海龟汤', 
             gameId: '1', 
             gameTitle: '海龟汤',
@@ -358,7 +358,7 @@ export const roomService = {
             visibility: RoomVisibility.PUBLIC
           },
           { 
-            id: 'room_002', 
+            roomId: 'room_002', 
             name: '趣味谜题', 
             gameId: '1', 
             gameTitle: '海龟汤',
@@ -388,67 +388,6 @@ export const roomService = {
     }
   },
 
-  // 获取已结束的游戏
-  async getEndedGames(userId: string): Promise<any[]> {
-    try {
-      // 从缓存获取
-      const endedGames = Taro.getStorageSync('endedGames') || '[]'
-      const games = JSON.parse(endedGames)
-      
-      // 筛选用户参与的游戏
-      const userGames = games.filter((game: any) => 
-        game.users && game.users.some((u: any) => u.openid === userId)
-      )
-      
-      // 添加mock数据用于测试
-      if (userGames.length === 0) {
-        return [
-          { 
-            id: 'game_001', 
-            roomId: 'room_003',
-            roomName: '神秘的手表',
-            gameId: '1', 
-            gameTitle: '海龟汤', 
-            hosterId: 'host_123',
-            hosterName: '小李',
-            endTime: Date.now() - 86400000, // 昨天
-            duration: 45, // 分钟
-            users: [{ openid: userId }]
-          },
-          { 
-            id: 'game_002', 
-            roomId: 'room_004',
-            roomName: '迷路的青蛙',
-            gameId: '1', 
-            gameTitle: '海龟汤', 
-            hosterId: userId,
-            hosterName: '玩家',
-            endTime: Date.now() - 172800000, // 前天
-            duration: 28, // 分钟
-            users: [{ openid: userId }]
-          },
-          { 
-            id: 'game_003', 
-            roomId: 'room_005',
-            roomName: '动物园奇案',
-            gameId: '2', 
-            gameTitle: '谜星探案', 
-            hosterId: 'host_456',
-            hosterName: '探长',
-            endTime: Date.now() - 259200000, // 3天前
-            duration: 35, // 分钟
-            users: [{ openid: userId }]
-          }
-        ]
-      }
-      
-      return userGames
-    } catch (error) {
-      console.error('获取已结束游戏失败:', error)
-      return []
-    }
-  },
-
   // 获取我创建的房间/游戏
   async getCreatedRooms(userId: string): Promise<any[]> {
     try {
@@ -600,7 +539,7 @@ export const roomService = {
         // 同时更新最近房间列表中的状态
         const recentRooms = Taro.getStorageSync('recentRooms') || '[]'
         const rooms = JSON.parse(recentRooms)
-        const roomIndex = rooms.findIndex((r: RecentRoom) => r.id === roomId)
+        const roomIndex = rooms.findIndex((r: RecentRoom) => r.roomId === roomId)
         
         if (roomIndex !== -1) {
           rooms[roomIndex].status = status

+ 1 - 1
src/stores/game.ts

@@ -46,7 +46,7 @@ export const useGameStore = defineStore('game', () => {
     }
 
     // 优先从缓存中查找
-    const cachedGame = games.value.find((game) => game.id === id)
+    const cachedGame = games.value.find((game) => game.gameId === id)
     if (cachedGame) {
       console.log('从缓存中找到游戏:', cachedGame)
 

+ 2 - 32
src/stores/games/turtlesoup.ts

@@ -218,7 +218,7 @@ export const useTurtleSoupStore = defineStore('turtlesoup', () => {
     submitting.value = true
     error.value = null
     try {
-      const result = await turtleSoupService.startGame(hostView.value.id)
+      const result = await turtleSoupService.startGame(hostView.value.roomId)
       if (result.success) {
         hostView.value = {
           ...hostView.value,
@@ -239,34 +239,6 @@ export const useTurtleSoupStore = defineStore('turtlesoup', () => {
     }
   }
 
-  async function submitSolution(solution: string) {
-    if (!playerView.value || !solution.trim()) {
-      error.value = '无效的解答或游戏未加载'
-      return { success: false, correct: false }
-    }
-    submitting.value = true
-    error.value = null
-    try {
-      const result = await turtleSoupService.submitSolution(playerView.value.id, solution)
-      if (result.success) {
-        return { success: true, correct: !!result.data?.correct }
-      } else {
-        error.value = result.message || '提交解答失败'
-        return { success: false, correct: false }
-      }
-    } catch (e) {
-      error.value = e instanceof Error ? e.message : '提交解答时发生错误'
-      return { success: false, correct: false }
-    } finally {
-      submitting.value = false
-    }
-  }
-
-  // 保存草稿
-  function saveDraft(content: string) {
-    localDraft.value = content
-  }
-
   // 更新进度
   async function updateProgress(progress: number) {
     if (!isHost.value || !hostView.value) {
@@ -275,7 +247,7 @@ export const useTurtleSoupStore = defineStore('turtlesoup', () => {
     }
     submitting.value = true
     try {
-      const result = await turtleSoupService.updateProgress(hostView.value.id, progress)
+      const result = await turtleSoupService.updateProgress(hostView.value.roomId, progress)
       if (result.success) {
         hostView.value = {
           ...hostView.value,
@@ -333,8 +305,6 @@ export const useTurtleSoupStore = defineStore('turtlesoup', () => {
     loadPlayerGame,
     gameStatusToRoomStatus,
     startGame,
-    submitSolution,
-    saveDraft,
     updateProgress,
     refreshGameData
   }

+ 5 - 47
src/stores/room.ts

@@ -11,13 +11,11 @@ import {
   RoomVisibility 
 } from '@/types/room'
 import { useUserStore } from '@/stores/user'
-import { useTabBarStore } from '@/stores/tabbar'
 
 
 export const useRoomStore = defineStore('room', () => {
   // 获取userStore
   const userStore = useUserStore()
-  const tabbarStore = useTabBarStore()
   // 当前房间信息
   const currentRoom = ref<Room | null>(null)
   
@@ -47,7 +45,7 @@ export const useRoomStore = defineStore('room', () => {
     const recentList: RecentRoom[] = [
       // 活跃房间
       ...historyState.activeRooms.map(room => ({
-        id: room.id,
+        roomId: room.roomId,
         name: room.name,
         gameTitle: room.gameTitle,
         lastVisitTime: room.createTime,
@@ -57,7 +55,7 @@ export const useRoomStore = defineStore('room', () => {
       })),
       // 已结束游戏
       ...historyState.endedGames.filter((game, index) => index < 5).map(game => ({
-        id: game.roomId || game.id,
+        roomId: game.roomId || game.roomId,
         name: game.roomName,
         gameTitle: game.gameTitle,
         lastVisitTime: game.endTime,
@@ -86,17 +84,6 @@ export const useRoomStore = defineStore('room', () => {
     }
   }
   
-  // 加载已结束游戏 - 统一数据源
-  async function loadEndedGames() {
-    try {
-      historyState.endedGames = await roomService.getEndedGames(userStore.openid)
-      return historyState.endedGames
-    } catch (error) {
-      console.error('加载已结束游戏失败:', error)
-      return []
-    }
-  }
-  
   // 检查用户是否已注册,如果未注册则返回错误
   async function checkUserRegistered(): Promise<{valid: boolean, message?: string}> {
     if (!userStore.isRegistered) {
@@ -297,7 +284,7 @@ export const useRoomStore = defineStore('room', () => {
     
     try {
       // 调用API更新用户信息
-      const result = await roomService.updateUserInRoom(currentRoom.value.id, userId, data)
+      const result = await roomService.updateUserInRoom(currentRoom.value.roomId, userId, data)
       
       if (result.success) {
         // 更新本地状态
@@ -324,7 +311,7 @@ export const useRoomStore = defineStore('room', () => {
     
     try {
       // 调用API更新房间状态
-      const result = await roomService.updateRoomStatus(currentRoom.value.id, status)
+      const result = await roomService.updateRoomStatus(currentRoom.value.roomId, status)
       
       if (result.success) {
         // 更新本地状态
@@ -398,12 +385,10 @@ export const useRoomStore = defineStore('room', () => {
       if (tabIndex === 0) { // 最近游戏
         // 加载进行中的房间和已结束的游戏
         await loadActiveRooms()
-        await loadEndedGames()
         
       } else if (tabIndex === 1) { // 我创建的
         // 确保数据已加载
         await loadActiveRooms()
-        await loadEndedGames()
         
         // 筛选我创建的内容
         historyState.activeRooms = historyState.activeRooms.filter(
@@ -416,7 +401,6 @@ export const useRoomStore = defineStore('room', () => {
       } else if (tabIndex === 2) { // 我参与的
         // 确保数据已加载
         await loadActiveRooms()
-        await loadEndedGames()
         
         // 筛选我参与但不是我创建的内容
         historyState.activeRooms = historyState.activeRooms.filter(
@@ -489,30 +473,6 @@ export const useRoomStore = defineStore('room', () => {
     }
   }
   
-    // 添加一个根据房间状态执行跳转的辅助方法
-    function navigateToRoomPage(
-      roomId: string,
-      additionalParams: Record<string, any> = {}
-    ) {
-      // 获取当前房间状态
-      const status = currentRoom.value?.status
-    
-      // 构建基础参数
-      const params = {
-        roomId,
-        ...additionalParams
-      }
-    
-      // 根据房间状态跳转到不同页面
-      if (status === RoomStatus.PLAYING) {
-        // 游戏进行中,跳转到游戏页面
-        tabbarStore.switchToRoomTab('/pages/room/play/index', { params })
-      } else {
-        // 等待中或其他状态,跳转到等待页面
-        tabbarStore.switchToRoomTab('/pages/room/waiting/index', { params })
-      }
-    }
-  
   // 返回状态和方法
   return {
     // 状态
@@ -531,7 +491,6 @@ export const useRoomStore = defineStore('room', () => {
     loadRoomInfo,
     loadRecentRooms,
     loadActiveRooms,
-    loadEndedGames,
     clearRoom,
     
     // 用户管理方法
@@ -553,7 +512,6 @@ export const useRoomStore = defineStore('room', () => {
     
     // 历史记录方法
     loadHistoryByTab,
-    joinRoomById,
-    navigateToRoomPage,
+    joinRoomById
   }
 })

+ 1 - 1
src/types/game.ts

@@ -7,7 +7,7 @@ export enum GameType {
 
 // 游戏数据接口
 export interface Game {
-    id: string; // 游戏ID
+    gameId: string; // 游戏ID
     type: GameType; // 游戏类型
     title: string; // 游戏标题
     image: string; // 游戏图片

+ 4 - 4
src/types/games/turtlesoup.ts

@@ -76,7 +76,7 @@ export interface TurtleSoupGameSettings {
  * 游戏基础视图
  */
 interface TurtleSoupGameBaseView {
-  id: string;
+  gameId: string;
   roomId: string;
   title: string;
   theme: TurtleSoupTheme;
@@ -97,7 +97,7 @@ interface TurtleSoupGameBaseView {
  */
 export interface TurtleSoupGameHostView extends TurtleSoupGameBaseView {
   players: {
-    id: string;
+    userId: string;
     name: string;
     avatar?: string;
     joinedAt: number;
@@ -107,14 +107,14 @@ export interface TurtleSoupGameHostView extends TurtleSoupGameBaseView {
 
 export interface TurtleSoupGamePlayerView extends TurtleSoupGameBaseView {
   myPlayerInfo: {
-    id: string;
+    userId: string;
     name: string;
     avatar?: string;
     joinedAt: number;
     questionCount: number;
   };
   otherPlayers: {
-    id: string;
+    userId: string;
     name: string;
     avatar?: string;
   }[];

+ 2 - 2
src/types/room.ts

@@ -28,7 +28,7 @@ export interface RoomUserInfo extends UserInfo {
 
 // 游戏房间
 export interface Room {
-  id: string;            // 房间唯一ID
+  roomId: string;            // 房间唯一ID
   name: string;          // 房间名称
   gameId: string;        // 游戏ID
   gameTitle: string;     // 游戏标题
@@ -45,7 +45,7 @@ export interface Room {
 
 // 最近房间记录
 export interface RecentRoom {
-  id: string;            // 房间ID
+  roomId: string;            // 房间ID
   name: string;          // 房间名称
   gameTitle: string;     // 游戏标题
   lastVisitTime: number; // 最后访问时间