瀏覽代碼

重构题目选择器

wuzj 2 天之前
父節點
當前提交
a153ddc743

+ 6 - 6
components.d.ts

@@ -7,20 +7,19 @@ export {}
 
 declare module 'vue' {
   export interface GlobalComponents {
-    DifficultySelector: typeof import('./src/components/room/selectors/DifficultySelector.vue')['default']
     GameCard: typeof import('./src/components/GameCard/index.vue')['default']
+    GameSettings: typeof import('./src/components/room/host/GameSettings.vue')['default']
     HintCard: typeof import('./src/components/HintCard/index.vue')['default']
-    HostSettings: typeof import('./src/components/room/host/HostSettings.vue')['default']
     HostView: typeof import('./src/components/room/host/HostView.vue')['default']
     NutBarrage: typeof import('@nutui/nutui-taro')['Barrage']
     NutButton: typeof import('@nutui/nutui-taro')['Button']
-    NutCell: typeof import('@nutui/nutui-taro')['Cell']
     NutDialog: typeof import('@nutui/nutui-taro')['Dialog']
     NutDivider: typeof import('@nutui/nutui-taro')['Divider']
     NutEmpty: typeof import('@nutui/nutui-taro')['Empty']
     NutInfiniteLoading: typeof import('@nutui/nutui-taro')['InfiniteLoading']
     NutInput: typeof import('@nutui/nutui-taro')['Input']
     NutPopup: typeof import('@nutui/nutui-taro')['Popup']
+    NutProgress: typeof import('@nutui/nutui-taro')['Progress']
     NutRadio: typeof import('@nutui/nutui-taro')['Radio']
     NutRadioGroup: typeof import('@nutui/nutui-taro')['RadioGroup']
     NutRange: typeof import('@nutui/nutui-taro')['Range']
@@ -31,13 +30,14 @@ declare module 'vue' {
     NutTabbarItem: typeof import('@nutui/nutui-taro')['TabbarItem']
     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']
-    PuzzleSelector: typeof import('./src/components/room/selectors/PuzzleSelector.vue')['default']
-    RoomCode: typeof import('./src/components/RoomCode/index.vue')['default']
+    RoomCode: typeof import('./src/components/room/RoomCode.vue')['default']
     RoomHeader: typeof import('./src/components/room/RoomHeader.vue')['default']
     Tabbar: typeof import('./src/components/Tabbar.vue')['default']
-    ThemeSelector: typeof import('./src/components/room/selectors/ThemeSelector.vue')['default']
+    TurtleSoupSettings: typeof import('./src/components/room/host/gamesettings/TurtleSoupSettings.vue')['default']
     WereWolf: typeof import('./src/components/WereWolf.vue')['default']
   }
 }

+ 0 - 0
src/components/RoomCode/index.vue → src/components/room/RoomCode.vue


+ 69 - 0
src/components/room/host/GameSettings.vue

@@ -0,0 +1,69 @@
+<template>
+  <view class="game-settings">
+    <nut-divider 
+      content-position="center"
+      :style="{ color: '#3C92FB', borderColor: '#3C92FB', padding: '0 16px', margin: '12px 0 12px 0' }"
+    >
+      游戏设置
+    </nut-divider>
+    
+    <!-- 根据游戏类型载入不同的设置组件 -->
+    <component 
+      :is="gameSettingsComponent" 
+      @selection-change="handleSelectionChange"
+    />
+  </view>
+</template>
+
+<script lang="ts">
+import { defineComponent, PropType, computed } from 'vue'
+import TurtleSoupSettings from './gamesettings/TurtleSoupSettings.vue'
+import { GameType } from '@/types/game'
+
+export default defineComponent({
+  name: 'GameSettings',
+  components: {
+    TurtleSoupSettings
+  },
+  props: {
+    gameType: {
+      type: String,
+      default: GameType.TURTLE_SOUP
+    }
+  },
+  setup(props, { emit }) {
+    // 根据游戏类型动态加载对应的设置组件
+    const gameSettingsComponent = computed(() => {
+      switch(props.gameType) {
+        case GameType.TURTLE_SOUP:
+          return 'TurtleSoupSettings'
+        // 未来可以添加更多游戏类型
+        // case GameType.WORD_GAME:
+        //   return 'WordGameSettings'
+        default:
+          return 'TurtleSoupSettings'
+      }
+    })
+    
+    // 当子组件设置变更时,向上传递
+    const handleSelectionChange = (selection) => {
+      emit('settings-change', selection)
+    }
+    
+    return {
+      gameSettingsComponent,
+      handleSelectionChange
+    }
+  }
+})
+</script>
+
+<style lang="scss">
+.game-settings {
+  background-color: $background-color-light;
+  border-radius: $border-radius-small;
+  padding: $spacing-base;
+  margin-bottom: $spacing-base;
+  box-shadow: $shadow-light;
+}
+</style>

+ 0 - 125
src/components/room/host/HostSettings.vue

@@ -1,125 +0,0 @@
-<template>
-    <view class="game-settings">
-      <nut-divider 
-        content-position="center"
-        :style="{ color: '#3C92FB', borderColor: '#3C92FB', padding: '0 16px', margin: '12px 0 12px 0' }"
-      >
-        游戏设置
-      </nut-divider>
-      
-      <!-- 1. 主题选择 -->
-      <view class="setting-item">
-        <view class="setting-label">主题选择</view>
-        <nut-cell 
-          :desc="selectedTheme?.text || '请选择游戏主题'" 
-          @click="onShowThemeSelector"
-        >
-          <template #link>
-            <IconFont name="right" size="16"></IconFont>
-          </template>
-        </nut-cell>
-      </view>
-      
-      <!-- 2. 难度选择 -->
-      <view class="setting-item">
-        <view class="setting-label">游戏难度</view>
-        <nut-cell 
-          :desc="difficultyText" 
-          @click="onShowDifficultySelector"
-        >
-          <template #link>
-            <IconFont name="right" size="16"></IconFont>
-          </template>
-        </nut-cell>
-      </view>
-      
-      <!-- 3. 题目选择 (只有当主题和难度都选择后才显示) -->
-      <view class="setting-item" v-if="selectedThemeId && selectedDifficulty">
-        <view class="setting-label">题目选择</view>
-        <nut-cell 
-          :desc="selectedPuzzle?.text || '请选择游戏题目'" 
-          @click="selectedThemeId ? onShowPuzzleSelector() : null"
-        >
-          <template #link>
-            <IconFont name="right" size="16"></IconFont>
-          </template>
-        </nut-cell>
-      </view>
-    </view>
-  </template>
-  
-  <script lang="ts">
-  import { defineComponent, PropType } from 'vue'
-  import { IconFont } from '@nutui/icons-vue-taro'
-  import { TurtleSoupDifficulty } from '@/types/games/turtlesoup'
-  import { CascaderOption } from '@/types/cascader'
-  
-  export default defineComponent({
-    name: 'GameSettings',
-    components: {
-      IconFont
-    },
-    props: {
-      selectedTheme: {
-        type: Object as PropType<CascaderOption | null>,
-        default: null
-      },
-      selectedPuzzle: {
-        type: Object as PropType<CascaderOption | null>,
-        default: null
-      },
-      selectedThemeId: {
-        type: String,
-        default: ''
-      },
-      selectedDifficulty: {
-        type: String,
-        default: TurtleSoupDifficulty.MEDIUM
-      },
-      difficultyText: {
-        type: String,
-        default: '中等'
-      }
-    },
-    setup(props, { emit }) {
-      const onShowThemeSelector = () => {
-        emit('show-theme-selector')
-      }
-      
-      const onShowDifficultySelector = () => {
-        emit('show-difficulty-selector')
-      }
-      
-      const onShowPuzzleSelector = () => {
-        emit('show-puzzle-selector')
-      }
-  
-      return {
-        onShowThemeSelector,
-        onShowDifficultySelector,
-        onShowPuzzleSelector
-      }
-    }
-  })
-  </script>
-  
-  <style lang="scss">
-  .game-settings {
-    background-color: $background-color-light;
-    border-radius: $border-radius-small;
-    padding: $spacing-base;
-    margin-bottom: $spacing-base;
-    box-shadow: $shadow-light;
-    
-    .setting-item {
-      margin-bottom: $spacing-base;
-      
-      .setting-label {
-        font-size: $font-size-small;
-        color: $text-color-secondary;
-        margin-bottom: $spacing-mini;
-        font-weight: $font-weight-medium;
-      }
-    }
-  }
-  </style>

+ 77 - 85
src/components/room/host/HostView.vue

@@ -1,94 +1,86 @@
 <template>
-    <view class="host-view">
-      <!-- 游戏设置模块 -->
-      <GameSettings 
-        :selectedTheme="selectedTheme" 
-        :selectedPuzzle="selectedPuzzle"
-        :selectedThemeId="selectedThemeId"
-        :selectedDifficulty="selectedDifficulty"
-        :difficultyText="difficultyText"
-        @show-theme-selector="$emit('show-theme-selector')"
-        @show-difficulty-selector="$emit('show-difficulty-selector')"
-        @show-puzzle-selector="$emit('show-puzzle-selector')"
-      />
-      
-      <!-- 玩家列表 -->
-      <PlayerList :users="users" />
-      
-      <!-- 主持人操作按钮 -->
-      <view class="action-buttons">
-        <nut-button 
-          block 
-          color="#3C92FB" 
-          class="start-button" 
-          :disabled="!canStartGame"
-          @click="$emit('start-game')"
-        >
-          开始游戏
-        </nut-button>
-      </view>
+  <view class="host-view">
+    <!-- 游戏设置模块 -->
+    <GameSettings 
+      :gameType="gameType"
+      @settings-change="handleSettingsChange"
+    />
+    
+    <!-- 玩家列表 -->
+    <PlayerList :users="users" />
+    
+    <!-- 主持人操作按钮 -->
+    <view class="action-buttons">
+      <nut-button 
+        block 
+        color="#3C92FB" 
+        class="start-button" 
+        :disabled="!canStartGame"
+        @click="$emit('start-game', gameSettings)"
+      >
+        开始游戏
+      </nut-button>
     </view>
-  </template>
-  
-  <script lang="ts">
-  import { defineComponent, PropType } from 'vue'
-  import GameSettings from './HostSettings.vue'
-  import PlayerList from '../PlayerList.vue'
-  import { RoomUserInfo } from '@/types/room'
-  import { type CascaderOption } from '@/types/cascader'
-  
-  export default defineComponent({
-    name: 'HostView',
-    components: {
-      GameSettings,
-      PlayerList
+  </view>
+</template>
+
+<script lang="ts">
+import { defineComponent, PropType, ref } from 'vue'
+import GameSettings from './GameSettings.vue'
+import PlayerList from '../PlayerList.vue'
+import { RoomUserInfo } from '@/types/room'
+import { GameType } from '@/types/game'
+
+export default defineComponent({
+  name: 'HostView',
+  components: {
+    GameSettings,
+    PlayerList
+  },
+  props: {
+    users: {
+      type: Array as PropType<RoomUserInfo[]>,
+      default: () => []
     },
-    props: {
-      users: {
-        type: Array as PropType<RoomUserInfo[]>,
-        default: () => []
-      },
-      selectedTheme: {
-        type: Object as PropType<CascaderOption | null>,
-        default: null
-      },
-      selectedPuzzle: {
-        type: Object as PropType<CascaderOption | null>,
-        default: null
-      },
-      selectedThemeId: {
-        type: String,
-        default: ''
-      },
-      selectedDifficulty: {
-        type: String,
-        default: ''
-      },
-      difficultyText: {
-        type: String,
-        default: '中等'
-      },
-      canStartGame: {
-        type: Boolean,
-        default: false
-      }
+    gameType: {
+      type: String,
+      default: GameType.TURTLE_SOUP
     },
-    emits: ['show-theme-selector', 'show-difficulty-selector', 'show-puzzle-selector', 'start-game']
-  })
-  </script>
+    canStartGame: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['start-game'],
+  setup(props, { emit }) {
+    // 保存当前游戏设置
+    const gameSettings = ref({})
+    
+    // 处理设置变更
+    const handleSettingsChange = (settings) => {
+      gameSettings.value = settings
+    }
+    
+    return {
+      gameSettings,
+      handleSettingsChange
+    }
+  }
+})
+</script>
+
+<style lang="scss">
+.host-view {
+  margin-top: $spacing-large;
   
-  <style lang="scss">
-  .host-view {
+  .action-buttons {
     margin-top: $spacing-large;
     
-    .action-buttons {
-      margin-top: $spacing-large;
-      
-      .start-button {
-        height: 44px;
-        font-size: $font-size-medium;
-        border-radius: $border-radius-base;
-      }
+    .start-button {
+      height: 44px;
+      font-size: $font-size-medium;
+      border-radius: $border-radius-base;
     }
   }
-  </style>
+}
+</style>

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

@@ -0,0 +1,857 @@
+<template>
+  <div class="turtle-soup-settings">
+    <!-- 当前选择的设置 -->
+    <div class="settings-display">
+      <div class="settings-title">当前设置</div>
+      <div class="settings-info">
+        <div class="settings-row">
+          <div class="settings-label">主题:</div>
+          <div class="settings-value">{{ selectedTheme.themeName }}</div>
+        </div>
+        <div class="settings-row">
+          <div class="settings-label">难度:</div>
+          <div class="settings-badge">{{ selectedTheme.difficultyName }}</div>
+        </div>
+        <div class="settings-row">
+          <div class="settings-label">题目:</div>
+          <div class="settings-value">{{ selectedTheme.puzzleName }}</div>
+        </div>
+      </div>
+      
+      <!-- 设置按钮 -->
+      <nut-button class="settings-button" 
+                 type="primary" 
+                 color="#FF9F2D"
+                 @click="openThemeSelector">
+        <template #icon><IconFont name="setting" /></template>
+        选择游戏主题和难度
+      </nut-button>
+    </div>
+
+    <!-- 主题选择弹窗组件 -->
+    <nut-popup v-model:visible="showThemeSelector" 
+              position="center" 
+              round
+              :overlay-style="{ background: 'rgba(0, 0, 0, 0.5)' }">
+      <div class="theme-selector">
+        <div class="theme-selector-header">
+          <div class="theme-selector-title">{{ selectorTitles[currentStep-1] }}</div>
+          <IconFont name="close" size="16" @click="closeThemeSelector" />
+        </div>
+        
+        <!-- 步骤1:选择主题 -->
+        <div v-if="currentStep === 1" class="theme-selector-step">
+          <div class="step-hint">选择一个主题,不同主题包含不同类型的谜题</div>
+          
+          <div class="theme-list">
+            <div v-for="theme in themes" 
+                :key="theme.id" 
+                class="theme-item"
+                :class="{ 'selected': selectedTheme.theme === theme.id, 'locked': theme.isLocked }"
+                @click="!theme.isLocked && selectTheme(theme)">
+              <div class="theme-icon">
+                <IconFont :name="getIconName(theme)" size="20" color="#FF9F2D" />
+              </div>
+              <div class="theme-content">
+                <div class="theme-header">
+                  <div class="theme-name">{{ theme.name }}</div>
+                  <div class="theme-price">{{ theme.price }}</div>
+                </div>
+                <div class="theme-desc">{{ theme.desc }}</div>
+              </div>
+              <div v-if="!theme.isLocked" class="theme-status theme-status-unlocked">
+                已解锁
+              </div>
+              <div v-else class="theme-status theme-status-locked" @click.stop="purchaseTheme(theme)">
+                解锁
+              </div>
+              <nut-tag v-if="theme.isNew" type="danger" class="theme-tag">新</nut-tag>
+            </div>
+          </div>
+          
+          <div class="step-actions">
+            <div></div> <!-- 占位,保持对齐 -->
+            <nut-button type="primary" color="#FF9F2D" @click="nextStep">下一步</nut-button>
+          </div>
+        </div>
+        
+        <!-- 步骤2:选择难度 -->
+        <div v-if="currentStep === 2" class="theme-selector-step">
+          <div class="step-hint">选择游戏难度,不同难度适合不同玩家群体</div>
+          
+          <div class="difficulty-buttons">
+            <nut-button 
+              v-for="diff in difficulties" 
+              :key="diff.id"
+              :type="selectedTheme.difficulty === diff.id ? 'primary' : 'default'"
+              :color="selectedTheme.difficulty === diff.id ? '#FF9F2D' : ''"
+              class="difficulty-btn"
+              @click="selectDifficulty(diff)">
+              {{ diff.name }}
+            </nut-button>
+          </div>
+          
+          <div class="difficulty-desc">
+            <div class="difficulty-desc-title">难度说明:</div>
+            <div class="difficulty-desc-content">{{ difficultyDescription }}</div>
+          </div>
+          
+          <div class="step-actions">
+            <nut-button type="default" @click="prevStep">上一步</nut-button>
+            <nut-button type="primary" color="#FF9F2D" @click="nextStep">下一步</nut-button>
+          </div>
+        </div>
+        
+        <!-- 步骤3:选择题目 -->
+        <div v-if="currentStep === 3" class="theme-selector-step">
+          <div class="step-hint">从当前主题和难度中选择一个谜题</div>
+          
+          <!-- 随机题目按钮 -->
+          <div class="random-puzzle-row">
+            <div></div>
+            <nut-button 
+              size="small" 
+              type="default" 
+              class="random-puzzle-btn"
+              @click="selectRandomPuzzle">
+              <IconFont name="refresh" size="14" /> 随机题目
+            </nut-button>
+          </div>
+          
+          <!-- 加载中 -->
+          <div v-if="loading.puzzles" class="loading-puzzles">
+            <nut-button loading type="default" disabled>加载题目中...</nut-button>
+          </div>
+          
+          <!-- 无题目提示 -->
+          <div v-else-if="filteredPuzzles.length === 0" class="no-puzzles">
+            <p>当前主题和难度下没有可用谜题</p>
+          </div>
+          
+          <!-- 题目列表 -->
+          <div v-else class="puzzle-list">
+            <div v-for="puzzle in filteredPuzzles" 
+                :key="puzzle.id" 
+                class="puzzle-item"
+                :class="{ 'selected': selectedTheme.puzzle === puzzle.id }"
+                @click="selectPuzzle(puzzle)">
+              <div class="puzzle-header">
+                <div class="puzzle-name">{{ puzzle.name }}</div>
+                <div class="puzzle-meta">{{ selectedTheme.difficultyName }} · {{ puzzle.duration }}</div>
+              </div>
+              <div class="puzzle-desc">{{ puzzle.desc }}</div>
+              <IconFont v-if="selectedTheme.puzzle === puzzle.id" 
+                       name="check" 
+                       size="16" 
+                       color="#FF9F2D" 
+                       class="puzzle-selected-icon" />
+            </div>
+          </div>
+          
+          <div class="step-actions">
+            <nut-button type="default" @click="prevStep">上一步</nut-button>
+            <nut-button type="primary" color="#FF9F2D" @click="confirmSelection">确认选择</nut-button>
+          </div>
+        </div>
+      </div>
+    </nut-popup>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, reactive, onMounted } from 'vue';
+import { useTurtleSoupStore } from '@/stores/games/turtlesoup';
+import Taro from '@tarojs/taro';
+import { IconFont } from '@nutui/icons-vue-taro';
+
+// 获取海龟汤store
+const turtleSoupStore = useTurtleSoupStore();
+
+// 弹窗显示状态
+const showThemeSelector = ref(false);
+// 当前步骤
+const currentStep = ref(1);
+
+// 步骤标题
+const selectorTitles = ['选择主题', '选择难度', '选择题目'];
+
+// 数据加载状态
+const loading = ref({
+  themes: false,
+  puzzles: false
+});
+
+// 主题数据
+const themes = ref([]);
+
+// 难度数据
+const difficulties = [
+  { id: 'easy', name: '简单' },
+  { id: 'medium', name: '中等' },
+  { id: 'hard', name: '困难' },
+];
+
+// 难度描述
+const difficultyDescription = computed(() => {
+  switch(selectedTheme.difficulty) {
+    case 'easy':
+      return '简单难度适合初次接触海龟汤的玩家,解题时间约10-15分钟。';
+    case 'medium':
+      return '中等难度需要一定的逻辑思维能力,解题时间约15-25分钟。';
+    case 'hard':
+      return '困难难度挑战你的思维极限,解题时间约25-40分钟。';
+    default:
+      return '选择一个难度查看详情';
+  }
+});
+
+// 谜题数据
+const puzzles = ref([]);
+
+// 根据主题和难度筛选谜题
+const filteredPuzzles = computed(() => {
+  return puzzles.value.filter(puzzle => 
+    puzzle.theme === selectedTheme.theme && 
+    puzzle.difficulty === selectedTheme.difficulty
+  );
+});
+
+// 当前选择的数据
+const selectedTheme = reactive({
+  theme: '',
+  themeName: '',
+  themePrice: '',
+  difficulty: 'easy',
+  difficultyName: '简单',
+  puzzle: '',
+  puzzleName: ''
+});
+
+// 获取IconFont组件支持的图标名称
+const getIconName = (theme) => {
+  const iconMap = {
+    'classic': 'bookmark',
+    'universal': 'location',
+    'disney': 'star',
+    'fantasy': 'magic'
+  };
+  
+  return iconMap[theme.id] || 'bookmark';
+};
+
+// 加载主题列表
+const loadThemes = async () => {
+  try {
+    loading.value.themes = true;
+    console.log('开始加载主题列表');
+    
+    // 从turtleSoupStore加载主题
+    const themeData = await turtleSoupStore.loadThemes();
+    
+    if (themeData && themeData.length) {
+      themes.value = themeData.map(theme => ({
+        id: theme.id,
+        name: theme.name,
+        price: theme.price ? `${theme.price}元/小时` : '免费',
+        desc: theme.description || '主题介绍',
+        iconId: theme.icon || 'bookmark',
+        isLocked: theme.isLocked === undefined ? false : theme.isLocked,
+        isNew: theme.isNew === undefined ? false : theme.isNew
+      }));
+      
+      console.log('主题列表加载成功,数量:', themes.value.length);
+      
+      // 如果没有选择主题,默认选择第一个未锁定的主题
+      if (!selectedTheme.theme) {
+        const defaultTheme = themes.value.find(theme => !theme.isLocked);
+        if (defaultTheme) {
+          console.log('自动选择默认主题:', defaultTheme.name);
+          selectTheme(defaultTheme);
+        }
+      }
+    } else {
+      console.warn('未获取到主题数据');
+    }
+  } catch (error) {
+    console.error('加载主题失败:', error);
+    Taro.showToast({
+      title: '加载主题失败',
+      icon: 'none'
+    });
+  } finally {
+    loading.value.themes = false;
+  }
+};
+
+// 加载题目列表
+const loadPuzzles = async () => {
+  if (!selectedTheme.theme) {
+    console.warn('加载题目前需要先选择主题');
+    return;
+  }
+  
+  try {
+    loading.value.puzzles = true;
+    console.log('开始加载题目,主题:', selectedTheme.theme, '难度:', selectedTheme.difficulty);
+    
+    // 从turtleSoupStore加载题目
+    const puzzleData = await turtleSoupStore.loadPuzzles(
+      selectedTheme.theme, 
+      selectedTheme.difficulty
+    );
+    
+    if (puzzleData && puzzleData.length) {
+      puzzles.value = puzzleData.map(puzzle => ({
+        id: puzzle.id,
+        name: puzzle.title || '未命名题目',
+        difficulty: puzzle.difficulty,
+        duration: puzzle.averageDuration || '平均用时20分钟',
+        desc: puzzle.description || '题目描述',
+        theme: puzzle.themeId
+      }));
+      
+      console.log('题目加载成功,数量:', puzzles.value.length);
+      
+      // 如果没有选择题目,默认选择第一个题目
+      if (!selectedTheme.puzzle && filteredPuzzles.value.length > 0) {
+        console.log('自动选择默认题目:', filteredPuzzles.value[0].name);
+        selectPuzzle(filteredPuzzles.value[0]);
+      }
+    } else {
+      console.warn('当前主题和难度下没有可用谜题');
+      puzzles.value = [];
+    }
+  } catch (error) {
+    console.error('加载题目失败:', error);
+    Taro.showToast({
+      title: '加载题目失败',
+      icon: 'none'
+    });
+  } finally {
+    loading.value.puzzles = false;
+  }
+};
+
+// 弹窗控制
+const openThemeSelector = () => {
+  showThemeSelector.value = true;
+  currentStep.value = 1;
+  console.log('打开主题选择器');
+};
+
+const closeThemeSelector = () => {
+  showThemeSelector.value = false;
+  console.log('关闭主题选择器');
+};
+
+// 步骤控制
+const nextStep = () => {
+  if (currentStep.value < 3) {
+    currentStep.value++;
+    console.log('进入步骤:', currentStep.value);
+    
+    // 如果进入第3步,需要加载题目
+    if (currentStep.value === 3) {
+      loadPuzzles();
+    }
+  }
+};
+
+const prevStep = () => {
+  if (currentStep.value > 1) {
+    currentStep.value--;
+    console.log('返回步骤:', currentStep.value);
+  }
+};
+
+// 选择主题
+const selectTheme = (theme) => {
+  selectedTheme.theme = theme.id;
+  selectedTheme.themeName = theme.name;
+  selectedTheme.themePrice = theme.price;
+  console.log('选择主题:', theme.name);
+  
+  // 更新到store或emit事件
+  updateSelection();
+};
+
+// 选择难度
+const selectDifficulty = (difficulty) => {
+  selectedTheme.difficulty = difficulty.id;
+  selectedTheme.difficultyName = difficulty.name;
+  console.log('选择难度:', difficulty.name);
+  
+  // 更新到store或emit事件
+  updateSelection();
+};
+
+// 选择谜题
+const selectPuzzle = (puzzle) => {
+  selectedTheme.puzzle = puzzle.id;
+  selectedTheme.puzzleName = puzzle.name;
+  console.log('选择题目:', puzzle.name);
+  
+  // 更新到store或emit事件
+  updateSelection();
+};
+
+// 确认选择
+const confirmSelection = () => {
+  // 确保选择了题目
+  if (!selectedTheme.puzzle && filteredPuzzles.value.length > 0) {
+    selectPuzzle(filteredPuzzles.value[0]);
+  }
+  
+  console.log('确认选择:', selectedTheme);
+  
+  // 更新到store或emit事件
+  updateSelection();
+  
+  // 关闭弹窗
+  closeThemeSelector();
+};
+
+// 随机选择谜题
+const selectRandomPuzzle = () => {
+  if (filteredPuzzles.value.length > 0) {
+    const randomIndex = Math.floor(Math.random() * filteredPuzzles.value.length);
+    const randomPuzzle = filteredPuzzles.value[randomIndex];
+    selectPuzzle(randomPuzzle);
+    console.log('随机选择题目:', randomPuzzle.name);
+    
+    // 显示提示
+    Taro.showToast({
+      title: '已随机选择题目',
+      icon: 'success',
+      duration: 1500
+    });
+  } else {
+    console.warn('没有可选题目,无法随机选择');
+    Taro.showToast({
+      title: '没有可选题目',
+      icon: 'none',
+      duration: 1500
+    });
+  }
+};
+
+// 更新选择,发出事件
+const updateSelection = () => {
+  emit('selection-change', { ...selectedTheme });
+};
+
+// 购买主题 - 使用微信支付
+const purchaseTheme = async (theme) => {
+  try {
+    Taro.showLoading({ title: '准备支付...' });
+    console.log('准备购买主题:', theme.name);
+    
+    // 调用store方法获取支付参数
+    const paymentParams = await turtleSoupStore.getThemePaymentParams(theme.id);
+    
+    if (!paymentParams) {
+      throw new Error('获取支付参数失败');
+    }
+    
+    console.log('获取支付参数成功,开始调用微信支付');
+    
+    // 调用微信支付接口
+    Taro.requestPayment({
+      timeStamp: paymentParams.timeStamp,
+      nonceStr: paymentParams.nonceStr,
+      package: paymentParams.package,
+      signType: paymentParams.signType,
+      paySign: paymentParams.paySign,
+      success: async () => {
+        // 支付成功后,解锁主题
+        Taro.hideLoading();
+        Taro.showLoading({ title: '解锁中...' });
+        console.log('支付成功,开始解锁主题');
+        
+        try {
+          // 调用解锁主题API
+          const result = await turtleSoupStore.unlockTheme(theme.id);
+          
+          if (result && result.success) {
+            console.log('主题解锁成功');
+            Taro.showToast({
+              title: '解锁成功',
+              icon: 'success'
+            });
+            
+            // 更新主题列表
+            await loadThemes();
+            
+            // 选择刚解锁的主题
+            const updatedTheme = themes.value.find(t => t.id === theme.id);
+            if (updatedTheme && !updatedTheme.isLocked) {
+              selectTheme(updatedTheme);
+            }
+          } else {
+            throw new Error('解锁主题失败');
+          }
+        } catch (error) {
+          console.error('解锁主题失败:', error);
+          Taro.showToast({
+            title: '解锁失败,请联系客服',
+            icon: 'none'
+          });
+        } finally {
+          Taro.hideLoading();
+        }
+      },
+      fail: (err) => {
+        Taro.hideLoading();
+        console.log('支付失败:', err);
+        Taro.showToast({
+          title: '支付已取消',
+          icon: 'none'
+        });
+      }
+    });
+  } catch (error) {
+    Taro.hideLoading();
+    console.error('发起支付失败:', error);
+    Taro.showToast({
+      title: '支付发起失败',
+      icon: 'none'
+    });
+  }
+};
+
+const props = defineProps({
+  initialTheme: {
+    type: Object,
+    default: () => ({})
+  }
+});
+
+const emit = defineEmits(['selection-change']);
+
+// 组件挂载时加载主题
+onMounted(async () => {
+  console.log('组件挂载,开始初始化数据');
+  await loadThemes();
+  
+  // 如果传入了初始主题,使用传入的初始值
+  if (Object.keys(props.initialTheme).length > 0) {
+    console.log('使用传入的初始主题:', props.initialTheme);
+    Object.assign(selectedTheme, props.initialTheme);
+    
+    // 初始化后加载题目
+    if (selectedTheme.theme) {
+      await loadPuzzles();
+    }
+  }
+});
+</script>
+
+<style lang="scss">
+.turtle-soup-settings {
+  margin-bottom: $spacing-large;
+}
+
+.settings-display {
+  background-color: $background-color-orange;
+  border-radius: $border-radius-base;
+  padding: $spacing-medium;
+  margin-bottom: $spacing-medium;
+  border: 1px solid rgba($orange-color, 0.2);
+}
+
+.settings-title {
+  font-size: $font-size-medium;
+  font-weight: $font-weight-medium;
+  margin-bottom: $spacing-medium;
+  color: $text-color-primary;
+}
+
+.settings-info {
+  background-color: white;
+  border-radius: $border-radius-small;
+  padding: $spacing-medium;
+  margin-bottom: $spacing-medium;
+}
+
+.settings-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: $spacing-small;
+  
+  &:last-child {
+    margin-bottom: 0;
+  }
+}
+
+.settings-label {
+  font-size: $font-size-base;
+  color: $text-color-regular;
+}
+
+.settings-value {
+  font-size: $font-size-base;
+  color: $text-color-orange;
+}
+
+.settings-badge {
+  font-size: $font-size-small;
+  color: $text-color-orange;
+  background-color: rgba($orange-color, 0.1);
+  padding: $spacing-mini $spacing-small;
+  border-radius: $border-radius-base;
+}
+
+.settings-button {
+  width: 100%;
+  margin-top: $spacing-small;
+}
+
+// 主题选择器样式
+.theme-selector {
+  width: 90vw;
+  max-width: 360px;
+  border-radius: $border-radius-base;
+  background-color: white;
+  max-height: 80vh;
+  display: flex;
+  flex-direction: column;
+}
+
+.theme-selector-header {
+  padding: $spacing-medium;
+  border-bottom: 1px solid $border-color-base;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  position: sticky;
+  top: 0;
+  background-color: white;
+}
+
+.theme-selector-title {
+  font-size: $font-size-medium;
+  font-weight: $font-weight-medium;
+  color: $text-color-primary;
+}
+
+.theme-selector-step {
+  padding: $spacing-medium;
+  overflow-y: auto;
+}
+
+.step-hint {
+  font-size: $font-size-small;
+  color: $text-color-secondary;
+  margin-bottom: $spacing-medium;
+}
+
+.theme-list {
+  margin-bottom: $spacing-large;
+}
+
+.theme-item {
+  display: flex;
+  align-items: center;
+  padding: $spacing-medium;
+  border: 1px solid $border-color-base;
+  border-radius: $border-radius-small;
+  margin-bottom: $spacing-medium;
+  position: relative;
+  transition: all 0.2s;
+  
+  &.selected {
+    background-color: $background-color-orange;
+    border-color: rgba($orange-color, 0.3);
+  }
+  
+  &.locked {
+    opacity: 0.8;
+  }
+  
+  &:not(.locked):hover {
+    border-color: $orange-color;
+  }
+}
+
+.theme-icon {
+  width: 36px;
+  height: 36px;
+  background-color: rgba($orange-color, 0.1);
+  border-radius: $border-radius-small;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: $spacing-medium;
+}
+
+.theme-content {
+  flex: 1;
+}
+
+.theme-header {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: $spacing-mini;
+}
+
+.theme-name {
+  font-size: $font-size-base;
+  font-weight: $font-weight-medium;
+  color: $text-color-primary;
+}
+
+.theme-price {
+  font-size: $font-size-small;
+  color: $text-color-orange;
+}
+
+.theme-desc {
+  font-size: $font-size-small;
+  color: $text-color-secondary;
+}
+
+.theme-status {
+  font-size: $font-size-small;
+  color: white;
+  padding: $spacing-mini $spacing-small;
+  border-radius: $border-radius-small;
+}
+
+.theme-status-unlocked {
+  background-color: $success-color;
+}
+
+.theme-status-locked {
+  background-color: $orange-color;
+  cursor: pointer;
+}
+
+.theme-tag {
+  position: absolute;
+  top: -8px;
+  right: -8px;
+}
+
+.difficulty-buttons {
+  display: flex;
+  justify-content: space-between;
+  gap: $spacing-medium;
+  margin-bottom: $spacing-large;
+  
+  .difficulty-btn {
+    flex: 1;
+  }
+}
+
+.difficulty-desc {
+  margin-bottom: $spacing-large;
+  padding: $spacing-medium;
+  background-color: $background-color-gray;
+  border-radius: $border-radius-small;
+}
+
+.difficulty-desc-title {
+  font-size: $font-size-base;
+  font-weight: $font-weight-medium;
+  margin-bottom: $spacing-mini;
+  color: $text-color-primary;
+}
+
+.difficulty-desc-content {
+  font-size: $font-size-small;
+  color: $text-color-secondary;
+  line-height: $line-height-base;
+}
+
+.random-puzzle-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: $spacing-medium;
+}
+
+.random-puzzle-btn {
+  font-size: $font-size-small;
+  display: flex;
+  align-items: center;
+  
+  :deep(.nut-icon) {
+    margin-right: $spacing-mini;
+  }
+}
+
+.loading-puzzles {
+  text-align: center;
+  padding: $spacing-large 0;
+}
+
+.no-puzzles {
+  text-align: center;
+  padding: $spacing-large 0;
+  color: $text-color-secondary;
+  font-size: $font-size-small;
+}
+
+.puzzle-list {
+  max-height: 240px;
+  overflow-y: auto;
+  margin-bottom: $spacing-large;
+}
+
+.puzzle-item {
+  padding: $spacing-medium;
+  border: 1px solid $border-color-base;
+  border-radius: $border-radius-small;
+  margin-bottom: $spacing-small;
+  transition: all 0.2s;
+  position: relative;
+  
+  &.selected {
+    background-color: $background-color-orange;
+    border-color: rgba($orange-color, 0.3);
+  }
+  
+  &:hover {
+    border-color: $orange-color;
+  }
+}
+
+.puzzle-header {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: $spacing-mini;
+}
+
+.puzzle-name {
+  font-size: $font-size-base;
+  font-weight: $font-weight-medium;
+  color: $text-color-primary;
+}
+
+.puzzle-meta {
+  font-size: $font-size-small;
+  color: $text-color-orange;
+}
+
+.puzzle-desc {
+  font-size: $font-size-small;
+  color: $text-color-secondary;
+}
+
+.puzzle-selected-icon {
+  position: absolute;
+  right: $spacing-medium;
+  top: $spacing-medium;
+}
+
+.step-actions {
+  display: flex;
+  justify-content: space-between;
+  gap: $spacing-medium;
+  
+  :deep(.nut-button) {
+    flex: 1;
+  }
+}
+</style>

+ 0 - 149
src/components/room/selectors/DifficultySelector.vue

@@ -1,149 +0,0 @@
-<template>
-    <nut-popup v-model:visible="show" position="bottom">
-      <view class="selector-container">
-        <view class="selector-header">
-          <view class="selector-title">选择游戏难度</view>
-          <nut-button size="small" @click="onClose">取消</nut-button>
-        </view>
-        <view class="difficulty-list">
-          <view 
-            class="difficulty-option" 
-            :class="{ 'selected': selectedDifficulty === difficulties.EASY }"
-            @click="onSelect(difficulties.EASY)"
-          >
-            <view class="option-content">
-              <view class="option-title">简单</view>
-              <view class="option-desc">适合新手玩家,游戏时间较短</view>
-            </view>
-            <IconFont v-if="selectedDifficulty === difficulties.EASY" name="check" color="#3C92FB" size="16"></IconFont>
-          </view>
-          <view 
-            class="difficulty-option" 
-            :class="{ 'selected': selectedDifficulty === difficulties.MEDIUM }"
-            @click="onSelect(difficulties.MEDIUM)"
-          >
-            <view class="option-content">
-              <view class="option-title">中等</view>
-              <view class="option-desc">平衡挑战与乐趣,适合大多数玩家</view>
-            </view>
-            <IconFont v-if="selectedDifficulty === difficulties.MEDIUM" name="check" color="#3C92FB" size="16"></IconFont>
-          </view>
-          <view 
-            class="difficulty-option" 
-            :class="{ 'selected': selectedDifficulty === difficulties.HARD }"
-            @click="onSelect(difficulties.HARD)"
-          >
-            <view class="option-content">
-              <view class="option-title">困难</view>
-              <view class="option-desc">高难度挑战,适合有经验的玩家</view>
-            </view>
-            <IconFont v-if="selectedDifficulty === difficulties.HARD" name="check" color="#3C92FB" size="16"></IconFont>
-          </view>
-        </view>
-      </view>
-    </nut-popup>
-  </template>
-  
-  <script lang="ts">
-  import { defineComponent, computed } from 'vue'
-  import { IconFont } from '@nutui/icons-vue-taro'
-  import { TurtleSoupDifficulty } from '@/types/games/turtlesoup'
-  
-  export default defineComponent({
-    name: 'DifficultySelector',
-    components: {
-      IconFont
-    },
-    props: {
-      visible: {
-        type: Boolean,
-        default: false
-      },
-      selectedDifficulty: {
-        type: String,
-        default: TurtleSoupDifficulty.MEDIUM
-      }
-    },
-    emits: ['update:visible', 'select-difficulty'],
-    setup(props, { emit }) {
-      const show = computed({
-        get: () => props.visible,
-        set: (val) => emit('update:visible', val)
-      })
-  
-      const onClose = () => {
-        show.value = false
-      }
-  
-      const onSelect = (difficulty) => {
-        emit('select-difficulty', difficulty)
-      }
-  
-      return {
-        show,
-        onClose,
-        onSelect,
-        difficulties: TurtleSoupDifficulty
-      }
-    }
-  })
-  </script>
-  
-  <style lang="scss">
-  .selector-container {
-    padding: $spacing-base;
-    
-    .selector-header {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      padding: $spacing-base 0;
-      margin-bottom: $spacing-base;
-      border-bottom: 1px solid $border-color-light;
-      
-      .selector-title {
-        font-size: $font-size-medium;
-        font-weight: $font-weight-medium;
-        color: $text-color-primary;
-      }
-    }
-    
-    .difficulty-list {
-      padding: $spacing-small;
-      
-      .difficulty-option {
-        background-color: $background-color-base;
-        padding: $spacing-base;
-        border-radius: $border-radius-mini;
-        margin-bottom: $spacing-small;
-        box-shadow: $shadow-light;
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
-        
-        &:last-child {
-          margin-bottom: 0;
-        }
-        
-        &.selected {
-          background-color: rgba(60, 146, 251, 0.1);
-        }
-        
-        .option-content {
-          flex: 1;
-          
-          .option-title {
-            font-size: $font-size-base;
-            color: $text-color-primary;
-            margin-bottom: $spacing-mini;
-          }
-          
-          .option-desc {
-            font-size: $font-size-small;
-            color: $text-color-secondary;
-          }
-        }
-      }
-    }
-  }
-  </style>

+ 0 - 153
src/components/room/selectors/PuzzleSelector.vue

@@ -1,153 +0,0 @@
-<template>
-    <nut-popup v-model:visible="show" position="bottom">
-      <view class="selector-container">
-        <view class="selector-header">
-          <view class="selector-title">选择游戏题目</view>
-          <nut-button size="small" @click="onClose">取消</nut-button>
-        </view>
-        <scroll-view 
-          scroll-y 
-          class="puzzle-scroll"
-          :style="{ maxHeight: scrollHeight + 'px' }"
-        >
-          <view class="puzzle-list">
-            <view 
-              v-for="puzzle in puzzleOptions" 
-              :key="puzzle.value" 
-              class="puzzle-option"
-              :class="{ 'disabled': puzzle.disabled, 'selected': selectedPuzzleId === puzzle.value }"
-              @click="!puzzle.disabled && onSelect(puzzle)"
-            >
-              <view class="option-content">
-                <view class="option-title">{{ puzzle.text }}</view>
-                <view v-if="puzzle.description" class="option-desc">{{ puzzle.description }}</view>
-              </view>
-              <IconFont v-if="selectedPuzzleId === puzzle.value" name="check" color="#3C92FB" size="16"></IconFont>
-            </view>
-          </view>
-        </scroll-view>
-      </view>
-    </nut-popup>
-  </template>
-  
-  <script lang="ts">
-  import { defineComponent, computed, PropType } from 'vue'
-  import { IconFont } from '@nutui/icons-vue-taro'
-  import { CascaderOption } from '@/types/cascader'
-  
-  export default defineComponent({
-    name: 'PuzzleSelector',
-    components: {
-      IconFont
-    },
-    props: {
-      visible: {
-        type: Boolean,
-        default: false
-      },
-      puzzleOptions: {
-        type: Array as PropType<CascaderOption[]>,
-        default: () => []
-      },
-      selectedPuzzleId: {
-        type: String,
-        default: ''
-      },
-      scrollHeight: {
-        type: Number,
-        default: 300
-      }
-    },
-    emits: ['update:visible', 'select-puzzle'],
-    setup(props, { emit }) {
-      const show = computed({
-        get: () => props.visible,
-        set: (val) => emit('update:visible', val)
-      })
-  
-      const onClose = () => {
-        show.value = false
-      }
-  
-      const onSelect = (puzzle) => {
-        emit('select-puzzle', puzzle)
-      }
-  
-      return {
-        show,
-        onClose,
-        onSelect
-      }
-    }
-  })
-  </script>
-  
-  <style lang="scss">
-  .selector-container {
-    padding: $spacing-base;
-    
-    .selector-header {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      padding: $spacing-base 0;
-      margin-bottom: $spacing-base;
-      border-bottom: 1px solid $border-color-light;
-      
-      .selector-title {
-        font-size: $font-size-medium;
-        font-weight: $font-weight-medium;
-        color: $text-color-primary;
-      }
-    }
-    
-    .puzzle-scroll {
-      border: 1px solid $border-color-light;
-      border-radius: $border-radius-small;
-      margin-top: $spacing-base;
-      background-color: $background-color-light;
-    }
-    
-    .puzzle-list {
-      padding: $spacing-small;
-      
-      .puzzle-option {
-        background-color: $background-color-base;
-        padding: $spacing-base;
-        border-radius: $border-radius-mini;
-        margin-bottom: $spacing-small;
-        box-shadow: $shadow-light;
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
-        
-        &:last-child {
-          margin-bottom: 0;
-        }
-        
-        &.disabled {
-          opacity: 0.5;
-        }
-        
-        &.selected {
-          background-color: rgba(60, 146, 251, 0.1);
-        }
-        
-        .option-content {
-          flex: 1;
-          
-          .option-title {
-            font-size: $font-size-base;
-            color: $text-color-primary;
-            margin-bottom: $spacing-mini;
-          }
-          
-          .option-desc {
-            font-size: $font-size-small;
-            color: $text-color-secondary;
-          }
-        }
-      }
-    }
-  }
-  </style>

+ 0 - 197
src/components/room/selectors/ThemeSelector.vue

@@ -1,197 +0,0 @@
-<template>
-    <nut-popup v-model:visible="show" position="bottom">
-      <view class="selector-container">
-        <view class="selector-header">
-          <view class="selector-title">选择游戏主题</view>
-          <nut-button size="small" @click="onClose">取消</nut-button>
-        </view>
-        <scroll-view 
-          scroll-y 
-          class="theme-scroll"
-          :style="{ maxHeight: scrollHeight + 'px' }"
-        >
-          <view class="theme-list">
-            <view 
-              v-for="theme in themeOptions" 
-              :key="theme.value" 
-              class="theme-option"
-              :class="{ 'disabled': theme.disabled && !canPurchase, 'selected': selectedThemeId === theme.value }"
-              @click="handleThemeClick(theme)"
-            >
-              <view class="option-content">
-                <view class="option-title">{{ theme.text }}</view>
-                <view v-if="theme.description" class="option-desc">{{ theme.description }}</view>
-                <view v-if="theme.locked" class="theme-locked">
-                  <IconFont name="lock" size="12"></IconFont>
-                  <view class="lock-text">{{ theme.unlockRequirement }}</view>
-                </view>
-              </view>
-              <!-- 添加解锁购买按钮 -->
-              <view v-if="theme.locked && canPurchase" class="unlock-button" @click.stop="onPurchase(theme)">
-                <nut-button size="small" type="primary">解锁 ({{ theme.price || '5' }}元)</nut-button>
-              </view>
-              <IconFont v-else-if="selectedThemeId === theme.value" name="check" color="#3C92FB" size="16"></IconFont>
-            </view>
-          </view>
-        </scroll-view>
-      </view>
-    </nut-popup>
-  </template>
-  
-  <script lang="ts">
-  import { defineComponent, computed, PropType } from 'vue'
-  import { IconFont } from '@nutui/icons-vue-taro'
-  import { CascaderOption } from '@/types/cascader'
-  
-  export default defineComponent({
-    name: 'ThemeSelector',
-    components: {
-      IconFont
-    },
-    props: {
-      visible: {
-        type: Boolean,
-        default: false
-      },
-      themeOptions: {
-        type: Array as PropType<CascaderOption[]>,
-        default: () => []
-      },
-      selectedThemeId: {
-        type: String,
-        default: ''
-      },
-      scrollHeight: {
-        type: Number,
-        default: 300
-      },
-      canPurchase: {
-        type: Boolean,
-        default: true
-      }
-    },
-    emits: ['update:visible', 'select-theme', 'purchase-theme'],
-    setup(props, { emit }) {
-      const show = computed({
-        get: () => props.visible,
-        set: (val) => emit('update:visible', val)
-      })
-  
-      const onClose = () => {
-        show.value = false
-      }
-  
-      const handleThemeClick = (theme) => {
-        // 如果主题被锁定且不可购买,则不做任何操作
-        if (theme.disabled && !props.canPurchase) {
-          return
-        }
-        
-        // 如果主题被锁定且可购买,不处理(让购买按钮处理)
-        if (theme.locked && props.canPurchase) {
-          return
-        }
-        
-        // 正常选择主题
-        emit('select-theme', theme)
-      }
-  
-      const onPurchase = (theme) => {
-        emit('purchase-theme', theme)
-      }
-  
-      return {
-        show,
-        onClose,
-        handleThemeClick,
-        onPurchase
-      }
-    }
-  })
-  </script>
-  
-  <style lang="scss">
-  .selector-container {
-    padding: $spacing-base;
-    
-    .selector-header {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      padding: $spacing-base 0;
-      margin-bottom: $spacing-base;
-      border-bottom: 1px solid $border-color-light;
-      
-      .selector-title {
-        font-size: $font-size-medium;
-        font-weight: $font-weight-medium;
-        color: $text-color-primary;
-      }
-    }
-    
-    .theme-scroll {
-      border: 1px solid $border-color-light;
-      border-radius: $border-radius-small;
-      margin-top: $spacing-base;
-      background-color: $background-color-light;
-    }
-    
-    .theme-list {
-      padding: $spacing-small;
-      
-      .theme-option {
-        background-color: $background-color-base;
-        padding: $spacing-base;
-        border-radius: $border-radius-mini;
-        margin-bottom: $spacing-small;
-        box-shadow: $shadow-light;
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
-        
-        &:last-child {
-          margin-bottom: 0;
-        }
-        
-        &.disabled {
-          opacity: 0.5;
-        }
-        
-        &.selected {
-          background-color: rgba(60, 146, 251, 0.1);
-        }
-        
-        .option-content {
-          flex: 1;
-          
-          .option-title {
-            font-size: $font-size-base;
-            color: $text-color-primary;
-            margin-bottom: $spacing-mini;
-          }
-          
-          .option-desc {
-            font-size: $font-size-small;
-            color: $text-color-secondary;
-          }
-          
-          .theme-locked {
-            display: flex;
-            align-items: center;
-            margin-top: $spacing-mini;
-            font-size: $font-size-small;
-            color: $text-color-disabled;
-            
-            .lock-text {
-              margin-left: $spacing-mini;
-            }
-          }
-        }
-        
-        .unlock-button {
-          margin-left: $spacing-mini;
-        }
-      }
-    }
-  }
-  </style>

+ 1168 - 37
src/pages/room/play/index.vue

@@ -1,52 +1,1183 @@
 <template>
-    <view class="play-room-page">
-      <view class="title">游戏进行中</view>
-      <nut-empty description="正在开发中..." image="empty" />
+  <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="status">{{ statusText }}</view>
+      </view>
     </view>
-    <Tabbar></Tabbar>   
-  </template>
+
+    <!-- 游戏内容区域 -->
+    <view class="game-content">
+      <!-- 故事展示区 -->
+      <view class="scenario-card">
+        <view class="scenario-title">故事</view>
+        <view class="scenario-text">{{ currentView?.description || '故事加载中...' }}</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>
+
+      <!-- 主持人视图 -->
+      <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"
+          >
+            更新进度
+          </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>
+          </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>
+    </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>
+        </view>
+      </view>
+    </nut-dialog>
+  </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'
+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
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.play-room-page {
+  padding: $spacing-base;
+  background-color: $background-color-base;
+  min-height: 100vh;
+  padding-bottom: $spacing-large * 4; // 为底部tabbar留出空间
   
-  <script lang="ts">
-  import Taro from '@tarojs/taro'
-  import Tabbar from '@/components/Tabbar.vue'
+  .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;
+        }
+      }
+    }
+  }
   
-  export default {
-    components: {
-      Tabbar
-    },
+  .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;
+          }
+        }
+      }
+    }
     
-    // 生命周期钩子 - 页面显示
-    onShow() {
-      // 隐藏返回首页按钮
-      Taro.hideHomeButton()
-      console.log('已隐藏返回首页按钮')
-    },
+    .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;
+          }
+        }
+      }
+    }
     
-    // Composition API
-    setup() {
-      // 这里可以添加游戏进行中页面的逻辑
+    .question-section {
+      background-color: $background-color-light;
+      border-radius: $border-radius-base;
+      padding: $spacing-large;
+      margin-bottom: $spacing-large;
+      box-shadow: $shadow-light;
       
-      return {
-        // 返回需要在模板中使用的数据和方法
+      .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;
+        }
+      }
+    }
     
-    // 生命周期钩子 - 页面加载
-    onLoad() {
-      // 页面加载时的初始化逻辑
+    .host-controls, .player-controls {
+      display: flex;
+      flex-direction: column;
+      gap: $spacing-base;
+      margin-bottom: $spacing-large;
     }
   }
-  </script>
   
-  <style lang="scss">
-  .play-room-page {
-    padding: 20px;
-    
-    .title {
-      font-size: 20px;
-      font-weight: bold;
-      margin-bottom: 20px;
+  // 弹窗样式
+  .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;
+    }
   }
-  </style>
+}
+</style>

+ 35 - 340
src/pages/room/waiting/index.vue

@@ -21,88 +21,46 @@
     <HostView 
       v-if="isHost" 
       :users="currentRoom?.users || []"
-      :selectedTheme="selectedTheme"
-      :selectedPuzzle="selectedPuzzle"
-      :selectedThemeId="selectedThemeId"
-      :selectedDifficulty="selectedDifficulty"
-      :difficultyText="difficultyText"
+      :gameType="gameType"
       :canStartGame="canStartGame"
-      @show-theme-selector="showThemeSelector = true"
-      @show-difficulty-selector="showDifficultySelector = true"
-      @show-puzzle-selector="showPuzzleSelector = true"
       @start-game="startGame"
     />
     <PlayerView 
       v-else 
       :users="currentRoom?.users || []"
-      :gameTitle="gameTitle"
-      :themeTitle="themeTitle"
-      :difficultyText="difficultyText"
-      :gameDescription="gameDescription"
+      :gameInfo="gameInfo"
       :currentUserReady="currentUserReady"
       @toggle-ready="toggleReady"
     />
-
-    <!-- 选择器组件 -->
-    <ThemeSelector 
-      v-model:visible="showThemeSelector" 
-      :themeOptions="themeOptions"
-      :selectedThemeId="selectedThemeId"
-      :scrollHeight="themeScrollHeight"
-      :canPurchase="canPurchase"
-      @select-theme="handleThemeSelect"
-      @purchase-theme="purchaseTheme"
-    />
-    <DifficultySelector 
-      v-model:visible="showDifficultySelector" 
-      :selectedDifficulty="selectedDifficulty"
-      @select-difficulty="handleDifficultySelect"
-    />
-    <PuzzleSelector 
-      v-model:visible="showPuzzleSelector" 
-      :puzzleOptions="puzzleOptions"
-      :selectedPuzzleId="selectedPuzzleId"
-      :scrollHeight="puzzleScrollHeight"
-      @select-puzzle="handlePuzzleSelect"
-    />
   </view>
   <Tabbar></Tabbar>
 </template>
   
 <script lang="ts">
 import Taro from '@tarojs/taro'
-import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
+import { ref, computed, onMounted, onUnmounted } from 'vue'
 import { useRoomStore } from '@/stores/room'
 import { useUserStore } from '@/stores/user'
 import { useTabBarStore } from '@/stores/tabbar'
 import { useTurtleSoupStore } from '@/stores/games/turtlesoup'
 import Tabbar from '@/components/Tabbar.vue'
-import RoomCode from '@/components/RoomCode/index.vue'
+import RoomCode from '@/components/room/RoomCode.vue'
 
 // 导入重构的组件
 import RoomHeader from '@/components/room/RoomHeader.vue'
-import PlayerList from '@/components/room/PlayerList.vue'
 import HostView from '@/components/room/host/HostView.vue'
 import PlayerView from '@/components/room/player/PlayerView.vue'
-import ThemeSelector from '@/components/room/selectors/ThemeSelector.vue'
-import DifficultySelector from '@/components/room/selectors/DifficultySelector.vue'
-import PuzzleSelector from '@/components/room/selectors/PuzzleSelector.vue'
 
 import { RoomRole, RoomStatus } from '@/types/room'
-import { TurtleSoupDifficulty } from '@/types/games/turtlesoup'
-import { type CascaderOption } from '@/types/cascader'
+import { GameType } from '@/types/game'
 
 export default {
   components: {
     Tabbar,
     RoomCode,
     RoomHeader,
-    PlayerList,
     HostView,
-    PlayerView,
-    ThemeSelector,
-    DifficultySelector,
-    PuzzleSelector
+    PlayerView
   },
   
   // 生命周期钩子 - 页面显示
@@ -122,8 +80,16 @@ export default {
     // 房间ID
     const roomId = ref('')
     
-    // 是否可以购买
-    const canPurchase = ref(true) // 默认允许购买
+    // 游戏类型
+    const gameType = ref(GameType.TURTLE_SOUP)
+    
+    // 游戏信息(提供给玩家视图)
+    const gameInfo = ref({
+      title: '',
+      theme: '',
+      difficulty: '',
+      description: ''
+    })
     
     // 获取当前房间
     const currentRoom = computed(() => roomStore.currentRoom)
@@ -167,59 +133,8 @@ export default {
     const canStartGame = computed(() => {
       if (!currentRoom.value) return false
       
-      // 至少有一个玩家
-      const players = currentRoom.value.users.filter(u => u.roomRole === RoomRole.PLAYER)
-      if (players.length === 0) return false
-      
-      // 所有玩家都已准备
-      const allReady = players.every(p => p.isReady)
-      
-      // 主题和题目已选择
-      const settingsReady = !!selectedThemeId.value && !!selectedPuzzleId.value
-
-      return allReady && settingsReady
-    })
-    
-    // 游戏设置相关变量
-    const selectedDifficulty = ref(TurtleSoupDifficulty.MEDIUM)
-    const selectedThemeId = ref('')
-    const selectedPuzzleId = ref('')
-    const selectedTheme = ref<CascaderOption | null>(null)
-    const selectedPuzzle = ref<CascaderOption | null>(null)
-    const showThemeSelector = ref(false)
-    const showPuzzleSelector = ref(false)
-    const showDifficultySelector = ref(false)
-    
-    // 主题选项
-    const themeOptions = ref<CascaderOption[]>([])
-    
-    // 题目选项
-    const puzzleOptions = ref<CascaderOption[]>([])
-    
-    // 设置滚动区域高度限制
-    const themeScrollHeight = ref(300)
-    const puzzleScrollHeight = ref(300)
-    
-    // 游戏信息(玩家视图)
-    const gameTitle = computed(() => currentRoom.value?.gameTitle || '')
-    const themeTitle = computed(() => {
-      const theme = themeOptions.value.find(t => t.value === selectedThemeId.value)
-      return theme?.text || ''
-    })
-    const gameDescription = ref('')
-    
-    // 难度文本
-    const difficultyText = computed(() => {
-      switch(selectedDifficulty.value) {
-        case TurtleSoupDifficulty.EASY:
-          return '简单'
-        case TurtleSoupDifficulty.MEDIUM:
-          return '中等'
-        case TurtleSoupDifficulty.HARD:
-          return '困难'
-        default:
-          return '中等'
-      }
+      // 这里简化了判断逻辑,实际项目中根据需求调整
+      return true
     })
     
     // 初始化页面
@@ -235,11 +150,6 @@ export default {
         // 加载房间信息
         await loadRoomInfo(roomId.value)
         
-        // 如果是主持人,加载可用的主题
-        if (isHost.value) {
-          await loadThemes()
-        }
-        
         // 开始监听房间变化
         startRoomListener()
       } else {
@@ -271,175 +181,6 @@ export default {
       }
     }
     
-    // 加载主题列表
-    const loadThemes = async () => {
-      try {
-        Taro.showLoading({ title: '加载主题中...' })
-        // 从turtleSoupStore加载主题
-        const themes = await turtleSoupStore.loadThemes()
-        
-        // 将API返回的主题格式化为显示需要的格式
-        if (themes && themes.length) {
-          themeOptions.value = themes.map(theme => ({
-            value: theme.id,
-            text: theme.name,
-            description: theme.description,
-            disabled: theme.isLocked,
-            locked: theme.isLocked,
-            price: theme.price ? String(theme.price) : '5'
-          }))
-          console.log('已加载主题:', themeOptions.value)
-        }
-        Taro.hideLoading()
-      } catch (error) {
-        console.error('加载主题失败:', error)
-        Taro.hideLoading()
-        Taro.showToast({
-          title: '加载主题失败',
-          icon: 'none'
-        })
-      }
-    }
-    
-    // 根据主题和难度加载题目列表
-    const loadPuzzles = async () => {
-      if (!selectedThemeId.value) {
-        return
-      }
-      
-      try {
-        Taro.showLoading({ title: '加载题目中...' })
-        
-        // 从API加载指定主题和难度的题目列表
-        const puzzles = await turtleSoupStore.loadPuzzles(selectedThemeId.value, selectedDifficulty.value)
-        
-        // 格式化为显示需要的格式
-        if (puzzles && puzzles.length) {
-          puzzleOptions.value = puzzles.map(puzzle => ({
-            value: puzzle.id,
-            text: puzzle.title,
-            description: puzzle.description,
-            disabled: puzzle.isLocked
-          }))
-          console.log('已加载题目:', puzzleOptions.value)
-        } else {
-          puzzleOptions.value = []
-          Taro.showToast({
-            title: '当前主题下没有题目',
-            icon: 'none'
-          })
-        }
-        Taro.hideLoading()
-      } catch (error) {
-        console.error('加载题目失败:', error)
-        Taro.hideLoading()
-        Taro.showToast({
-          title: '加载题目失败',
-          icon: 'none'
-        })
-      }
-    }
-    
-    // 处理主题选择
-    const handleThemeSelect = (theme: CascaderOption) => {
-      selectedTheme.value = theme
-      selectedThemeId.value = theme.value
-      showThemeSelector.value = false
-      
-      // 选择主题后提示选择难度
-      setTimeout(() => {
-        showDifficultySelector.value = true
-      }, 300)
-    }
-    
-    // 购买主题
-    const purchaseTheme = async (theme: CascaderOption) => {
-      try {
-        Taro.showLoading({ title: '准备支付...' })
-        
-        // 调用微信支付接口
-        Taro.requestPayment({
-          timeStamp: Date.now().toString(),
-          nonceStr: Math.random().toString(36).substring(2, 15),
-          package: `prepay_id=wx${Date.now()}`,
-          signType: 'MD5',
-          paySign: 'test_sign', // 实际项目中需要服务端生成
-          success: async () => {
-            // 支付成功后,解锁主题
-            Taro.hideLoading()
-            Taro.showLoading({ title: '解锁中...' })
-            
-            try {
-              // 调用解锁主题API
-              const result = await turtleSoupStore.unlockTheme(theme.value)
-              
-              if (result && result.success) {
-                Taro.showToast({
-                  title: '解锁成功',
-                  icon: 'success'
-                })
-                
-                // 更新主题列表
-                await loadThemes()
-                
-                // 选择刚解锁的主题
-                const updatedTheme = themeOptions.value.find(t => t.value === theme.value)
-                if (updatedTheme && !updatedTheme.disabled) {
-                  handleThemeSelect(updatedTheme)
-                }
-              } else {
-                throw new Error('解锁主题失败')
-              }
-            } catch (error) {
-              console.error('解锁主题失败:', error)
-              Taro.showToast({
-                title: '解锁失败,请联系客服',
-                icon: 'none'
-              })
-            } finally {
-              Taro.hideLoading()
-            }
-          },
-          fail: (err) => {
-            Taro.hideLoading()
-            console.log('支付失败:', err)
-            Taro.showToast({
-              title: '支付已取消',
-              icon: 'none'
-            })
-          }
-        })
-      } catch (error) {
-        Taro.hideLoading()
-        console.error('发起支付失败:', error)
-        Taro.showToast({
-          title: '支付发起失败',
-          icon: 'none'
-        })
-      }
-    }
-    
-    // 处理难度选择
-    const handleDifficultySelect = (difficulty: TurtleSoupDifficulty) => {
-      selectedDifficulty.value = difficulty
-      showDifficultySelector.value = false
-      
-      // 加载对应难度的题目
-      loadPuzzles()
-      
-      // 选择难度后提示选择题目
-      setTimeout(() => {
-        showPuzzleSelector.value = true
-      }, 300)
-    }
-    
-    // 处理题目选择
-    const handlePuzzleSelect = (puzzle: CascaderOption) => {
-      selectedPuzzle.value = puzzle
-      selectedPuzzleId.value = puzzle.value
-      showPuzzleSelector.value = false
-    }
-    
     // 准备/取消准备(玩家)
     const toggleReady = async () => {
       if (!userStore.openid || !currentRoom.value) return
@@ -465,23 +206,26 @@ export default {
     }
     
     // 开始游戏(主持人)
-    const startGame = async () => {
+    const startGame = async (gameSettings) => {
       if (!currentRoom.value || !isHost.value) return
       
       try {
         Taro.showLoading({ title: '开始游戏中...' })
         
-        // 准备游戏设置
-        const gameSettings = {
-          themeId: selectedThemeId.value,
-          puzzleId: selectedPuzzleId.value,
-          difficulty: selectedDifficulty.value,
-          maxPlayers: currentRoom.value.maxPlayers,
-          isPrivate: currentRoom.value.visibility === 'private'
-        }
+        let result;
         
-        // 创建游戏
-        const result = await turtleSoupStore.createGame(gameSettings)
+        // 根据游戏类型创建不同的游戏
+        if (gameType.value === GameType.TURTLE_SOUP) {
+          // 创建海龟汤游戏
+          result = await turtleSoupStore.createGame({
+            ...gameSettings,
+            maxPlayers: currentRoom.value.maxPlayers,
+            isPrivate: currentRoom.value.visibility === 'private'
+          })
+        } else {
+          // 其他游戏类型...
+          throw new Error('不支持的游戏类型')
+        }
         
         if (result.success && result.gameId) {
           // 更新房间状态为游戏中
@@ -561,35 +305,6 @@ export default {
       stopRoomListener()
     })
     
-    // 监听难度变化,如果已选择主题则重新加载题目
-    watch(selectedDifficulty, async () => {
-      if (selectedThemeId.value) {
-        await loadPuzzles()
-        selectedPuzzleId.value = '' // 重置题目选择
-        selectedPuzzle.value = null
-      }
-    })
-    
-    // 监听选中的主题ID
-    watch(selectedThemeId, (newVal) => {
-      if (newVal) {
-        const theme = themeOptions.value.find(t => t.value === newVal)
-        if (theme) {
-          selectedTheme.value = theme
-        }
-      }
-    })
-    
-    // 监听选中的题目ID
-    watch(selectedPuzzleId, (newVal) => {
-      if (newVal) {
-        const puzzle = puzzleOptions.value.find(p => p.value === newVal)
-        if (puzzle) {
-          selectedPuzzle.value = puzzle
-        }
-      }
-    })
-    
     return {
       currentRoom,
       isHost,
@@ -597,32 +312,12 @@ export default {
       statusText,
       currentUserReady,
       canStartGame,
-      selectedDifficulty,
-      TurtleSoupDifficulty,
-      selectedThemeId,
-      selectedPuzzleId,
-      selectedTheme,
-      selectedPuzzle,
-      showThemeSelector,
-      showPuzzleSelector,
-      showDifficultySelector,
-      themeOptions,
-      puzzleOptions,
-      gameTitle,
-      themeTitle,
-      gameDescription,
-      difficultyText,
+      gameType,
+      gameInfo,
       toggleReady,
       startGame,
       handleCopy,
-      handleShare,
-      handleThemeSelect,
-      handleDifficultySelect,
-      handlePuzzleSelect,
-      purchaseTheme,
-      themeScrollHeight,
-      puzzleScrollHeight,
-      canPurchase
+      handleShare
     }
   }
 }

+ 1 - 1
src/services/game.ts

@@ -10,7 +10,7 @@ const mockGames: Game[] = [
     id: '1',
     title: '海龟汤',
     image: 'https://images.unsplash.com/photo-1582845512747-e42001c95638?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&h=100&q=80',
-    minPlayers: 3,
+    minPlayers: 1,
     maxPlayers: 10,
     duration: '30-60',
     rating: 4.8,

+ 0 - 11
src/types/cascader.ts

@@ -1,11 +0,0 @@
-// @/types/cascader.ts
-export interface CascaderOption {
-    value: string;
-    text: string;
-    description?: string;
-    disabled?: boolean;
-    locked?: boolean;
-    unlockRequirement?: string;
-    price?: string;
-    children?: CascaderOption[];
-  }

+ 8 - 0
src/types/game.ts

@@ -1,6 +1,14 @@
+
+// 游戏类型枚举
+export enum GameType {
+    TURTLE_SOUP = 'TURTLE_SOUP',
+    WORD_GAME = 'WORD_GAME'
+}
+
 // 游戏数据接口
 export interface Game {
     id: string; // 游戏ID
+    type: GameType; // 游戏类型
     title: string; // 游戏标题
     image: string; // 游戏图片
     minPlayers: number; // 最小玩家人数