CreateActivity.vue 17.9 KB
<template>
    <div class="min-h-screen bg-gray-50 py-8">
        <div class="container mx-auto px-4">
            <div class="max-w-4xl 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>

                <!-- Form -->
                <form class="p-6" @submit.prevent="handlePreview">
                    <div class="space-y-8">
                        <!-- Basic Information Section -->
                        <div>
                            <h2 class="text-xl font-medium text-gray-900 border-b pb-2">基本信息</h2>
                            <div class="mt-4 space-y-6">
                                <!-- Title -->
                                <Input id="title" label="活动标题" name="title" v-model="formState.title"
                                    placeholder="例如:《百年孤独》读书分享会" :error="formErrors.title" required />

                                <!-- Description -->
                                <div>
                                    <label for="description" class="block text-sm font-medium text-gray-700 mb-1">
                                        活动描述 <span class="text-red-500">*</span>
                                    </label>
                                    <textarea id="description" name="description" rows="8"
                                        v-model="formState.description" :class="[
                                            'block w-full border-gray-300 rounded-md shadow-sm focus:ring-green-500 focus:border-green-500 sm:text-sm',
                                            formErrors.description ? 'border-red-300' : ''
                                        ]" placeholder="请描述活动内容、流程安排、参与须知等信息..."></textarea>
                                    <p v-if="formErrors.description" class="mt-1 text-sm text-red-600">{{
                                        formErrors.description }}</p>
                                </div>

                                <!-- Cover Image -->
                                <div>
                                    <label for="cover_image" class="block text-sm font-medium text-gray-700 mb-1">
                                        封面图片
                                    </label>
                                    <div class="mt-1 flex items-center space-x-4">
                                        <div class="w-48 h-32 bg-gray-100 rounded-md overflow-hidden">
                                            <img :src="formState.cover_image" alt="活动封面预览"
                                                class="w-full h-full object-cover" />
                                        </div>
                                        <Input id="cover_image" name="cover_image" v-model="formState.cover_image"
                                            placeholder="输入图片URL或上传图片" />
                                    </div>
                                    <p class="mt-1 text-sm text-gray-500">
                                        建议尺寸:1200 x 675 像素,格式:JPG、PNG
                                    </p>
                                </div>

                                <!-- Tags -->
                                <div>
                                    <label for="tagInput" class="block text-sm font-medium text-gray-700 mb-1">
                                        活动标签
                                    </label>
                                    <div class="flex flex-wrap items-center gap-2 mb-2">
                                        <div v-for="(tag, index) in formState.tags" :key="index"
                                            class="bg-green-50 text-green-700 rounded-full px-3 py-1 text-sm flex items-center">
                                            <span>{{ tag }}</span>
                                            <button type="button" @click="removeTag(tag)"
                                                class="ml-1 text-green-500 hover:text-green-700 focus:outline-none">
                                                &times;
                                            </button>
                                        </div>
                                        <Input id="tagInput" name="tagInput" v-model="formState.tagInput"
                                            @keydown="handleTagKeyDown" placeholder="输入标签并按回车" class="flex-1" />
                                    </div>
                                    <p class="text-sm text-gray-500">
                                        添加相关标签,使活动更容易被发现,如:小说、科普、心理学等
                                    </p>
                                </div>
                            </div>
                        </div>

                        <!-- Time and Location Section -->
                        <div>
                            <h2 class="text-xl font-medium text-gray-900 border-b pb-2">时间与地点</h2>
                            <div class="mt-4 space-y-6">
                                <!-- Activity Time -->
                                <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
                                    <Input id="start_time" type="datetime-local" label="活动开始时间" name="start_time"
                                        v-model="formState.start_time" :error="formErrors.start_time" required />

                                    <Input id="end_time" type="datetime-local" label="活动结束时间" name="end_time"
                                        v-model="formState.end_time" :error="formErrors.end_time" required />
                                </div>

                                <!-- Registration Time -->
                                <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
                                    <Input id="registration_start" type="datetime-local" label="报名开始时间"
                                        name="registration_start" v-model="formState.registration_start"
                                        :error="formErrors.registration_start" required />

                                    <Input id="registration_end" type="datetime-local" label="报名结束时间"
                                        name="registration_end" v-model="formState.registration_end"
                                        :error="formErrors.registration_end" required />
                                </div>

                                <!-- Activity Type -->
                                <div>
                                    <label class="block text-sm font-medium text-gray-700 mb-1">
                                        活动形式 <span class="text-red-500">*</span>
                                    </label>
                                    <div class="flex space-x-4">
                                        <label class="inline-flex items-center">
                                            <input type="radio" name="activity_type" value="offline"
                                                v-model="formState.activity_type"
                                                class="h-4 w-4 text-green-600 border-gray-300 focus:ring-green-500" />
                                            <span class="ml-2">线下活动</span>
                                        </label>
                                        <label class="inline-flex items-center">
                                            <input type="radio" name="activity_type" value="online"
                                                v-model="formState.activity_type"
                                                class="h-4 w-4 text-green-600 border-gray-300 focus:ring-green-500" />
                                            <span class="ml-2">线上活动</span>
                                        </label>
                                    </div>
                                </div>

                                <!-- Location or Online Link -->
                                <Input v-if="formState.activity_type === 'offline'" id="location" label="活动地点"
                                    name="location" v-model="formState.location" placeholder="例如:北京市海淀区中关村图书馆三层读书区"
                                    :error="formErrors.location" required />
                                <Input v-else id="online_link" label="线上会议信息" name="online_link"
                                    v-model="formState.online_link" placeholder="例如:Zoom会议ID或飞书会议链接"
                                    :error="formErrors.online_link" required />
                            </div>
                        </div>

                        <!-- Settings Section -->
                        <div>
                            <h2 class="text-xl font-medium text-gray-900 border-b pb-2">活动设置</h2>
                            <div class="mt-4 space-y-6">
                                <!-- Maximum Participants -->
                                <div class="max-w-xs">
                                    <Input id="max_participants" type="number" label="最大参与人数" name="max_participants"
                                        v-model="formState.max_participants" min="1" max="500"
                                        :error="formErrors.max_participants" />
                                </div>

                                <!-- Visibility -->
                                <div>
                                    <label class="block text-sm font-medium text-gray-700 mb-1">
                                        活动可见性
                                    </label>
                                    <label class="inline-flex items-center">
                                        <input type="checkbox" name="is_public" v-model="formState.is_public"
                                            class="h-4 w-4 text-green-600 border-gray-300 rounded focus:ring-green-500" />
                                        <span class="ml-2">公开活动(所有人可见)</span>
                                    </label>
                                    <p class="mt-1 text-sm text-gray-500">
                                        {{ formState.is_public ? '活动将在首页和搜索结果中显示' : '活动仅对邀请的用户可见' }}
                                    </p>
                                </div>
                            </div>
                        </div>

                        <!-- Submit Buttons -->
                        <div class="flex justify-end space-x-4 pt-6">
                            <Button type="button" variant="secondary" @click="goBack">
                                取消
                            </Button>
                            <Button type="submit" variant="primary" :disabled="isLoading">
                                预览活动
                            </Button>
                        </div>
                    </div>
                </form>
            </div>
        </div>

        <!-- Preview Modal -->
        <Modal v-model:isOpen="showPreviewModal" title="活动预览" size="lg">
            <template #default>
                <div class="py-4">
                    <div class="space-y-6">
                        <!-- Preview Cover -->
                        <div class="relative h-48 rounded-lg overflow-hidden bg-gray-200">
                            <img :src="formState.cover_image" :alt="formState.title"
                                class="w-full h-full object-cover" />
                        </div>

                        <!-- Preview Title -->
                        <h2 class="text-2xl font-bold text-gray-900">{{ formState.title }}</h2>

                        <!-- Preview Tags -->
                        <div v-if="formState.tags.length > 0" class="flex flex-wrap gap-2">
                            <span v-for="(tag, index) in formState.tags" :key="index"
                                class="bg-green-50 text-green-700 rounded-full px-3 py-1 text-sm">
                                {{ tag }}
                            </span>
                        </div>

                        <!-- Preview Description -->
                        <div class="prose max-w-none text-gray-700">
                            <p v-for="(paragraph, idx) in formState.description.split('\n')" :key="idx" class="mb-2">
                                {{ paragraph }}
                            </p>
                        </div>
                    </div>
                </div>
            </template>
            <template #footer>
                <Button @click="showPreviewModal = false" variant="secondary" class="mr-3">
                    返回编辑
                </Button>
                <Button @click="handleSubmit" variant="primary" :disabled="isLoading">
                    {{ isLoading ? '创建中...' : '创建活动' }}
                </Button>
            </template>
        </Modal>
    </div>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useAppStore } from '../stores/app'
import Button from '../components/shared/Button.vue'
import Input from '../components/shared/Input.vue'
import Modal from '../components/shared/Modal.vue'

const router = useRouter()
const appStore = useAppStore()
const { currentUser } = appStore

const isLoading = ref(false)
const showPreviewModal = ref(false)
const formErrors = reactive({})

// Form state
const formState = reactive({
    title: '',
    description: '',
    cover_image: '/assets/images/LEVRaos2Ero.jpg',
    start_time: '',
    end_time: '',
    registration_start: '',
    registration_end: '',
    activity_type: 'offline', // 'online' or 'offline'
    location: '',
    online_link: '',
    max_participants: 20,
    is_public: true,
    tags: [],
    tagInput: '' // For managing tag input
})

// Check if user is logged in
onMounted(() => {
    if (!currentUser) {
        // In a real app, redirect to login
        alert('请先登录再创建活动')
        router.push('/')
    }
})

// Handle tag input
const handleTagKeyDown = (e) => {
    if (e.key === 'Enter') {
        e.preventDefault()
        addTag()
    }
}

// Add tag to the list
const addTag = () => {
    const tag = formState.tagInput.trim()
    if (tag && !formState.tags.includes(tag)) {
        formState.tags.push(tag)
        formState.tagInput = ''
    }
}

// Remove tag from the list
const removeTag = (tagToRemove) => {
    formState.tags = formState.tags.filter(tag => tag !== tagToRemove)
}

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

    if (!formState.title.trim()) {
        errors.title = '请输入活动标题'
    }

    if (!formState.description.trim()) {
        errors.description = '请输入活动描述'
    }

    if (!formState.start_time) {
        errors.start_time = '请选择活动开始时间'
    }

    if (!formState.end_time) {
        errors.end_time = '请选择活动结束时间'
    } else if (new Date(formState.end_time) <= new Date(formState.start_time)) {
        errors.end_time = '结束时间必须晚于开始时间'
    }

    if (!formState.registration_start) {
        errors.registration_start = '请选择报名开始时间'
    }

    if (!formState.registration_end) {
        errors.registration_end = '请选择报名结束时间'
    } else if (new Date(formState.registration_end) <= new Date(formState.registration_start)) {
        errors.registration_end = '报名结束时间必须晚于开始时间'
    }

    if (formState.activity_type === 'offline' && !formState.location.trim()) {
        errors.location = '请输入活动地点'
    }

    if (formState.activity_type === 'online' && !formState.online_link.trim()) {
        errors.online_link = '请输入线上会议链接'
    }

    if (formState.max_participants <= 0) {
        errors.max_participants = '参与人数必须大于0'
    }

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

// Preview activity before submission
const handlePreview = (e) => {
    e.preventDefault()
    if (validateForm()) {
        showPreviewModal.value = true
    } else {
        // Scroll to the first error
        const firstErrorField = Object.keys(formErrors)[0]
        const element = document.getElementById(firstErrorField)
        if (element) {
            element.scrollIntoView({ behavior: 'smooth', block: 'center' })
        }
    }
}

// Submit form to create activity
const handleSubmit = async () => {
    if (validateForm()) {
        isLoading.value = true
        try {
            // Create form data for submission
            const activityData = { ...formState }
            delete activityData.tagInput
            activityData.participant_count = 0
            activityData.is_published = true

            const result = await appStore.createActivity(activityData)

            if (result.success) {
                alert('活动创建成功!')
                router.push(`/activity/${result.activityId}`)
            } else {
                alert(`创建失败: ${result.error || '未知错误'}`)
                isLoading.value = false
            }
        } catch (error) {
            console.error('Failed to create activity:', error)
            alert('创建活动失败,请稍后再试')
            isLoading.value = false
        }
    }
}

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

// Navigation
const goBack = () => {
    router.back()
}
</script>