refactor(components): 替换Tabs组件为内联实现
在ActivityDetail.vue和UserProfile.vue中,将Tabs组件替换为内联实现,以提高代码的可读性和维护性。同时修复了路径中的空格问题,并调整了路由参数的命名。
Showing
6 changed files
with
253 additions
and
318 deletions
| ... | @@ -10,7 +10,6 @@ | ... | @@ -10,7 +10,6 @@ |
| 10 | "preview": "vite preview" | 10 | "preview": "vite preview" |
| 11 | }, | 11 | }, |
| 12 | "dependencies": { | 12 | "dependencies": { |
| 13 | - "@vitejs/plugin-vue-jsx": "4.1.2", | ||
| 14 | "axios": "^1.8.4", | 13 | "axios": "^1.8.4", |
| 15 | "pinia": "^2.1.7", | 14 | "pinia": "^2.1.7", |
| 16 | "vue": "^3.4.21", | 15 | "vue": "^3.4.21", |
| ... | @@ -18,6 +17,7 @@ | ... | @@ -18,6 +17,7 @@ |
| 18 | }, | 17 | }, |
| 19 | "devDependencies": { | 18 | "devDependencies": { |
| 20 | "@vitejs/plugin-vue": "^5.0.4", | 19 | "@vitejs/plugin-vue": "^5.0.4", |
| 20 | + "@vitejs/plugin-vue-jsx": "4.1.2", | ||
| 21 | "@vue/compiler-sfc": "^3.4.21", | 21 | "@vue/compiler-sfc": "^3.4.21", |
| 22 | "autoprefixer": "^10.4.20", | 22 | "autoprefixer": "^10.4.20", |
| 23 | "eslint": "^9.9.0", | 23 | "eslint": "^9.9.0", | ... | ... |
| ... | @@ -8,9 +8,6 @@ importers: | ... | @@ -8,9 +8,6 @@ importers: |
| 8 | 8 | ||
| 9 | .: | 9 | .: |
| 10 | dependencies: | 10 | dependencies: |
| 11 | - '@vitejs/plugin-vue-jsx': | ||
| 12 | - specifier: 4.1.2 | ||
| 13 | - version: 4.1.2(vite@5.4.18)(vue@3.5.13) | ||
| 14 | axios: | 11 | axios: |
| 15 | specifier: ^1.8.4 | 12 | specifier: ^1.8.4 |
| 16 | version: 1.8.4 | 13 | version: 1.8.4 |
| ... | @@ -27,6 +24,9 @@ importers: | ... | @@ -27,6 +24,9 @@ importers: |
| 27 | '@vitejs/plugin-vue': | 24 | '@vitejs/plugin-vue': |
| 28 | specifier: ^5.0.4 | 25 | specifier: ^5.0.4 |
| 29 | version: 5.2.3(vite@5.4.18)(vue@3.5.13) | 26 | version: 5.2.3(vite@5.4.18)(vue@3.5.13) |
| 27 | + '@vitejs/plugin-vue-jsx': | ||
| 28 | + specifier: 4.1.2 | ||
| 29 | + version: 4.1.2(vite@5.4.18)(vue@3.5.13) | ||
| 30 | '@vue/compiler-sfc': | 30 | '@vue/compiler-sfc': |
| 31 | specifier: ^3.4.21 | 31 | specifier: ^3.4.21 |
| 32 | version: 3.5.13 | 32 | version: 3.5.13 | ... | ... |
| ... | @@ -16,7 +16,7 @@ | ... | @@ -16,7 +16,7 @@ |
| 16 | 16 | ||
| 17 | <!-- Tab content --> | 17 | <!-- Tab content --> |
| 18 | <div class="pt-4"> | 18 | <div class="pt-4"> |
| 19 | - <component :is="tabs[currentTab]?.content" /> | 19 | + <component :is="tabs[currentTab]?.component" /> |
| 20 | </div> | 20 | </div> |
| 21 | </div> | 21 | </div> |
| 22 | </template> | 22 | </template> | ... | ... |
| 1 | +/* | ||
| 2 | + * @Date: 2025-04-17 14:26:17 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-04-21 13:26:32 | ||
| 5 | + * @FilePath: /mlaj-reading-club/src/main.js | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | + */ | ||
| 1 | import { createApp } from 'vue' | 8 | import { createApp } from 'vue' |
| 2 | import { createPinia } from 'pinia' | 9 | import { createPinia } from 'pinia' |
| 3 | import { createRouter, createWebHistory } from 'vue-router' | 10 | import { createRouter, createWebHistory } from 'vue-router' |
| ... | @@ -16,7 +23,7 @@ const router = createRouter({ | ... | @@ -16,7 +23,7 @@ const router = createRouter({ |
| 16 | history: createWebHistory(), | 23 | history: createWebHistory(), |
| 17 | routes: [ | 24 | routes: [ |
| 18 | { path: '/', component: () => import('./pages/HomePage.vue') }, | 25 | { path: '/', component: () => import('./pages/HomePage.vue') }, |
| 19 | - { path: '/activity/:activityId', component: () => import('./pages/ActivityDetail.vue') }, | 26 | + { path: '/activity/:id', component: () => import('./pages/ActivityDetail.vue') }, |
| 20 | { path: '/create-activity', component: () => import('./pages/CreateActivity.vue') }, | 27 | { path: '/create-activity', component: () => import('./pages/CreateActivity.vue') }, |
| 21 | { path: '/profile', component: () => import('./pages/UserProfile.vue') }, | 28 | { path: '/profile', component: () => import('./pages/UserProfile.vue') }, |
| 22 | { path: '/registration/:activityId', component: () => import('./pages/Registration.vue') }, | 29 | { path: '/registration/:activityId', component: () => import('./pages/Registration.vue') }, | ... | ... |
| ... | @@ -75,7 +75,64 @@ | ... | @@ -75,7 +75,64 @@ |
| 75 | </div> | 75 | </div> |
| 76 | 76 | ||
| 77 | <!-- Activity Tabs --> | 77 | <!-- Activity Tabs --> |
| 78 | - <Tabs :tabs="tabs" /> | 78 | + <div> |
| 79 | + <!-- Tab headers --> | ||
| 80 | + <div class="flex border-b border-gray-200"> | ||
| 81 | + <button v-for="(tab, index) in tabs" :key="index" | ||
| 82 | + class="text-base py-2 px-4 font-medium transition-all duration-200 focus:outline-none border-b-2" | ||
| 83 | + :class="currentTab === index | ||
| 84 | + ? 'border-green-500 text-green-600' | ||
| 85 | + : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'" | ||
| 86 | + @click="currentTab = index" role="tab" :aria-selected="currentTab === index"> | ||
| 87 | + {{ tab.label }} | ||
| 88 | + </button> | ||
| 89 | + </div> | ||
| 90 | + | ||
| 91 | + <!-- Tab content --> | ||
| 92 | + <div class="pt-4"> | ||
| 93 | + <div v-if="tabs[currentTab].label === '活动详情'"> | ||
| 94 | + <div class="space-y-4"> | ||
| 95 | + <div v-for="(paragraph, idx) in activity.description.split('\n')" :key="idx" | ||
| 96 | + class="mb-4"> | ||
| 97 | + {{ paragraph }} | ||
| 98 | + </div> | ||
| 99 | + </div> | ||
| 100 | + </div> | ||
| 101 | + <div v-if="tabs[currentTab].label === '参与须知'"> | ||
| 102 | + <div class="space-y-4"> | ||
| 103 | + <h3 class="font-medium text-lg">参与须知</h3> | ||
| 104 | + <ul class="list-disc pl-5 space-y-2"> | ||
| 105 | + <li>请准时到达活动地点或登录线上会议</li> | ||
| 106 | + <li>请提前阅读相关书籍或材料</li> | ||
| 107 | + <li>活动开始后,请将手机调至静音模式</li> | ||
| 108 | + <li>尊重他人发言,不打断他人</li> | ||
| 109 | + <li>可携带笔记本进行记录</li> | ||
| 110 | + <li>如需取消参与,请提前24小时通知主办方</li> | ||
| 111 | + </ul> | ||
| 112 | + </div> | ||
| 113 | + </div> | ||
| 114 | + <div v-if="tabs[currentTab].label === '常见问题'"> | ||
| 115 | + | ||
| 116 | + <div class="space-y-6"> | ||
| 117 | + <div> | ||
| 118 | + <h4 class="font-medium text-gray-900">如何取消报名?</h4> | ||
| 119 | + <p class="mt-2 text-gray-600"> | ||
| 120 | + 您可以在"我的活动"页面找到已报名的活动,点击"取消报名"按钮即可。请注意,活动开始前24小时内取消将无法获得退款。</p> | ||
| 121 | + </div> | ||
| 122 | + <div> | ||
| 123 | + <h4 class="font-medium text-gray-900">活动材料如何获取?</h4> | ||
| 124 | + <p class="mt-2 text-gray-600">报名成功后,您将在"我的活动"页面看到活动详情,相关材料可在页面底部下载或通过邮件接收。 | ||
| 125 | + </p> | ||
| 126 | + </div> | ||
| 127 | + <div> | ||
| 128 | + <h4 class="font-medium text-gray-900">线上活动如何参加?</h4> | ||
| 129 | + <p class="mt-2 text-gray-600"> | ||
| 130 | + 线上活动将在活动开始前30分钟发送会议链接到您的邮箱和手机短信,您也可以在"我的活动"页面找到入口链接。</p> | ||
| 131 | + </div> | ||
| 132 | + </div> | ||
| 133 | + </div> | ||
| 134 | + </div> | ||
| 135 | + </div> | ||
| 79 | </div> | 136 | </div> |
| 80 | 137 | ||
| 81 | <!-- Sidebar --> | 138 | <!-- Sidebar --> |
| ... | @@ -175,7 +232,7 @@ | ... | @@ -175,7 +232,7 @@ |
| 175 | <div class="mt-2 flex items-center"> | 232 | <div class="mt-2 flex items-center"> |
| 176 | <span class="text-sm text-green-700 mr-2">报名状态:</span> | 233 | <span class="text-sm text-green-700 mr-2">报名状态:</span> |
| 177 | <span :class="registrationStatusBadge.class">{{ registrationStatusBadge.text | 234 | <span :class="registrationStatusBadge.class">{{ registrationStatusBadge.text |
| 178 | - }}</span> | 235 | + }}</span> |
| 179 | </div> | 236 | </div> |
| 180 | </div> | 237 | </div> |
| 181 | 238 | ||
| ... | @@ -226,13 +283,17 @@ | ... | @@ -226,13 +283,17 @@ |
| 226 | </template> | 283 | </template> |
| 227 | 284 | ||
| 228 | <script setup> | 285 | <script setup> |
| 229 | -import { ref, computed, onMounted } from 'vue' | 286 | +import { ref, computed, onMounted, h, defineComponent } from 'vue' |
| 230 | import { useRoute, useRouter } from 'vue-router' | 287 | import { useRoute, useRouter } from 'vue-router' |
| 231 | import { useAppStore } from '../stores/app' | 288 | import { useAppStore } from '../stores/app' |
| 232 | import Button from '../components/shared/Button.vue' | 289 | import Button from '../components/shared/Button.vue' |
| 233 | import Modal from '../components/shared/Modal.vue' | 290 | import Modal from '../components/shared/Modal.vue' |
| 234 | -import Tabs from '../components/shared/Tabs.vue' | ||
| 235 | import ActivityCard from '../components/shared/ActivityCard.vue' | 291 | import ActivityCard from '../components/shared/ActivityCard.vue' |
| 292 | +import activitiesData from '../data/activities.json' | ||
| 293 | +import registrationsData from '../data/registrations.json' | ||
| 294 | + | ||
| 295 | +const activities = ref(activitiesData.activities) | ||
| 296 | +const registrations = ref(registrationsData.registrations) | ||
| 236 | 297 | ||
| 237 | const route = useRoute() | 298 | const route = useRoute() |
| 238 | const router = useRouter() | 299 | const router = useRouter() |
| ... | @@ -246,6 +307,7 @@ const showRegisterModal = ref(false) | ... | @@ -246,6 +307,7 @@ const showRegisterModal = ref(false) |
| 246 | const similarActivities = ref([]) | 307 | const similarActivities = ref([]) |
| 247 | const hasRegistered = ref(false) | 308 | const hasRegistered = ref(false) |
| 248 | const registrationStatus = ref(null) | 309 | const registrationStatus = ref(null) |
| 310 | +const currentTab = ref(0) | ||
| 249 | 311 | ||
| 250 | // Computed | 312 | // Computed |
| 251 | const activityStatus = computed(() => { | 313 | const activityStatus = computed(() => { |
| ... | @@ -305,44 +367,13 @@ const registrationStatusBadge = computed(() => { | ... | @@ -305,44 +367,13 @@ const registrationStatusBadge = computed(() => { |
| 305 | const tabs = computed(() => [ | 367 | const tabs = computed(() => [ |
| 306 | { | 368 | { |
| 307 | label: '活动详情', | 369 | label: '活动详情', |
| 308 | - content: activity.value?.description.split('\n').map((paragraph, idx) => ( | ||
| 309 | - `<p key="${idx}" class="mb-4">${paragraph}</p>` | ||
| 310 | - )).join('') | ||
| 311 | }, | 370 | }, |
| 312 | { | 371 | { |
| 313 | label: '参与须知', | 372 | label: '参与须知', |
| 314 | - content: ` | ||
| 315 | - <h3 class="font-medium text-lg mb-4">参与须知</h3> | ||
| 316 | - <ul class="list-disc pl-5 space-y-2"> | ||
| 317 | - <li>请准时到达活动地点或登录线上会议</li> | ||
| 318 | - <li>请提前阅读相关书籍或材料</li> | ||
| 319 | - <li>活动开始后,请将手机调至静音模式</li> | ||
| 320 | - <li>尊重他人发言,不打断他人</li> | ||
| 321 | - <li>可携带笔记本进行记录</li> | ||
| 322 | - <li>如需取消参与,请提前24小时通知主办方</li> | ||
| 323 | - </ul> | ||
| 324 | - ` | ||
| 325 | }, | 373 | }, |
| 326 | { | 374 | { |
| 327 | label: '常见问题', | 375 | label: '常见问题', |
| 328 | - content: ` | 376 | + } |
| 329 | - <div className="py-4"> | ||
| 330 | - <div className="space-y-6"> | ||
| 331 | - <div> | ||
| 332 | - <h4 className="font-medium text-gray-900">如何取消报名?</h4> | ||
| 333 | - <p className="mt-2 text-gray-600">您可以在"我的活动"页面找到已报名的活动,点击"取消报名"按钮即可。请注意,活动开始前24小时内取消将无法获得退款。</p> | ||
| 334 | - </div> | ||
| 335 | - <div> | ||
| 336 | - <h4 className="font-medium text-gray-900">活动材料如何获取?</h4> | ||
| 337 | - <p className="mt-2 text-gray-600">报名成功后,您将在"我的活动"页面看到活动详情,相关材料可在页面底部下载或通过邮件接收。</p> | ||
| 338 | - </div> | ||
| 339 | - <div> | ||
| 340 | - <h4 className="font-medium text-gray-900">线上活动如何参加?</h4> | ||
| 341 | - <p className="mt-2 text-gray-600">线上活动将在活动开始前30分钟发送会议链接到您的邮箱和手机短信,您也可以在"我的活动"页面找到入口链接。</p> | ||
| 342 | - </div> | ||
| 343 | - </div> | ||
| 344 | - </div> | ||
| 345 | - `} | ||
| 346 | ]) | 377 | ]) |
| 347 | 378 | ||
| 348 | // Functions | 379 | // Functions |
| ... | @@ -351,7 +382,7 @@ const goBack = () => { | ... | @@ -351,7 +382,7 @@ const goBack = () => { |
| 351 | } | 382 | } |
| 352 | 383 | ||
| 353 | const getActivityImage = (activityId) => { | 384 | const getActivityImage = (activityId) => { |
| 354 | - return `/ assets / images / activities / ${activityId}.jpg` | 385 | + return `/assets/images/activities/${activityId}.jpg` |
| 355 | } | 386 | } |
| 356 | 387 | ||
| 357 | const formatDate = (dateString) => { | 388 | const formatDate = (dateString) => { | ... | ... |
| ... | @@ -56,12 +56,170 @@ | ... | @@ -56,12 +56,170 @@ |
| 56 | <!-- Profile Content --> | 56 | <!-- Profile Content --> |
| 57 | <div class="container mx-auto py-8 px-4"> | 57 | <div class="container mx-auto py-8 px-4"> |
| 58 | <div class="bg-white rounded-lg shadow-sm overflow-hidden"> | 58 | <div class="bg-white rounded-lg shadow-sm overflow-hidden"> |
| 59 | - <Tabs :tabs="profileTabs" /> | 59 | + <!-- Tab headers --> |
| 60 | + <div class="flex border-b border-gray-200"> | ||
| 61 | + <button v-for="(tab, index) in ['个人资料', '我的活动', '报名记录']" :key="index" | ||
| 62 | + class="text-base py-2 px-4 font-medium transition-all duration-200 focus:outline-none border-b-2" | ||
| 63 | + :class="currentTab === index | ||
| 64 | + ? 'border-green-500 text-green-600' | ||
| 65 | + : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'" | ||
| 66 | + @click="currentTab = index" role="tab" :aria-selected="currentTab === index"> | ||
| 67 | + {{ tab }} | ||
| 68 | + </button> | ||
| 69 | + </div> | ||
| 70 | + <!-- Tab content --> | ||
| 71 | + <div class="pt-4"> | ||
| 72 | + <!-- 个人资料 --> | ||
| 73 | + <div v-show="currentTab === 0" class="p-6"> | ||
| 74 | + <div class="grid grid-cols-1 md:grid-cols-2 gap-8"> | ||
| 75 | + <!-- Left column --> | ||
| 76 | + <div> | ||
| 77 | + <h3 class="text-lg font-medium text-gray-900 mb-4">基本信息</h3> | ||
| 78 | + <div class="space-y-4"> | ||
| 79 | + <!-- Name --> | ||
| 80 | + <div> | ||
| 81 | + <h4 class="text-sm font-medium text-gray-500">姓名</h4> | ||
| 82 | + <p class="mt-1 text-gray-900">{{ currentUser?.name }}</p> | ||
| 83 | + </div> | ||
| 84 | + <!-- Location --> | ||
| 85 | + <div> | ||
| 86 | + <h4 class="text-sm font-medium text-gray-500">所在地</h4> | ||
| 87 | + <p class="mt-1 text-gray-900">{{ currentUser?.location || '未设置' }}</p> | ||
| 88 | + </div> | ||
| 89 | + <div> | ||
| 90 | + <h4 class="text-sm font-medium text-gray-500">性别</h4> | ||
| 91 | + <p class="mt-1 text-gray-900">{{ currentUser.gender || '未设置' }}</p> | ||
| 92 | + </div> | ||
| 93 | + <div> | ||
| 94 | + <h4 class="text-sm font-medium text-gray-500">年龄段</h4> | ||
| 95 | + <p class="mt-1 text-gray-900">{{ currentUser.age_group || '未设置' }}</p> | ||
| 96 | + </div> | ||
| 97 | + <div> | ||
| 98 | + <h4 class="text-sm font-medium text-gray-500">注册日期</h4> | ||
| 99 | + <p class="mt-1 text-gray-900"> | ||
| 100 | + {{ formatRegistrationDate(currentUser.registration_date) }} | ||
| 101 | + </p> | ||
| 102 | + </div> | ||
| 103 | + </div> | ||
| 104 | + </div> | ||
| 105 | + <!-- Right column --> | ||
| 106 | + <div> | ||
| 107 | + <h3 class="text-lg font-medium text-gray-900 mb-4">联系方式</h3> | ||
| 108 | + <div class="space-y-4"> | ||
| 109 | + <!-- Email --> | ||
| 110 | + <div> | ||
| 111 | + <h4 class="text-sm font-medium text-gray-500">电子邮箱</h4> | ||
| 112 | + <p class="mt-1 text-gray-900">{{ currentUser?.email }}</p> | ||
| 113 | + </div> | ||
| 114 | + <!-- Phone --> | ||
| 115 | + <div> | ||
| 116 | + <h4 class="text-sm font-medium text-gray-500">手机号码</h4> | ||
| 117 | + <p class="mt-1 text-gray-900">{{ currentUser?.phone }}</p> | ||
| 118 | + </div> | ||
| 119 | + </div> | ||
| 120 | + | ||
| 121 | + <h3 class="text-lg font-medium text-gray-900 mt-8 mb-4">个人简介</h3> | ||
| 122 | + <p class="text-gray-700 whitespace-pre-wrap"> | ||
| 123 | + {{ currentUser.bio || '暂无个人简介' }} | ||
| 124 | + </p> | ||
| 125 | + </div> | ||
| 126 | + </div> | ||
| 127 | + | ||
| 128 | + <div class="mt-8"> | ||
| 129 | + <h3 class="text-lg font-medium text-gray-900 mb-4">阅读兴趣</h3> | ||
| 130 | + <div v-if="currentUser.interests && currentUser.interests.length > 0"> | ||
| 131 | + <div class="flex flex-wrap gap-2"> | ||
| 132 | + <span v-for="(interest, index) in currentUser.interests" :key="index" | ||
| 133 | + class="bg-green-50 text-green-700 px-3 py-1 rounded-full text-sm"> | ||
| 134 | + {{ interest }} | ||
| 135 | + </span> | ||
| 136 | + </div> | ||
| 137 | + </div> | ||
| 138 | + <div v-else> | ||
| 139 | + <p class="text-gray-500">暂无兴趣标签</p> | ||
| 140 | + </div> | ||
| 141 | + </div> | ||
| 142 | + </div> | ||
| 143 | + <!-- 我的活动 --> | ||
| 144 | + <div v-show="currentTab === 1" class="p-6"> | ||
| 145 | + <div class="mb-8"> | ||
| 146 | + <h3 class="text-lg font-medium text-gray-900 mb-4"> | ||
| 147 | + <span class="inline-block w-3 h-3 bg-green-500 rounded-full mr-2"></span> | ||
| 148 | + <span>即将参加的活动</span> | ||
| 149 | + </h3> | ||
| 150 | + <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> | ||
| 151 | + <ActivityCard v-for="activity in upcomingActivities" :key="activity.id" | ||
| 152 | + :activity="activity" /> | ||
| 153 | + </div> | ||
| 154 | + </div> | ||
| 155 | + </div> | ||
| 156 | + <!-- 报名记录 --> | ||
| 157 | + <div v-show="currentTab === 2" class="p-6"> | ||
| 158 | + <div class="overflow-x-auto"> | ||
| 159 | + <table class="min-w-full divide-y divide-gray-200"> | ||
| 160 | + <thead class="bg-gray-50"> | ||
| 161 | + <tr> | ||
| 162 | + <th | ||
| 163 | + class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||
| 164 | + 活动信息</th> | ||
| 165 | + <th | ||
| 166 | + class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||
| 167 | + 报名时间</th> | ||
| 168 | + <th | ||
| 169 | + class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||
| 170 | + 状态</th> | ||
| 171 | + <th | ||
| 172 | + class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | ||
| 173 | + 操作</th> | ||
| 174 | + </tr> | ||
| 175 | + </thead> | ||
| 176 | + <tbody class="bg-white divide-y divide-gray-200"> | ||
| 177 | + <tr v-for="registration in userRegistrations" :key="registration.id"> | ||
| 178 | + <td class="px-6 py-4 whitespace-nowrap"> | ||
| 179 | + <div class="flex items-center"> | ||
| 180 | + <div class="text-sm font-medium text-gray-900"> | ||
| 181 | + {{activities.find(a => a.id === | ||
| 182 | + registration.activity_id)?.title || '未知活动' | ||
| 183 | + }} | ||
| 184 | + </div> | ||
| 185 | + </div> | ||
| 186 | + </td> | ||
| 187 | + <td class="px-6 py-4 whitespace-nowrap"> | ||
| 188 | + <div class="text-sm text-gray-900"> | ||
| 189 | + {{ formatRegistrationDate(registration.registration_time) }} | ||
| 190 | + </div> | ||
| 191 | + </td> | ||
| 192 | + <td class="px-6 py-4 whitespace-nowrap"> | ||
| 193 | + <span :class="[ | ||
| 194 | + 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium', | ||
| 195 | + registration.status === 'approved' ? 'bg-green-100 text-green-800' : | ||
| 196 | + registration.status === 'pending' ? 'bg-yellow-100 text-yellow-800' : | ||
| 197 | + 'bg-gray-100 text-gray-800' | ||
| 198 | + ]"> | ||
| 199 | + {{ registration.status === 'approved' ? '已通过' : | ||
| 200 | + registration.status === 'pending' ? '审核中' : | ||
| 201 | + registration.status }} | ||
| 202 | + </span> | ||
| 203 | + </td> | ||
| 204 | + <td class="px-6 py-4 whitespace-nowrap text-sm font-medium"> | ||
| 205 | + <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"> | ||
| 206 | + 查看详情 | ||
| 207 | + </router-link> | ||
| 208 | + <router-link v-if="registration.status === 'approved'" :to="`/check-in/${registration.activity_id}`" class="text-blue-600 hover:text-blue-900"> | ||
| 209 | + 签到 | ||
| 210 | + </router-link> | ||
| 211 | + </td> | ||
| 212 | + </tr> | ||
| 213 | + </tbody> | ||
| 214 | + </table> | ||
| 215 | + </div> | ||
| 216 | + </div> | ||
| 217 | + </div> | ||
| 60 | </div> | 218 | </div> |
| 61 | </div> | 219 | </div> |
| 62 | 220 | ||
| 63 | <!-- Edit Profile Modal --> | 221 | <!-- Edit Profile Modal --> |
| 64 | - <Modal v-model:show="isEditModalOpen" title="编辑个人资料"> | 222 | + <Modal :isOpen="isEditModalOpen" title="编辑个人资料"> |
| 65 | <div class="space-y-4"> | 223 | <div class="space-y-4"> |
| 66 | <Input v-model="editForm.name" label="姓名" placeholder="请输入姓名" /> | 224 | <Input v-model="editForm.name" label="姓名" placeholder="请输入姓名" /> |
| 67 | <Input v-model="editForm.email" label="电子邮箱" type="email" placeholder="请输入电子邮箱" /> | 225 | <Input v-model="editForm.email" label="电子邮箱" type="email" placeholder="请输入电子邮箱" /> |
| ... | @@ -107,18 +265,25 @@ import { ref, computed, onMounted } from 'vue' | ... | @@ -107,18 +265,25 @@ import { ref, computed, onMounted } from 'vue' |
| 107 | import { useAppStore } from '../stores/app' | 265 | import { useAppStore } from '../stores/app' |
| 108 | import Button from '../components/shared/Button.vue' | 266 | import Button from '../components/shared/Button.vue' |
| 109 | import Input from '../components/shared/Input.vue' | 267 | import Input from '../components/shared/Input.vue' |
| 110 | -import Tabs from '../components/shared/Tabs.vue' | ||
| 111 | import ActivityCard from '../components/shared/ActivityCard.vue' | 268 | import ActivityCard from '../components/shared/ActivityCard.vue' |
| 112 | import Modal from '../components/shared/Modal.vue' | 269 | import Modal from '../components/shared/Modal.vue' |
| 113 | 270 | ||
| 114 | -const store = useAppStore() | 271 | + |
| 115 | -const { currentUser, activities, registrations } = store | 272 | +import activitiesData from '../data/activities.json' |
| 273 | +import registrationsData from '../data/registrations.json' | ||
| 274 | +import usersData from '../data/users.json' | ||
| 275 | + | ||
| 276 | + | ||
| 277 | +const activities = ref(activitiesData.activities) | ||
| 278 | +const registrations = ref(registrationsData.registrations) | ||
| 279 | +const currentUser = ref(usersData.users[0]) | ||
| 116 | 280 | ||
| 117 | const userRegistrations = ref([]) | 281 | const userRegistrations = ref([]) |
| 118 | const registeredActivities = ref([]) | 282 | const registeredActivities = ref([]) |
| 119 | const pastActivities = ref([]) | 283 | const pastActivities = ref([]) |
| 120 | const upcomingActivities = ref([]) | 284 | const upcomingActivities = ref([]) |
| 121 | const isEditModalOpen = ref(false) | 285 | const isEditModalOpen = ref(false) |
| 286 | +const currentTab = ref(0) | ||
| 122 | const editForm = ref({ | 287 | const editForm = ref({ |
| 123 | name: '', | 288 | name: '', |
| 124 | bio: '', | 289 | bio: '', |
| ... | @@ -173,276 +338,6 @@ const formatRegistrationDate = (dateString) => { | ... | @@ -173,276 +338,6 @@ const formatRegistrationDate = (dateString) => { |
| 173 | }).format(date) | 338 | }).format(date) |
| 174 | } | 339 | } |
| 175 | 340 | ||
| 176 | -// Profile tabs configuration | ||
| 177 | -const profileTabs = computed(() => [ | ||
| 178 | - { | ||
| 179 | - label: "个人资料", | ||
| 180 | - content: { | ||
| 181 | - component: 'div', | ||
| 182 | - class: 'p-6', | ||
| 183 | - children: [ | ||
| 184 | - // Basic Info section | ||
| 185 | - { | ||
| 186 | - component: 'div', | ||
| 187 | - class: 'grid grid-cols-1 md:grid-cols-2 gap-8', | ||
| 188 | - children: [ | ||
| 189 | - // Left column | ||
| 190 | - { | ||
| 191 | - component: 'div', | ||
| 192 | - children: [ | ||
| 193 | - { | ||
| 194 | - component: 'h3', | ||
| 195 | - class: 'text-lg font-medium text-gray-900 mb-4', | ||
| 196 | - text: '基本信息' | ||
| 197 | - }, | ||
| 198 | - // User info fields | ||
| 199 | - { | ||
| 200 | - component: 'div', | ||
| 201 | - class: 'space-y-4', | ||
| 202 | - children: [ | ||
| 203 | - // Name | ||
| 204 | - { | ||
| 205 | - component: 'div', | ||
| 206 | - children: [ | ||
| 207 | - { | ||
| 208 | - component: 'h4', | ||
| 209 | - class: 'text-sm font-medium text-gray-500', | ||
| 210 | - text: '姓名' | ||
| 211 | - }, | ||
| 212 | - { | ||
| 213 | - component: 'p', | ||
| 214 | - class: 'mt-1 text-gray-900', | ||
| 215 | - text: currentUser.value?.name | ||
| 216 | - } | ||
| 217 | - ] | ||
| 218 | - }, | ||
| 219 | - // Location | ||
| 220 | - { | ||
| 221 | - component: 'div', | ||
| 222 | - children: [ | ||
| 223 | - { | ||
| 224 | - component: 'h4', | ||
| 225 | - class: 'text-sm font-medium text-gray-500', | ||
| 226 | - text: '所在地' | ||
| 227 | - }, | ||
| 228 | - { | ||
| 229 | - component: 'p', | ||
| 230 | - class: 'mt-1 text-gray-900', | ||
| 231 | - text: currentUser.value?.location || '未设置' | ||
| 232 | - } | ||
| 233 | - ] | ||
| 234 | - } | ||
| 235 | - ] | ||
| 236 | - } | ||
| 237 | - ] | ||
| 238 | - }, | ||
| 239 | - // Right column | ||
| 240 | - { | ||
| 241 | - component: 'div', | ||
| 242 | - children: [ | ||
| 243 | - { | ||
| 244 | - component: 'h3', | ||
| 245 | - class: 'text-lg font-medium text-gray-900 mb-4', | ||
| 246 | - text: '联系方式' | ||
| 247 | - }, | ||
| 248 | - // Contact info | ||
| 249 | - { | ||
| 250 | - component: 'div', | ||
| 251 | - class: 'space-y-4', | ||
| 252 | - children: [ | ||
| 253 | |||
| 254 | - { | ||
| 255 | - component: 'div', | ||
| 256 | - children: [ | ||
| 257 | - { | ||
| 258 | - component: 'h4', | ||
| 259 | - class: 'text-sm font-medium text-gray-500', | ||
| 260 | - text: '电子邮箱' | ||
| 261 | - }, | ||
| 262 | - { | ||
| 263 | - component: 'p', | ||
| 264 | - class: 'mt-1 text-gray-900', | ||
| 265 | - text: currentUser.value?.email | ||
| 266 | - } | ||
| 267 | - ] | ||
| 268 | - }, | ||
| 269 | - // Phone | ||
| 270 | - { | ||
| 271 | - component: 'div', | ||
| 272 | - children: [ | ||
| 273 | - { | ||
| 274 | - component: 'h4', | ||
| 275 | - class: 'text-sm font-medium text-gray-500', | ||
| 276 | - text: '手机号码' | ||
| 277 | - }, | ||
| 278 | - { | ||
| 279 | - component: 'p', | ||
| 280 | - class: 'mt-1 text-gray-900', | ||
| 281 | - text: currentUser.value?.phone | ||
| 282 | - } | ||
| 283 | - ] | ||
| 284 | - } | ||
| 285 | - ] | ||
| 286 | - } | ||
| 287 | - ] | ||
| 288 | - } | ||
| 289 | - ] | ||
| 290 | - } | ||
| 291 | - ] | ||
| 292 | - } | ||
| 293 | - }, | ||
| 294 | - { | ||
| 295 | - label: "我的活动", | ||
| 296 | - content: { | ||
| 297 | - component: 'div', | ||
| 298 | - class: 'p-6', | ||
| 299 | - children: [ | ||
| 300 | - // Upcoming activities section | ||
| 301 | - { | ||
| 302 | - component: 'div', | ||
| 303 | - class: 'mb-8', | ||
| 304 | - children: [ | ||
| 305 | - { | ||
| 306 | - component: 'h3', | ||
| 307 | - class: 'text-lg font-medium text-gray-900 mb-4', | ||
| 308 | - children: [ | ||
| 309 | - { | ||
| 310 | - component: 'span', | ||
| 311 | - class: 'inline-block w-3 h-3 bg-green-500 rounded-full mr-2' | ||
| 312 | - }, | ||
| 313 | - { | ||
| 314 | - component: 'span', | ||
| 315 | - text: '即将参加的活动' | ||
| 316 | - } | ||
| 317 | - ] | ||
| 318 | - }, | ||
| 319 | - // Activity cards | ||
| 320 | - { | ||
| 321 | - component: 'div', | ||
| 322 | - class: 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6', | ||
| 323 | - children: upcomingActivities.value.map(activity => ({ | ||
| 324 | - component: ActivityCard, | ||
| 325 | - props: { activity } | ||
| 326 | - })) | ||
| 327 | - } | ||
| 328 | - ] | ||
| 329 | - } | ||
| 330 | - ] | ||
| 331 | - } | ||
| 332 | - }, | ||
| 333 | - { | ||
| 334 | - label: "报名记录", | ||
| 335 | - content: { | ||
| 336 | - component: 'div', | ||
| 337 | - class: 'p-6', | ||
| 338 | - children: [ | ||
| 339 | - { | ||
| 340 | - component: 'div', | ||
| 341 | - class: 'overflow-x-auto', | ||
| 342 | - children: [ | ||
| 343 | - // Registration table | ||
| 344 | - { | ||
| 345 | - component: 'table', | ||
| 346 | - class: 'min-w-full divide-y divide-gray-200', | ||
| 347 | - children: [ | ||
| 348 | - // Table header | ||
| 349 | - { | ||
| 350 | - component: 'thead', | ||
| 351 | - class: 'bg-gray-50', | ||
| 352 | - children: [ | ||
| 353 | - { | ||
| 354 | - component: 'tr', | ||
| 355 | - children: [ | ||
| 356 | - { | ||
| 357 | - component: 'th', | ||
| 358 | - class: 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider', | ||
| 359 | - text: '活动信息' | ||
| 360 | - }, | ||
| 361 | - { | ||
| 362 | - component: 'th', | ||
| 363 | - class: 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider', | ||
| 364 | - text: '报名时间' | ||
| 365 | - }, | ||
| 366 | - { | ||
| 367 | - component: 'th', | ||
| 368 | - class: 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider', | ||
| 369 | - text: '状态' | ||
| 370 | - } | ||
| 371 | - ] | ||
| 372 | - } | ||
| 373 | - ] | ||
| 374 | - }, | ||
| 375 | - // Table body | ||
| 376 | - { | ||
| 377 | - component: 'tbody', | ||
| 378 | - class: 'bg-white divide-y divide-gray-200', | ||
| 379 | - children: userRegistrations.value.map(registration => ({ | ||
| 380 | - component: 'tr', | ||
| 381 | - key: registration.id, | ||
| 382 | - children: [ | ||
| 383 | - // Activity info | ||
| 384 | - { | ||
| 385 | - component: 'td', | ||
| 386 | - class: 'px-6 py-4 whitespace-nowrap', | ||
| 387 | - children: [ | ||
| 388 | - { | ||
| 389 | - component: 'div', | ||
| 390 | - class: 'flex items-center', | ||
| 391 | - children: [ | ||
| 392 | - { | ||
| 393 | - component: 'div', | ||
| 394 | - class: 'text-sm font-medium text-gray-900', | ||
| 395 | - text: activities.value.find(a => a.id === registration.activity_id)?.title || '未知活动' | ||
| 396 | - } | ||
| 397 | - ] | ||
| 398 | - } | ||
| 399 | - ] | ||
| 400 | - }, | ||
| 401 | - // Registration time | ||
| 402 | - { | ||
| 403 | - component: 'td', | ||
| 404 | - class: 'px-6 py-4 whitespace-nowrap', | ||
| 405 | - children: [ | ||
| 406 | - { | ||
| 407 | - component: 'div', | ||
| 408 | - class: 'text-sm text-gray-900', | ||
| 409 | - text: formatRegistrationDate(registration.registration_time) | ||
| 410 | - } | ||
| 411 | - ] | ||
| 412 | - }, | ||
| 413 | - // Status | ||
| 414 | - { | ||
| 415 | - component: 'td', | ||
| 416 | - class: 'px-6 py-4 whitespace-nowrap', | ||
| 417 | - children: [ | ||
| 418 | - { | ||
| 419 | - component: 'span', | ||
| 420 | - class: `inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${registration.status === 'approved' | ||
| 421 | - ? 'bg-green-100 text-green-800' | ||
| 422 | - : registration.status === 'pending' | ||
| 423 | - ? 'bg-yellow-100 text-yellow-800' | ||
| 424 | - : 'bg-gray-100 text-gray-800' | ||
| 425 | - }`, | ||
| 426 | - text: registration.status === 'approved' | ||
| 427 | - ? '已通过' | ||
| 428 | - : registration.status === 'pending' | ||
| 429 | - ? '审核中' | ||
| 430 | - : registration.status | ||
| 431 | - } | ||
| 432 | - ] | ||
| 433 | - } | ||
| 434 | - ] | ||
| 435 | - })) | ||
| 436 | - } | ||
| 437 | - ] | ||
| 438 | - } | ||
| 439 | - ] | ||
| 440 | - } | ||
| 441 | - ] | ||
| 442 | - } | ||
| 443 | - } | ||
| 444 | -]) | ||
| 445 | - | ||
| 446 | // Lifecycle hooks | 341 | // Lifecycle hooks |
| 447 | onMounted(() => { | 342 | onMounted(() => { |
| 448 | if (currentUser.value) { | 343 | if (currentUser.value) { |
| ... | @@ -456,6 +351,8 @@ onMounted(() => { | ... | @@ -456,6 +351,8 @@ onMounted(() => { |
| 456 | } | 351 | } |
| 457 | } | 352 | } |
| 458 | 353 | ||
| 354 | + console.warn(registrations.value); | ||
| 355 | + console.warn(activities.value); | ||
| 459 | if (currentUser.value && registrations.value && activities.value) { | 356 | if (currentUser.value && registrations.value && activities.value) { |
| 460 | // Filter user registrations | 357 | // Filter user registrations |
| 461 | const userRegs = registrations.value.filter(reg => reg.user_id === currentUser.value.id) | 358 | const userRegs = registrations.value.filter(reg => reg.user_id === currentUser.value.id) | ... | ... |
-
Please register or login to post a comment