wuzj 6 dni temu
rodzic
commit
81b3f2bd70
3 zmienionych plików z 399 dodań i 208 usunięć
  1. 1 2
      components.d.ts
  2. 28 28
      src/pages/history/index.vue
  3. 370 178
      src/pages/room/waiting/index.vue

+ 1 - 2
components.d.ts

@@ -11,7 +11,7 @@ declare module 'vue' {
     HintCard: typeof import('./src/components/HintCard/index.vue')['default']
     NutBarrage: typeof import('@nutui/nutui-taro')['Barrage']
     NutButton: typeof import('@nutui/nutui-taro')['Button']
-    NutCascader: typeof import('@nutui/nutui-taro')['Cascader']
+    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']
@@ -19,7 +19,6 @@ declare module 'vue' {
     NutInput: typeof import('@nutui/nutui-taro')['Input']
     NutPopup: typeof import('@nutui/nutui-taro')['Popup']
     NutRadio: typeof import('@nutui/nutui-taro')['Radio']
-    NutRadiogroup: typeof import('@nutui/nutui-taro')['Radiogroup']
     NutRadioGroup: typeof import('@nutui/nutui-taro')['RadioGroup']
     NutRange: typeof import('@nutui/nutui-taro')['Range']
     NutRate: typeof import('@nutui/nutui-taro')['Rate']

+ 28 - 28
src/pages/history/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <view class="history-page">
+    <view class="history-page">
     <view class="tab-container">
       <nut-tabs v-model="activeTab">
         <nut-tab-pane title="最近游戏" pane-key="0">
@@ -240,33 +240,33 @@
         </nut-tab-pane>
       </nut-tabs>
     </view>
-  </view>
-  <Tabbar></Tabbar>
-</template>
-
-<script lang="ts">
+    </view>
+    <Tabbar></Tabbar>
+  </template>
+  
+  <script lang="ts">
 import { ref, onMounted, computed, watch } from 'vue'
-import Tabbar from '@/components/Tabbar.vue'
-import Taro from '@tarojs/taro'
+  import Tabbar from '@/components/Tabbar.vue'
+  import Taro from '@tarojs/taro'
 import { useRoomStore } from '@/stores/room'
 import { useUserStore } from '@/stores/user'
-
-export default {
-  components: {
-    Tabbar
-  },
   
-  // 页面显示时的生命周期钩子
-  onShow() {
-    // 隐藏返回首页按钮
-    Taro.hideHomeButton()
+  export default {
+    components: {
+      Tabbar
+    },
+    
+    // 页面显示时的生命周期钩子
+    onShow() {
+      // 隐藏返回首页按钮
+      Taro.hideHomeButton()
     
     // 刷新数据
     this.refreshHistory()
-  },
-  
-  // setup函数用于Composition API
-  setup() {
+    },
+    
+    // setup函数用于Composition API
+    setup() {
     const roomStore = useRoomStore()
     const userStore = useUserStore()
     
@@ -390,13 +390,13 @@ export default {
       formatTime,
       joinActiveRoom,
       viewGameDetails
+      }
     }
   }
-}
-</script>
-
-<style lang="scss">
-.history-page {
+  </script>
+  
+  <style lang="scss">
+  .history-page {
   padding: $spacing-base;
   background-color: $background-color-base;
   min-height: 100vh;
@@ -579,5 +579,5 @@ export default {
   border-radius: $border-radius-base;
   box-shadow: $shadow-light;
   margin-bottom: $spacing-base;
-}
-</style>
+  }
+  </style>

+ 370 - 178
src/pages/room/waiting/index.vue

@@ -33,43 +33,43 @@
           游戏设置
         </nut-divider>
         
-        <!-- 主题选择器 -->
-        <nut-popup position="bottom" v-model:visible="showThemeSelector">
-          <nut-cascader
-            v-model="selectedThemeValue"
-            v-model:visible="showThemeSelector"
-            title="选择游戏主题"
-            text-key="text"
-            value-key="value"
-            children-key="children"
-            :options="themeOptions"
-            @change="onThemeChange"
-          />
-        </nut-popup>
-
-        <!-- 题目选择器 -->
-        <nut-popup position="bottom" v-model:visible="showPuzzleSelector">
-          <nut-cascader
-            v-model="selectedPuzzleValue"
-            v-model:visible="showPuzzleSelector"
-            title="选择游戏题目"
-            text-key="text"
-            value-key="value"
-            :options="puzzleOptions"
-            @change="onPuzzleChange"
-          />
-        </nut-popup>
+        <!-- 主题选择 -->
+        <view class="setting-item">
+          <view class="setting-label">主题选择</view>
+          <nut-cell 
+            :desc="selectedTheme?.text || '请选择游戏主题'" 
+            @click="showThemeSelector = true"
+          >
+            <template #link>
+              <IconFont name="right" size="16"></IconFont>
+            </template>
+          </nut-cell>
+        </view>
+        
+        <!-- 题目选择 -->
+        <view class="setting-item" v-if="selectedThemeId">
+          <view class="setting-label">题目选择</view>
+          <nut-cell 
+            :desc="selectedPuzzle?.text || '请选择游戏题目'" 
+            @click="showPuzzleSelector = true"
+          >
+            <template #link>
+              <IconFont name="right" size="16"></IconFont>
+            </template>
+          </nut-cell>
+        </view>
         
         <!-- 难度选择 -->
         <view class="setting-item">
           <view class="setting-label">游戏难度</view>
-          <view class="difficulty-options">
-            <nut-radiogroup v-model="selectedDifficulty" direction="horizontal">
-              <nut-radio label="easy" icon-name="check" icon-active-color="#3C92FB">简单</nut-radio>
-              <nut-radio label="medium" icon-name="check" icon-active-color="#3C92FB">中等</nut-radio>
-              <nut-radio label="hard" icon-name="check" icon-active-color="#3C92FB">困难</nut-radio>
-            </nut-radiogroup>
-          </view>
+          <nut-cell 
+            :desc="difficultyText" 
+            @click="showDifficultySelector = true"
+          >
+            <template #link>
+              <IconFont name="right" size="16"></IconFont>
+            </template>
+          </nut-cell>
         </view>
       </view>
       
@@ -90,7 +90,7 @@
             :class="{ 'is-host': user.roomRole === 'hoster' }"
           >
             <view class="player-avatar">
-              <image :src="user.avatar" mode="aspectFill" />
+              <image :src="user.avatar || '/assets/default-avatar.png'" mode="aspectFill" @error="handleAvatarError" />
             </view>
             <view class="player-info">
               <view class="player-name">{{ user.nickname }}</view>
@@ -163,7 +163,7 @@
             :class="{ 'is-host': user.roomRole === 'hoster' }"
           >
             <view class="player-avatar">
-              <image :src="user.avatar" mode="aspectFill" />
+              <image :src="user.avatar || '/assets/default-avatar.png'" mode="aspectFill" @error="handleAvatarError" />
             </view>
             <view class="player-info">
               <view class="player-name">{{ user.nickname }}</view>
@@ -189,31 +189,120 @@
       </view>
     </view>
 
-    <!-- 主题选择器 -->
-    <nut-popup position="bottom" v-model:visible="showThemeSelector">
-      <nut-cascader
-        v-model="selectedThemeValue"
-        title="选择游戏主题"
-        :options="themeOptions"
-        @change="onThemeChange"
-        @close="showThemeSelector = false"
-      />
+    <!-- 主题选择弹窗 -->
+    <nut-popup v-model:visible="showThemeSelector" position="bottom">
+      <view class="selector-container">
+        <view class="selector-header">
+          <view class="selector-title">选择游戏主题</view>
+          <nut-button size="small" @click="showThemeSelector = false">取消</nut-button>
+        </view>
+        <scroll-view 
+          scroll-y 
+          class="theme-scroll"
+          :style="{ maxHeight: themeScrollHeight + 'px' }"
+        >
+          <view class="theme-list">
+            <view 
+              v-for="theme in themeOptions" 
+              :key="theme.value" 
+              class="theme-option"
+              :class="{ 'disabled': theme.disabled, 'selected': selectedThemeId === theme.value }"
+              @click="!theme.disabled && handleThemeSelect(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>
+              <IconFont v-if="selectedThemeId === theme.value" name="check" color="#3C92FB" size="16"></IconFont>
+            </view>
+          </view>
+        </scroll-view>
+      </view>
     </nut-popup>
 
-    <!-- 题目选择器 -->
-    <nut-popup position="bottom" v-model:visible="showPuzzleSelector">
-      <nut-cascader
-        v-model="selectedPuzzleValue"
-        title="选择游戏题目"
-        :options="puzzleOptions"
-        @change="onPuzzleChange"
-        @close="showPuzzleSelector = false"
-      />
+    <!-- 题目选择弹窗 -->
+    <nut-popup v-model:visible="showPuzzleSelector" position="bottom">
+      <view class="selector-container">
+        <view class="selector-header">
+          <view class="selector-title">选择游戏题目</view>
+          <nut-button size="small" @click="showPuzzleSelector = false">取消</nut-button>
+        </view>
+        <scroll-view 
+          scroll-y 
+          class="puzzle-scroll"
+          :style="{ maxHeight: puzzleScrollHeight + '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 && handlePuzzleSelect(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>
+
+    <!-- 难度选择弹窗 -->
+    <nut-popup v-model:visible="showDifficultySelector" position="bottom">
+      <view class="selector-container">
+        <view class="selector-header">
+          <view class="selector-title">选择游戏难度</view>
+          <nut-button size="small" @click="showDifficultySelector = false">取消</nut-button>
+        </view>
+        <view class="difficulty-list">
+          <view 
+            class="difficulty-option" 
+            :class="{ 'selected': selectedDifficulty === TurtleSoupDifficulty.EASY }"
+            @click="handleDifficultySelect(TurtleSoupDifficulty.EASY)"
+          >
+            <view class="option-content">
+              <view class="option-title">简单</view>
+              <view class="option-desc">适合新手玩家,游戏时间较短</view>
+            </view>
+            <IconFont v-if="selectedDifficulty === TurtleSoupDifficulty.EASY" name="check" color="#3C92FB" size="16"></IconFont>
+          </view>
+          <view 
+            class="difficulty-option" 
+            :class="{ 'selected': selectedDifficulty === TurtleSoupDifficulty.MEDIUM }"
+            @click="handleDifficultySelect(TurtleSoupDifficulty.MEDIUM)"
+          >
+            <view class="option-content">
+              <view class="option-title">中等</view>
+              <view class="option-desc">平衡挑战与乐趣,适合大多数玩家</view>
+            </view>
+            <IconFont v-if="selectedDifficulty === TurtleSoupDifficulty.MEDIUM" name="check" color="#3C92FB" size="16"></IconFont>
+          </view>
+          <view 
+            class="difficulty-option" 
+            :class="{ 'selected': selectedDifficulty === TurtleSoupDifficulty.HARD }"
+            @click="handleDifficultySelect(TurtleSoupDifficulty.HARD)"
+          >
+            <view class="option-content">
+              <view class="option-title">困难</view>
+              <view class="option-desc">高难度挑战,适合有经验的玩家</view>
+            </view>
+            <IconFont v-if="selectedDifficulty === TurtleSoupDifficulty.HARD" name="check" color="#3C92FB" size="16"></IconFont>
+          </view>
+        </view>
+      </view>
     </nut-popup>
   </view>
   <Tabbar></Tabbar>
 </template>
-
+  
 <script lang="ts">
 import Taro from '@tarojs/taro'
 import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
@@ -222,6 +311,7 @@ import { useUserStore } from '@/stores/user'
 import { useTurtleSoupStore } from '@/stores/games/turtlesoup'
 import Tabbar from '@/components/Tabbar.vue'
 import RoomCode from '@/components/RoomCode/index.vue'
+import { IconFont } from '@nutui/icons-vue-taro'
 import { RoomRole, RoomStatus } from '@/types/room'
 import { TurtleSoupDifficulty } from '@/types/games/turtlesoup'
 
@@ -229,14 +319,18 @@ import { TurtleSoupDifficulty } from '@/types/games/turtlesoup'
 interface CascaderOption {
   value: string;
   text: string;
+  description?: string;
   disabled?: boolean;
+  locked?: boolean;
+  unlockRequirement?: string;
   children?: CascaderOption[];
 }
 
 export default {
   components: {
     Tabbar,
-    RoomCode
+    RoomCode,
+    IconFont
   },
   
   // 生命周期钩子 - 页面显示
@@ -293,6 +387,13 @@ export default {
       return currentUser ? currentUser.isReady : false
     })
     
+    // 处理头像加载错误
+    const handleAvatarError = (e: any) => {
+      console.log('头像加载失败:', e);
+      // 设置默认头像
+      e.target.src = '/assets/default-avatar.png';
+    }
+    
     // 是否可以开始游戏(主持人功能)
     const canStartGame = computed(() => {
       if (!currentRoom.value) return false
@@ -305,59 +406,37 @@ export default {
       const allReady = players.every(p => p.isReady)
       
       // 主题和题目已选择
-      const settingsReady = selectedTheme.value && selectedPuzzle.value
+      const settingsReady = selectedThemeId.value && selectedPuzzleId.value
       
       return allReady && settingsReady
     })
     
     // 游戏设置相关变量
     const selectedDifficulty = ref(TurtleSoupDifficulty.MEDIUM)
-    const showThemeSelector = ref(false)
-    const showPuzzleSelector = ref(false)
-    
-    // 主题和题目选择值
-    const selectedThemeValue = ref(['']) // 初始化为空数组或者根据默认值设置
-    const selectedPuzzleValue = ref(['']) // 初始化为空数组或者根据默认值设置
-    
-    // 选中的主题和题目对象,用于显示
+    const selectedThemeId = ref('') // 添加选中主题ID
+    const selectedPuzzleId = ref('') // 添加选中题目ID
     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[]>([
-      {
-        value: 'mystery',
-        text: '神秘主题',
-        children: [
-          { value: 'detective', text: '侦探故事' },
-          { value: 'supernatural', text: '超自然现象' }
-        ]
-      },
-      {
-        value: 'adventure',
-        text: '冒险主题',
-        children: [
-          { value: 'treasure', text: '寻宝冒险' },
-          { value: 'survival', text: '荒野求生' }
-        ]
-      },
-      {
-        value: 'scifi',
-        text: '科幻主题',
-        disabled: false,
-        children: [
-          { value: 'space', text: '太空探险' },
-          { value: 'future', text: '未来世界' }
-        ]
-      }
-    ])
+    const themeOptions = ref<CascaderOption[]>([])
     
-    // 题目选项(将根据选择的主题动态加载)
+    // 题目选项
     const puzzleOptions = ref<CascaderOption[]>([])
     
+    // 设置滚动区域高度限制
+    const themeScrollHeight = ref(300) // 最多显示约3个选项
+    const puzzleScrollHeight = ref(300) // 最多显示约3个选项
+    
     // 游戏信息(玩家视图)
     const gameTitle = computed(() => currentRoom.value?.gameTitle || '')
-    const themeTitle = computed(() => selectedTheme.value?.text || '')
+    const themeTitle = computed(() => {
+      const theme = themeOptions.value.find(t => t.value === selectedThemeId.value)
+      return theme?.text || ''
+    })
     const gameDescription = ref('')
     
     // 难度文本
@@ -387,7 +466,7 @@ export default {
         // 加载房间信息
         await loadRoomInfo(roomId.value)
         
-        // 如果是主持人,加载可用的主题和题目
+        // 如果是主持人,加载可用的主题
         if (isHost.value) {
           await loadThemes()
         }
@@ -426,76 +505,119 @@ export default {
     // 加载主题列表
     const loadThemes = async () => {
       try {
-        // 可以从turtleSoupStore加载主题
+        Taro.showLoading({ title: '加载主题中...' })
+        // 从turtleSoupStore加载主题
         const themes = await turtleSoupStore.loadThemes()
         
-        // 将API返回的主题格式化为Cascader需要的格式
+        // 将API返回的主题格式化为显示需要的格式
         if (themes && themes.length) {
           themeOptions.value = themes.map(theme => ({
             value: theme.id,
             text: theme.name,
-            disabled: theme.isLocked
+            description: theme.description,
+            disabled: theme.isLocked, // 根据是否解锁决定是否可选
+            locked: theme.isLocked,
+            unlockRequirement: theme.unlockRequirement || '完成相关任务'
           }))
+          console.log('已加载主题:', themeOptions.value)
         }
+        Taro.hideLoading()
       } catch (error) {
         console.error('加载主题失败:', error)
+        Taro.hideLoading()
+        Taro.showToast({
+          title: '加载主题失败',
+          icon: 'none'
+        })
       }
     }
     
-    // 根据主题加载题目列表
-    const loadPuzzles = async (themeId: string) => {
+    // 根据主题和难度加载题目列表
+    const loadPuzzles = async () => {
+      if (!selectedThemeId.value) {
+        return
+      }
+      
       try {
-        // 从API加载指定主题的题目列表
-        const puzzles = await turtleSoupStore.loadPuzzles(themeId, selectedDifficulty.value)
+        Taro.showLoading({ title: '加载题目中...' })
         
-        // 格式化为Cascader需要的格式
+        // 从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 onThemeChange = async (value: string[], pathNodes: CascaderOption[]) => {
-      if (value.length > 0 && pathNodes.length > 0) {
-        // 保存选中的主题
-        selectedTheme.value = {
-          value: value[0],
-          text: pathNodes[0].text
-        }
-        
-        // 加载该主题下的题目
-        await loadPuzzles(value[0])
-        
-        // 重置题目选择
-        selectedPuzzle.value = null
-        selectedPuzzleValue.value = ['']
-        
-        // 关闭选择器
-        showThemeSelector.value = false
+    // 处理主题选择
+    const handleThemeSelect = (theme: CascaderOption) => {
+      if (theme.disabled) {
+        Taro.showToast({
+          title: '该主题暂未解锁',
+          icon: 'none'
+        })
+        return
       }
+      
+      selectedTheme.value = theme
+      selectedThemeId.value = theme.value // 设置选中的主题ID
+      showThemeSelector.value = false
+      
+      // 选择主题后提示选择难度
+      setTimeout(() => {
+        showDifficultySelector.value = true
+      }, 300)
     }
     
-    // 监听题目选择变化
-    const onPuzzleChange = (value: string[], pathNodes: CascaderOption[]) => {
-      if (value.length > 0 && pathNodes.length > 0) {
-        // 保存选中的题目
-        selectedPuzzle.value = {
-          value: value[0],
-          text: pathNodes[0].text
-        }
-        
-        // 关闭选择器
-        showPuzzleSelector.value = false
+    // 处理难度选择
+    const handleDifficultySelect = (difficulty: TurtleSoupDifficulty) => {
+      selectedDifficulty.value = difficulty
+      showDifficultySelector.value = false
+      
+      // 加载对应难度的题目
+      loadPuzzles()
+      
+      // 选择难度后提示选择题目
+      setTimeout(() => {
+        showPuzzleSelector.value = true
+      }, 300)
+    }
+    
+    // 处理题目选择
+    const handlePuzzleSelect = (puzzle: CascaderOption) => {
+      if (puzzle.disabled) {
+        Taro.showToast({
+          title: '该题目暂未解锁',
+          icon: 'none'
+        })
+        return
       }
+      
+      selectedPuzzle.value = puzzle
+      selectedPuzzleId.value = puzzle.value // 设置选中的题目ID
+      showPuzzleSelector.value = false
     }
     
     // 准备/取消准备(玩家)
@@ -531,8 +653,8 @@ export default {
         
         // 准备游戏设置
         const gameSettings = {
-          themeId: selectedTheme.value?.value || '',
-          puzzleId: selectedPuzzle.value?.value || '',
+          themeId: selectedThemeId.value,
+          puzzleId: selectedPuzzleId.value,
           difficulty: selectedDifficulty.value,
           maxPlayers: currentRoom.value.maxPlayers,
           isPrivate: currentRoom.value.visibility === 'private'
@@ -602,18 +724,8 @@ export default {
     // 处理分享按钮点击
     const handleShare = (code: string) => {
       console.log('分享房间码:', code)
-      // 这里不需要额外处理,因为RoomCode组件内部已处理了分享逻辑
     }
     
-    // 监听难度变化,如果已选择主题则重新加载题目
-    watch(selectedDifficulty, async (newVal) => {
-      if (selectedTheme.value) {
-        await loadPuzzles(selectedTheme.value.value)
-        selectedPuzzle.value = null // 重置题目选择
-        selectedPuzzleValue.value = [''] // 重置级联选择器的值
-      }
-    })
-    
     // 页面加载时初始化
     onMounted(() => {
       initPage()
@@ -624,6 +736,35 @@ 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,
@@ -632,10 +773,14 @@ export default {
       currentUserReady,
       canStartGame,
       selectedDifficulty,
+      TurtleSoupDifficulty,
+      selectedThemeId,
+      selectedPuzzleId,
       selectedTheme,
       selectedPuzzle,
       showThemeSelector,
       showPuzzleSelector,
+      showDifficultySelector,
       themeOptions,
       puzzleOptions,
       gameTitle,
@@ -646,10 +791,12 @@ export default {
       startGame,
       handleCopy,
       handleShare,
-      onThemeChange,
-      onPuzzleChange,
-      selectedThemeValue,
-      selectedPuzzleValue
+      handleThemeSelect,
+      handleDifficultySelect,
+      handlePuzzleSelect,
+      handleAvatarError,
+      themeScrollHeight,
+      puzzleScrollHeight
     }
   },
   
@@ -659,7 +806,7 @@ export default {
   }
 }
 </script>
-
+  
 <style lang="scss">
 .waiting-room-page {
   padding: $spacing-base;
@@ -674,8 +821,8 @@ export default {
       display: flex;
       align-items: center;
       margin-bottom: $spacing-base;
-      
-      .title {
+    
+    .title {
         font-size: $font-size-large;
         font-weight: $font-weight-bold;
         color: $text-color-primary;
@@ -729,6 +876,7 @@ export default {
         font-size: $font-size-small;
         color: $text-color-secondary;
         margin-bottom: $spacing-mini;
+        font-weight: $font-weight-medium;
       }
       
       .setting-value {
@@ -817,14 +965,17 @@ export default {
         }
         
         &.is-host {
-          background-color: $background-color-orange;
+          background-color: rgba(255, 235, 210, 0.3); // 浅橙色背景
           border-radius: $border-radius-mini;
+          padding: $spacing-base;
         }
         
         .player-avatar {
           width: 40px;
           height: 40px;
           margin-right: $spacing-base;
+          border-radius: 50%;
+          overflow: hidden;
           
           image {
             width: 100%;
@@ -874,42 +1025,83 @@ export default {
     }
   }
   
-  .action-buttons {
-    margin-top: $spacing-large;
-    
-    .start-button, .ready-button {
-      height: 44px;
-      font-size: $font-size-medium;
-      border-radius: $border-radius-base;
-    }
-  }
-  
-  // 分享弹窗相关样式
-  .share-dialog-content {
-    padding: $spacing-large;
+  // 选择器相关样式
+  .selector-container {
+    padding: $spacing-base;
     
-    .qrcode-container {
-      margin: 0 auto;
-      width: 200px;
-      height: 200px;
+    .selector-header {
       display: flex;
-      justify-content: center;
+      justify-content: space-between;
       align-items: center;
+      padding: $spacing-base 0;
+      margin-bottom: $spacing-base;
+      border-bottom: 1px solid $border-color-light;
       
-      .qrcode-img {
-        width: 100%;
-        height: 100%;
+      .selector-title {
+        font-size: $font-size-medium;
+        font-weight: $font-weight-medium;
+        color: $text-color-primary;
       }
     }
     
-    .room-info {
-      margin-top: $spacing-large;
-      text-align: center;
+    .theme-scroll, .puzzle-scroll {
+      border: 1px solid $border-color-light;
+      border-radius: $border-radius-small;
+      margin-top: $spacing-base;
+      background-color: $background-color-light;
+    }
+    
+    .theme-list, .puzzle-list, .difficulty-list {
+      padding: $spacing-small;
       
-      .room-id, .room-password {
-        font-size: $font-size-base;
-        color: $text-color-primary;
+      .theme-option, .puzzle-option, .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;
+        }
+        
+        &.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;
+          }
+        }
       }
     }
   }