|
@@ -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;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|