|
19 timmar sedan | |
---|---|---|
config | 19 timmar sedan | |
src | 19 timmar sedan | |
types | 1 dag sedan | |
.editorconfig | 1 dag sedan | |
.eslintrc | 1 dag sedan | |
.gitignore | 1 dag sedan | |
README.md | 1 dag sedan | |
babel.config.js | 1 dag sedan | |
components.d.ts | 19 timmar sedan | |
package.json | 20 timmar sedan | |
pnpm-lock.yaml | 20 timmar sedan | |
project.config.json | 1 dag sedan | |
project.tt.json | 1 dag sedan | |
tsconfig.json | 1 dag sedan |
产品名称:基于微信小程序的排队解闷小游戏合集
产品愿景:将排队时的无聊等待转变为社交互动的契机,通过线上小游戏引导线下交流,增进陌生人或团队成员间的互动与了解。
核心价值主张:
创建房间(主持人)
加入房间
主持人(游戏创建者)
玩家
海龟汤(首发核心游戏)
其他游戏类型(后续拓展)
客户端 (Taro)
↑↓
云开发 API
↑↓
云函数 (业务逻辑)
↑↓
云数据库 (数据存储)
interface User {
id: string; // 用户唯一ID
openId: string; // 微信OpenID
nickname: string; // 昵称
avatarUrl: string; // 头像
createdAt: Date; // 注册时间
stats: { // 统计数据
gamesPlayed: number;
gamesHosted: number;
solvedRate: number; // 解谜成功率
};
}
interface Room {
id: string; // 房间ID
roomCode: string; // 6位房间码
gameType: string; // 游戏类型
gameId: string; // 游戏内容ID
hostId: string; // 主持人ID
createdAt: Date; // 创建时间
status: RoomStatus; // 等待中/进行中/已结束
settings: { // 房间设置
maxPlayers: number;
isPublic: boolean;
difficulty: string;
hintRevealMode: string; // 自动/手动
};
location?: { // 可选位置信息
latitude: number;
longitude: number;
name?: string; // 位置名称
};
players: RoomMember[]; // 玩家列表
}
interface RoomMember {
id: string; // 用户ID
role: RoomRole; // 主持人/玩家
nickname: string; // 昵称
avatarUrl: string; // 头像
joinTime: Date; // 加入时间
isReady: boolean; // 准备状态
}
enum RoomRole {
HOST = 'host', // 主持人
PLAYER = 'player' // 玩家
}
enum RoomStatus {
WAITING = 'waiting', // 等待中
PLAYING = 'playing', // 游戏中
ENDED = 'ended' // 已结束
}
interface TurtleSoupGame {
id: string; // 游戏ID
roomId: string; // 关联房间ID
title: string; // 标题
initialStory: string; // 初始故事
hints: string[]; // 提示列表
solution: string; // 解答
revealedHints: number[]; // 已公开提示索引
questions: Question[]; // 问题列表
startTime: Date; // 开始时间
endTime?: Date; // 结束时间
status: GameStatus; // 游戏状态
}
interface Question {
id: string; // 问题ID
playerId: string; // 提问玩家ID
playerName: string; // 提问玩家名称
content: string; // 问题内容
answer?: string; // 回答 (是/否/不相关)
createdAt: Date; // 创建时间
answeredAt?: Date; // 回答时间
}
enum GameStatus {
PREPARING = 'preparing', // 准备中
ONGOING = 'ongoing', // 进行中
SOLVED = 'solved', // 已解决
EXPIRED = 'expired' // 已过期
}
接口名称 | 功能描述 | 参数 | 返回值 |
---|---|---|---|
createRoom | 创建游戏房间 | gameType, settings | roomId, roomCode |
joinRoom | 加入游戏房间 | roomCode | roomDetail |
getRoomDetail | 获取房间详情 | roomId | roomDetail |
startGame | 开始游戏 | roomId | gameId |
leaveRoom | 离开房间 | roomId | success |
接口名称 | 功能描述 | 参数 | 返回值 |
---|---|---|---|
getGameData | 获取游戏数据 | gameId, role | filteredGameData |
submitQuestion | 提交问题 | gameId, content | questionId |
answerQuestion | 回答问题 | questionId, answer | success |
revealHint | 公开提示 | gameId, hintIndex | success |
endGame | 结束游戏 | gameId, result | gameResult |
HostOnly组件:仅主持人可见内容
<template>
<slot v-if="isHost"></slot>
</template>
<script setup lang="ts">
import { useRoomRole } from '@/hooks/useRoomRole';
const props = defineProps<{
roomId: string
}>();
const { isHost } = useRoomRole(props.roomId);
</script>
PlayerOnly组件:仅玩家可见内容
<template>
<slot v-if="isPlayer"></slot>
</template>
<script setup lang="ts">
import { useRoomRole } from '@/hooks/useRoomRole';
const props = defineProps<{
roomId: string
}>();
const { isPlayer } = useRoomRole(props.roomId);
</script>
/pages/
/index/ # 游戏广场
/game-detail/ # 游戏详情
/room/
/create/ # 创建房间(主持人专用)
/join/ # 加入房间(玩家专用)
/waiting/ # 等待室(带角色参数)
/play/ # 游戏进行中(带角色参数)
/profile/ # 个人中心
/history/ # 游戏历史
src/ ├── app.config.ts # 小程序全局配置 ├── app.scss # 全局样式 ├── app.ts # 应用入口 ├── assets/ # 静态资源 │ ├── images/ # 图片资源 │ └── styles/ # 样式资源 │ ├── variables.scss # 样式变量 │ └── mixins.scss # 样式混合 ├── components/ # 全局组件 │ ├── HostOnly/ │ └── PlayerOnly/ ├── composables/ # 可组合式函数 │ └── useRoomRole.ts ├── pages/ # 页面 │ ├── index/ # 游戏广场 │ ├── game-detail/ # 游戏详情 │ ├── room/ # 房间相关页面 │ │ ├── create/ # 创建房间 │ │ ├── join/ # 加入房间 │ │ ├── waiting/ # 等待室 │ │ └── play/ # 游戏进行中 │ ├── profile/ # 个人中心 │ └── history/ # 游戏历史 ├── services/ # API服务 │ ├── api/ # API定义 │ ├── request.ts # 请求封装 │ └── cloud.ts # 云函数封装 ├── stores/ # Pinia状态管理 │ ├── modules/ # 状态模块 │ │ ├── user.ts # 用户状态 │ │ ├── room.ts # 房间状态 │ │ └── game.ts # 游戏状态 │ └── index.ts # 状态入口 ├── types/ # TypeScript类型 │ ├── room.ts # 房间相关类型 │ ├── game.ts # 游戏相关类型 │ └── user.ts # 用户相关类型 └── utils/ # 工具函数
├── common.ts # 通用工具
├── format.ts # 格式化工具
└── storage.ts # 存储工具
useRoomRole
组合式API获取和验证用户角色// 根据角色过滤游戏数据
function filterGameDataByRole(gameData, role: RoomRole) {
if (role === RoomRole.HOST) {
// 主持人可以看到所有数据
return gameData;
} else {
// 玩家只能看到部分数据
return {
...gameData,
initialStory: gameData.initialStory,
revealedHints: gameData.revealedHints,
// 隐藏未公开信息
solution: undefined,
unreveaedHints: undefined,
secretNotes: undefined
};
}
}
// hooks/useRoomRole.ts
import { ref, onMounted, computed } from 'vue';
import Taro from '@tarojs/taro';
import { RoomRole } from '@/types';
export function useRoomRole(roomId: string) {
const role = ref<RoomRole | null>(null);
onMounted(() => {
// 从路由参数获取角色
const params = Taro.getCurrentInstance().router?.params;
let roleFromParams = params?.role as RoomRole;
// 如果没有,从服务器获取
if (!roleFromParams && roomId) {
Taro.cloud.callFunction({
name: 'getRoomMemberRole',
data: { roomId }
}).then(res => {
role.value = res.result.role;
});
} else {
role.value = roleFromParams;
}
});
const isHost = computed(() => role.value === RoomRole.HOST);
const isPlayer = computed(() => role.value === RoomRole.PLAYER);
return {
role,
isHost,
isPlayer
};
}
// composables/useRoomData.ts
import { ref, onMounted, onUnmounted } from 'vue';
import Taro from '@tarojs/taro';
import type { Room } from '@/types';
export function useRoomData(roomId: string) {
const roomData = ref<Room | null>(null);
let watcher = null;
// 设置房间数据监听
onMounted(() => {
const db = Taro.cloud.database();
watcher = db.collection('rooms')
.doc(roomId)
.watch({
onChange: function(snapshot) {
roomData.value = snapshot.docs[0];
},
onError: function(err) {
console.error('监听房间失败', err);
}
});
});
// 组件卸载时关闭监听
onUnmounted(() => {
if (watcher) {
watcher.close();
}
});
return {
roomData
};
}