浏览代码

重构struture和store

wuzj 1 周之前
父节点
当前提交
47184ff18a

+ 20 - 13
README.md

@@ -354,20 +354,27 @@ src/
 │   │   └── play/       # 游戏进行中(带角色参数)
 │   │   └── play/       # 游戏进行中(带角色参数)
 │   ├── profile/        # 个人中心
 │   ├── profile/        # 个人中心
 │   └── history/        # 游戏历史
 │   └── history/        # 游戏历史
-├── services/           # API服务
-│   ├── api/            # API定义
-│   │   ├── game.ts     # 游戏
-│   │   ├── room.ts     # 房间
-│   │   └── index.ts    # 
-│   ├── request.ts      # 请求封装
-│   └── cloud.ts        # 云函数封装
+├── services/
+│   ├── api/
+│   │   ├── index.ts         # API 入口点,导出所有 API
+│   │   ├── game.ts          # 游戏列表/通用游戏信息的 API
+│   │   ├── room.ts          # 房间管理 API
+│   │   └── games/           # 特定游戏 API 目录
+│   │       ├── index.ts     # 导出所有游戏 API
+│   │       ├── turtlesoup.ts  # 海龟汤游戏 API
+│   │       ├── werewolf.ts    # 狼人杀游戏 API
+│   │       └── script.ts      # 剧本杀游戏 API
+│   └── cloud.ts             # 云函数调用封装
 ├── stores/             # Pinia状态管理
 ├── stores/             # Pinia状态管理
-│   ├── modules/        # 状态模块
-│   │   ├── user.ts     # 用户状态
-│   │   ├── room.ts     # 房间状态
-│   │   └── game.ts     # 游戏状态
-│   ├── tabbar/         # tabbar状态
-│   └── index.ts        # 状态入口
+│   ├── index.ts             # Store 入口点,导出所有 Store
+│   ├── user.ts              # 用户 Store
+│   ├── room.ts              # 房间 Store
+│   ├── game.ts              # 游戏列表/通用游戏 Store
+│   └── games/               # 特定游戏 Store 目录
+│       ├── index.ts         # 导出所有游戏 Store
+│       ├── turtlesoup.ts    # 海龟汤游戏 Store
+│       ├── werewolf.ts      # 狼人杀游戏 Store
+│       └── script.ts        # 剧本杀游戏 Store
 ├── types/              # TypeScript类型
 ├── types/              # TypeScript类型
 │   ├── global.d.ts      # 全局
 │   ├── global.d.ts      # 全局
 │   └── vue.d.ts         # vue
 │   └── vue.d.ts         # vue

+ 1 - 0
components.d.ts

@@ -31,5 +31,6 @@ declare module 'vue' {
     QuestionCard: typeof import('./src/components/QuestionCard/index.vue')['default']
     QuestionCard: typeof import('./src/components/QuestionCard/index.vue')['default']
     RoomCode: typeof import('./src/components/RoomCode/index.vue')['default']
     RoomCode: typeof import('./src/components/RoomCode/index.vue')['default']
     Tabbar: typeof import('./src/components/Tabbar.vue')['default']
     Tabbar: typeof import('./src/components/Tabbar.vue')['default']
+    WereWolf: typeof import('./src/components/WereWolf.vue')['default']
   }
   }
 }
 }

+ 262 - 0
src/components/WereWolf.vue

@@ -0,0 +1,262 @@
+<template>
+    <div class="werewolf-game">
+      <div v-if="loading">加载中...</div>
+      <div v-else-if="error">{{ error }}</div>
+      <div v-else-if="currentGame">
+        <!-- 游戏信息 -->
+        <div class="game-info">
+          <h1>{{ currentGame.title }}</h1>
+          <div class="game-status">
+            <span>当前回合: {{ currentGame.currentRound }}</span>
+            <span>阶段: {{ phaseText }}</span>
+            <span v-if="remainingTime > 0">剩余时间: {{ remainingTime }}秒</span>
+          </div>
+        </div>
+        
+        <!-- 角色信息 -->
+        <div class="role-info" v-if="myRole">
+          <h2>你的角色: {{ roleText }}</h2>
+          <button @click="showRoleInformation">查看角色介绍</button>
+        </div>
+        
+        <!-- 玩家列表 -->
+        <div class="players-list">
+          <h2>玩家列表</h2>
+          <div class="player-item" 
+               v-for="player in currentGame.players" 
+               :key="player.id"
+               :class="{ 
+                 'is-dead': player.isDead, 
+                 'is-current': player.id === currentPlayer?.id,
+                 'is-selectable': isPlayerSelectable(player.id)
+               }"
+               @click="handlePlayerClick(player.id)">
+            <img :src="player.avatar" :alt="player.name" />
+            <div class="player-name">{{ player.name }}</div>
+            <div class="player-status" v-if="player.isDead">已死亡</div>
+          </div>
+        </div>
+        
+        <!-- 游戏日志 -->
+        <div class="game-logs">
+          <h2>游戏日志</h2>
+          <div class="log-item" v-for="(log, index) in currentGame.logs" :key="index">
+            <span class="log-round">第{{ log.round }}轮</span>
+            <span class="log-phase">{{ getPhaseText(log.phase) }}</span>
+            <span class="log-content">{{ log.content }}</span>
+          </div>
+        </div>
+        
+        <!-- 游戏结果 -->
+        <div class="game-result" v-if="gameResult">
+          <h2>游戏结束!</h2>
+          <div class="result-text">{{ resultText }}</div>
+        </div>
+      </div>
+      <div v-else>
+        <button @click="loadWerewolfGame('game_id')">加载游戏</button>
+      </div>
+    </div>
+  </template>
+  
+  <script lang="ts">
+  import { computed, ref } from 'vue'
+  import { useWerewolfStore } from '@/stores/games/werewolf'
+  
+  export default {
+    setup() {
+      const store = useWerewolfStore()
+      
+      // 计算属性
+      const currentGame = computed(() => store.currentGame)
+      const currentPlayer = computed(() => store.currentPlayer)
+      const loading = computed(() => store.loading)
+      const error = computed(() => store.error)
+      const myRole = computed(() => store.myRole)
+      const remainingTime = computed(() => store.remainingTime)
+      const gameResult = computed(() => store.gameResult)
+      
+      // 角色文本显示
+      const roleText = computed(() => {
+        switch (myRole.value) {
+          case 'werewolf': return '狼人';
+          case 'villager': return '村民';
+          case 'seer': return '预言家';
+          case 'witch': return '女巫';
+          case 'hunter': return '猎人';
+          case 'guard': return '守卫';
+          default: return '未知角色';
+        }
+      })
+      
+      // 阶段文本显示
+      const phaseText = computed(() => {
+        if (!currentGame.value) return '';
+        
+        switch (currentGame.value.phase) {
+          case 'waiting': return '等待开始';
+          case 'night': return '夜晚';
+          case 'day': return '白天讨论';
+          case 'vote': return '投票';
+          case 'ended': return '游戏结束';
+          default: return '';
+        }
+      })
+      
+      // 结果文本显示
+      const resultText = computed(() => {
+        switch (gameResult.value) {
+          case 'werewolf_win': return '狼人阵营胜利!';
+          case 'villager_win': return '村民阵营胜利!';
+          case 'couple_win': return '情侣胜利!';
+          case 'draw': return '平局!';
+          default: return '';
+        }
+      })
+      
+      // 阶段文本转换
+      const getPhaseText = (phase: string) => {
+        switch (phase) {
+          case 'waiting': return '等待开始';
+          case 'night': return '夜晚';
+          case 'day': return '白天';
+          case 'vote': return '投票';
+          case 'ended': return '游戏结束';
+          default: return phase;
+        }
+      }
+      
+      // 检查玩家是否可选择
+      const isPlayerSelectable = (playerId: string) => {
+        const targets = store.availableTargets;
+        return targets.some(p => p.id === playerId);
+      }
+      
+      // 处理玩家点击
+      const handlePlayerClick = (playerId: string) => {
+        if (!isPlayerSelectable(playerId)) return;
+        
+        const game = currentGame.value;
+        if (!game) return;
+        
+        // 根据当前游戏阶段和行动类型执行相应操作
+        if (game.phase === 'night') {
+          switch (game.currentAction) {
+            case 'kill': store.killPlayer(playerId); break;
+            case 'check': store.checkPlayer(playerId); break;
+            case 'save': store.savePlayer(playerId); break;
+            case 'poison': store.poisonPlayer(playerId); break;
+            case 'protect': store.protectPlayer(playerId); break;
+          }
+        } else if (game.phase === 'vote') {
+          store.votePlayer(playerId);
+        }
+      }
+      
+      // 加载游戏
+      const loadWerewolfGame = async (gameId: string) => {
+        await store.loadGame(gameId, 'player');
+      }
+      
+      return {
+        currentGame,
+        currentPlayer,
+        loading,
+        error,
+        myRole,
+        remainingTime,
+        gameResult,
+        roleText,
+        phaseText,
+        resultText,
+        getPhaseText,
+        isPlayerSelectable,
+        handlePlayerClick,
+        loadWerewolfGame,
+        showRoleInformation: () => store.showRoleInformation()
+      }
+    }
+  }
+  </script>
+  
+  <style scoped>
+  .werewolf-game {
+    padding: 20px;
+  }
+  
+  .game-info {
+    margin-bottom: 20px;
+  }
+  
+  .role-info {
+    margin-bottom: 20px;
+    padding: 10px;
+    background-color: #f5f5f5;
+    border-radius: 5px;
+  }
+  
+  .players-list {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
+    gap: 10px;
+    margin-bottom: 20px;
+  }
+  
+  .player-item {
+    padding: 10px;
+    border: 1px solid #ddd;
+    border-radius: 5px;
+    text-align: center;
+    cursor: pointer;
+  }
+  
+  .player-item.is-dead {
+    opacity: 0.5;
+  }
+  
+  .player-item.is-current {
+    border-color: #3c92fb;
+    background-color: rgba(60, 146, 251, 0.1);
+  }
+  
+  .player-item.is-selectable {
+    border-color: #4caf50;
+    background-color: rgba(76, 175, 80, 0.1);
+  }
+  
+  .player-item img {
+    width: 50px;
+    height: 50px;
+    border-radius: 50%;
+    object-fit: cover;
+  }
+  
+  .game-logs {
+    margin-bottom: 20px;
+    max-height: 200px;
+    overflow-y: auto;
+  }
+  
+  .log-item {
+    padding: 5px;
+    border-bottom: 1px solid #eee;
+  }
+  
+  .log-round, .log-phase {
+    margin-right: 10px;
+    font-weight: bold;
+  }
+  
+  .game-result {
+    padding: 20px;
+    background-color: #f5f5f5;
+    border-radius: 5px;
+    text-align: center;
+  }
+  
+  .result-text {
+    font-size: 24px;
+    font-weight: bold;
+    color: #3c92fb;
+  }
+  </style>

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

@@ -66,7 +66,7 @@ import { ref } from 'vue'
 import { useGameStore, type Game } from '@/stores/game'
 import { useGameStore, type Game } from '@/stores/game'
 import { useUserStore } from '@/stores/user'
 import { useUserStore } from '@/stores/user'
 import { useRoomStore } from '@/stores/room'
 import { useRoomStore } from '@/stores/room'
-import { roomAPI } from '@/services/api'
+import { roomAPI } from '@/services/api/room'
 import Tabbar from '@/components/Tabbar.vue'
 import Tabbar from '@/components/Tabbar.vue'
 
 
 // 随机房间名称列表
 // 随机房间名称列表

+ 0 - 203
src/services/api.ts

@@ -1,203 +0,0 @@
-import Taro from '@tarojs/taro'
-import { Game } from '@/stores/game'
-
-// 控制是否使用云函数(true)或mock数据(false)
-const USE_CLOUD = false
-
-// Mock数据
-const mockGames: Game[] = [
-  {
-    id: '1',
-    title: '海龟汤',
-    image: 'https://images.unsplash.com/photo-1582845512747-e42001c95638?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&h=100&q=80',
-    players: '3-10',
-    duration: '30-60',
-    rating: 4.8,
-    isNew: true,
-    isHot: true,
-    description: '海龟汤是一种猜谜游戏,游戏开始时,主持人会讲述一个故事的结果,参与者需要通过提问来猜测故事的真相。',
-    rules: '1. 主持人讲述一个故事的结果\n2. 参与者通过提问来猜测故事的真相\n3. 主持人只能回答"是"、"否"或"不重要"',
-    category: '推理',
-    tips: [
-      '提问时要注意逻辑思维',
-      '尝试从多个角度思考问题',
-      '注意主持人的回答,特别是"不重要"的部分'
-    ],
-    examples: [
-      {
-        question: '一个人走进酒吧,点了一杯水,喝完就离开了。为什么?',
-        answer: '这个人有打嗝的困扰,想通过喝水来缓解。'
-      }
-    ]
-  },
-  {
-    id: '2',
-    title: '剧本杀',
-    image: 'https://images.unsplash.com/photo-1529156069898-49953e39b3ac?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&h=100&q=80',
-    players: '4-8',
-    duration: '120-180',
-    rating: 4.5,
-    isNew: false,
-    isHot: true,
-    description: '剧本杀是一种角色扮演游戏,每个玩家扮演一个角色,通过阅读剧本和相互交流来解决一个谜题。',
-    rules: '1. 每个玩家扮演一个角色\n2. 阅读剧本并获取个人信息\n3. 通过交流和推理解决谜题',
-    category: '角色扮演',
-    tips: [
-      '认真阅读你的角色背景',
-      '注意收集和分析信息',
-      '积极参与角色扮演和讨论'
-    ],
-    examples: [
-      {
-        question: '谁是凶手?',
-        answer: '根据线索和证据,管家是凶手。'
-      }
-    ]
-  },
-  {
-    id: '3',
-    title: '狼人杀',
-    image: 'https://images.unsplash.com/photo-1529156069898-49953e39b3ac?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&h=100&q=80',
-    players: '6-12',
-    duration: '30-45',
-    rating: 4.7,
-    isNew: false,
-    isHot: true,
-    description: '狼人杀是一种桌游,玩家分为狼人和村民两个阵营,狼人试图消灭村民,村民则要找出并消灭狼人。',
-    rules: '1. 玩家分为狼人和村民两个阵营\n2. 夜晚狼人选择一名玩家"杀死"\n3. 白天所有人讨论并投票处决一名玩家',
-    category: '桌游',
-    tips: [
-      '仔细观察其他玩家的言行',
-      '理性分析每晚的死亡情况',
-      '善用自己的角色技能'
-    ],
-    examples: [
-      {
-        question: '如何判断谁是狼人?',
-        answer: '通过分析发言逻辑、投票行为和表情变化,找出可疑的玩家。'
-      }
-    ]
-  }
-]
-
-// Mock用户数据
-const mockUser = {
-  nickname: '测试用户',
-  avatar: 'https://example.com/avatar.jpg',
-  openid: 'mock_openid_123456',
-}
-
-// API方法
-export const gameAPI = {
-  async getGames(): Promise<Game[]> {
-    if (USE_CLOUD && process.env.TARO_ENV === 'weapp') {
-      try {
-        // 云函数实现
-        const result = await Taro.cloud.callFunction({
-          name: 'getGames',
-        })
-        
-        // 使用类型断言处理结果
-        if (result && typeof result === 'object' && 'result' in result) {
-          const cloudResult = result.result as any
-          return cloudResult?.data || mockGames
-        }
-        return mockGames
-      } catch (error) {
-        console.error('获取游戏列表失败:', error)
-        return mockGames
-      }
-    } else {
-      // Mock实现
-      return new Promise(resolve => {
-        setTimeout(() => {
-          resolve(mockGames)
-        }, 500) // 模拟网络延迟
-      })
-    }
-  },
-
-  async getGameDetail(id: string): Promise<Game | null> {
-    if (USE_CLOUD && process.env.TARO_ENV === 'weapp') {
-      try {
-        // 云函数实现
-        const result = await Taro.cloud.callFunction({
-          name: 'getGameDetail',
-          data: { id }
-        })
-        
-        // 使用类型断言处理结果
-        if (result && typeof result === 'object' && 'result' in result) {
-          const cloudResult = result.result as any
-          return cloudResult?.data || null
-        }
-        return null
-      } catch (error) {
-        console.error('获取游戏详情失败:', error)
-        return null
-      }
-    } else {
-      // Mock实现
-      return new Promise(resolve => {
-        setTimeout(() => {
-          const game = mockGames.find(g => g.id === id)
-          resolve(game || null)
-        }, 300) // 模拟网络延迟
-      })
-    }
-  }
-}
-
-export const userAPI = {
-  async getUserInfo() {
-    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
-        }
-        return mockUser
-      } catch (error) {
-        console.error('获取用户信息失败:', error)
-        return mockUser
-      }
-    } else {
-      // Mock实现
-      return new Promise(resolve => {
-        setTimeout(() => {
-          resolve(mockUser)
-        }, 300)
-      })
-    }
-  },
-  
-  // 新增方法:获取OpenID
-  async getOpenId(code: string) {
-    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)
-      }
-    }
-    
-    // Mock实现
-    return { openid: 'mock_openid_' + Date.now() }
-  }
-}
-
-// 导出roomAPI
-export { roomAPI } from './api/room' 

+ 137 - 42
src/services/api/game.ts

@@ -1,47 +1,142 @@
-import { callCloudFunction } from '../cloud'
-import type { TurtleSoupGame, GameResult } from '@/types/game'
+import Taro from '@tarojs/taro'
+import { Game } from '@/stores/game'
 
 
-/**
- * 获取游戏数据
- * @param gameId 游戏ID
- * @param role 用户角色
- */
-export function getGameData(gameId: string, role: string) {
-  return callCloudFunction<{ game: TurtleSoupGame }>('getGameData', { gameId, role })
-}
+// 控制是否使用云函数(true)或mock数据(false)
+const USE_CLOUD = false
 
 
-/**
- * 提交问题
- * @param gameId 游戏ID
- * @param content 问题内容
- */
-export function submitQuestion(gameId: string, content: string) {
-  return callCloudFunction<{ questionId: string }>('submitQuestion', { gameId, content })
-}
+// Mock数据
+const mockGames: Game[] = [
+  {
+    id: '1',
+    title: '海龟汤',
+    image: 'https://images.unsplash.com/photo-1582845512747-e42001c95638?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&h=100&q=80',
+    players: '3-10',
+    duration: '30-60',
+    rating: 4.8,
+    isNew: true,
+    isHot: true,
+    description: '海龟汤是一种猜谜游戏,游戏开始时,主持人会讲述一个故事的结果,参与者需要通过提问来猜测故事的真相。',
+    rules: '1. 主持人讲述一个故事的结果\n2. 参与者通过提问来猜测故事的真相\n3. 主持人只能回答"是"、"否"或"不重要"',
+    category: '推理',
+    tips: [
+      '提问时要注意逻辑思维',
+      '尝试从多个角度思考问题',
+      '注意主持人的回答,特别是"不重要"的部分'
+    ],
+    examples: [
+      {
+        question: '一个人走进酒吧,点了一杯水,喝完就离开了。为什么?',
+        answer: '这个人有打嗝的困扰,想通过喝水来缓解。'
+      }
+    ]
+  },
+  {
+    id: '2',
+    title: '剧本杀',
+    image: 'https://images.unsplash.com/photo-1529156069898-49953e39b3ac?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&h=100&q=80',
+    players: '4-8',
+    duration: '120-180',
+    rating: 4.5,
+    isNew: false,
+    isHot: true,
+    description: '剧本杀是一种角色扮演游戏,每个玩家扮演一个角色,通过阅读剧本和相互交流来解决一个谜题。',
+    rules: '1. 每个玩家扮演一个角色\n2. 阅读剧本并获取个人信息\n3. 通过交流和推理解决谜题',
+    category: '角色扮演',
+    tips: [
+      '认真阅读你的角色背景',
+      '注意收集和分析信息',
+      '积极参与角色扮演和讨论'
+    ],
+    examples: [
+      {
+        question: '谁是凶手?',
+        answer: '根据线索和证据,管家是凶手。'
+      }
+    ]
+  },
+  {
+    id: '3',
+    title: '狼人杀',
+    image: 'https://images.unsplash.com/photo-1529156069898-49953e39b3ac?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&h=100&q=80',
+    players: '6-12',
+    duration: '30-45',
+    rating: 4.7,
+    isNew: false,
+    isHot: true,
+    description: '狼人杀是一种桌游,玩家分为狼人和村民两个阵营,狼人试图消灭村民,村民则要找出并消灭狼人。',
+    rules: '1. 玩家分为狼人和村民两个阵营\n2. 夜晚狼人选择一名玩家"杀死"\n3. 白天所有人讨论并投票处决一名玩家',
+    category: '桌游',
+    tips: [
+      '仔细观察其他玩家的言行',
+      '理性分析每晚的死亡情况',
+      '善用自己的角色技能'
+    ],
+    examples: [
+      {
+        question: '如何判断谁是狼人?',
+        answer: '通过分析发言逻辑、投票行为和表情变化,找出可疑的玩家。'
+      }
+    ]
+  }
+]
 
 
-/**
- * 回答问题
- * @param questionId 问题ID
- * @param answer 答案
- */
-export function answerQuestion(questionId: string, answer: string) {
-  return callCloudFunction<{ success: boolean }>('answerQuestion', { questionId, answer })
-}
+// 游戏通用API
+export const gameAPI = {
+  async getGames(): Promise<Game[]> {
+    if (USE_CLOUD && process.env.TARO_ENV === 'weapp') {
+      try {
+        // 云函数实现
+        const result = await Taro.cloud.callFunction({
+          name: 'getGames',
+        })
+        
+        // 使用类型断言处理结果
+        if (result && typeof result === 'object' && 'result' in result) {
+          const cloudResult = result.result as any
+          return cloudResult?.data || mockGames
+        }
+        return mockGames
+      } catch (error) {
+        console.error('获取游戏列表失败:', error)
+        return mockGames
+      }
+    } else {
+      // Mock实现
+      return new Promise(resolve => {
+        setTimeout(() => {
+          resolve(mockGames)
+        }, 500) // 模拟网络延迟
+      })
+    }
+  },
 
 
-/**
- * 公开提示
- * @param gameId 游戏ID
- * @param hintIndex 提示索引
- */
-export function revealHint(gameId: string, hintIndex: number) {
-  return callCloudFunction<{ success: boolean }>('revealHint', { gameId, hintIndex })
-}
-
-/**
- * 结束游戏
- * @param gameId 游戏ID
- * @param result 游戏结果
- */
-export function endGame(gameId: string, result: { solved: boolean; solvedBy?: string }) {
-  return callCloudFunction<{ gameResult: GameResult }>('endGame', { gameId, result })
+  async getGameDetail(id: string): Promise<Game | null> {
+    if (USE_CLOUD && process.env.TARO_ENV === 'weapp') {
+      try {
+        // 云函数实现
+        const result = await Taro.cloud.callFunction({
+          name: 'getGameDetail',
+          data: { id }
+        })
+        
+        // 使用类型断言处理结果
+        if (result && typeof result === 'object' && 'result' in result) {
+          const cloudResult = result.result as any
+          return cloudResult?.data || null
+        }
+        return null
+      } catch (error) {
+        console.error('获取游戏详情失败:', error)
+        return null
+      }
+    } else {
+      // Mock实现
+      return new Promise(resolve => {
+        setTimeout(() => {
+          const game = mockGames.find(g => g.id === id)
+          resolve(game || null)
+        }, 300) // 模拟网络延迟
+      })
+    }
+  }
 }
 }

+ 30 - 0
src/services/api/games/index.ts

@@ -0,0 +1,30 @@
+// 导出所有特定游戏API
+export { turtleSoupAPI } from './turtlesoup'
+// 未来会添加其他游戏API
+export { werewolfAPI } from './werewolf'
+// export { scriptAPI } from './script'
+
+// 游戏类型常量
+export const GAME_TYPES = {
+  TURTLE_SOUP: 'turtlesoup',
+  WEREWOLF: 'werewolf',
+  SCRIPT: 'script'
+}
+
+// 根据游戏类型获取对应的API
+import { turtleSoupAPI } from './turtlesoup'
+import { werewolfAPI } from './werewolf'
+
+export function getGameAPIByType(type: string) {
+  switch(type) {
+    case GAME_TYPES.TURTLE_SOUP:
+      return turtleSoupAPI
+    // 未来会添加其他游戏类型
+    case GAME_TYPES.WEREWOLF:
+      return werewolfAPI
+    // case GAME_TYPES.SCRIPT:
+    //   return scriptAPI
+    default:
+      throw new Error(`未知的游戏类型: ${type}`)
+  }
+}

+ 76 - 0
src/services/api/games/turtlesoup.ts

@@ -0,0 +1,76 @@
+import { callCloudFunction } from '../../cloud'
+
+// 海龟汤游戏数据类型
+export interface TurtleSoupGame {
+  id: string;
+  title: string;
+  story: string;
+  solution: string;
+  hints: string[];
+  questions: TurtleSoupQuestion[];
+  // 其他海龟汤特有属性
+}
+
+export interface TurtleSoupQuestion {
+  id: string;
+  content: string;
+  answer: string;
+  askedBy: string;
+  timestamp: number;
+}
+
+export interface GameResult {
+  solved: boolean;
+  solvedBy?: string;
+  timeUsed: number;
+  questionsCount: number;
+  hintsUsed: number[];
+}
+
+// 海龟汤游戏相关API
+export const turtleSoupAPI = {
+  /**
+   * 获取游戏数据
+   * @param gameId 游戏ID
+   * @param role 用户角色
+   */
+  getGameData(gameId: string, role: string) {
+    return callCloudFunction<{ game: TurtleSoupGame }>('getTurtleSoupGame', { gameId, role })
+  },
+
+  /**
+   * 提交问题
+   * @param gameId 游戏ID
+   * @param content 问题内容
+   */
+  submitQuestion(gameId: string, content: string) {
+    return callCloudFunction<{ questionId: string }>('submitTurtleSoupQuestion', { gameId, content })
+  },
+
+  /**
+   * 回答问题
+   * @param questionId 问题ID
+   * @param answer 答案
+   */
+  answerQuestion(questionId: string, answer: string) {
+    return callCloudFunction<{ success: boolean }>('answerTurtleSoupQuestion', { questionId, answer })
+  },
+
+  /**
+   * 公开提示
+   * @param gameId 游戏ID
+   * @param hintIndex 提示索引
+   */
+  revealHint(gameId: string, hintIndex: number) {
+    return callCloudFunction<{ success: boolean }>('revealTurtleSoupHint', { gameId, hintIndex })
+  },
+
+  /**
+   * 结束游戏
+   * @param gameId 游戏ID
+   * @param result 游戏结果
+   */
+  endGame(gameId: string, result: { solved: boolean; solvedBy?: string }) {
+    return callCloudFunction<{ gameResult: GameResult }>('endTurtleSoupGame', { gameId, result })
+  }
+}

+ 19 - 0
src/services/api/games/werewolf.ts

@@ -0,0 +1,19 @@
+import { callCloudFunction } from '../../cloud'
+import type { WerewolfGame, WerewolfPlayer } from '@/stores/games/werewolf'
+
+// 狼人杀游戏相关API
+export const werewolfAPI = {
+  // 获取游戏数据
+  getGameData(gameId: string, role: string) {
+    return callCloudFunction<{ game: WerewolfGame }>('getWerewolfGame', { gameId, role })
+  },
+
+  // 玩家行动
+  playerAction(gameId: string, playerId: string, action: string, target?: string) {
+    return callCloudFunction<{ success: boolean }>('werewolfAction', { 
+      gameId, playerId, action, target 
+    })
+  },
+
+  // ... 狼人杀特有方法
+}

+ 8 - 2
src/services/api/index.ts

@@ -1,2 +1,8 @@
-export * from './room'
-export * from './game'
+export { gameAPI } from './game'
+export { roomAPI } from './room'
+export * from './games'
+
+// 导出用户API
+export const userAPI = {
+  // ... 保留现有的userAPI实现
+}

+ 62 - 0
src/services/api/user.ts

@@ -0,0 +1,62 @@
+import Taro from '@tarojs/taro'
+
+// 控制是否使用云函数(true)或mock数据(false)
+const USE_CLOUD = false
+
+// Mock用户数据
+const mockUser = {
+    nickname: '测试用户',
+    avatar: 'https://example.com/avatar.jpg',
+    openid: 'mock_openid_123456',
+  }
+
+export const userAPI = {
+    async getUserInfo() {
+      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
+          }
+          return mockUser
+        } catch (error) {
+          console.error('获取用户信息失败:', error)
+          return mockUser
+        }
+      } else {
+        // Mock实现
+        return new Promise(resolve => {
+          setTimeout(() => {
+            resolve(mockUser)
+          }, 300)
+        })
+      }
+    },
+    
+    // 新增方法:获取OpenID
+    async getOpenId(code: string) {
+      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)
+        }
+      }
+      
+      // Mock实现
+      return { openid: 'mock_openid_' + Date.now() }
+    }
+}

+ 32 - 29
src/services/cloud.ts

@@ -35,41 +35,43 @@ export async function callCloudFunction<T = any>(
   }
   }
   
   
   try {
   try {
-    // 基于Taro 4.x的调用方式
-    const { result } = await Taro.cloud.callFunction({
+    // 确保云环境已初始化
+    try {
+      Taro.cloud.init({
+        env: 'cloud1' // 替换为您实际的云环境ID
+      })
+    } catch (e) {
+      // 忽略已初始化的错误
+    }
+
+    const result = await Taro.cloud.callFunction({
       name,
       name,
       data
       data
     })
     })
-    
-    // 隐藏加载中
-    if (opt.showLoading) {
-      Taro.hideLoading()
-    }
-    
-    if (!result) {
-      if (opt.showError) {
-        Taro.showToast({
-          title: '请求失败',
-          icon: 'none'
-        })
+
+    if (result && result.result) {
+      // 隐藏加载中
+      if (opt.showLoading) {
+        Taro.hideLoading()
       }
       }
-      throw new Error('云函数返回结果为空')
-    }
-    
-    // 业务错误处理 (假设result可能包含error和success字段)
-    const typedResult = result as any
-    if (typedResult.error || typedResult.success === false) {
-      if (opt.showError) {
-        Taro.showToast({
-          title: typedResult.error || '请求失败',
-          icon: 'none'
-        })
+      
+      // 业务错误处理 (假设result可能包含error和success字段)
+      const typedResult = result.result as any
+      if (typedResult.error || typedResult.success === false) {
+        if (opt.showError) {
+          Taro.showToast({
+            title: typedResult.error || '请求失败',
+            icon: 'none'
+          })
+        }
+        throw new Error(typedResult.error || '请求失败')
       }
       }
-      throw new Error(typedResult.error || '请求失败')
+      
+      // 返回data字段或整个result对象
+      return (typedResult.data !== undefined ? typedResult.data : typedResult) as T
     }
     }
     
     
-    // 返回data字段或整个result对象
-    return (typedResult.data !== undefined ? typedResult.data : typedResult) as T
+    throw new Error(`云函数 ${name} 没有返回结果`)
   } catch (error: any) {
   } catch (error: any) {
     // 隐藏加载中
     // 隐藏加载中
     if (opt.showLoading) {
     if (opt.showLoading) {
@@ -84,6 +86,7 @@ export async function callCloudFunction<T = any>(
       })
       })
     }
     }
     
     
-    throw error
+    console.error(`调用云函数 ${name} 失败:`, error)
+    return Promise.reject(error)
   }
   }
 }
 }

+ 1 - 1
src/stores/game.ts

@@ -1,5 +1,5 @@
 import { defineStore } from 'pinia'
 import { defineStore } from 'pinia'
-import { gameAPI } from '@/services/api'
+import { gameAPI } from '@/services/api/game'
 
 
 // 游戏数据接口
 // 游戏数据接口
 export interface Game {
 export interface Game {

+ 25 - 0
src/stores/games/index.ts

@@ -0,0 +1,25 @@
+// 导出所有游戏Store
+export { useTurtleSoupStore } from './turtlesoup'
+// 未来会添加其他游戏Store
+export { useWerewolfStore } from './werewolf'
+// export { useScriptStore } from './script'
+
+// 游戏类型与Store的映射
+import { useTurtleSoupStore } from './turtlesoup'
+import { useWerewolfStore } from './werewolf'
+import { GAME_TYPES } from '@/services/api/games'
+
+// 根据游戏类型获取对应的Store
+export function getGameStoreByType(type: string) {
+  switch(type) {
+    case GAME_TYPES.TURTLE_SOUP:
+      return useTurtleSoupStore
+    // 未来会添加其他游戏类型
+    case GAME_TYPES.WEREWOLF:
+      return useWerewolfStore
+    // case GAME_TYPES.SCRIPT:
+    //   return useScriptStore
+    default:
+      throw new Error(`未知的游戏类型: ${type}`)
+  }
+}

+ 69 - 0
src/stores/games/turtlesoup.ts

@@ -0,0 +1,69 @@
+import { defineStore } from 'pinia'
+import { turtleSoupAPI } from '@/services/api/games'
+
+// 海龟汤游戏状态
+export interface TurtleSoupGame {
+  id: string
+  title: string
+  story: string
+  solution: string
+  hints: string[]
+  questions: TurtleSoupQuestion[]
+  // ... 其他特定字段
+}
+
+export interface TurtleSoupQuestion {
+  id: string
+  content: string
+  answer: string
+  askedBy: string
+  timestamp: number
+}
+
+export const useTurtleSoupStore = defineStore('turtlesoup', {
+  state: () => ({
+    currentGame: null as TurtleSoupGame | null,
+    loading: false,
+    revealedHints: [] as number[],
+    // ... 其他状态
+  }),
+  
+  actions: {
+    // 加载游戏数据
+    async loadGame(gameId: string, role: string) {
+      this.loading = true
+      try {
+        const result = await turtleSoupAPI.getGameData(gameId, role)
+        if (result && result.game) {
+          this.currentGame = result.game
+          return true
+        }
+        return false
+      } catch (error) {
+        console.error('加载海龟汤游戏失败:', error)
+        return false
+      } finally {
+        this.loading = false
+      }
+    },
+    
+    // 提交问题
+    async submitQuestion(content: string) {
+      if (!this.currentGame) return false
+      
+      try {
+        const result = await turtleSoupAPI.submitQuestion(this.currentGame.id, content)
+        if (result && result.questionId) {
+          // 更新本地状态...
+          return true
+        }
+        return false
+      } catch (error) {
+        console.error('提交问题失败:', error)
+        return false
+      }
+    },
+    
+    // ... 其他方法
+  }
+})

+ 421 - 0
src/stores/games/werewolf.ts

@@ -0,0 +1,421 @@
+import { defineStore } from 'pinia'
+import { werewolfAPI } from '@/services/api/games/werewolf'
+
+// 玩家角色类型
+export type WerewolfRole = 
+  | 'werewolf'     // 狼人
+  | 'villager'     // 村民
+  | 'seer'         // 预言家
+  | 'witch'        // 女巫
+  | 'hunter'       // 猎人
+  | 'guard'        // 守卫
+  | 'idiot'        // 白痴
+  | 'elder'        // 长老
+  | 'cupid'        // 丘比特
+  | 'thief'        // 盗贼
+  | 'moderator';   // 主持人/上帝
+
+// 游戏阶段
+export type GamePhase =
+  | 'waiting'      // 等待开始
+  | 'night'        // 夜晚
+  | 'day'          // 白天
+  | 'vote'         // 投票
+  | 'ended';       // 游戏结束
+
+// 行动类型
+export type ActionType = 
+  | 'kill'         // 狼人杀人
+  | 'check'        // 预言家查验
+  | 'save'         // 女巫救人
+  | 'poison'       // 女巫毒人
+  | 'protect'      // 守卫保护
+  | 'shoot'        // 猎人开枪
+  | 'vote'         // 投票
+  | 'pair';        // 丘比特连线
+
+// 玩家状态
+export type PlayerStatus = 
+  | 'alive'        // 存活
+  | 'dead'         // 死亡
+  | 'protected'    // 受保护
+  | 'poisoned'     // 中毒
+  | 'coupled';     // 情侣
+
+// 游戏结果类型
+export type GameResult = 
+  | 'werewolf_win'  // 狼人阵营胜利
+  | 'villager_win'  // 村民阵营胜利
+  | 'couple_win'    // 情侣胜利
+  | 'draw'          // 平局
+  | null;           // 游戏进行中
+
+// 玩家信息
+export interface WerewolfPlayer {
+  id: string;
+  name: string;
+  avatar: string;
+  role: WerewolfRole;
+  status: PlayerStatus;
+  isReady: boolean;
+  isDead: boolean;
+  actions: {
+    [key in ActionType]?: boolean;
+  };
+  // 特殊状态
+  isLovers?: boolean;      // 是否为情侣
+  lastWords?: string;      // 遗言
+  voteTarget?: string;     // 投票目标
+  deadRound?: number;      // 死亡回合
+}
+
+// 游戏日志
+export interface GameLog {
+  round: number;
+  phase: GamePhase;
+  content: string;
+  timestamp: number;
+  isPublic: boolean;
+  targetPlayers?: string[];
+}
+
+// 游戏状态
+export interface WerewolfGame {
+  id: string;
+  title: string;
+  hosterId: string;
+  hosterName: string;
+  createTime: number;
+  startTime: number | null;
+  endTime: number | null;
+  players: WerewolfPlayer[];
+  currentRound: number;
+  phase: GamePhase;
+  result: GameResult;
+  currentAction: ActionType | null;
+  timeLimit: number;      // 每个阶段的时间限制(秒)
+  logs: GameLog[];
+  options: {
+    roles: {
+      [key in WerewolfRole]?: number; // 角色数量配置
+    };
+    includeSpecialRoles: boolean; // 是否包含特殊角色
+    allowLastWords: boolean;      // 是否允许遗言
+    nightTimeLimit: number;       // 夜晚时间限制
+    dayTimeLimit: number;         // 白天时间限制
+    voteTimeLimit: number;        // 投票时间限制
+  };
+}
+
+// 定义 Store
+export const useWerewolfStore = defineStore('werewolf', {
+  state: () => ({
+    currentGame: null as WerewolfGame | null,
+    loading: false,
+    error: null as string | null,
+    currentPlayer: null as WerewolfPlayer | null,
+    timer: null as number | null,
+    remainingTime: 0,
+    myRole: null as WerewolfRole | null,
+    myActions: [] as ActionType[],
+    gameResult: null as GameResult,
+    showRoleInfo: false,
+    showActionInfo: false
+  }),
+  
+  getters: {
+    // 获取活着的玩家
+    alivePlayers: (state) => {
+      if (!state.currentGame) return [];
+      return state.currentGame.players.filter(p => !p.isDead);
+    },
+    
+    // 获取死亡的玩家
+    deadPlayers: (state) => {
+      if (!state.currentGame) return [];
+      return state.currentGame.players.filter(p => p.isDead);
+    },
+    
+    // 获取狼人玩家
+    werewolfPlayers: (state) => {
+      if (!state.currentGame) return [];
+      return state.currentGame.players.filter(p => p.role === 'werewolf');
+    },
+    
+    // 获取村民阵营玩家
+    villagerTeamPlayers: (state) => {
+      if (!state.currentGame) return [];
+      return state.currentGame.players.filter(p => 
+        p.role !== 'werewolf' && p.role !== 'moderator'
+      );
+    },
+    
+    // 当前玩家是否是狼人
+    isWerewolf: (state) => {
+      return state.myRole === 'werewolf';
+    },
+    
+    // 是否是夜晚
+    isNightPhase: (state) => {
+      return state.currentGame?.phase === 'night';
+    },
+    
+    // 是否是白天
+    isDayPhase: (state) => {
+      return state.currentGame?.phase === 'day';
+    },
+    
+    // 是否是投票阶段
+    isVotePhase: (state) => {
+      return state.currentGame?.phase === 'vote';
+    },
+    
+    // 游戏是否结束
+    isGameEnded: (state) => {
+      return state.currentGame?.phase === 'ended';
+    },
+    
+    // 当前玩家是否可以行动
+    canAct: (state) => {
+      if (!state.currentGame || !state.myRole || !state.currentPlayer) return false;
+      if (state.currentPlayer.isDead) return false;
+      
+      const { phase, currentAction } = state.currentGame;
+      
+      // 根据游戏阶段和角色确定是否可以行动
+      if (phase === 'night') {
+        switch (currentAction) {
+          case 'kill': return state.myRole === 'werewolf';
+          case 'check': return state.myRole === 'seer';
+          case 'save': 
+          case 'poison': return state.myRole === 'witch';
+          case 'protect': return state.myRole === 'guard';
+          case 'pair': return state.myRole === 'cupid';
+          default: return false;
+        }
+      } else if (phase === 'vote') {
+        return !state.currentPlayer.voteTarget;
+      }
+      
+      return false;
+    },
+    
+    // 获取当前可选择的目标玩家
+    availableTargets: (state) => {
+      if (!state.currentGame || !state.myRole) return [];
+      
+      const { phase, currentAction, players } = state.currentGame;
+      
+      // 根据不同行动类型过滤可选目标
+      if (phase === 'night') {
+        switch (currentAction) {
+          case 'kill': // 狼人只能杀死非狼人
+            return players.filter(p => !p.isDead && p.role !== 'werewolf');
+          case 'check': // 预言家查验活着的玩家
+            return players.filter(p => !p.isDead);
+          case 'save': // 女巫只能救死亡的玩家
+            return players.filter(p => p.status === 'poisoned');
+          case 'poison': // 女巫只能毒活着的玩家
+            return players.filter(p => !p.isDead);
+          case 'protect': // 守卫只能保护活着的玩家
+            return players.filter(p => !p.isDead);
+          default:
+            return [];
+        }
+      } else if (phase === 'vote') {
+        // 投票阶段可以选择所有活着的玩家(除了自己)
+        return players.filter(p => !p.isDead && p.id !== state.currentPlayer?.id);
+      }
+      
+      return [];
+    }
+  },
+  
+  actions: {
+    // 加载游戏数据
+    async loadGame(gameId: string, role: string) {
+      this.loading = true;
+      this.error = null;
+      
+      try {
+        const result = await werewolfAPI.getGameData(gameId, role);
+        if (result && result.game) {
+          this.currentGame = result.game;
+          
+          // 找到当前玩家
+          if (this.currentGame.players) {
+            // 这里需要根据实际情况从某处获取当前用户ID
+            const currentUserId = 'current_user_id'; // 应替换为实际的用户ID获取方式
+            this.currentPlayer = this.currentGame.players.find(p => p.id === currentUserId) || null;
+            this.myRole = this.currentPlayer?.role || null;
+          }
+          
+          return true;
+        }
+        
+        this.error = '加载游戏数据失败';
+        return false;
+      } catch (err) {
+        console.error('加载狼人杀游戏失败:', err);
+        this.error = err instanceof Error ? err.message : '未知错误';
+        return false;
+      } finally {
+        this.loading = false;
+      }
+    },
+    
+    // 准备游戏
+    async readyGame() {
+      if (!this.currentGame || !this.currentPlayer) return false;
+      
+      try {
+        await werewolfAPI.playerAction(
+          this.currentGame.id,
+          this.currentPlayer.id,
+          'ready'
+        );
+        return true;
+      } catch (err) {
+        console.error('准备游戏失败:', err);
+        return false;
+      }
+    },
+    
+    // 玩家行动
+    async performAction(action: ActionType, targetId?: string) {
+      if (!this.currentGame || !this.currentPlayer) return false;
+      
+      try {
+        await werewolfAPI.playerAction(
+          this.currentGame.id,
+          this.currentPlayer.id,
+          action,
+          targetId
+        );
+        return true;
+      } catch (err) {
+        console.error('执行行动失败:', err);
+        return false;
+      }
+    },
+    
+    // 狼人杀人
+    async killPlayer(targetId: string) {
+      return this.performAction('kill', targetId);
+    },
+    
+    // 预言家查验
+    async checkPlayer(targetId: string) {
+      return this.performAction('check', targetId);
+    },
+    
+    // 女巫救人
+    async savePlayer(targetId: string) {
+      return this.performAction('save', targetId);
+    },
+    
+    // 女巫毒人
+    async poisonPlayer(targetId: string) {
+      return this.performAction('poison', targetId);
+    },
+    
+    // 守卫保护
+    async protectPlayer(targetId: string) {
+      return this.performAction('protect', targetId);
+    },
+    
+    // 投票
+    async votePlayer(targetId: string) {
+      return this.performAction('vote', targetId);
+    },
+    
+    // 猎人开枪
+    async shootPlayer(targetId: string) {
+      return this.performAction('shoot', targetId);
+    },
+    
+    // 丘比特连线
+    async couplePlayer(player1Id: string, player2Id: string) {
+      try {
+        await werewolfAPI.playerAction(
+          this.currentGame!.id,
+          this.currentPlayer!.id,
+          'pair',
+          JSON.stringify([player1Id, player2Id])
+        );
+        return true;
+      } catch (err) {
+        console.error('丘比特连线失败:', err);
+        return false;
+      }
+    },
+    
+    // 留遗言
+    async leaveLastWords(message: string) {
+      if (!this.currentGame || !this.currentPlayer) return false;
+      
+      try {
+        await werewolfAPI.playerAction(
+          this.currentGame.id,
+          this.currentPlayer.id,
+          'lastWords',
+          message
+        );
+        return true;
+      } catch (err) {
+        console.error('留遗言失败:', err);
+        return false;
+      }
+    },
+    
+    // 开始倒计时
+    startTimer(seconds: number) {
+      this.remainingTime = seconds;
+      
+      // 清除现有的计时器
+      if (this.timer !== null) {
+        clearInterval(this.timer);
+      }
+      
+      // 设置新的计时器
+      this.timer = window.setInterval(() => {
+        if (this.remainingTime > 0) {
+          this.remainingTime--;
+        } else {
+          if (this.timer !== null) {
+            clearInterval(this.timer);
+            this.timer = null;
+          }
+        }
+      }, 1000);
+    },
+    
+    // 停止倒计时
+    stopTimer() {
+      if (this.timer !== null) {
+        clearInterval(this.timer);
+        this.timer = null;
+      }
+    },
+    
+    // 显示角色信息
+    showRoleInformation() {
+      this.showRoleInfo = true;
+    },
+    
+    // 隐藏角色信息
+    hideRoleInformation() {
+      this.showRoleInfo = false;
+    },
+    
+    // 清理游戏数据
+    clearGame() {
+      this.currentGame = null;
+      this.currentPlayer = null;
+      this.myRole = null;
+      this.myActions = [];
+      this.gameResult = null;
+      this.stopTimer();
+    }
+  }
+}); 

+ 1 - 1
src/stores/user.ts

@@ -1,6 +1,6 @@
 import { defineStore } from 'pinia'
 import { defineStore } from 'pinia'
 import Taro from '@tarojs/taro'
 import Taro from '@tarojs/taro'
-import { userAPI } from '@/services/api'
+import { userAPI } from '@/services/api/user'
 import { ref } from 'vue'
 import { ref } from 'vue'
 
 
 export type UserRole = 'player' | 'hoster'
 export type UserRole = 'player' | 'hoster'