ActivitySignupPage.vue
6.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
<template>
<div class="min-h-screen flex flex-col bg-gradient-to-br from-green-50 via-teal-50 to-blue-50 py-12 px-4 sm:px-6 lg:px-8">
<div class="sm:mx-auto sm:w-full sm:max-w-md">
<h2 class="text-center text-2xl font-bold text-gray-800 mb-2">活动报名</h2>
<p class="text-center text-gray-600">{{ activity?.title }}</p>
</div>
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<FrostedGlass class="py-8 px-6 rounded-lg">
<!-- 活动基本信息 -->
<div class="mb-6 space-y-4">
<div class="flex items-center justify-between text-sm">
<span class="text-gray-500">活动时间</span>
<span class="text-gray-700">{{ activity?.period }}</span>
</div>
<div class="flex items-center justify-between text-sm">
<span class="text-gray-500">活动地点</span>
<span class="text-gray-700">{{ activity?.location }}</span>
</div>
<div class="flex items-center justify-between text-sm">
<span class="text-gray-500">活动费用</span>
<span class="text-red-500 font-medium">¥{{ activity?.price }}/人</span>
</div>
</div>
<div v-if="error" class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-md">
{{ error }}
</div>
<form class="space-y-6" @submit.prevent="handleSubmit">
<div>
<label for="name" class="block text-sm font-medium text-gray-700">
联系人姓名 <span class="text-red-500">*</span>
</label>
<input
id="name"
v-model="formData.name"
type="text"
required
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
/>
</div>
<div>
<label for="phone" class="block text-sm font-medium text-gray-700">
联系电话 <span class="text-red-500">*</span>
</label>
<input
id="phone"
v-model="formData.phone"
type="tel"
required
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
/>
</div>
<div>
<label for="participants" class="block text-sm font-medium text-gray-700">
参与人数 <span class="text-red-500">*</span>
</label>
<input
id="participants"
v-model="formData.participants"
type="number"
min="1"
:max="activity?.maxParticipants - activity?.participantsCount"
required
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
/>
</div>
<div>
<label for="remark" class="block text-sm font-medium text-gray-700">
备注信息
</label>
<textarea
id="remark"
v-model="formData.remark"
rows="3"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
></textarea>
</div>
<div class="flex items-center">
<input
id="agreeTerms"
v-model="formData.agreeTerms"
type="checkbox"
class="h-4 w-4 text-green-600 focus:ring-green-500 border-gray-300 rounded"
/>
<label for="agreeTerms" class="ml-2 block text-sm text-gray-700">
我已阅读并同意 <a href="#" class="text-green-600 hover:text-green-500">活动协议</a>
</label>
</div>
<div class="space-y-2">
<div class="flex items-center justify-between text-sm">
<span class="text-gray-500">费用合计</span>
<span class="text-red-500 font-bold">¥{{ totalAmount }}</span>
</div>
<button
type="submit"
:disabled="loading"
class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
:class="{ 'opacity-70 cursor-not-allowed': loading }"
>
{{ loading ? '提交中...' : '提交报名' }}
</button>
</div>
</form>
</FrostedGlass>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import FrostedGlass from '@/components/ui/FrostedGlass.vue'
import { activities } from '@/utils/mockData'
import { useTitle } from '@vueuse/core';
const $route = useRoute();
const $router = useRouter();
useTitle($route.meta.title);
const route = useRoute()
const router = useRouter()
const activity = ref(null)
const formData = reactive({
name: '',
phone: '',
participants: 1,
remark: '',
agreeTerms: false
})
const error = ref('')
const loading = ref(false)
// 获取活动数据
onMounted(() => {
const id = route.params.id
const foundActivity = activities.find((a) => a.id === id)
if (foundActivity) {
activity.value = foundActivity
} else {
router.push('/activities')
}
})
// 计算总金额
const totalAmount = computed(() => {
return activity.value ? activity.value.price * formData.participants : 0
})
const handleSubmit = async () => {
if (!formData.name || !formData.phone || !formData.participants) {
error.value = '请填写所有必填字段'
return
}
if (!formData.agreeTerms) {
error.value = '请阅读并同意活动协议'
return
}
try {
error.value = ''
loading.value = true
// TODO: 实现报名和支付逻辑
await new Promise(resolve => setTimeout(resolve, 1000))
// 报名成功后跳转回活动详情页,使用replace避免返回到报名页
router.replace(`/activities/${route.params.id}`)
} catch (e) {
error.value = '报名失败,请稍后重试'
} finally {
loading.value = false
}
}
</script>