wuzj пре 5 дана
родитељ
комит
69578500a7
6 измењених фајлова са 133 додато и 120 уклоњено
  1. 2 2
      package.json
  2. 15 8
      pnpm-lock.yaml
  3. 39 61
      src/components/RoomCode/index.vue
  4. 1 1
      src/pages/room/create/index.vue
  5. 75 47
      src/pages/room/waiting/index.vue
  6. 1 1
      src/services/user.ts

+ 2 - 2
package.json

@@ -42,8 +42,8 @@
     "@tarojs/shared": "4.0.9",
     "@tarojs/taro": "4.0.9",
     "pinia": "^3.0.1",
-    "qrcode-base64": "^1.0.1",
-    "vue": "^3.3.0"
+    "vue": "^3.3.0",
+    "weapp-qrcode": "^1.0.0"
   },
   "devDependencies": {
     "@babel/core": "^7.8.0",

+ 15 - 8
pnpm-lock.yaml

@@ -62,12 +62,12 @@ importers:
       pinia:
         specifier: ^3.0.1
         version: 3.0.1(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))
-      qrcode-base64:
-        specifier: ^1.0.1
-        version: 1.0.1
       vue:
         specifier: ^3.3.0
         version: 3.5.13(typescript@5.8.2)
+      weapp-qrcode:
+        specifier: ^1.0.0
+        version: 1.0.0
     devDependencies:
       '@babel/core':
         specifier: ^7.8.0
@@ -3205,6 +3205,9 @@ packages:
     resolution: {integrity: sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==}
     engines: {node: '>=4'}
 
+  extend@3.0.2:
+    resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
+
   external-editor@3.1.0:
     resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
     engines: {node: '>=4'}
@@ -5119,9 +5122,6 @@ packages:
     resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
     engines: {node: '>=6'}
 
-  qrcode-base64@1.0.1:
-    resolution: {integrity: sha512-B0BDmnRSvbj47Nq/+IeZjj22afNrf4rwIloNrSXN6OwnkoO+Q0hsUa53gCEfqNQjx+OcytF3azvQhi6wV2Tk6g==}
-
   qs@6.13.0:
     resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
     engines: {node: '>=0.6'}
@@ -6112,6 +6112,9 @@ packages:
   wcwidth@1.0.1:
     resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
 
+  weapp-qrcode@1.0.0:
+    resolution: {integrity: sha512-4sa3W0rGDVJ9QaeZpAKlAuUxVyjhDwiUqHyGK/jJMsRMXnhb4yO8qWU/pZruMo+iT5J6CraS67lDMFb1VY+RaA==}
+
   webidl-conversions@7.0.0:
     resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
     engines: {node: '>=12'}
@@ -9932,6 +9935,8 @@ snapshots:
       ext-list: 2.2.2
       sort-keys-length: 1.0.1
 
+  extend@3.0.2: {}
+
   external-editor@3.1.0:
     dependencies:
       chardet: 0.7.0
@@ -11808,8 +11813,6 @@ snapshots:
 
   punycode@2.3.1: {}
 
-  qrcode-base64@1.0.1: {}
-
   qs@6.13.0:
     dependencies:
       side-channel: 1.1.0
@@ -12915,6 +12918,10 @@ snapshots:
     dependencies:
       defaults: 1.0.4
 
+  weapp-qrcode@1.0.0:
+    dependencies:
+      extend: 3.0.2
+
   webidl-conversions@7.0.0: {}
 
   webpack-chain@6.5.1:

+ 39 - 61
src/components/RoomCode/index.vue

@@ -6,7 +6,7 @@
       <nut-button size="small" type="primary" @click="handleShare">分享房间</nut-button>
       <nut-button size="small" plain @click="handleCopy">复制</nut-button>
     </view>
-    
+
     <!-- 分享弹窗 -->
     <nut-dialog
       title="分享房间"
@@ -17,7 +17,7 @@
       <template #footer>
         <view class="share-dialog-content">
           <view class="qrcode-container">
-            <image id="qrcode" class="qrcode-img" :src="qrcodeUrl" />
+            <canvas canvas-id="qrcode" id="qrcode" class="qrcode-canvas" />
           </view>
           <view class="room-info">
             <view class="room-id">房间号: {{ code }}</view>
@@ -32,57 +32,55 @@
 </template>
 
 <script setup lang="ts">
-import { defineProps, defineEmits, ref } from 'vue';
+import { defineProps, ref } from 'vue';
 import Taro from '@tarojs/taro';
-import qrcodeBase64 from 'qrcode-base64';
+import drawQrcode from 'weapp-qrcode';
 
 const props = defineProps({
   code: { type: String, required: true },
   password: { type: String, default: '' }
 });
 
-const emit = defineEmits(['share', 'copy']);
-
-// 将 showDialog 暴露给模板
 const showDialog = ref(false);
-const qrcodeUrl = ref('');
 
 // 生成二维码
 const generateQRCode = () => {
   if (!props.code) return;
-  
+
   // 构建房间链接数据
   let roomData = `room?id=${props.code}`;
-  
-  // 如果有密码,也加入密码
   if (props.password) {
     roomData += `&pwd=${props.password}`;
   }
-  
+
   try {
-    // 使用qrcode-base64库生成二维码
-    qrcodeUrl.value = qrcodeBase64.generateQRCodeBase64(roomData, {
-      size: 200,
-      margin: 2,
-      color: {
-        dark: '#333333',
-        light: '#FFFFFF'
-      }
-    });
+      console.log('开始生成二维码:', roomData);
+      
+      // 使用 weapp-qrcode 绘制二维码到 Canvas
+      drawQrcode({
+        width: 200,
+        height: 200,
+        canvasId: 'qrcode', // Canvas 的 ID
+        text: roomData,
+        // 以下是可选的配置
+        background: '#ffffff',
+        foreground: '#000000',
+        ctx: Taro.createCanvasContext('qrcode'), // 显式提供上下文
+      });
+      
+      console.log('二维码生成完成');
   } catch (error) {
     console.error('生成二维码失败:', error);
   }
 };
 
 const handleShare = () => {
-  // 生成二维码
-  generateQRCode();
-  
-  // 显示分享弹窗
   showDialog.value = true;
   
-  // 触发分享事件
-  emit('share', props.code);
+  // 等待弹窗完全显示后再生成二维码
+  setTimeout(() => {
+    generateQRCode();
+  }, 300);
 };
 
 const handleCopy = () => {
@@ -92,69 +90,49 @@ const handleCopy = () => {
       Taro.showToast({
         title: '房间码已复制',
         icon: 'success',
-        duration: 2000
+        duration: 2000,
       });
-      emit('copy', props.code);
-    }
+    },
   });
 };
 </script>
 
 <style lang="scss">
 .room-code {
-  background-color: $background-color-gold;
-  border-radius: $border-radius-small;
-  padding: $spacing-base;
-  margin: $spacing-base 0;
   text-align: center;
-  
+
   &__title {
-    font-size: $font-size-small;
-    color: $text-color-secondary;
-    margin-bottom: $spacing-mini;
+    margin-bottom: 10px;
   }
-  
+
   &__value {
-    font-size: $font-size-xlarge;
-    font-weight: $font-weight-bold;
-    color: $text-color-primary;
-    letter-spacing: 2px;
-    margin-bottom: $spacing-base;
+    font-size: 24px;
+    font-weight: bold;
+    margin-bottom: 20px;
   }
-  
+
   &__actions {
     display: flex;
     justify-content: center;
-    gap: $spacing-small;
+    gap: 10px;
   }
 }
 
 .share-dialog-content {
-  padding: $spacing-base;
-  
   .qrcode-container {
     margin: 0 auto;
     width: 200px;
     height: 200px;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    
-    .qrcode-img {
+
+    .qrcode-canvas {
       width: 100%;
       height: 100%;
     }
   }
-  
+
   .room-info {
-    margin-top: $spacing-large;
+    margin-top: 20px;
     text-align: center;
-    
-    .room-id, .room-password {
-      font-size: $font-size-base;
-      color: $text-color-primary;
-      margin-bottom: $spacing-small;
-    }
   }
 }
 </style>

+ 1 - 1
src/pages/room/create/index.vue

@@ -183,7 +183,7 @@ export default {
         
         // 构建房间数据 - 使用滑块选择的最大人数
         const roomData = {
-          id: `room_${Date.now()}_${Math.floor(Math.random() * 1000)}`,
+          id: `Room_${Math.floor(Math.random() * 1000)}`,
           name: roomName.value,
           gameId: gameInfo.value.id || '',
           gameTitle: gameInfo.value.title || '',

+ 75 - 47
src/pages/room/waiting/index.vue

@@ -33,23 +33,32 @@
           游戏设置
         </nut-divider>
         
-        <!-- 主题选择 -->
-        <view class="setting-item">
-          <view class="setting-label">游戏主题</view>
-          <view class="setting-value" @click="showThemeSelector = true">
-            {{ selectedTheme?.text || '请选择主题' }}
-            <down size="12" color="#999"></down>
-          </view>
-        </view>
-        
-        <!-- 题目选择 -->
-        <view class="setting-item" v-if="selectedTheme">
-          <view class="setting-label">游戏题目</view>
-          <view class="setting-value" @click="showPuzzleSelector = true">
-            {{ selectedPuzzle?.text || '请选择题目' }}
-            <down size="12" color="#999"></down>
-          </view>
-        </view>
+        <!-- 主题选择器 -->
+        <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">
@@ -303,11 +312,17 @@ export default {
     
     // 游戏设置相关变量
     const selectedDifficulty = ref(TurtleSoupDifficulty.MEDIUM)
-    const selectedTheme = ref<CascaderOption | null>(null)
-    const selectedPuzzle = ref<CascaderOption | null>(null)
     const showThemeSelector = ref(false)
     const showPuzzleSelector = ref(false)
     
+    // 主题和题目选择值
+    const selectedThemeValue = ref(['']) // 初始化为空数组或者根据默认值设置
+    const selectedPuzzleValue = ref(['']) // 初始化为空数组或者根据默认值设置
+    
+    // 选中的主题和题目对象,用于显示
+    const selectedTheme = ref<CascaderOption | null>(null)
+    const selectedPuzzle = ref<CascaderOption | null>(null)
+    
     // 主题选项
     const themeOptions = ref<CascaderOption[]>([
       {
@@ -329,7 +344,7 @@ export default {
       {
         value: 'scifi',
         text: '科幻主题',
-        disabled: false, // 暂时改为false或删除此属性
+        disabled: false,
         children: [
           { value: 'space', text: '太空探险' },
           { value: 'future', text: '未来世界' }
@@ -415,13 +430,11 @@ export default {
         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, // 根据是否解锁决定是否可选
-            children: [] // 选择主题后会动态加载题目列表
+            disabled: theme.isLocked
           }))
         }
       } catch (error) {
@@ -440,7 +453,7 @@ export default {
           puzzleOptions.value = puzzles.map(puzzle => ({
             value: puzzle.id,
             text: puzzle.title,
-            disabled: puzzle.isLocked // 使用isLocked替换!puzzle.isUnlocked
+            disabled: puzzle.isLocked
           }))
         } else {
           puzzleOptions.value = []
@@ -451,21 +464,38 @@ export default {
     }
     
     // 监听主题选择变化
-    const onThemeChange = async ({ value, selectedOptions }: any) => {
-      // 保存选中的主题
-      selectedTheme.value = selectedOptions[0]
-      
-      // 加载该主题下的题目
-      await loadPuzzles(value[0])
-      
-      // 重置题目选择
-      selectedPuzzle.value = null
+    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 onPuzzleChange = ({ selectedOptions }: any) => {
-      // 保存选中的题目
-      selectedPuzzle.value = selectedOptions[0]
+    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
+      }
     }
     
     // 准备/取消准备(玩家)
@@ -575,6 +605,15 @@ export default {
       // 这里不需要额外处理,因为RoomCode组件内部已处理了分享逻辑
     }
     
+    // 监听难度变化,如果已选择主题则重新加载题目
+    watch(selectedDifficulty, async (newVal) => {
+      if (selectedTheme.value) {
+        await loadPuzzles(selectedTheme.value.value)
+        selectedPuzzle.value = null // 重置题目选择
+        selectedPuzzleValue.value = [''] // 重置级联选择器的值
+      }
+    })
+    
     // 页面加载时初始化
     onMounted(() => {
       initPage()
@@ -585,17 +624,6 @@ export default {
       stopRoomListener()
     })
     
-    // 监听难度变化,如果已选择主题则重新加载题目
-    watch(selectedDifficulty, async (newVal) => {
-      if (selectedTheme.value) {
-        await loadPuzzles(selectedTheme.value.value)
-        selectedPuzzle.value = null // 重置题目选择
-      }
-    })
-    
-    const selectedThemeValue = ref([])
-    const selectedPuzzleValue = ref([])
-    
     return {
       currentRoom,
       isHost,

+ 1 - 1
src/services/user.ts

@@ -9,7 +9,7 @@ import Taro from '@tarojs/taro'
 // Mock用户数据 - 扩展以包含更多字段
 const createMockUser = () => ({
   nickname: '测试用户',
-  avatar: 'https://example.com/avatar.jpg',
+  avatar: 'https://images.unsplash.com/photo-1582845512747-e42001c95638?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&h=100&q=80',
   openid: 'mock_openid_' + Date.now().toString().slice(-6),
   role: UserRole.USER,
   isRegistered: true,