feat: add game library CRUD/import/export/favorites/comments, fix team creation

- Game library: add/delete games per group, JSON/CSV import/export, favorites, star ratings & comments
- Fix team session creation: add creator to members array, handle null currentGroup
- Fix image loading: rename SVG files from .png to .svg extensions
- Add PocketBase migrations for game_comments and game_favorites collections
- Remove seed data script

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
congsh
2026-04-17 21:03:20 +08:00
parent 802712c662
commit 4b97c99e56
23 changed files with 996 additions and 348 deletions
@@ -82,7 +82,7 @@ function confirmCustomGame() {
@click="selectGame(game)"
>
<img
:src="game.cover || '/game-placeholder.png'"
:src="game.cover || '/game-placeholder.svg'"
:alt="game.name"
class="game-cover"
/>
@@ -59,7 +59,7 @@ async function inviteMember(userId: string, username: string) {
class="member-item"
>
<img
:src="member.avatar || '/default-avatar.png'"
:src="member.avatar || '/default-avatar.svg'"
:alt="member.username"
class="avatar"
/>
@@ -49,7 +49,7 @@ async function rejectInvitation() {
<div class="invitation-card">
<div class="invitation-header">
<img
:src="invitation.expand?.from?.avatar || '/default-avatar.png'"
:src="invitation.expand?.from?.avatar || '/default-avatar.svg'"
:alt="invitation.expand?.from?.username"
class="avatar"
/>
@@ -1,16 +1,20 @@
<!-- src/components/team/TeamSessionPanel.vue -->
<script setup lang="ts">
import { computed } from 'vue'
import { computed, ref } from 'vue'
import { useTeamStore } from '@/stores/team'
import { useGroupStore } from '@/stores/group'
import { useUserStore } from '@/stores/user'
import { TeamStatusMap } from '@/types'
import { ElMessageBox } from 'element-plus'
import { ElMessageBox, ElMessage } from 'element-plus'
import GameSelectDialog from '@/components/team/GameSelectDialog.vue'
const teamStore = useTeamStore()
const groupStore = useGroupStore()
const userStore = useUserStore()
const session = computed(() => teamStore.currentSession)
const statusText = computed(() => session.value ? TeamStatusMap[session.value.status] : '')
const showGameSelect = ref(false)
const memberDetails = computed(() => {
if (!session.value) return []
@@ -43,6 +47,35 @@ async function endGame() {
// 用户取消
}
}
async function handleGameSelected(gameName: string) {
let group = groupStore.currentGroup
if (!group) {
if (groupStore.groups.length > 0) {
group = groupStore.groups[0]
await groupStore.setCurrentGroup(group.id)
} else {
ElMessage.warning('请先创建或加入一个群组')
return
}
}
if (teamStore.currentSession) return
const userId = userStore.userId
try {
await teamStore.createSession({
sourceGroup: group.id,
name: `${gameName} 小队`,
gameName,
members: [userId]
})
ElMessage.success('临时小队创建成功!')
} catch (error: any) {
console.error('创建临时小队失败:', error)
ElMessage.error('创建临时小队失败:' + (error.message || '未知错误'))
}
}
</script>
<template>
@@ -66,7 +99,7 @@ async function endGame() {
class="member-item"
>
<img
:src="member.avatar || '/default-avatar.png'"
:src="member.avatar || '/default-avatar.svg'"
:alt="member.username"
class="avatar"
/>
@@ -87,8 +120,13 @@ async function endGame() {
</div>
<div v-else class="no-session">
<p>暂未加入任何临时小组</p>
<p class="no-session-text">暂未加入任何临时小组</p>
<button class="create-team-btn" @click="showGameSelect = true">
创建临时小队
</button>
</div>
<GameSelectDialog v-model="showGameSelect" @select="handleGameSelected" />
</template>
<style scoped>
@@ -251,9 +289,30 @@ async function endGame() {
background: var(--gg-bg-card);
border: 1px solid var(--gg-border);
border-radius: var(--gg-radius-md);
padding: 48px 32px;
padding: 40px 32px;
text-align: center;
}
.no-session-text {
color: var(--gg-text-muted);
font-size: 15px;
margin: 0 0 20px;
}
.create-team-btn {
padding: 12px 32px;
border: none;
border-radius: var(--gg-radius-sm);
background: var(--gg-gradient-green);
color: white;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: box-shadow 0.3s, transform 0.15s;
}
.create-team-btn:hover {
box-shadow: 0 4px 20px rgba(5, 150, 105, 0.3);
transform: translateY(-1px);
}
</style>