hookehuyr

feat(课程详情页): 添加课程评论功能

在课程详情页中新增评论功能,用户可以对已购买的课程进行评分和评论。新增了ReviewPopup组件用于显示评论弹窗,并在mockData中添加了isPurchased和isReviewed字段以模拟用户购买和评论状态。
......@@ -18,6 +18,7 @@ declare module 'vue' {
GradientHeader: typeof import('./components/ui/GradientHeader.vue')['default']
LiveStreamCard: typeof import('./components/ui/LiveStreamCard.vue')['default']
MenuItem: typeof import('./components/ui/MenuItem.vue')['default']
ReviewPopup: typeof import('./components/ui/ReviewPopup.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SearchBar: typeof import('./components/ui/SearchBar.vue')['default']
......@@ -38,6 +39,7 @@ declare module 'vue' {
VanRate: typeof import('vant/es')['Rate']
VanTab: typeof import('vant/es')['Tab']
VanTabs: typeof import('vant/es')['Tabs']
VanToast: typeof import('vant/es')['Toast']
VanUploader: typeof import('vant/es')['Uploader']
VideoPlayer: typeof import('./components/ui/VideoPlayer.vue')['default']
}
......
<!--
* @Date: 2025-03-24 16:57:55
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-03-24 17:17:44
* @FilePath: /mlaj/src/components/ui/ReviewPopup.vue
* @Description: 文件描述
-->
<template>
<van-popup
:show="show"
@update:show="emit('update:show', $event)"
position="bottom"
round
>
<div class="p-4">
<div class="text-lg font-bold text-center mb-4">课程评价</div>
<div class="flex justify-center mb-4">
<van-rate
v-model="rating"
:size="24"
color="#ffd21e"
void-icon="star"
void-color="#eee"
/>
</div>
<van-field
v-model="content"
rows="3"
type="textarea"
placeholder="请输入您的评价内容"
class="mb-4"
/>
<div class="flex justify-end">
<van-button
round
type="default"
style="margin-right: 0.5rem"
@click="handleCancel"
>取消</van-button
>
<van-button round type="primary" color="#4CAF50" @click="handleSubmit"
>提交评价</van-button
>
</div>
</div>
</van-popup>
<van-toast v-model:show="show_toast">
<template #message>
{{ message }}
</template>
</van-toast>
</template>
<script setup>
import { ref } from "vue";
const props = defineProps({
show: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:show", "submit"]);
const rating = ref(5);
const content = ref("");
const handleCancel = () => {
emit("update:show", false);
rating.value = 5;
content.value = "";
};
const show_toast = ref(false);
const message = ref("");
const handleSubmit = () => {
if (rating.value === 0) {
show_toast.value = true;
message.value = "请选择评分";
return;
}
if (!content.value.trim()) {
show_toast.value = true;
message.value = "请输入评论内容";
return;
}
emit("submit", {
rating: rating.value,
content: content.value.trim(),
});
show_toast.value = true;
message.value = "评论提交成功";
handleCancel();
};
</script>
......@@ -50,6 +50,8 @@ export const courses = [
price: 365,
updatedLessons: 16,
subscribers: 1140,
isPurchased: true,
isReviewed: true,
expireDate: '2025-04-17 00:00:00',
description: `【美乐考前赋能营】
结合平台多年亲子教育的实践经验,
......@@ -77,7 +79,9 @@ export const courses = [
imageUrl: 'https://cdn.ipadbiz.cn/mlaj/images/GGCP6vshpPY.jpg', // Updated with new image
price: 3665,
updatedLessons: 16,
subscribers: 1140
subscribers: 1140,
isPurchased: true,
isReviewed: false
},
{
id: 'course-3',
......@@ -86,7 +90,9 @@ export const courses = [
imageUrl: 'https://cdn.ipadbiz.cn/mlaj/images/2Juj2cXWB7U.jpg', // Updated with new image
price: 1280,
updatedLessons: 16,
subscribers: 1140
subscribers: 1140,
isPurchased: false,
isReviewed: false
}
];
......
......@@ -244,15 +244,45 @@
¥{{ Math.round(course?.price * 1.2) }}
</div>
</div>
<button
<van-button
v-if="!isPurchased"
@click="handlePurchase"
class="bg-gradient-to-r from-green-500 to-green-600 text-white px-6 py-2 rounded-full text-sm font-medium shadow-md"
round
block
color="linear-gradient(to right, #22c55e, #16a34a)"
class="shadow-md"
>
立即购买
</button>
</van-button>
<van-button
v-else-if="!isReviewed"
@click="showReviewPopup = true"
round
block
color="linear-gradient(to right, #3b82f6, #2563eb)"
class="shadow-md"
>
立即评论
</van-button>
<van-button
v-else
@click="router.push(`/courses/${course?.id}/reviews`)"
round
block
color="linear-gradient(to right, #6b7280, #4b5563)"
class="shadow-md"
>
查看评论
</van-button>
</div>
</div>
</div>
<!-- Review Popup -->
<ReviewPopup
v-model:show="showReviewPopup"
@submit="handleReviewSubmit"
/>
</AppLayout>
</template>
......@@ -261,6 +291,7 @@ import { ref, onMounted, defineComponent, h } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import AppLayout from '@/components/layout/AppLayout.vue'
import FrostedGlass from '@/components/ui/FrostedGlass.vue'
import ReviewPopup from '@/components/ui/ReviewPopup.vue'
import { courses } from '@/utils/mockData'
import { useCart } from '@/contexts/cart'
import { useTitle } from '@vueuse/core';
......@@ -272,6 +303,9 @@ const router = useRouter()
const course = ref(null)
const activeTab = ref('课程特色')
const isFavorite = ref(false)
const isPurchased = ref(false)
const isReviewed = ref(false)
const showReviewPopup = ref(false)
const { addToCart, proceedToCheckout } = useCart()
// Handle favorite toggle
......@@ -333,12 +367,21 @@ const handlePurchase = () => {
}
}
// Handle review submit
const handleReviewSubmit = (review) => {
// TODO: 对接评论提交接口
console.log('Review submitted:', review)
isReviewed.value = true
}
// Fetch course data
onMounted(() => {
const id = route.params.id
const foundCourse = courses.find(c => c.id === id)
if (foundCourse) {
course.value = foundCourse
isPurchased.value = foundCourse.isPurchased
isReviewed.value = foundCourse.isReviewed
} else {
// Course not found, redirect to courses page
router.push('/courses')
......