index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. <template>
  2. <view class="create-room-page">
  3. <view class="room-header">
  4. <view class="game-info" v-if="gameInfo">
  5. <image class="game-image" :src="gameInfo.image" mode="aspectFill" />
  6. <view class="game-detail">
  7. <view class="game-title">{{ gameInfo.title }}</view>
  8. <view class="game-meta">
  9. <text>{{ gameInfo.players }}人</text>
  10. <text>{{ gameInfo.duration }}分钟</text>
  11. </view>
  12. </view>
  13. </view>
  14. </view>
  15. <view class="room-settings">
  16. <nut-divider
  17. content-position="center"
  18. :style="{ color: '#3C92FB', borderColor: '#3C92FB', padding: '0 16px', margin: '10px 0 20px 0' }"
  19. >
  20. 房间设置
  21. </nut-divider>
  22. <view class="setting-item">
  23. <view class="setting-label">房间名称</view>
  24. <nut-input v-model="roomName" placeholder="请输入房间名称..." />
  25. <view class="random-name" @click="generateRandomName">随机名称</view>
  26. </view>
  27. <view class="setting-item">
  28. <view class="setting-label">最大人数</view>
  29. <view class="setting-slider">
  30. <nut-range v-model="maxPlayers" :min="2" :max="12" inactive-color="#E5E5E5" button-color="#3C92FB" active-color="#3C92FB"></nut-range>
  31. <view class="slider-value">{{ maxPlayers }}人</view>
  32. </view>
  33. </view>
  34. <view class="setting-item">
  35. <view class="setting-label">房间可见性</view>
  36. <view class="visibility-options">
  37. <nut-radio-group v-model="roomVisibility" direction="horizontal">
  38. <nut-radio label="private" icon-name="check" icon-active-color="#3C92FB">仅限密码</nut-radio>
  39. <nut-radio label="public" icon-name="check" icon-active-color="#3C92FB">公开房间</nut-radio>
  40. </nut-radio-group>
  41. </view>
  42. </view>
  43. <view class="setting-item" v-if="roomVisibility === 'private'">
  44. <view class="setting-label">房间密码</view>
  45. <nut-input v-model="roomPassword" placeholder="请设置房间密码..." />
  46. </view>
  47. </view>
  48. <view class="action-buttons">
  49. <nut-button block color="#3C92FB" class="create-button" @click="createRoom">
  50. 创建并开始
  51. </nut-button>
  52. </view>
  53. </view>
  54. <Tabbar></Tabbar>
  55. </template>
  56. <script lang="ts">
  57. import Taro from '@tarojs/taro'
  58. import { ref } from 'vue'
  59. import { useGameStore, type Game } from '@/stores/game'
  60. import { useUserStore } from '@/stores/user'
  61. import { useRoomStore } from '@/stores/room'
  62. import { roomAPI } from '@/services/api/room'
  63. import Tabbar from '@/components/Tabbar.vue'
  64. // 随机房间名称列表
  65. const ROOM_NAME_PREFIXES = ['欢乐', '快乐', '奇妙', '有趣', '精彩', '神秘', '魔法', '激情', '疯狂', '幻想']
  66. const ROOM_NAME_SUFFIXES = ['小队', '团队', '俱乐部', '联盟', '聚会', '战队', '家族', '帮派', '协会', '同盟']
  67. export default {
  68. components: {
  69. Tabbar
  70. },
  71. // 生命周期钩子 - 页面显示
  72. onShow() {
  73. // 隐藏返回首页按钮
  74. Taro.hideHomeButton()
  75. console.log('已隐藏返回首页按钮')
  76. },
  77. // Composition API
  78. setup() {
  79. // 初始化store
  80. const gameStore = useGameStore()
  81. const userStore = useUserStore()
  82. const roomStore = useRoomStore()
  83. // 房间设置
  84. const roomName = ref('')
  85. const maxPlayers = ref(6)
  86. const roomVisibility = ref('private')
  87. const roomPassword = ref('')
  88. // 添加明确的类型注解
  89. const gameInfo = ref<Game | null>(null)
  90. // 生成随机房间名称
  91. const generateRandomName = () => {
  92. const prefix = ROOM_NAME_PREFIXES[Math.floor(Math.random() * ROOM_NAME_PREFIXES.length)]
  93. const suffix = ROOM_NAME_SUFFIXES[Math.floor(Math.random() * ROOM_NAME_SUFFIXES.length)]
  94. roomName.value = `${prefix}${gameInfo.value?.title || '游戏'}${suffix}`
  95. }
  96. // 获取游戏信息
  97. const initGameInfo = async () => {
  98. // 确保用户是主持人角色
  99. userStore.setRole('hoster')
  100. const pages = Taro.getCurrentPages()
  101. const currentPage = pages[pages.length - 1]
  102. const gameId = currentPage.$taroParams?.gameId
  103. if (!gameId) {
  104. Taro.showToast({
  105. title: '游戏ID不存在',
  106. icon: 'none'
  107. })
  108. return
  109. }
  110. try {
  111. // 获取游戏详情
  112. const game = await gameStore.getGameDetail(gameId)
  113. if (game) {
  114. gameInfo.value = game
  115. // 设置默认房间名
  116. roomName.value = `${game.title}房间`
  117. // 从游戏的人数范围设置最大人数
  118. const playersRange = game.players.split('-')
  119. if (playersRange.length > 1) {
  120. const max = parseInt(playersRange[1])
  121. if (!isNaN(max)) {
  122. maxPlayers.value = Math.min(max, 12) // 限制最大12人
  123. }
  124. }
  125. }
  126. } catch (error) {
  127. console.error('获取游戏信息失败:', error)
  128. Taro.showToast({
  129. title: '获取游戏信息失败',
  130. icon: 'none'
  131. })
  132. }
  133. }
  134. // 创建房间
  135. const createRoom = async () => {
  136. if (!roomName.value) {
  137. Taro.showToast({
  138. title: '请输入房间名称',
  139. icon: 'none'
  140. })
  141. return
  142. }
  143. if (roomVisibility.value === 'private' && !roomPassword.value) {
  144. Taro.showToast({
  145. title: '请设置房间密码',
  146. icon: 'none'
  147. })
  148. return
  149. }
  150. try {
  151. Taro.showLoading({ title: '创建房间中...' })
  152. // 构建房间数据 - 修复status类型
  153. const roomData = {
  154. id: `room_${Date.now()}_${Math.floor(Math.random() * 1000)}`,
  155. name: roomName.value,
  156. gameId: gameInfo.value?.id || '',
  157. gameTitle: gameInfo.value?.title || '',
  158. maxPlayers: maxPlayers.value,
  159. visibility: roomVisibility.value as 'private' | 'public',
  160. password: roomVisibility.value === 'private' ? roomPassword.value : '',
  161. hosterId: userStore.openid || 'temp_host_id',
  162. hosterName: userStore.nickname || '未知用户',
  163. createTime: Date.now(),
  164. status: 'waiting' as 'waiting' | 'playing' | 'ended', // 使用类型断言确保类型匹配
  165. users: [{
  166. id: userStore.openid || 'temp_host_id',
  167. name: userStore.nickname || '未知用户',
  168. avatar: userStore.avatar || '',
  169. role: 'hoster' as 'hoster' | 'player',
  170. joinTime: Date.now()
  171. }]
  172. }
  173. // 保存到store
  174. roomStore.setCurrentRoom(roomData)
  175. // 同步到服务器
  176. const result = await roomAPI.createRoom(roomData)
  177. if (result.success) {
  178. Taro.navigateTo({
  179. url: `/pages/room/waiting/index?roomId=${result.roomId}`
  180. })
  181. } else {
  182. throw new Error('创建房间失败')
  183. }
  184. } catch (error) {
  185. console.error('创建房间失败:', error)
  186. Taro.showToast({
  187. title: '创建房间失败',
  188. icon: 'none'
  189. })
  190. } finally {
  191. Taro.hideLoading()
  192. }
  193. }
  194. // 页面初始化时获取游戏信息
  195. initGameInfo()
  196. return {
  197. roomName,
  198. maxPlayers,
  199. roomVisibility,
  200. roomPassword,
  201. gameInfo,
  202. createRoom,
  203. generateRandomName
  204. }
  205. },
  206. // 生命周期钩子 - 页面加载
  207. onLoad() {
  208. // 其他初始化逻辑
  209. }
  210. }
  211. </script>
  212. <style lang="scss">
  213. .create-room-page {
  214. padding: $spacing-base;
  215. background-color: $background-color-base;
  216. min-height: 100vh;
  217. padding-bottom: $spacing-large * 4; // 为底部tabbar留出空间
  218. .room-header {
  219. margin-bottom: $spacing-small;
  220. .game-info {
  221. display: flex;
  222. background-color: $background-color-light;
  223. border-radius: $border-radius-small;
  224. overflow: hidden;
  225. box-shadow: $shadow-light;
  226. .game-image {
  227. width: 120px;
  228. height: 120px;
  229. object-fit: cover; // 修复图片显示问题
  230. }
  231. .game-detail {
  232. flex: 1;
  233. padding: $spacing-large;
  234. display: flex;
  235. flex-direction: column;
  236. justify-content: center;
  237. .game-title {
  238. font-size: $font-size-medium;
  239. font-weight: $font-weight-medium;
  240. color: $text-color-primary;
  241. margin-bottom: $spacing-base;
  242. }
  243. .game-meta {
  244. font-size: $font-size-small;
  245. color: $text-color-secondary;
  246. text {
  247. margin-right: $spacing-large;
  248. display: inline-block;
  249. &:before {
  250. content: '•';
  251. margin-right: 4px;
  252. }
  253. &:first-child:before {
  254. content: '';
  255. margin-right: 0;
  256. }
  257. }
  258. }
  259. }
  260. }
  261. }
  262. .room-settings {
  263. background-color: $background-color-light;
  264. border-radius: $border-radius-small;
  265. padding: $spacing-large;
  266. margin-bottom: $spacing-large;
  267. box-shadow: $shadow-light;
  268. .setting-item {
  269. margin-bottom: $spacing-large;
  270. position: relative;
  271. .setting-label {
  272. font-size: $font-size-small;
  273. color: $text-color-secondary;
  274. margin-bottom: $spacing-mini;
  275. }
  276. .random-name {
  277. position: absolute;
  278. right: 0;
  279. top: 0;
  280. font-size: $font-size-small;
  281. color: $primary-color;
  282. padding: $spacing-mini $spacing-small;
  283. cursor: pointer;
  284. }
  285. .setting-slider {
  286. display: flex;
  287. align-items: center;
  288. margin-top: $spacing-base;
  289. .slider-value {
  290. width: 60px;
  291. text-align: center;
  292. font-size: $font-size-small;
  293. color: $text-color-primary;
  294. font-weight: $font-weight-medium;
  295. }
  296. }
  297. .visibility-options {
  298. margin-top: $spacing-base;
  299. }
  300. }
  301. }
  302. .action-buttons {
  303. padding: $spacing-large 0;
  304. .create-button {
  305. height: 44px;
  306. font-size: $font-size-medium;
  307. border-radius: $border-radius-base;
  308. }
  309. }
  310. }
  311. </style>