wuzj il y a 1 semaine
Parent
commit
e403557399
4 fichiers modifiés avec 289 ajouts et 265 suppressions
  1. 1 1
      src/app.ts
  2. 128 37
      src/services/api/user.ts
  3. 149 227
      src/stores/user.ts
  4. 11 0
      src/utils/db-helper.ts

+ 1 - 1
src/app.ts

@@ -31,7 +31,7 @@ const App = createApp({
     tabBarStore.updateSelected()
     
     // 获取用户信息
-    userStore.getUserInfo()
+    userStore.initUser()
   },
   onShow(options) {
     console.log('App onShow', options)

+ 128 - 37
src/services/api/user.ts

@@ -1,62 +1,153 @@
+// services/api/user.ts
 import Taro from '@tarojs/taro'
+import { ensureStringId } from '@/utils/db-helper'
+
+// 明确定义接口类型
+export interface UserInfo {
+  openid: string;
+  nickname?: string;
+  avatar?: string;
+  role?: string;
+}
+
+export interface OpenIdResult {
+  openid: string;
+  [key: string]: any;
+}
+
+export interface ApiResult<T> {
+  success: boolean;
+  data?: T;
+  message?: string;
+}
 
 // 控制是否使用云函数(true)或mock数据(false)
-const USE_CLOUD = false
+const USE_CLOUD = true
 
 // Mock用户数据
 const mockUser = {
-    nickname: '测试用户',
-    avatar: 'https://example.com/avatar.jpg',
-    openid: 'mock_openid_123456',
-  }
+  nickname: '测试用户',
+  avatar: 'https://example.com/avatar.jpg',
+  openid: 'mock_openid_123456',
+}
 
 export const userAPI = {
-    async getUserInfo() {
+  // 获取用户信息
+  async getUserInfo(): Promise<ApiResult<UserInfo>> {
+    try {
       if (USE_CLOUD && process.env.TARO_ENV === 'weapp') {
-        try {
-          // 云函数实现
-          const result = await Taro.cloud.callFunction({
-            name: 'getUserInfo',
-          })
-          
-          // 使用类型断言处理结果
-          if (result && typeof result === 'object' && 'result' in result) {
-            const cloudResult = result.result as any
-            return cloudResult?.data || mockUser
+        // 云函数实现
+        const result = await Taro.cloud.callFunction({
+          name: 'getUserInfo',
+        })
+        
+        // 使用类型断言处理结果
+        if (result && typeof result === 'object' && 'result' in result) {
+          const cloudResult = result.result as any
+          return { 
+            success: true, 
+            data: cloudResult?.data || mockUser 
           }
-          return mockUser
-        } catch (error) {
-          console.error('获取用户信息失败:', error)
-          return mockUser
         }
+        return { success: false, message: '获取用户信息失败' }
       } else {
         // Mock实现
         return new Promise(resolve => {
           setTimeout(() => {
-            resolve(mockUser)
+            resolve({ success: true, data: mockUser })
           }, 300)
         })
       }
-    },
-    
-    // 新增方法:获取OpenID
-    async getOpenId(code: string) {
+    } catch (error) {
+      console.error('获取用户信息失败:', error)
+      return { success: false, message: error.message || '获取用户信息失败' }
+    }
+  },
+  
+  // 获取OpenID
+  async getOpenId(code: string): Promise<ApiResult<OpenIdResult>> {
+    try {
       if (USE_CLOUD && process.env.TARO_ENV === 'weapp') {
-        try {
-          const result = await Taro.cloud.callFunction({
-            name: 'getOpenId',
-            data: { code }
-          })
-          
-          if (result && result.result) {
-            return result.result
-          }
-        } catch (error) {
-          console.error('获取OpenID失败:', error)
+        const result = await Taro.cloud.callFunction({
+          name: 'getOpenId',
+          data: { code }
+        })
+        
+        if (result && result.result) {
+          return { success: true, data: result.result as OpenIdResult }
         }
+        return { success: false, message: '获取OpenID失败' }
       }
       
       // Mock实现
-      return { openid: 'mock_openid_' + Date.now() }
+      return { 
+        success: true, 
+        data: { openid: 'mock_openid_' + Date.now() } 
+      }
+    } catch (error) {
+      console.error('获取OpenID失败:', error)
+      return { success: false, message: error.message || '获取OpenID失败' }
     }
+  },
+  
+  // 保存用户信息到云端
+  async saveUserToCloud(userData: UserInfo): Promise<ApiResult<string>> {
+    try {
+      if (!USE_CLOUD || process.env.TARO_ENV !== 'weapp') {
+        return { success: true, data: 'mock_doc_id' } // Mock成功
+      }
+      
+      // 确保云已初始化
+      try {
+        await Taro.cloud.init({
+          env: (Taro.cloud as any).DYNAMIC_CURRENT_ENV
+        })
+      } catch (initError) {
+        console.warn('云初始化可能已完成:', initError)
+        // 继续执行,即使初始化报错
+      }
+      
+      const db = Taro.cloud.database()
+      
+      // 查询用户是否存在
+      const userQuery = await db.collection('users').where({
+        openid: userData.openid
+      }).get()
+      
+      // 如果用户已存在,更新信息
+      if (userQuery.data && userQuery.data.length > 0) {
+        // 修复: 使用类型断言确保 _id 转换为字符串
+        const docId = ensureStringId(userQuery.data[0]._id)
+        
+        await db.collection('users').doc(docId).update({
+          data: {
+            nickname: userData.nickname,
+            avatar: userData.avatar,
+            role: userData.role,
+            updateTime: db.serverDate()
+          }
+        })
+        return { success: true, data: docId }
+      } else {
+        // 创建新用户
+        const addResult = await db.collection('users').add({
+          data: {
+            ...userData,
+            createTime: db.serverDate(),
+            updateTime: db.serverDate()
+          }
+        })
+        
+        // 修复: 确保返回的 _id 是字符串
+        const newId = typeof addResult._id === 'number' 
+          ? String(addResult._id) 
+          : addResult._id as string
+          
+        return { success: true, data: newId }
+      }
+    } catch (error) {
+      console.error('保存用户信息到云端失败:', error)
+      return { success: false, message: error.message || '保存用户信息失败' }
+    }
+  }
 }

+ 149 - 227
src/stores/user.ts

@@ -1,162 +1,168 @@
-//管理用户信息、登录状态、角色权限等
+// stores/user.ts - 管理用户信息、登录状态、角色权限等
 import { defineStore } from 'pinia'
 import Taro from '@tarojs/taro'
-import { userAPI } from '@/services/api/user'
-import { ref } from 'vue'
+import { userAPI, type UserInfo} from '@/services/api/user'
+import { ref, computed } from 'vue'
 
 export type UserRole = 'player' | 'hoster'
 
-// 定义API返回类型接口
-interface OpenIdResult {
-  openid: string;
-  [key: string]: any;
-}
-
-interface UserState {
-  role: UserRole
-  nickname: string
-  avatar: string
-  openid: string
-  roomId: string | null
-  currentGameId: string | null
-  isRegistered: boolean
-  currentRoom: string | null
-}
-
 export const useUserStore = defineStore('user', () => {
+  // 基本状态
   const openid = ref('')
   const nickname = ref('')
   const avatar = ref('')
-  const role = ref<'player' | 'hoster'>('player')
+  const role = ref<UserRole>('player')
   const isRegistered = ref(false)
   const currentRoom = ref<string | null>(null)
   const currentGameId = ref<string | null>(null)
+  const loading = ref(false)
+  const error = ref<string | null>(null)
+  
+  // 计算属性
+  const userInfo = computed<UserInfo>(() => ({
+    openid: openid.value,
+    nickname: nickname.value,
+    avatar: avatar.value,
+    role: role.value
+  }))
+  
+  // 保存用户状态到本地存储
+  function saveUserToStorage() {
+    const userData = {
+      openid: openid.value,
+      nickname: nickname.value,
+      avatar: avatar.value,
+      role: role.value,
+      currentRoom: currentRoom.value,
+      isRegistered: isRegistered.value,
+    }
+    
+    Taro.setStorageSync('userInfo', JSON.stringify(userData))
+    return userData
+  }
+  
+  // 从本地存储加载用户状态
+  function loadUserFromStorage(): boolean {
+    try {
+      const userInfo = Taro.getStorageSync('userInfo')
+      if (!userInfo) return false
+      
+      const data = JSON.parse(userInfo)
+      openid.value = data.openid || ''
+      nickname.value = data.nickname || ''
+      avatar.value = data.avatar || ''
+      role.value = data.role || 'player'
+      isRegistered.value = !!data.isRegistered
+      currentRoom.value = data.currentRoom || null
+      
+      return true
+    } catch (error) {
+      console.error('加载用户信息失败:', error)
+      return false
+    }
+  }
   
   // 初始化用户信息
   async function initUser() {
-    // 从本地存储加载用户信息
-    const userInfo = Taro.getStorageSync('userInfo')
-    if (userInfo) {
-      try {
-        const info = JSON.parse(userInfo)
-        openid.value = info.openid || ''
-        nickname.value = info.nickname || ''
-        avatar.value = info.avatar || ''
-        role.value = info.role || 'player'
-        isRegistered.value = !!info.nickname && !!info.openid
-        
-        // 如果有房间ID,恢复
-        if (info.currentRoom) {
-          currentRoom.value = info.currentRoom
-        }
-        
+    loading.value = true
+    error.value = null
+    
+    try {
+      // 1. 尝试从本地存储加载
+      const localDataLoaded = loadUserFromStorage()
+      
+      // 2. 如果本地有数据且已注册,直接返回成功
+      if (localDataLoaded && isRegistered.value) {
         return true
-      } catch (e) {
-        console.error('解析用户信息失败:', e)
       }
+      
+      // 3. 如果本地有openid但未注册,保持现状
+      if (localDataLoaded && openid.value && !isRegistered.value) {
+        return true
+      }
+      
+      // 4. 否则,进行静默登录获取openid
+      return await silentLogin()
+    } catch (e) {
+      console.error('初始化用户失败:', e)
+      error.value = '初始化用户失败'
+      return false
+    } finally {
+      loading.value = false
     }
-    
-    // 如果没有本地信息,尝试静默登录
-    return silentLogin()
   }
   
   // 静默登录 - 仅获取openid
   async function silentLogin() {
+    loading.value = true
+    error.value = null
+    
     try {
       const loginRes = await Taro.login()
-      if (loginRes.code) {
-        // 修复: 确保返回类型正确
-        const result = await userAPI.getOpenId(loginRes.code) as OpenIdResult
-        if (result && result.openid) {
-          openid.value = result.openid
-          
-          // 保存到本地
-          Taro.setStorageSync('userInfo', JSON.stringify({
-            openid: result.openid,
-            role: role.value
-          }))
-          
-          return true
-        }
+      if (!loginRes.code) {
+        error.value = '登录失败'
+        return false
       }
+      
+      const result = await userAPI.getOpenId(loginRes.code)
+      if (!result.success || !result.data) {
+        error.value = result.message || '获取OpenID失败'
+        return false
+      }
+      
+      // 设置openid
+      openid.value = result.data.openid
+      saveUserToStorage()
+      return true
+    } catch (e) {
+      console.error('静默登录失败:', e)
+      error.value = '静默登录失败'
       return false
-    } catch (error) {
-      console.error('静默登录失败:', error)
-      return false
+    } finally {
+      loading.value = false
     }
   }
   
   // 注册用户 - 获取用户信息
   async function registerUser() {
+    loading.value = true
+    error.value = null
+    
     try {
+      // 确保有openid
+      if (!openid.value) {
+        const loginSuccess = await silentLogin()
+        if (!loginSuccess) return false
+      }
+      
       // 微信环境使用getUserProfile
       if (process.env.TARO_ENV === 'weapp') {
         const profileRes = await Taro.getUserProfile({
           desc: '用于完善用户资料'
         })
         
-        if (profileRes.userInfo) {
-          nickname.value = profileRes.userInfo.nickName
-          avatar.value = profileRes.userInfo.avatarUrl
-          isRegistered.value = true
-          
-          // 保存到本地
-          const userInfo = {
-            openid: openid.value,
-            nickname: profileRes.userInfo.nickName,
-            avatar: profileRes.userInfo.avatarUrl,
-            role: role.value,
-            currentRoom: currentRoom.value
-          }
-          Taro.setStorageSync('userInfo', JSON.stringify(userInfo))
-          
-          // 同步到云端
-          if (process.env.TARO_ENV === 'weapp' && Taro.cloud) {
-            try {
-              // 修复: 使用正确的云初始化检查方式
-              // 注意: Taro.cloud.init()会返回void,不需要检查inited属性
-              await Taro.cloud.init({
-                env: (Taro.cloud as any).DYNAMIC_CURRENT_ENV
-              })
-              
-              // 注意:云开发数据库操作
-              const db = Taro.cloud.database()
-              const userQuery = await db.collection('users').where({
-                openid: openid.value
-              }).get()
-              
-              // 如果用户已存在,更新信息
-              if (userQuery.data && userQuery.data.length > 0) {
-                // 修复: 强制类型转换
-                const docId = userQuery.data[0]._id as string
-                await db.collection('users').doc(docId).update({
-                  data: {
-                    nickname: nickname.value,
-                    avatar: avatar.value,
-                    updateTime: db.serverDate()
-                  }
-                })
-              } else {
-                // 创建新用户
-                await db.collection('users').add({
-                  data: {
-                    openid: openid.value,
-                    nickname: nickname.value,
-                    avatar: avatar.value,
-                    role: role.value,
-                    createTime: db.serverDate(),
-                    updateTime: db.serverDate()
-                  }
-                })
-              }
-            } catch (cloudError) {
-              console.error('同步用户信息到云端失败:', cloudError)
-              // 继续执行,即使云同步失败
-            }
-          }
-          
-          return true
+        if (!profileRes.userInfo) {
+          error.value = '获取用户信息失败'
+          return false
         }
+        
+        // 更新本地状态
+        nickname.value = profileRes.userInfo.nickName
+        avatar.value = profileRes.userInfo.avatarUrl
+        isRegistered.value = true
+        
+        // 保存到本地存储
+        saveUserToStorage()
+        
+        // 同步到云端 - 使用Service层处理
+        await userAPI.saveUserToCloud({
+          openid: openid.value,
+          nickname: nickname.value,
+          avatar: avatar.value,
+          role: role.value
+        })
+        
+        return true
       } else {
         // 非微信环境模拟注册
         nickname.value = '模拟用户_' + Date.now().toString().slice(-4)
@@ -164,62 +170,28 @@ export const useUserStore = defineStore('user', () => {
         isRegistered.value = true
         
         // 保存到本地
-        const userInfo = {
-          openid: openid.value,
-          nickname: nickname.value,
-          avatar: avatar.value,
-          role: role.value,
-          currentRoom: currentRoom.value
-        }
-        Taro.setStorageSync('userInfo', JSON.stringify(userInfo))
-        
+        saveUserToStorage()
         return true
       }
-      
-      return false
-    } catch (error) {
-      console.error('用户注册失败:', error)
+    } catch (e) {
+      console.error('用户注册失败:', e)
+      error.value = '用户注册失败'
       return false
+    } finally {
+      loading.value = false
     }
   }
   
   // 设置用户角色
-  function setRole(newRole: 'player' | 'hoster') {
+  function setRole(newRole: UserRole) {
     role.value = newRole
-    
-    // 更新本地存储
-    const userInfo = Taro.getStorageSync('userInfo')
-    if (userInfo) {
-      try {
-        const info = JSON.parse(userInfo)
-        info.role = newRole
-        Taro.setStorageSync('userInfo', JSON.stringify(info))
-      } catch (e) {
-        console.error('更新用户角色失败:', e)
-      }
-    }
+    saveUserToStorage()
   }
   
   // 设置当前房间
   function setCurrentRoom(roomId: string) {
     currentRoom.value = roomId
-    
-    // 更新本地存储
-    const userInfo = Taro.getStorageSync('userInfo')
-    if (userInfo) {
-      try {
-        const info = JSON.parse(userInfo)
-        info.currentRoom = roomId
-        Taro.setStorageSync('userInfo', JSON.stringify(info))
-      } catch (e) {
-        console.error('更新当前房间失败:', e)
-      }
-    }
-  }
-  
-  // 检查是否已注册
-  function checkIsRegistered() {
-    return isRegistered.value
+    saveUserToStorage()
   }
   
   // 退出登录
@@ -229,84 +201,32 @@ export const useUserStore = defineStore('user', () => {
     avatar.value = ''
     isRegistered.value = false
     currentRoom.value = null
+    currentGameId.value = null
     
     // 清除本地存储
     Taro.removeStorageSync('userInfo')
   }
   
-  // 检查用户登录状态
-  async function checkLoginStatus() {
-    try {
-      // 从本地存储获取用户信息
-      const userInfo = Taro.getStorageSync('userInfo')
-      if (userInfo) {
-        const info = JSON.parse(userInfo)
-        openid.value = info.openid || ''
-        nickname.value = info.nickname || ''
-        avatar.value = info.avatar || ''
-        isRegistered.value = !!info.openid && !!info.nickname
-        return true
-      }
-      
-      // 如果没有本地信息,尝试静默登录获取openid
-      const loginRes = await Taro.login()
-      if (loginRes.code) {
-        // 修复: 确保返回类型正确
-        const result = await userAPI.getOpenId(loginRes.code) as OpenIdResult
-        if (result && result.openid) {
-          openid.value = result.openid
-          // 保存到本地
-          Taro.setStorageSync('userInfo', JSON.stringify({ openid: result.openid }))
-          return true
-        }
-      }
-      
-      return false
-    } catch (error) {
-      console.error('检查登录状态失败:', error)
-      return false
-    }
-  }
-  
   // 设置当前游戏
   function setCurrentGame(gameId: string | null) {
-    currentGameId.value = gameId  // 修复: 使用ref value
-  }
-  
-  // 获取用户信息
-  async function getUserInfo() {
-    try {
-      // 尝试从本地存储获取角色
-      const roleValue = Taro.getStorageSync('userRole') as UserRole | ''
-      if (roleValue) {
-        role.value = roleValue
-      }
-      
-      // 获取用户信息
-      const userData = await userAPI.getUserInfo()
-      if (userData) {
-        nickname.value = userData.nickname || '游客'
-        avatar.value = userData.avatar || ''
-        openid.value = userData.openid || ''
-      }
-    } catch (error) {
-      console.error('获取用户信息失败:', error)
-    }
+    currentGameId.value = gameId
   }
   
   // 重置用户角色到玩家
   function resetToPlayer() {
-    role.value = 'player'  // 修复: 使用ref value
-    Taro.setStorageSync('userRole', 'player')
+    role.value = 'player'
+    saveUserToStorage()
   }
   
   // 清除房间和游戏信息
   function clearGameSession() {
-    currentRoom.value = null  // 修复: 使用ref value
-    currentGameId.value = null  // 修复: 使用ref value
+    currentRoom.value = null
+    currentGameId.value = null
+    saveUserToStorage()
   }
   
   return {
+    // 状态
     openid,
     nickname,
     avatar,
@@ -314,16 +234,18 @@ export const useUserStore = defineStore('user', () => {
     isRegistered,
     currentRoom,
     currentGameId,
+    loading,
+    error,
+    userInfo,
+    
+    // 方法
     initUser,
     registerUser,
     setRole,
     setCurrentRoom,
-    checkIsRegistered,
     logout,
-    checkLoginStatus,
     setCurrentGame,
-    getUserInfo,
     resetToPlayer,
     clearGameSession
   }
-}) 
+})

+ 11 - 0
src/utils/db-helper.ts

@@ -0,0 +1,11 @@
+/**
+ * 确保文档ID是字符串类型
+ * @param id 云数据库文档ID(可能是字符串或数字)
+ * @returns 转换后的字符串ID
+ */
+export function ensureStringId(id: string | number | undefined): string {
+  if (id === undefined) {
+    return '';
+  }
+  return String(id);
+}