hookehuyr

feat(课程评价): 添加课程评价页面及路由

新增课程评价页面,包含评分和评论列表功能。同时更新路由配置,支持从课程详情页跳转到评价页面
...@@ -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>
......
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>