UserProfile.vue 20.4 KB
<template>
    <div class="min-h-screen bg-gray-50">
        <!-- Loading UI if no current user -->
        <div v-if="!currentUser" class="min-h-screen flex justify-center items-center bg-gray-50">
            <div class="text-center">
                <div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-green-500 mx-auto"></div>
                <p class="mt-4 text-gray-600">加载用户资料...</p>
            </div>
        </div>

        <template v-else>
            <!-- Profile Header -->
            <div class="bg-gradient-to-r from-green-500 to-blue-500 py-12 px-4">
                <div class="container mx-auto">
                    <div class="flex flex-col md:flex-row items-center justify-between">
                        <!-- User Info -->
                        <div class="flex flex-col md:flex-row items-center">
                            <div
                                class="h-24 w-24 md:h-32 md:w-32 rounded-full overflow-hidden border-4 border-white mb-4 md:mb-0 md:mr-6">
                                <img :src="currentUser.avatar || '/src/assets/images/avatars/default_avatar.svg'"
                                    :alt="currentUser.name" class="h-full w-full object-cover" />
                            </div>
                            <div class="text-center md:text-left">
                                <h1 class="text-2xl md:text-3xl font-bold text-white">{{ currentUser.name }}</h1>
                                <p class="text-white mt-2 opacity-90">@{{ currentUser.username }}</p>
                                <div class="flex flex-wrap justify-center md:justify-start mt-3 gap-2">
                                    <span v-for="(interest, index) in displayedInterests" :key="index"
                                        class="bg-white bg-opacity-20 text-white px-3 py-1 rounded-full text-sm">
                                        {{ interest }}
                                    </span>
                                    <span v-if="currentUser.interests && currentUser.interests.length > 3"
                                        class="bg-white bg-opacity-20 text-white px-3 py-1 rounded-full text-sm">
                                        +{{ currentUser.interests.length - 3 }}
                                    </span>
                                </div>
                            </div>
                        </div>

                        <!-- Actions -->
                        <div class="mt-6 md:mt-0 flex gap-2">
                            <Button @click="isEditModalOpen = true" variant="secondary">
                                <template #leftIcon>
                                    <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20"
                                        fill="currentColor">
                                        <path
                                            d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
                                    </svg>
                                </template>
                                编辑资料
                            </Button>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Profile Content -->
            <div class="container mx-auto py-8 px-4">
                <div class="bg-white rounded-lg shadow-sm overflow-hidden">
                    <!-- Tab headers -->
                    <div class="flex border-b border-gray-200">
                        <button v-for="(tab, index) in ['个人资料', '我的活动', '报名记录']" :key="index"
                            class="text-base py-2 px-4 font-medium transition-all duration-200 focus:outline-none border-b-2"
                            :class="currentTab === index
                                ? 'border-green-500 text-green-600'
                                : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'"
                            @click="currentTab = index" role="tab" :aria-selected="currentTab === index">
                            {{ tab }}
                        </button>
                    </div>
                    <!-- Tab content -->
                    <div class="pt-4">
                        <!-- 个人资料 -->
                        <div v-show="currentTab === 0" class="p-6">
                            <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
                                <!-- Left column -->
                                <div>
                                    <h3 class="text-lg font-medium text-gray-900 mb-4">基本信息</h3>
                                    <div class="space-y-4">
                                        <!-- Name -->
                                        <div>
                                            <h4 class="text-sm font-medium text-gray-500">姓名</h4>
                                            <p class="mt-1 text-gray-900">{{ currentUser?.name }}</p>
                                        </div>
                                        <!-- Location -->
                                        <div>
                                            <h4 class="text-sm font-medium text-gray-500">所在地</h4>
                                            <p class="mt-1 text-gray-900">{{ currentUser?.location || '未设置' }}</p>
                                        </div>
                                        <div>
                                            <h4 class="text-sm font-medium text-gray-500">性别</h4>
                                            <p class="mt-1 text-gray-900">{{ currentUser.gender || '未设置' }}</p>
                                        </div>
                                        <div>
                                            <h4 class="text-sm font-medium text-gray-500">年龄段</h4>
                                            <p class="mt-1 text-gray-900">{{ currentUser.age_group || '未设置' }}</p>
                                        </div>
                                        <div>
                                            <h4 class="text-sm font-medium text-gray-500">注册日期</h4>
                                            <p class="mt-1 text-gray-900">
                                                {{ formatRegistrationDate(currentUser.registration_date) }}
                                            </p>
                                        </div>
                                    </div>
                                </div>
                                <!-- Right column -->
                                <div>
                                    <h3 class="text-lg font-medium text-gray-900 mb-4">联系方式</h3>
                                    <div class="space-y-4">
                                        <!-- Email -->
                                        <div>
                                            <h4 class="text-sm font-medium text-gray-500">电子邮箱</h4>
                                            <p class="mt-1 text-gray-900">{{ currentUser?.email }}</p>
                                        </div>
                                        <!-- Phone -->
                                        <div>
                                            <h4 class="text-sm font-medium text-gray-500">手机号码</h4>
                                            <p class="mt-1 text-gray-900">{{ currentUser?.phone }}</p>
                                        </div>
                                    </div>

                                    <h3 class="text-lg font-medium text-gray-900 mt-8 mb-4">个人简介</h3>
                                    <p class="text-gray-700 whitespace-pre-wrap">
                                        {{ currentUser.bio || '暂无个人简介' }}
                                    </p>
                                </div>
                            </div>

                            <div class="mt-8">
                                <h3 class="text-lg font-medium text-gray-900 mb-4">阅读兴趣</h3>
                                <div v-if="currentUser.interests && currentUser.interests.length > 0">
                                    <div class="flex flex-wrap gap-2">
                                        <span v-for="(interest, index) in currentUser.interests" :key="index"
                                            class="bg-green-50 text-green-700 px-3 py-1 rounded-full text-sm">
                                            {{ interest }}
                                        </span>
                                    </div>
                                </div>
                                <div v-else>
                                    <p class="text-gray-500">暂无兴趣标签</p>
                                </div>
                            </div>
                        </div>
                        <!-- 我的活动 -->
                        <div v-show="currentTab === 1" class="p-6">
                            <div class="mb-8">
                                <h3 class="text-lg font-medium text-gray-900 mb-4">
                                    <span class="inline-block w-3 h-3 bg-green-500 rounded-full mr-2"></span>
                                    <span>即将参加的活动</span>
                                </h3>
                                <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
                                    <ActivityCard v-for="activity in upcomingActivities" :key="activity.id"
                                        :activity="activity" />
                                </div>
                            </div>
                        </div>
                        <!-- 报名记录 -->
                        <div v-show="currentTab === 2" class="p-6">
                            <div class="overflow-x-auto">
                                <table class="min-w-full divide-y divide-gray-200">
                                    <thead class="bg-gray-50">
                                        <tr>
                                            <th
                                                class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                                活动信息</th>
                                            <th
                                                class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                                报名时间</th>
                                            <th
                                                class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                                状态</th>
                                            <th
                                                class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                                操作</th>
                                        </tr>
                                    </thead>
                                    <tbody class="bg-white divide-y divide-gray-200">
                                        <tr v-for="registration in userRegistrations" :key="registration.id">
                                            <td class="px-6 py-4 whitespace-nowrap">
                                                <div class="flex items-center">
                                                    <div class="text-sm font-medium text-gray-900">
                                                        {{activities.find(a => a.id ===
                                                            registration.activity_id)?.title || '未知活动'
                                                        }}
                                                    </div>
                                                </div>
                                            </td>
                                            <td class="px-6 py-4 whitespace-nowrap">
                                                <div class="text-sm text-gray-900">
                                                    {{ formatRegistrationDate(registration.registration_time) }}
                                                </div>
                                            </td>
                                            <td class="px-6 py-4 whitespace-nowrap">
                                                <span :class="[
                                                    'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium',
                                                    registration.status === 'approved' ? 'bg-green-100 text-green-800' :
                                                        registration.status === 'pending' ? 'bg-yellow-100 text-yellow-800' :
                                                            'bg-gray-100 text-gray-800'
                                                ]">
                                                    {{ registration.status === 'approved' ? '已通过' :
                                                        registration.status === 'pending' ? '审核中' :
                                                            registration.status }}
                                                </span>
                                            </td>
                                            <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
                                                <router-link v-if="activities.find(a => a.id === registration.activity_id)" :to="`/activity/${registration.activity_id}`" class="text-green-600 hover:text-green-900 mr-4">
                                                    查看详情
                                                </router-link>
                                                <router-link v-if="registration.status === 'approved'" :to="`/check-in/${registration.activity_id}`" class="text-blue-600 hover:text-blue-900">
                                                    签到
                                                </router-link>
                                            </td>
                                        </tr>
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Edit Profile Modal -->
            <Modal :isOpen="isEditModalOpen" title="编辑个人资料">
                <div class="space-y-4">
                    <Input v-model="editForm.name" label="姓名" placeholder="请输入姓名" />
                    <Input v-model="editForm.email" label="电子邮箱" type="email" placeholder="请输入电子邮箱" />
                    <Input v-model="editForm.phone" label="手机号码" placeholder="请输入手机号码" />
                    <Input v-model="editForm.location" label="所在地" placeholder="请输入所在地" />
                    <div>
                        <label class="block text-sm font-medium text-gray-700 mb-1">个人简介</label>
                        <textarea v-model="editForm.bio" rows="4"
                            class="w-full rounded-md border-gray-300 shadow-sm focus:border-green-500 focus:ring-green-500"
                            placeholder="请输入个人简介"></textarea>
                    </div>
                    <div>
                        <label class="block text-sm font-medium text-gray-700 mb-1">兴趣标签</label>
                        <div class="flex flex-wrap gap-2 mb-2">
                            <span v-for="interest in editForm.interests" :key="interest"
                                class="bg-green-50 text-green-700 px-3 py-1 rounded-full text-sm flex items-center">
                                {{ interest }}
                                <button @click="handleRemoveInterest(interest)"
                                    class="ml-2 text-green-500 hover:text-green-700">
                                    ×
                                </button>
                            </span>
                        </div>
                        <div class="flex gap-2">
                            <Input v-model="interestInput" placeholder="添加兴趣标签" @keyup.enter="handleAddInterest" />
                            <Button @click="handleAddInterest" variant="secondary">添加</Button>
                        </div>
                    </div>
                </div>
                <template #footer>
                    <div class="flex justify-end gap-2">
                        <Button @click="isEditModalOpen = false" variant="ghost">取消</Button>
                        <Button @click="handleSubmitEdit" variant="primary">保存</Button>
                    </div>
                </template>
            </Modal>
        </template>
    </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'
import Button from '../components/shared/Button.vue'
import Input from '../components/shared/Input.vue'
import ActivityCard from '../components/shared/ActivityCard.vue'
import Modal from '../components/shared/Modal.vue'


import activitiesData from '../data/activities.json'
import registrationsData from '../data/registrations.json'
import usersData from '../data/users.json'


const activities = ref(activitiesData.activities)
const registrations = ref(registrationsData.registrations)
const currentUser = ref(usersData.users[0])

const userRegistrations = ref([])
const registeredActivities = ref([])
const pastActivities = ref([])
const upcomingActivities = ref([])
const isEditModalOpen = ref(false)
const currentTab = ref(0)
const editForm = ref({
    name: '',
    bio: '',
    email: '',
    phone: '',
    location: '',
    interests: []
})
const interestInput = ref('')

// Computed properties
const displayedInterests = computed(() => {
    return currentUser.value?.interests?.slice(0, 3) || []
})

// Methods
const handleInputChange = (e) => {
    const { name, value } = e.target
    editForm.value[name] = value
}

const handleAddInterest = () => {
    const interest = interestInput.value.trim()
    if (interest && !editForm.value.interests.includes(interest)) {
        editForm.value.interests.push(interest)
        interestInput.value = ''
    }
}

const handleRemoveInterest = (interestToRemove) => {
    editForm.value.interests = editForm.value.interests.filter(
        interest => interest !== interestToRemove
    )
}

const handleSubmitEdit = () => {
    // In a real app, this would make an API call to update the user profile
    console.log('Profile update submitted:', editForm.value)
    isEditModalOpen.value = false
    // Update would be handled through context in a real app
}

const formatRegistrationDate = (dateString) => {
    const date = new Date(dateString.replace(' ', 'T'))
    return new Intl.DateTimeFormat('zh-CN', {
        year: 'numeric',
        month: 'short',
        day: 'numeric',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
    }).format(date)
}

// Lifecycle hooks
onMounted(() => {
    if (currentUser.value) {
        editForm.value = {
            name: currentUser.value.name || '',
            bio: currentUser.value.bio || '',
            email: currentUser.value.email || '',
            phone: currentUser.value.phone || '',
            location: currentUser.value.location || '',
            interests: currentUser.value.interests || []
        }
    }

    if (currentUser.value && registrations.value && activities.value) {
        // Filter user registrations
        const userRegs = registrations.value.filter(reg => reg.user_id === currentUser.value.id)
        userRegistrations.value = userRegs

        // Get activities the user has registered for
        const regActivityIds = userRegs.map(reg => reg.activity_id)
        const regActivities = activities.value.filter(act => regActivityIds.includes(act.id))
        registeredActivities.value = regActivities

        const now = new Date()

        // Split activities into past and upcoming
        pastActivities.value = regActivities.filter(act => {
            return new Date(act.end_time.replace(' ', 'T')) < now
        })

        upcomingActivities.value = regActivities.filter(act => {
            return new Date(act.start_time.replace(' ', 'T')) > now
        })
    }
})
</script>