Files
gamegroup2/frontend/src/components/team/InvitationCard.vue
T
congsh cfdbaf1095 feat: UI redesign v0.0.2 — color unification, navigation improvements, mobile support
- Unify color palette from mixed green/blue/purple to consistent green theme
- Sidebar: add text labels to create/join group buttons for discoverability
- Header: add quick action buttons (create group, join group, notifications)
- Mobile: add hamburger menu with slide-out sidebar and overlay
- Home: add prominent CTA buttons, onboarding card for empty state
- Join group dialog: add search-by-name mode alongside existing ID lookup
- Games library: inline group selector dropdown instead of external selection

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 12:24:20 +08:00

217 lines
4.6 KiB
Vue

<!-- 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.svg'"
: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(--gg-bg-card);
border: 1px solid var(--gg-border);
border-radius: var(--gg-radius-md);
padding: 20px;
transition: border-color 0.2s, box-shadow 0.2s;
}
.invitation-card:hover {
border-color: var(--gg-primary);
box-shadow: 0 0 16px rgba(5, 150, 105, 0.12);
}
.invitation-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 14px;
}
.avatar {
width: 48px;
height: 48px;
border-radius: 50%;
object-fit: cover;
border: 2px solid var(--gg-border);
}
.invitation-info {
display: flex;
flex-direction: column;
gap: 4px;
}
.inviter-name {
font-size: 15px;
font-weight: 700;
color: var(--gg-text);
}
.invitation-text {
font-size: 13px;
color: var(--gg-text-secondary);
}
.session-info {
display: flex;
flex-direction: column;
gap: 4px;
padding: 12px 14px;
background: var(--gg-bg);
border-radius: var(--gg-radius-sm);
margin-bottom: 14px;
border: 1px solid var(--gg-border);
}
.game-name {
font-size: 14px;
font-weight: 600;
color: var(--gg-text);
}
.session-name {
font-size: 12px;
color: var(--gg-text-secondary);
}
.invitation-actions {
display: flex;
gap: 8px;
}
.action-btn {
flex: 1;
padding: 10px;
border: none;
border-radius: var(--gg-radius-sm);
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: opacity 0.2s, box-shadow 0.2s;
}
.accept {
background: var(--gg-gradient-success);
color: white;
}
.accept:hover {
opacity: 0.9;
box-shadow: 0 0 12px rgba(34, 197, 94, 0.3);
}
.reject {
background: var(--gg-bg-elevated);
color: var(--gg-text);
border: 1px solid var(--gg-border);
}
.reject:hover {
border-color: var(--gg-danger);
color: var(--gg-danger);
}
.reject-input {
margin-top: 12px;
}
.reject-input textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--gg-border);
border-radius: var(--gg-radius-sm);
font-size: 13px;
resize: none;
background: var(--gg-bg);
color: var(--gg-text);
font-family: inherit;
}
.reject-input textarea::placeholder {
color: var(--gg-text-muted);
}
.reject-input textarea:focus {
outline: none;
border-color: var(--gg-primary);
box-shadow: 0 0 8px rgba(5, 150, 105, 0.15);
}
</style>