12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109 |
- <template>
- <view class="waiting-room-page">
- <!-- 房间头部信息 -->
- <view class="room-header">
- <view class="room-title">
- <view class="title">{{ currentRoom?.name || '等待房间' }}</view>
- <view class="tag" :class="isHost ? 'host-tag' : 'player-tag'">
- {{ isHost ? '主持人' : '玩家' }}
- </view>
- </view>
- <view class="status-info">
- <view class="players-count">{{ playerCount }}/{{ currentRoom?.maxPlayers || 0 }}人已加入</view>
- <view class="status-text">{{ statusText }}</view>
- </view>
- </view>
- <!-- 房间码展示和分享 -->
- <RoomCode
- :code="currentRoom?.id || ''"
- :password="currentRoom?.password"
- @share="handleShare"
- @copy="handleCopy"
- />
- <!-- 主持人视图 -->
- <view v-if="isHost" class="host-view">
- <!-- 游戏设置模块 -->
- <view class="game-settings">
- <nut-divider
- content-position="center"
- :style="{ color: '#3C92FB', borderColor: '#3C92FB', padding: '0 16px', margin: '10px 0 20px 0' }"
- >
- 游戏设置
- </nut-divider>
-
- <!-- 主题选择 -->
- <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>
- <nut-cell
- :desc="difficultyText"
- @click="showDifficultySelector = true"
- >
- <template #link>
- <IconFont name="right" size="16"></IconFont>
- </template>
- </nut-cell>
- </view>
- </view>
-
- <!-- 玩家列表 -->
- <view class="player-list">
- <nut-divider
- content-position="center"
- :style="{ color: '#3C92FB', borderColor: '#3C92FB', padding: '0 16px', margin: '20px 0 10px 0' }"
- >
- 玩家列表
- </nut-divider>
-
- <view class="players">
- <view
- v-for="user in currentRoom?.users"
- :key="user.openid"
- class="player-item"
- :class="{ 'is-host': user.roomRole === 'hoster' }"
- >
- <view class="player-avatar">
- <image :src="user.avatar || '/assets/default-avatar.png'" mode="aspectFill" @error="handleAvatarError" />
- </view>
- <view class="player-info">
- <view class="player-name">{{ user.nickname }}</view>
- <view class="player-role">{{ user.roomRole === 'hoster' ? '主持人' : '玩家' }}</view>
- </view>
- <view class="player-status" :class="{ 'ready': user.isReady }">
- {{ user.isReady ? '已准备' : '未准备' }}
- </view>
- </view>
- </view>
- </view>
-
- <!-- 主持人操作按钮 -->
- <view class="action-buttons">
- <nut-button
- block
- color="#3C92FB"
- class="start-button"
- :disabled="!canStartGame"
- @click="startGame"
- >
- 开始游戏
- </nut-button>
- </view>
- </view>
- <!-- 玩家视图 -->
- <view v-else class="player-view">
- <!-- 游戏信息展示 -->
- <view class="game-info">
- <nut-divider
- content-position="center"
- :style="{ color: '#3C92FB', borderColor: '#3C92FB', padding: '0 16px', margin: '10px 0 20px 0' }"
- >
- 游戏信息
- </nut-divider>
-
- <view class="info-card">
- <view class="game-title">{{ gameTitle }}</view>
- <view class="game-meta">
- <view class="meta-item">
- <view class="meta-label">游戏主题</view>
- <view class="meta-value">{{ themeTitle || '待定' }}</view>
- </view>
- <view class="meta-item">
- <view class="meta-label">游戏难度</view>
- <view class="meta-value">{{ difficultyText }}</view>
- </view>
- </view>
- <view class="game-desc">
- {{ gameDescription || '正在等待主持人设置游戏...' }}
- </view>
- </view>
- </view>
-
- <!-- 玩家列表 -->
- <view class="player-list">
- <nut-divider
- content-position="center"
- :style="{ color: '#3C92FB', borderColor: '#3C92FB', padding: '0 16px', margin: '20px 0 10px 0' }"
- >
- 玩家列表
- </nut-divider>
-
- <view class="players">
- <view
- v-for="user in currentRoom?.users"
- :key="user.openid"
- class="player-item"
- :class="{ 'is-host': user.roomRole === 'hoster' }"
- >
- <view class="player-avatar">
- <image :src="user.avatar || '/assets/default-avatar.png'" mode="aspectFill" @error="handleAvatarError" />
- </view>
- <view class="player-info">
- <view class="player-name">{{ user.nickname }}</view>
- <view class="player-role">{{ user.roomRole === 'hoster' ? '主持人' : '玩家' }}</view>
- </view>
- <view class="player-status" :class="{ 'ready': user.isReady }">
- {{ user.isReady ? '已准备' : '未准备' }}
- </view>
- </view>
- </view>
- </view>
-
- <!-- 玩家操作按钮 -->
- <view class="action-buttons">
- <nut-button
- block
- :color="currentUserReady ? '#999' : '#3C92FB'"
- class="ready-button"
- @click="toggleReady"
- >
- {{ currentUserReady ? '取消准备' : '准备' }}
- </nut-button>
- </view>
- </view>
- <!-- 主题选择弹窗 -->
- <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 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'
- import { useRoomStore } from '@/stores/room'
- 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'
- // 主题和题目的数据类型
- interface CascaderOption {
- value: string;
- text: string;
- description?: string;
- disabled?: boolean;
- locked?: boolean;
- unlockRequirement?: string;
- children?: CascaderOption[];
- }
- export default {
- components: {
- Tabbar,
- RoomCode,
- IconFont
- },
-
- // 生命周期钩子 - 页面显示
- onShow() {
- // 隐藏返回首页按钮
- Taro.hideHomeButton()
- },
-
- // Composition API
- setup() {
- // 初始化store
- const roomStore = useRoomStore()
- const userStore = useUserStore()
- const turtleSoupStore = useTurtleSoupStore()
-
- // 房间ID
- const roomId = ref('')
-
- // 获取当前房间
- const currentRoom = computed(() => roomStore.currentRoom)
-
- // 判断当前用户是否是主持人
- const isHost = computed(() => {
- if (!currentRoom.value || !userStore.openid) return false
- return roomStore.getUserRole(userStore.openid) === RoomRole.HOSTER
- })
-
- // 计算玩家数量
- const playerCount = computed(() => {
- return currentRoom.value?.users.length || 0
- })
-
- // 房间状态文本
- const statusText = computed(() => {
- if (!currentRoom.value) return '等待中'
-
- switch(currentRoom.value.status) {
- case RoomStatus.WAITING:
- return '等待玩家加入...'
- case RoomStatus.PLAYING:
- return '游戏中...'
- case RoomStatus.ENDED:
- return '已结束'
- default:
- return '等待中'
- }
- })
-
- // 当前用户是否已准备
- const currentUserReady = computed(() => {
- if (!currentRoom.value || !userStore.openid) return false
-
- const currentUser = currentRoom.value.users.find(u => u.openid === userStore.openid)
- 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
-
- // 至少有一个玩家
- 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('') // 添加选中主题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[]>([])
-
- // 题目选项
- const puzzleOptions = ref<CascaderOption[]>([])
-
- // 设置滚动区域高度限制
- const themeScrollHeight = ref(300) // 最多显示约3个选项
- const puzzleScrollHeight = ref(300) // 最多显示约3个选项
-
- // 游戏信息(玩家视图)
- 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 '中等'
- }
- })
-
- // 初始化页面
- const initPage = async () => {
- // 获取路由参数中的房间ID
- const pages = Taro.getCurrentPages()
- const currentPage = pages[pages.length - 1]
- const routeParams = currentPage.$taroParams
-
- if (routeParams && routeParams.roomId) {
- roomId.value = routeParams.roomId
-
- // 加载房间信息
- await loadRoomInfo(roomId.value)
-
- // 如果是主持人,加载可用的主题
- if (isHost.value) {
- await loadThemes()
- }
-
- // 开始监听房间变化
- startRoomListener()
- } else {
- Taro.showToast({
- title: '房间ID不存在',
- icon: 'none'
- })
-
- // 延迟返回
- setTimeout(() => {
- Taro.navigateBack()
- }, 1500)
- }
- }
-
- // 加载房间信息
- const loadRoomInfo = async (id: string) => {
- try {
- const result = await roomStore.loadRoomInfo(id)
- if (!result.success) {
- throw new Error(result.message || '加载房间信息失败')
- }
- } catch (error) {
- console.error('加载房间信息失败:', error)
- Taro.showToast({
- title: error instanceof Error ? error.message : '加载房间信息失败',
- icon: 'none'
- })
- }
- }
-
- // 加载主题列表
- 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,
- unlockRequirement: theme.unlockRequirement || '完成相关任务'
- }))
- 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) => {
- 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 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
- }
-
- // 准备/取消准备(玩家)
- const toggleReady = async () => {
- if (!userStore.openid || !currentRoom.value) return
-
- try {
- Taro.showLoading({ title: '处理中...' })
-
- // 更新玩家准备状态
- const newStatus = !currentUserReady.value
-
- // 调用API更新准备状态
- await roomStore.updateUserInRoom(userStore.openid, { isReady: newStatus })
-
- Taro.hideLoading()
- } catch (error) {
- console.error('切换准备状态失败:', error)
- Taro.showToast({
- title: '操作失败',
- icon: 'none'
- })
- Taro.hideLoading()
- }
- }
-
- // 开始游戏(主持人)
- const startGame = async () => {
- 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'
- }
-
- // 创建游戏
- const result = await turtleSoupStore.createGame(gameSettings)
-
- if (result.success && result.gameId) {
- // 更新房间状态为游戏中
- await roomStore.updateRoomStatus(RoomStatus.PLAYING)
-
- // 导航到游戏页面
- Taro.redirectTo({
- url: `/pages/room/play/index?roomId=${currentRoom.value.id}&gameId=${result.gameId}`
- })
- } else {
- throw new Error('创建游戏失败')
- }
- } catch (error) {
- console.error('开始游戏失败:', error)
- Taro.showToast({
- title: error instanceof Error ? error.message : '开始游戏失败',
- icon: 'none'
- })
- } finally {
- Taro.hideLoading()
- }
- }
-
- // 房间监听器
- let roomInterval: NodeJS.Timeout | null = null
-
- // 开始监听房间变化
- const startRoomListener = () => {
- // 定期刷新房间状态
- roomInterval = setInterval(async () => {
- if (roomId.value) {
- await loadRoomInfo(roomId.value)
-
- // 检查房间状态,如果变为"游戏中",需要跳转到游戏页面
- if (currentRoom.value && currentRoom.value.status === RoomStatus.PLAYING) {
- stopRoomListener()
-
- // 跳转到游戏页面
- Taro.redirectTo({
- url: `/pages/room/play/index?roomId=${roomId.value}`
- })
- }
- }
- }, 3000) // 每3秒刷新一次
- }
-
- // 停止监听房间变化
- const stopRoomListener = () => {
- if (roomInterval) {
- clearInterval(roomInterval)
- roomInterval = null
- }
- }
-
- // 处理复制按钮点击
- const handleCopy = (code: string) => {
- console.log('房间码已复制:', code)
- }
-
- // 处理分享按钮点击
- const handleShare = (code: string) => {
- console.log('分享房间码:', code)
- }
-
- // 页面加载时初始化
- onMounted(() => {
- initPage()
- })
-
- // 页面卸载时停止监听
- onUnmounted(() => {
- 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,
- playerCount,
- statusText,
- currentUserReady,
- canStartGame,
- selectedDifficulty,
- TurtleSoupDifficulty,
- selectedThemeId,
- selectedPuzzleId,
- selectedTheme,
- selectedPuzzle,
- showThemeSelector,
- showPuzzleSelector,
- showDifficultySelector,
- themeOptions,
- puzzleOptions,
- gameTitle,
- themeTitle,
- gameDescription,
- difficultyText,
- toggleReady,
- startGame,
- handleCopy,
- handleShare,
- handleThemeSelect,
- handleDifficultySelect,
- handlePuzzleSelect,
- handleAvatarError,
- themeScrollHeight,
- puzzleScrollHeight
- }
- },
-
- // 生命周期钩子 - 页面加载
- onLoad() {
- // 使用setup方法处理页面加载
- }
- }
- </script>
-
- <style lang="scss">
- .waiting-room-page {
- padding: $spacing-base;
- background-color: $background-color-base;
- min-height: 100vh;
- padding-bottom: $spacing-large * 4; // 为底部tabbar留出空间
-
- .room-header {
- margin-bottom: $spacing-base;
-
- .room-title {
- display: flex;
- align-items: center;
- margin-bottom: $spacing-base;
-
- .title {
- font-size: $font-size-large;
- font-weight: $font-weight-bold;
- color: $text-color-primary;
- }
-
- .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: white;
- }
-
- &.player-tag {
- background-color: $blue-light-color;
- color: white;
- }
- }
- }
-
- .status-info {
- display: flex;
- justify-content: space-between;
- align-items: center;
-
- .players-count, .status-text {
- font-size: $font-size-base;
- color: $text-color-secondary;
- }
- }
- }
-
- .host-view, .player-view {
- margin-top: $spacing-large;
- }
-
- .game-settings {
- background-color: $background-color-light;
- border-radius: $border-radius-small;
- padding: $spacing-large;
- margin-bottom: $spacing-large;
- box-shadow: $shadow-light;
-
- .setting-item {
- margin-bottom: $spacing-large;
-
- .setting-label {
- font-size: $font-size-small;
- color: $text-color-secondary;
- margin-bottom: $spacing-mini;
- font-weight: $font-weight-medium;
- }
-
- .setting-value {
- padding: $spacing-base 0;
- border-bottom: 1px solid $border-color-light;
- display: flex;
- justify-content: space-between;
- align-items: center;
- font-size: $font-size-medium;
- color: $text-color-primary;
-
- &:active {
- background-color: $background-color-gray;
- }
- }
-
- .difficulty-options {
- margin-top: $spacing-base;
- }
- }
- }
-
- .game-info {
- background-color: $background-color-light;
- border-radius: $border-radius-small;
- padding: $spacing-large;
- margin-bottom: $spacing-large;
- box-shadow: $shadow-light;
-
- .info-card {
- .game-title {
- font-size: $font-size-medium;
- font-weight: $font-weight-medium;
- color: $text-color-primary;
- margin-bottom: $spacing-base;
- }
-
- .game-meta {
- display: flex;
- flex-wrap: wrap;
- margin-bottom: $spacing-large;
-
- .meta-item {
- flex: 1;
- min-width: 50%;
- margin-bottom: $spacing-base;
-
- .meta-label {
- font-size: $font-size-small;
- color: $text-color-secondary;
- margin-bottom: $spacing-mini;
- }
-
- .meta-value {
- font-size: $font-size-base;
- color: $text-color-primary;
- font-weight: $font-weight-medium;
- }
- }
- }
-
- .game-desc {
- font-size: $font-size-base;
- color: $text-color-regular;
- line-height: $line-height-loose;
- }
- }
- }
-
- .player-list {
- background-color: $background-color-light;
- border-radius: $border-radius-small;
- padding: $spacing-large;
- margin-bottom: $spacing-large;
- box-shadow: $shadow-light;
-
- .players {
- .player-item {
- display: flex;
- align-items: center;
- padding: $spacing-base 0;
- border-bottom: 1px solid $border-color-light;
-
- &:last-child {
- border-bottom: none;
- }
-
- &.is-host {
- 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%;
- height: 100%;
- border-radius: 50%;
- }
- }
-
- .player-info {
- flex: 1;
-
- .player-name {
- font-size: $font-size-base;
- color: $text-color-primary;
- font-weight: $font-weight-medium;
- }
-
- .player-role {
- font-size: $font-size-small;
- color: $text-color-secondary;
- }
- }
-
- .player-status {
- padding: $spacing-mini $spacing-base;
- border-radius: $border-radius-mini;
- font-size: $font-size-small;
- background-color: $background-color-gray;
- color: $text-color-secondary;
-
- &.ready {
- background-color: $success-color;
- color: white;
- }
- }
- }
- }
- }
-
- .action-buttons {
- margin-top: $spacing-large;
-
- .start-button, .ready-button {
- height: 44px;
- font-size: $font-size-medium;
- border-radius: $border-radius-base;
- }
- }
-
- // 选择器相关样式
- .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, .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;
-
- .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;
- }
- }
- }
- }
- }
- }
- </style>
|