Registration.vue 15.6 KB
<template>
    <div class="min-h-screen bg-gray-50">
        <!-- Loading State -->
        <div v-if="loading" class="min-h-screen flex justify-center items-center bg-gray-50">
            <div class="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-green-500"></div>
        </div>

        <!-- Error State -->
        <div v-else-if="error || !activity"
            class="min-h-screen flex flex-col justify-center items-center bg-gray-50 px-4">
            <div class="text-red-500 text-6xl mb-4">
                <svg xmlns="http://www.w3.org/2000/svg" class="h-24 w-24" fill="none" viewBox="0 0 24 24"
                    stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
                        d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
                </svg>
            </div>
            <h1 class="text-2xl font-bold mb-2">出错了</h1>
            <p class="text-gray-600 mb-6">{{ error || '无法加载活动信息' }}</p>
            <Button @click="$router.push('/')" variant="primary">返回首页</Button>
        </div>

        <!-- Registration Closed State -->
        <div v-else-if="!isRegistrationOpen"
            class="min-h-screen flex flex-col justify-center items-center bg-gray-50 px-4">
            <div class="text-yellow-500 text-6xl mb-4">
                <svg xmlns="http://www.w3.org/2000/svg" class="h-24 w-24" fill="none" viewBox="0 0 24 24"
                    stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
                        d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
                </svg>
            </div>
            <h1 class="text-2xl font-bold mb-2">报名已截止</h1>
            <p class="text-gray-600 mb-6">该活动的报名时间为 {{ formatDate(activity.registration_start) }} 至 {{
                formatDate(activity.registration_end) }}</p>
            <Button @click="$router.push(`/activity/${activityId}`)" variant="primary">查看活动详情</Button>
        </div>

        <!-- Full State -->
        <div v-else-if="isFull" class="min-h-screen flex flex-col justify-center items-center bg-gray-50 px-4">
            <div class="text-blue-500 text-6xl mb-4">
                <svg xmlns="http://www.w3.org/2000/svg" class="h-24 w-24" fill="none" viewBox="0 0 24 24"
                    stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
                        d="M13 10V3L4 14h7v7l9-11h-7z" />
                </svg>
            </div>
            <h1 class="text-2xl font-bold mb-2">名额已满</h1>
            <p class="text-gray-600 mb-6">该活动名额已满,请关注其他精彩活动</p>
            <div class="flex space-x-4">
                <Button @click="$router.push(`/activity/${activityId}`)" variant="secondary">查看活动详情</Button>
                <Button @click="$router.push('/')" variant="primary">浏览更多活动</Button>
            </div>
        </div>

        <!-- Registration Form -->
        <div v-else class="py-8">
            <div class="container mx-auto px-4">
                <div class="max-w-3xl mx-auto bg-white rounded-lg shadow-sm overflow-hidden">
                    <!-- Header -->
                    <div class="bg-gradient-to-r from-green-500 to-blue-500 px-6 py-4">
                        <h1 class="text-2xl font-bold text-white">活动报名</h1>
                    </div>

                    <!-- Activity Info -->
                    <div class="border-b border-gray-200 bg-gray-50 px-6 py-4">
                        <h2 class="text-xl font-semibold text-gray-800 mb-2">{{ activity.title }}</h2>
                        <div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm text-gray-600">
                            <div class="flex items-center">
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500 mr-2" fill="none"
                                    viewBox="0 0 24 24" stroke="currentColor">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
                                        d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
                                </svg>
                                <span>活动时间:{{ formatDate(activity.start_time) }}</span>
                            </div>
                            <div class="flex items-center">
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500 mr-2" fill="none"
                                    viewBox="0 0 24 24" stroke="currentColor">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
                                        d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
                                        d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
                                </svg>
                                <span>{{ activity.activity_type === 'online' ? '线上活动' : `活动地点:${activity.location}`
                                    }}</span>
                            </div>
                            <div class="flex items-center">
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500 mr-2" fill="none"
                                    viewBox="0 0 24 24" stroke="currentColor">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
                                        d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
                                </svg>
                                <span>已报名:{{ activity.participant_count }}/{{ activity.max_participants }}</span>
                            </div>
                            <div class="flex items-center">
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500 mr-2" fill="none"
                                    viewBox="0 0 24 24" stroke="currentColor">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
                                        d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
                                </svg>
                                <span>报名截止:{{ formatDate(activity.registration_end) }}</span>
                            </div>
                        </div>
                    </div>

                    <!-- Registration Form -->
                    <form class="p-6" @submit.prevent="handleSubmit">
                        <h3 class="text-lg font-medium text-gray-900 mb-4">填写报名信息</h3>

                        <div class="space-y-6">
                            <Input id="name" label="姓名" v-model="formState.name" :error="formErrors.name"
                                placeholder="请输入您的姓名" required />

                            <Input id="phone" label="手机号码" v-model="formState.phone" :error="formErrors.phone"
                                placeholder="请输入您的手机号码" required />

                            <Input id="email" label="电子邮箱" type="email" v-model="formState.email"
                                :error="formErrors.email" placeholder="请输入您的电子邮箱" required />

                            <div>
                                <label for="reason" class="block text-sm font-medium text-gray-700 mb-1">
                                    参加原因 <span class="text-red-500">*</span>
                                </label>
                                <textarea id="reason" v-model="formState.reason" rows="4" :class="[
                                    'block w-full border-gray-300 rounded-md shadow-sm focus:ring-green-500 focus:border-green-500 sm:text-sm',
                                    formErrors.reason ? 'border-red-300' : ''
                                ]" placeholder="请简要说明您参加本次活动的原因或期望..."></textarea>
                                <p v-if="formErrors.reason" class="mt-1 text-sm text-red-600">{{ formErrors.reason }}
                                </p>
                            </div>

                            <div class="flex items-start">
                                <div class="flex items-center h-5">
                                    <input id="agreeTerms" type="checkbox" v-model="formState.agreeTerms"
                                        class="focus:ring-green-500 h-4 w-4 text-green-600 border-gray-300 rounded" />
                                </div>
                                <div class="ml-3 text-sm">
                                    <label for="agreeTerms"
                                        :class="['font-medium', formErrors.agreeTerms ? 'text-red-600' : 'text-gray-700']">
                                        我同意活动条款和隐私政策
                                    </label>
                                    <p class="text-gray-500">
                                        注册即表示您同意我们的
                                        <a href="#" class="text-green-600 hover:text-green-500">服务条款</a>和
                                        <a href="#" class="text-green-600 hover:text-green-500">隐私政策</a>。
                                    </p>
                                    <p v-if="formErrors.agreeTerms" class="mt-1 text-sm text-red-600">{{
                                        formErrors.agreeTerms }}</p>
                                </div>
                            </div>
                        </div>

                        <div class="mt-8 flex justify-between">
                            <Button type="button" variant="secondary" @click="$router.push(`/activity/${activityId}`)">
                                返回活动详情
                            </Button>
                            <Button type="submit" variant="primary" :disabled="isSubmitting">
                                {{ isSubmitting ? '提交中...' : '确认报名' }}
                            </Button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import activitiesData from '../data/activities.json'
import registrationsData from '../data/registrations.json'
import Button from '../components/shared/Button.vue'
import Input from '../components/shared/Input.vue'

const route = useRoute()
const router = useRouter()
const activities = ref(activitiesData.activities)
const registrations = ref(registrationsData.registrations)

const activityId = route.params.activityId
const activity = ref(null)
const loading = ref(true)
const error = ref(null)
const isSubmitting = ref(false)

const formState = ref({
    name: '',
    phone: '',
    email: '',
    reason: '',
    agreeTerms: false
})

const formErrors = ref({})

// Fetch activity details
onMounted(async () => {
    try {
        const foundActivity = activities.value.find(a => a.id === activityId)
        if (foundActivity) {
                activity.value = foundActivity

                // Pre-fill form with user data if available
                if (store.currentUser) {
                    formState.value = {
                        ...formState.value,
                        name: store.currentUser.name || '',
                        phone: store.currentUser.phone || '',
                        email: store.currentUser.email || ''
                    }
                }
            } else {
                error.value = '未找到活动信息'
            }

        loading.value = false
    } catch (err) {
        console.error('Failed to fetch activity details:', err)
        error.value = '加载活动详情失败'
        loading.value = false
    }
})

// Validate form
const validateForm = () => {
    const errors = {}

    if (!formState.value.name.trim()) {
        errors.name = '请输入您的姓名'
    }

    if (!formState.value.phone.trim()) {
        errors.phone = '请输入您的手机号码'
    } else if (!/^1[3-9]\d{9}$/.test(formState.value.phone)) {
        errors.phone = '请输入有效的手机号码'
    }

    if (!formState.value.email.trim()) {
        errors.email = '请输入您的电子邮箱'
    } else if (!/\S+@\S+\.\S+/.test(formState.value.email)) {
        errors.email = '请输入有效的电子邮箱'
    }

    if (!formState.value.reason.trim()) {
        errors.reason = '请简要说明参加原因'
    }

    if (!formState.value.agreeTerms) {
        errors.agreeTerms = '请同意活动条款和隐私政策'
    }

    formErrors.value = errors
    return Object.keys(errors).length === 0
}

// Handle form submission
const handleSubmit = async () => {
    if (!store.currentUser) {
        alert('请先登录再进行报名')
        // Redirect to login page in a real app
        return
    }

    if (!validateForm()) {
        // Focus on first error field
        const firstErrorField = Object.keys(formErrors.value)[0]
        const element = document.getElementById(firstErrorField)
        if (element) {
            element.scrollIntoView({ behavior: 'smooth', block: 'center' })
        }
        return
    }

    isSubmitting.value = true

    try {
        const registrationData = {
            fields: {
                姓名: true,
                手机号: true,
                邮箱: true,
                参加原因: true
            },
            answers: {
                姓名: formState.value.name,
                手机号: formState.value.phone,
                邮箱: formState.value.email,
                参加原因: formState.value.reason
            }
        }

        const result = await store.registerForActivity(activityId, registrationData)

        if (result.success) {
            alert('报名成功,请等待审核')
            router.push(`/activity/${activityId}`)
        } else {
            alert(`报名失败: ${result.error}`)
            isSubmitting.value = false
        }
    } catch (error) {
        console.error('Registration error:', error)
        alert('报名失败,请稍后再试')
        isSubmitting.value = false
    }
}

// Format date for display
const formatDate = (dateString) => {
    if (!dateString) return ''
    const date = new Date(dateString.replace(' ', 'T'))
    return new Intl.DateTimeFormat('zh-CN', {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
    }).format(date)
}

// Check if registration is still open
const isRegistrationOpen = computed(() => {
    if (!activity.value) return false
    return (
        new Date() >= new Date(activity.value.registration_start.replace(' ', 'T')) &&
        new Date() <= new Date(activity.value.registration_end.replace(' ', 'T'))
    )
})

// Check if activity is full
const isFull = computed(() => {
    if (!activity.value) return false
    return activity.value.participant_count >= activity.value.max_participants
})
</script>