feat: add GameGroup2 project with frontend and backend

- Add .gitignore for Node.js and PocketBase projects
- Add frontend (Vue 3 + Vite + TypeScript)
- Add backend (PocketBase)
- Add deployment scripts and Docker compose configs

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
congsh
2026-04-17 15:45:54 +08:00
parent 2db391901c
commit 2ce8985747
56 changed files with 3981 additions and 783 deletions
@@ -0,0 +1,195 @@
<!-- src/components/team/InvitationCard.vue -->
<script setup lang="ts">
import { ref } from 'vue'
import type { Invitation } from '@/types'
import { respondInvitation } from '@/api/invitations'
import { ElMessage } from 'element-plus'
interface Props {
invitation: Invitation
}
const props = defineProps<Props>()
const emit = defineEmits<{
responded: [id: string, accepted: boolean]
}>()
const rejectReason = ref('')
const showRejectInput = ref(false)
async function acceptInvitation() {
try {
await respondInvitation(props.invitation.id, 'accepted')
ElMessage.success('已接受邀请')
emit('responded', props.invitation.id, true)
} catch (error: any) {
ElMessage.error(error.message || '接受邀请失败')
}
}
async function rejectInvitation() {
if (!showRejectInput.value) {
showRejectInput.value = true
return
}
try {
await respondInvitation(props.invitation.id, 'rejected', rejectReason.value)
ElMessage.success('已拒绝邀请')
emit('responded', props.invitation.id, false)
showRejectInput.value = false
rejectReason.value = ''
} catch (error: any) {
ElMessage.error(error.message || '拒绝邀请失败')
}
}
</script>
<template>
<div class="invitation-card">
<div class="invitation-header">
<img
:src="invitation.expand?.from?.avatar || '/default-avatar.png'"
:alt="invitation.expand?.from?.username"
class="avatar"
/>
<div class="invitation-info">
<span class="inviter-name">{{ invitation.expand?.from?.username }}</span>
<span class="invitation-text">邀请你加入</span>
</div>
</div>
<div v-if="invitation.expand?.teamSession" class="session-info">
<span class="game-name">{{ invitation.expand.teamSession.gameName }}</span>
<span class="session-name">{{ invitation.expand.teamSession.name }}</span>
</div>
<div class="invitation-actions">
<button class="action-btn accept" @click="acceptInvitation">
接受
</button>
<button class="action-btn reject" @click="rejectInvitation">
{{ showRejectInput ? '确认拒绝' : '拒绝' }}
</button>
</div>
<div v-if="showRejectInput" class="reject-input">
<textarea
v-model="rejectReason"
placeholder="填写拒绝原因(可选)"
rows="2"
/>
</div>
</div>
</template>
<style scoped>
.invitation-card {
background: var(--el-bg-color);
border-radius: 12px;
padding: 16px;
border: 1px solid var(--el-border-color);
}
.invitation-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.avatar {
width: 48px;
height: 48px;
border-radius: 50%;
object-fit: cover;
}
.invitation-info {
display: flex;
flex-direction: column;
gap: 4px;
}
.inviter-name {
font-size: 15px;
font-weight: 600;
}
.invitation-text {
font-size: 13px;
color: var(--el-text-color-secondary);
}
.session-info {
display: flex;
flex-direction: column;
gap: 4px;
padding: 12px;
background: var(--el-bg-color-page);
border-radius: 8px;
margin-bottom: 12px;
}
.game-name {
font-size: 14px;
font-weight: 500;
}
.session-name {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.invitation-actions {
display: flex;
gap: 8px;
}
.action-btn {
flex: 1;
padding: 10px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.accept {
background: var(--el-color-primary);
color: white;
}
.accept:hover {
background: var(--el-color-primary-light-3);
}
.reject {
background: var(--el-fill-color);
color: var(--el-text-color-primary);
}
.reject:hover {
background: var(--el-fill-color-dark);
}
.reject-input {
margin-top: 12px;
}
.reject-input textarea {
width: 100%;
padding: 8px;
border: 1px solid var(--el-border-color);
border-radius: 6px;
font-size: 13px;
resize: none;
}
.reject-input textarea:focus {
outline: none;
border-color: var(--el-color-primary);
}
</style>