cfdbaf1095
- 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>
217 lines
4.6 KiB
Vue
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>
|