feat:新评论

This commit is contained in:
小张 2025-08-30 03:57:24 +08:00
parent 2aab0f7152
commit 06dfacc074
2 changed files with 320 additions and 92 deletions

View File

@ -141,12 +141,12 @@
</div>
</div>
<!-- 课程描述 -->
<!-- 课程描述
<div class="course-description">
<p>{{ course.description ||
'本课程深度聚焦问题让每一位教师了解并学习使用DeepSeek结合办公自动化职业岗位标准以实际工作任务为引导强调课程内容的易用性和岗位要求的匹配性。课程内容与全国计算机等级考试、"1+X"WPS办公应用职业技能等级证书技能大赛紧密结合课程设置紧密对应实际全面共享可为职业工作人员、在校学生、创行教师提供服务与学习支持。'
}}</p>
</div>
</div> -->
<!-- 讲师信息 -->
<div class="instructors-section">
@ -175,7 +175,7 @@
<button class="tab-btn" :class="{ active: activeTab === 'intro' }"
@click="activeTab = 'intro'">课程介绍</button>
<button class="tab-btn" :class="{ active: activeTab === 'comments' }"
@click="activeTab = 'comments'">评论(1251)</button>
@click="activeTab = 'comments'">评论({{ commentsCount }})</button>
</div>
<!-- 标签页内容区域 -->
@ -222,9 +222,26 @@
</div>
<div class="comment-list">
<div class="comment-item" v-for="comment in displayComments" :key="comment.id">
<!-- 加载状态 -->
<div v-if="commentsLoading" class="comments-loading">
<p>正在加载评论...</p>
</div>
<!-- 错误状态 -->
<div v-else-if="commentsError" class="comments-error">
<p>{{ commentsError }}</p>
<button @click="loadCourseComments" class="retry-btn">重试</button>
</div>
<!-- 无评论状态 -->
<div v-else-if="displayComments.length === 0" class="no-comments">
<p>暂无评论快来发表第一条评论吧</p>
</div>
<!-- 评论列表 -->
<div v-else class="comment-item" v-for="comment in displayComments" :key="comment.id">
<div class="comment-avatar">
<img :src="comment.avatar" :alt="comment.username" />
<SafeAvatar :src="comment.avatar" :name="comment.username" :size="40" />
</div>
<div class="comment-content">
<div class="comment-header">
@ -712,7 +729,8 @@ import { useRoute, useRouter } from 'vue-router'
import { useAuth } from '@/composables/useAuth'
import { useUserStore } from '@/stores/user'
import { CourseApi } from '@/api/modules/course'
import type { Course, CourseSection } from '@/api/types'
import type { Course, CourseSection, CourseComment } from '@/api/types'
import SafeAvatar from '@/components/common/SafeAvatar.vue'
import LoginModal from '@/components/auth/LoginModal.vue'
import RegisterModal from '@/components/auth/RegisterModal.vue'
@ -976,35 +994,28 @@ const formatTotalDuration = () => {
return `${hours}小时${minutes}分钟`
}
const displayComments = ref([
{
id: 1,
username: '学习者小王',
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
time: '2天前',
content: '老师讲得很详细,从零基础到实际应用都有涉及,非常适合初学者!',
likes: 23,
type: 'comment'
},
{
id: 2,
username: 'AI爱好者',
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
time: '5天前',
content: '课程内容很实用跟着做了几个项目收获很大。推荐给想学AI的朋友们',
likes: 18,
type: 'comment'
},
{
id: 3,
username: '程序员小李',
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
time: '1周前',
content: 'DeepSeek确实是个很强大的工具通过这个课程学会了很多实用技巧。',
likes: 31,
type: 'comment'
}
])
//
const comments = ref<CourseComment[]>([])
const commentsLoading = ref(false)
const commentsError = ref('')
//
const displayComments = computed(() => {
return comments.value.map(comment => ({
id: comment.id,
username: comment.userName,
avatar: comment.userAvatar,
time: comment.timeAgo,
content: comment.content,
likes: comment.likeCount || 0,
type: comment.isTop ? 'note' : 'comment'
}))
})
//
const commentsCount = computed(() => {
return comments.value.length
})
//
const newComment = ref('')
@ -1025,22 +1036,63 @@ const handleTextareaClick = (event: MouseEvent) => {
}
}
//
const loadCourseComments = async () => {
if (!courseId.value || courseId.value.trim() === '') {
commentsError.value = '课程ID无效'
console.error('课程ID无效:', courseId.value)
return
}
try {
commentsLoading.value = true
commentsError.value = ''
console.log('调用API获取课程评论...')
const response = await CourseApi.getCourseComments(courseId.value)
console.log('评论API响应:', response)
if (response.code === 0 || response.code === 200) {
if (response.data && Array.isArray(response.data)) {
//
const sortedComments = response.data.sort((a, b) => {
//
if (a.isTop !== b.isTop) {
return a.isTop ? -1 : 1 //
}
//
return new Date(b.createTime).getTime() - new Date(a.createTime).getTime()
})
comments.value = sortedComments
console.log('✅ 评论数据设置成功:', comments.value)
} else {
console.log('⚠️ API返回的评论数据为空')
comments.value = []
}
} else {
console.log('⚠️ API返回错误')
commentsError.value = response.message || '获取评论失败'
comments.value = []
}
} catch (err) {
console.error('加载课程评论失败:', err)
commentsError.value = '获取评论失败'
comments.value = []
} finally {
commentsLoading.value = false
}
}
//
const submitComment = () => {
if (newComment.value.trim()) {
const newCommentObj = {
id: Date.now(),
username: '当前用户',
avatar: 'https://via.placeholder.com/40x40/1890ff/ffffff?text=我',
time: '刚刚',
content: newComment.value,
likes: 0,
type: 'comment'
}
displayComments.value.unshift(newCommentObj)
newComment.value = ''
// API
console.log('评论已提交:', newCommentObj)
console.log('提交评论:', newComment.value)
//
// await CourseApi.submitComment(courseId.value, newComment.value)
// loadCourseComments()
newComment.value = ''
}
}
@ -1599,6 +1651,7 @@ onMounted(() => {
initializeMockState() //
loadCourseDetail()
loadCourseSections()
loadCourseComments() //
})
</script>
@ -4569,4 +4622,36 @@ onMounted(() => {
.reply-action-btn:hover {
color: #1890ff;
}
/* 评论状态样式 */
.comments-loading, .comments-error, .no-comments {
padding: 40px 20px;
text-align: center;
color: #666;
font-size: 14px;
}
.comments-error {
color: #ff4d4f;
}
.comments-error .retry-btn {
margin-top: 10px;
padding: 8px 16px;
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background 0.2s;
}
.comments-error .retry-btn:hover {
background: #40a9ff;
}
.no-comments {
color: #999;
}
</style>

View File

@ -142,7 +142,7 @@
<button class="tab-btn" :class="{ active: courseActiveTab === 'subtitles' }"
@click="courseActiveTab = 'subtitles'">字幕列表</button>
<button class="tab-btn" :class="{ active: courseActiveTab === 'comments' }"
@click="courseActiveTab = 'comments'">评论(1251)</button>
@click="courseActiveTab = 'comments'">评论({{ commentsCount }})</button>
</div>
<!-- 标签页内容区域 -->
@ -258,9 +258,26 @@
</div>
<div class="comment-list">
<div class="comment-item" v-for="comment in displayComments" :key="comment.id">
<!-- 加载状态 -->
<div v-if="commentsLoading" class="comments-loading">
<p>正在加载评论...</p>
</div>
<!-- 错误状态 -->
<div v-else-if="commentsError" class="comments-error">
<p>{{ commentsError }}</p>
<button @click="loadCourseComments" class="retry-btn">重试</button>
</div>
<!-- 无评论状态 -->
<div v-else-if="displayComments.length === 0" class="no-comments">
<p>暂无评论快来发表第一条评论吧</p>
</div>
<!-- 评论列表 -->
<div v-else class="comment-item" v-for="comment in displayComments" :key="comment.id">
<div class="comment-avatar">
<img :src="comment.avatar" :alt="comment.username" />
<SafeAvatar :src="comment.avatar" :name="comment.username" :size="40" />
</div>
<div class="comment-content">
<div class="comment-header">
@ -958,9 +975,10 @@ import { useMessage } from 'naive-ui'
// import { useAuth } from '@/composables/useAuth'
// import { useUserStore } from '@/stores/user'
import { CourseApi } from '@/api/modules/course'
import type { Course, CourseSection } from '@/api/types'
import type { Course, CourseSection, CourseComment } from '@/api/types'
import QuillEditor from '@/components/common/QuillEditor.vue'
import DPlayerVideo from '@/components/course/DPlayerVideo.vue'
import SafeAvatar from '@/components/common/SafeAvatar.vue'
// import LoginModal from '@/components/auth/LoginModal.vue'
// import RegisterModal from '@/components/auth/RegisterModal.vue'
@ -1225,35 +1243,28 @@ const overallProgress = computed(() => {
// return `${hours}${minutes}`
// }
const displayComments = ref([
{
id: 1,
username: '学习者小王',
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
time: '2天前',
content: '老师讲得很详细,从零基础到实际应用都有涉及,非常适合初学者!',
likes: 23,
type: 'comment'
},
{
id: 2,
username: 'AI爱好者',
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
time: '5天前',
content: '课程内容很实用跟着做了几个项目收获很大。推荐给想学AI的朋友们',
likes: 18,
type: 'comment'
},
{
id: 3,
username: '程序员小李',
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
time: '1周前',
content: 'DeepSeek确实是个很强大的工具通过这个课程学会了很多实用技巧。',
likes: 31,
type: 'comment'
}
])
//
const comments = ref<CourseComment[]>([])
const commentsLoading = ref(false)
const commentsError = ref('')
//
const displayComments = computed(() => {
return comments.value.map(comment => ({
id: comment.id,
username: comment.userName,
avatar: comment.userAvatar,
time: comment.timeAgo,
content: comment.content,
likes: comment.likeCount || 0,
type: comment.isTop ? 'note' : 'comment'
}))
})
//
const commentsCount = computed(() => {
return comments.value.length
})
//
const newComment = ref('')
@ -1274,22 +1285,63 @@ const handleTextareaClick = (event: MouseEvent) => {
}
}
//
const loadCourseComments = async () => {
if (!courseId.value || courseId.value.trim() === '') {
commentsError.value = '课程ID无效'
console.error('课程ID无效:', courseId.value)
return
}
try {
commentsLoading.value = true
commentsError.value = ''
console.log('调用API获取课程评论...')
const response = await CourseApi.getCourseComments(courseId.value)
console.log('评论API响应:', response)
if (response.code === 0 || response.code === 200) {
if (response.data && Array.isArray(response.data)) {
//
const sortedComments = response.data.sort((a, b) => {
//
if (a.isTop !== b.isTop) {
return a.isTop ? -1 : 1 //
}
//
return new Date(b.createTime).getTime() - new Date(a.createTime).getTime()
})
comments.value = sortedComments
console.log('✅ 评论数据设置成功:', comments.value)
} else {
console.log('⚠️ API返回的评论数据为空')
comments.value = []
}
} else {
console.log('⚠️ API返回错误')
commentsError.value = response.message || '获取评论失败'
comments.value = []
}
} catch (err) {
console.error('加载课程评论失败:', err)
commentsError.value = '获取评论失败'
comments.value = []
} finally {
commentsLoading.value = false
}
}
//
const submitComment = () => {
if (newComment.value.trim()) {
const newCommentObj = {
id: Date.now(),
username: '当前用户',
avatar: 'https://via.placeholder.com/40x40/1890ff/ffffff?text=我',
time: '刚刚',
content: newComment.value,
likes: 0,
type: 'comment'
}
displayComments.value.unshift(newCommentObj)
newComment.value = ''
// API
console.log('评论已提交:', newCommentObj)
console.log('提交评论:', newComment.value)
//
// await CourseApi.submitComment(courseId.value, newComment.value)
// loadCourseComments()
newComment.value = ''
}
}
@ -1975,6 +2027,7 @@ onMounted(() => {
console.log('课程详情页加载完成课程ID:', courseId.value)
loadCourseDetail()
loadCourseSections()
loadCourseComments() //
//
const shouldRefresh = sessionStorage.getItem('refreshCourseExchanged')
@ -2903,6 +2956,64 @@ onActivated(() => {
color: #0088D1;
}
/* 课程描述内容样式 */
.course-description-content {
padding: 0;
line-height: 1.6;
color: #333;
width: 100%;
}
/* 课程描述中的图片样式 */
.course-description-content img {
max-width: 100%;
height: auto;
display: block;
margin: 10px auto;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* 课程描述中的段落样式 */
.course-description-content p {
margin: 16px 0;
font-size: 14px;
line-height: 1.6;
color: #666;
}
/* 课程描述中的标题样式 */
.course-description-content h1,
.course-description-content h2,
.course-description-content h3,
.course-description-content h4,
.course-description-content h5,
.course-description-content h6 {
margin: 20px 0 10px 0;
color: #333;
font-weight: 600;
}
/* 课程描述中的列表样式 */
.course-description-content ul,
.course-description-content ol {
margin: 16px 0;
padding-left: 20px;
}
.course-description-content li {
margin: 8px 0;
line-height: 1.6;
}
/* 默认描述样式 */
.default-description {
color: #666;
display: flex;
justify-content: space-between;
align-items: center;
}
.course-content-detail {
margin-top: 16px;
padding: 16px;
@ -6046,4 +6157,36 @@ onActivated(() => {
font-size: 16px;
margin: 0;
}
/* 评论状态样式 */
.comments-loading, .comments-error, .no-comments {
padding: 40px 20px;
text-align: center;
color: #666;
font-size: 14px;
}
.comments-error {
color: #ff4d4f;
}
.comments-error .retry-btn {
margin-top: 10px;
padding: 8px 16px;
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background 0.2s;
}
.comments-error .retry-btn:hover {
background: #40a9ff;
}
.no-comments {
color: #999;
}
</style>