feat(课程评价): 添加课程评价页面及路由
新增课程评价页面,包含评分和评论列表功能。同时更新路由配置,支持从课程详情页跳转到评价页面
Showing
4 changed files
with
158 additions
and
2 deletions
| ... | @@ -21,5 +21,7 @@ declare module 'vue' { | ... | @@ -21,5 +21,7 @@ declare module 'vue' { |
| 21 | RouterView: typeof import('vue-router')['RouterView'] | 21 | RouterView: typeof import('vue-router')['RouterView'] |
| 22 | SearchBar: typeof import('./components/ui/SearchBar.vue')['default'] | 22 | SearchBar: typeof import('./components/ui/SearchBar.vue')['default'] |
| 23 | SummerCampCard: typeof import('./components/ui/SummerCampCard.vue')['default'] | 23 | SummerCampCard: typeof import('./components/ui/SummerCampCard.vue')['default'] |
| 24 | + VanList: typeof import('vant/es')['List'] | ||
| 25 | + VanRate: typeof import('vant/es')['Rate'] | ||
| 24 | } | 26 | } |
| 25 | } | 27 | } | ... | ... |
| 1 | /* | 1 | /* |
| 2 | * @Date: 2025-03-20 20:36:36 | 2 | * @Date: 2025-03-20 20:36:36 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2025-03-21 10:27:02 | 4 | + * @LastEditTime: 2025-03-21 11:33:55 |
| 5 | * @FilePath: /mlaj/src/router/index.js | 5 | * @FilePath: /mlaj/src/router/index.js |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | */ | 7 | */ |
| ... | @@ -15,48 +15,96 @@ const routes = [ | ... | @@ -15,48 +15,96 @@ const routes = [ |
| 15 | meta: { title: '首页' }, | 15 | meta: { title: '首页' }, |
| 16 | }, | 16 | }, |
| 17 | { | 17 | { |
| 18 | + path: '/courses/:id/reviews', | ||
| 19 | + name: 'CourseReviews', | ||
| 20 | + component: () => import('../views/courses/CourseReviewsPage.vue'), | ||
| 21 | + meta: { title: '课程评价' } | ||
| 22 | + }, | ||
| 23 | + { | ||
| 18 | path: '/courses', | 24 | path: '/courses', |
| 19 | name: 'Courses', | 25 | name: 'Courses', |
| 20 | component: () => import('../views/courses/CoursesPage.vue'), | 26 | component: () => import('../views/courses/CoursesPage.vue'), |
| 21 | meta: { title: '课程列表' }, | 27 | meta: { title: '课程列表' }, |
| 22 | }, | 28 | }, |
| 23 | { | 29 | { |
| 30 | + path: '/courses/:id/reviews', | ||
| 31 | + name: 'CourseReviews', | ||
| 32 | + component: () => import('../views/courses/CourseReviewsPage.vue'), | ||
| 33 | + meta: { title: '课程评价' } | ||
| 34 | + }, | ||
| 35 | + { | ||
| 24 | path: '/courses/:id', | 36 | path: '/courses/:id', |
| 25 | name: 'CourseDetail', | 37 | name: 'CourseDetail', |
| 26 | component: () => import('../views/courses/CourseDetailPage.vue'), | 38 | component: () => import('../views/courses/CourseDetailPage.vue'), |
| 27 | meta: { title: '课程详情' }, | 39 | meta: { title: '课程详情' }, |
| 28 | }, | 40 | }, |
| 29 | { | 41 | { |
| 42 | + path: '/courses/:id/reviews', | ||
| 43 | + name: 'CourseReviews', | ||
| 44 | + component: () => import('../views/courses/CourseReviewsPage.vue'), | ||
| 45 | + meta: { title: '课程评价' } | ||
| 46 | + }, | ||
| 47 | + { | ||
| 30 | path: '/profile', | 48 | path: '/profile', |
| 31 | name: 'Profile', | 49 | name: 'Profile', |
| 32 | component: () => import('../views/profile/ProfilePage.vue'), | 50 | component: () => import('../views/profile/ProfilePage.vue'), |
| 33 | meta: { title: '个人中心' }, | 51 | meta: { title: '个人中心' }, |
| 34 | }, | 52 | }, |
| 35 | { | 53 | { |
| 54 | + path: '/courses/:id/reviews', | ||
| 55 | + name: 'CourseReviews', | ||
| 56 | + component: () => import('../views/courses/CourseReviewsPage.vue'), | ||
| 57 | + meta: { title: '课程评价' } | ||
| 58 | + }, | ||
| 59 | + { | ||
| 36 | path: '/login', | 60 | path: '/login', |
| 37 | name: 'Login', | 61 | name: 'Login', |
| 38 | component: () => import('../views/auth/LoginPage.vue'), | 62 | component: () => import('../views/auth/LoginPage.vue'), |
| 39 | meta: { title: '登录' } | 63 | meta: { title: '登录' } |
| 40 | }, | 64 | }, |
| 41 | { | 65 | { |
| 66 | + path: '/courses/:id/reviews', | ||
| 67 | + name: 'CourseReviews', | ||
| 68 | + component: () => import('../views/courses/CourseReviewsPage.vue'), | ||
| 69 | + meta: { title: '课程评价' } | ||
| 70 | + }, | ||
| 71 | + { | ||
| 42 | path: '/register', | 72 | path: '/register', |
| 43 | name: 'Register', | 73 | name: 'Register', |
| 44 | component: () => import('../views/auth/RegisterPage.vue'), | 74 | component: () => import('../views/auth/RegisterPage.vue'), |
| 45 | meta: { title: '注册' } | 75 | meta: { title: '注册' } |
| 46 | }, | 76 | }, |
| 47 | { | 77 | { |
| 78 | + path: '/courses/:id/reviews', | ||
| 79 | + name: 'CourseReviews', | ||
| 80 | + component: () => import('../views/courses/CourseReviewsPage.vue'), | ||
| 81 | + meta: { title: '课程评价' } | ||
| 82 | + }, | ||
| 83 | + { | ||
| 48 | path: '/forgotPwd', | 84 | path: '/forgotPwd', |
| 49 | name: 'ForgotPassword', | 85 | name: 'ForgotPassword', |
| 50 | component: () => import('../views/auth/ForgotPasswordPage.vue'), | 86 | component: () => import('../views/auth/ForgotPasswordPage.vue'), |
| 51 | meta: { title: '忘记密码' } | 87 | meta: { title: '忘记密码' } |
| 52 | }, | 88 | }, |
| 53 | { | 89 | { |
| 90 | + path: '/courses/:id/reviews', | ||
| 91 | + name: 'CourseReviews', | ||
| 92 | + component: () => import('../views/courses/CourseReviewsPage.vue'), | ||
| 93 | + meta: { title: '课程评价' } | ||
| 94 | + }, | ||
| 95 | + { | ||
| 54 | path: '/activities', | 96 | path: '/activities', |
| 55 | name: 'Activities', | 97 | name: 'Activities', |
| 56 | component: () => import('../views/activities/ActivitiesPage.vue'), | 98 | component: () => import('../views/activities/ActivitiesPage.vue'), |
| 57 | meta: { title: '活动列表' } | 99 | meta: { title: '活动列表' } |
| 58 | }, | 100 | }, |
| 59 | { | 101 | { |
| 102 | + path: '/courses/:id/reviews', | ||
| 103 | + name: 'CourseReviews', | ||
| 104 | + component: () => import('../views/courses/CourseReviewsPage.vue'), | ||
| 105 | + meta: { title: '课程评价' } | ||
| 106 | + }, | ||
| 107 | + { | ||
| 60 | path: '/activities/:id', | 108 | path: '/activities/:id', |
| 61 | name: 'ActivityDetail', | 109 | name: 'ActivityDetail', |
| 62 | component: () => import('../views/activities/ActivityDetailPage.vue'), | 110 | component: () => import('../views/activities/ActivityDetailPage.vue'), |
| ... | @@ -64,6 +112,12 @@ const routes = [ | ... | @@ -64,6 +112,12 @@ const routes = [ |
| 64 | meta: { title: '活动详情' } | 112 | meta: { title: '活动详情' } |
| 65 | }, | 113 | }, |
| 66 | { | 114 | { |
| 115 | + path: '/courses/:id/reviews', | ||
| 116 | + name: 'CourseReviews', | ||
| 117 | + component: () => import('../views/courses/CourseReviewsPage.vue'), | ||
| 118 | + meta: { title: '课程评价' } | ||
| 119 | + }, | ||
| 120 | + { | ||
| 67 | path: '/activities/:id/signup', | 121 | path: '/activities/:id/signup', |
| 68 | name: 'ActivitySignup', | 122 | name: 'ActivitySignup', |
| 69 | component: () => import('../views/activities/ActivitySignupPage.vue'), | 123 | component: () => import('../views/activities/ActivitySignupPage.vue'), |
| ... | @@ -72,6 +126,12 @@ const routes = [ | ... | @@ -72,6 +126,12 @@ const routes = [ |
| 72 | } | 126 | } |
| 73 | }, | 127 | }, |
| 74 | { | 128 | { |
| 129 | + path: '/courses/:id/reviews', | ||
| 130 | + name: 'CourseReviews', | ||
| 131 | + component: () => import('../views/courses/CourseReviewsPage.vue'), | ||
| 132 | + meta: { title: '课程评价' } | ||
| 133 | + }, | ||
| 134 | + { | ||
| 75 | path: '/checkout', | 135 | path: '/checkout', |
| 76 | name: 'CheckoutPage', | 136 | name: 'CheckoutPage', |
| 77 | component: () => import('../views/checkout/CheckoutPage.vue'), | 137 | component: () => import('../views/checkout/CheckoutPage.vue'), | ... | ... |
| ... | @@ -173,7 +173,10 @@ | ... | @@ -173,7 +173,10 @@ |
| 173 | </div> | 173 | </div> |
| 174 | </div> | 174 | </div> |
| 175 | 175 | ||
| 176 | - <button class="w-full text-center text-green-600 mt-3 text-sm"> | 176 | + <button |
| 177 | + @click="router.push(`/courses/${course?.id}/reviews`)" | ||
| 178 | + class="w-full text-center text-green-600 mt-3 text-sm" | ||
| 179 | + > | ||
| 177 | 查看全部评价 | 180 | 查看全部评价 |
| 178 | </button> | 181 | </button> |
| 179 | </FrostedGlass> | 182 | </FrostedGlass> | ... | ... |
src/views/courses/CourseReviewsPage.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-03-21 11:33:26 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-21 11:33:27 | ||
| 5 | + * @FilePath: /mlaj/src/views/courses/CourseReviewsPage.vue | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <AppLayout title="全部评价"> | ||
| 10 | + <div class="px-4 py-3"> | ||
| 11 | + <!-- Overall Rating --> | ||
| 12 | + <div class="flex items-center justify-between mb-4"> | ||
| 13 | + <div class="flex items-center"> | ||
| 14 | + <div class="text-2xl font-bold mr-2">4.9</div> | ||
| 15 | + <van-rate v-model="overallRating" readonly allow-half /> | ||
| 16 | + </div> | ||
| 17 | + <div class="text-gray-500 text-sm">126条评论</div> | ||
| 18 | + </div> | ||
| 19 | + | ||
| 20 | + <!-- Reviews List --> | ||
| 21 | + <van-list | ||
| 22 | + v-model:loading="loading" | ||
| 23 | + :finished="finished" | ||
| 24 | + finished-text="没有更多了" | ||
| 25 | + @load="onLoad" | ||
| 26 | + > | ||
| 27 | + <div v-for="(review, index) in reviews" :key="index" class="mb-4 pb-4 border-b border-gray-100 last:border-0"> | ||
| 28 | + <div class="flex justify-between items-center mb-2"> | ||
| 29 | + <div class="flex items-center flex-1 min-w-0 mr-2"> | ||
| 30 | + <div class="flex-grow"> | ||
| 31 | + <span class="font-medium text-sm block">{{ review.username }}</span> | ||
| 32 | + </div> | ||
| 33 | + </div> | ||
| 34 | + <van-rate v-model="review.rating" readonly allow-half class="scale-75 -mr-2 flex-shrink-0" /> | ||
| 35 | + </div> | ||
| 36 | + <p class="text-gray-600 text-sm mb-2">{{ review.content }}</p> | ||
| 37 | + <div class="text-gray-400 text-xs">{{ review.date }}</div> | ||
| 38 | + </div> | ||
| 39 | + </van-list> | ||
| 40 | + </div> | ||
| 41 | + </AppLayout> | ||
| 42 | +</template> | ||
| 43 | + | ||
| 44 | +<script setup> | ||
| 45 | +import { ref } from 'vue' | ||
| 46 | +import { useRoute } from 'vue-router' | ||
| 47 | +import AppLayout from '@/components/layout/AppLayout.vue' | ||
| 48 | +import { Rate, List } from 'vant' | ||
| 49 | + | ||
| 50 | +const route = useRoute() | ||
| 51 | +const overallRating = ref(4.9) | ||
| 52 | +const loading = ref(false) | ||
| 53 | +const finished = ref(false) | ||
| 54 | +const reviews = ref([]) | ||
| 55 | + | ||
| 56 | +// Mock data for demonstration | ||
| 57 | +const mockReviews = [ | ||
| 58 | + { | ||
| 59 | + username: '王小明', | ||
| 60 | + avatar: '', | ||
| 61 | + rating: 5, | ||
| 62 | + content: '课程内容非常实用,老师讲解清晰,帮助我和孩子度过了考前紧张期。', | ||
| 63 | + date: '2024-06-15' | ||
| 64 | + }, | ||
| 65 | + { | ||
| 66 | + username: '李晓华', | ||
| 67 | + avatar: '', | ||
| 68 | + rating: 4.5, | ||
| 69 | + content: '老师提供的减压方法很有效,孩子学习状态明显改善,感谢这个课程!', | ||
| 70 | + date: '2024-06-10' | ||
| 71 | + }, | ||
| 72 | + // Add more mock reviews here | ||
| 73 | +] | ||
| 74 | + | ||
| 75 | +const onLoad = () => { | ||
| 76 | + // Simulate async data loading | ||
| 77 | + setTimeout(() => { | ||
| 78 | + const newReviews = mockReviews.map(review => ({ | ||
| 79 | + ...review, | ||
| 80 | + avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${Math.random()}` | ||
| 81 | + })) | ||
| 82 | + reviews.value.push(...newReviews) | ||
| 83 | + loading.value = false | ||
| 84 | + | ||
| 85 | + // Set finished when no more data | ||
| 86 | + if (reviews.value.length >= 20) { | ||
| 87 | + finished.value = true | ||
| 88 | + } | ||
| 89 | + }, 1000) | ||
| 90 | +} | ||
| 91 | +</script> |
-
Please register or login to post a comment