StudyDetailPage.vue 15.5 KB
<!--
 * @Date: 2025-04-07
 * @Description: 学习详情页面
-->
<template>
    <div class="study-detail-page bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen pb-20">
        <div v-if="course" class="flex flex-col h-screen">
            <!-- 固定区域:视频播放和标签页 -->
            <div class="fixed top-0 left-0 right-0 z-10 top-wrapper">
                <!-- 视频播放区域 -->
                <div class="w-full bg-black relative">
                    <!-- 视频封面和播放按钮 -->
                    <div v-if="!isPlaying" class="relative w-full" style="aspect-ratio: 16/9;">
                        <img :src="course.cover" :alt="course.title" class="w-full h-full object-cover" />
                        <div class="absolute inset-0 flex items-center justify-center cursor-pointer"
                            @click="startPlay">
                            <div
                                class="w-24 h-24 rounded-full bg-black/50 flex items-center justify-center hover:bg-black/70 transition-colors">
                                <font-awesome-icon icon="circle-play" class="text-5xl text-white"
                                    style="font-size: 3rem;" />
                            </div>
                        </div>
                    </div>
                    <!-- 视频播放器 -->
                    <VideoPlayer v-show="isPlaying" ref="videoPlayerRef" :video-url="course.videoUrl" :autoplay="false"
                        @onPlay="handleVideoPlay" @onPause="handleVideoPause" />
                </div>
                <!-- 标签页区域 -->
                <div class="px-4 py-3 bg-white">
                    <van-tabs v-model:active="activeTab" sticky animated swipeable shrink @change="handleTabChange">
                        <van-tab title="介绍" name="intro">
                        </van-tab>
                        <van-tab :title-style="{ 'min-width': '50%' }" name="comments">
                            <template #title>评论(999)</template>
                        </van-tab>
                    </van-tabs>
                </div>
            </div>

            <!-- 滚动区域:介绍和评论内容 -->
            <div class="overflow-y-auto flex-1" :style="{paddingTop: topWrapperHeight, height: 'calc(100vh - ' + topWrapperHeight + ')'}">
                <div id="intro" class="py-4 px-4">
                    <h1 class="text-lg font-bold mb-2">{{ course.title }}</h1>
                    <div class="text-gray-500 text-sm flex items-center gap-2">
                        <span>{{ course.date }}</span>
                        <span class="text-gray-300">|</span>
                        <span>{{ course.studyCount || 0 }}次学习</span>
                    </div>
                </div>

                <div class="h-2 bg-gray-100"></div>

                <div id="comment" class="py-4 px-4 space-y-4" :style="{ paddingBottom: bottomWrapperHeight }">
                    <div class="flex justify-between items-center mb-4">
                        <div class="text-gray-900 font-medium text-sm">评论 ({{ comments.length }})</div>
                        <div class="text-gray-500 cursor-pointer text-sm" @click="showCommentPopup = true">查看更多</div>
                    </div>
                    <!-- 显示前三条评论 -->
                    <div v-for="comment in comments.slice(0, 3)" :key="comment.id" class="border-b border-gray-100 last:border-b-0 py-4">
                        <div class="flex">
                            <img :src="comment.avatar" class="w-10 h-10 rounded-full flex-shrink-0" style="margin-right: 0.5rem;" />
                            <div class="flex-1 ml-3">
                                <div class="flex justify-between items-center mb-1">
                                    <span class="font-medium text-gray-900">{{ comment.username }}</span>
                                    <div class="flex items-center space-x-1">
                                        <span class="text-sm text-gray-500">{{ comment.likes }}</span> &nbsp;
                                        <van-icon
                                            :name="comment.isLiked ? 'like' : 'like-o'"
                                            :class="{'text-red-500': comment.isLiked, 'text-gray-400': !comment.isLiked}"
                                            @click="toggleLike(comment)"
                                            class="text-lg cursor-pointer"
                                        />
                                    </div>
                                </div>
                                <p class="text-gray-700 text-sm mb-1">{{ comment.content }}</p>
                                <div class="text-gray-400 text-xs">{{ comment.time }}</div>
                            </div>
                        </div>
                    </div>
                    <van-popup v-model:show="showCommentPopup" position="bottom" round closeable safe-area-inset-bottom style="height: 80%">
                        <div class="flex flex-col h-full">
                            <!-- 固定头部 -->
                            <div class="flex-none px-4 py-3 border-b bg-white sticky top-0 z-10">
                                <div class="text-lg font-medium">全部评论 ({{ comments.length }})</div>
                            </div>

                            <!-- 可滚动的评论列表 -->
                            <div class="flex-1 overflow-y-auto">
                                <div class="px-4 py-2 pb-16">
                                    <div v-for="comment in comments" :key="comment.id" class="border-b border-gray-100 last:border-b-0 py-4">
                                        <div class="flex">
                                            <img :src="comment.avatar" class="w-10 h-10 rounded-full flex-shrink-0" style="margin-right: 0.5rem;" />
                                            <div class="flex-1 ml-3">
                                                <div class="flex justify-between items-center mb-1">
                                                    <span class="font-medium text-gray-900">{{ comment.username }}</span>
                                                    <div class="flex items-center space-x-1">
                                                        <span class="text-sm text-gray-500">{{ comment.likes }}</span> &nbsp;
                                                        <van-icon
                                                            :name="comment.isLiked ? 'like' : 'like-o'"
                                                            :class="{'text-red-500': comment.isLiked, 'text-gray-400': !comment.isLiked}"
                                                            @click="toggleLike(comment)"
                                                            class="text-lg cursor-pointer"
                                                        />
                                                    </div>
                                                </div>
                                                <p class="text-gray-700 text-sm mb-1">{{ comment.content }}</p>
                                                <div class="text-gray-400 text-xs">{{ comment.time }}</div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>

                            <!-- 固定底部输入框 -->
                            <div class="flex-none border-t px-4 py-2 bg-white sticky bottom-0 z-10">
                                <div class="flex items-center space-x-2">
                                    <van-field
                                        v-model="popupComment"
                                        rows="1"
                                        autosize
                                        type="textarea"
                                        placeholder="请输入评论"
                                        class="flex-1 bg-gray-100 rounded-lg"
                                    />
                                    <van-button
                                        type="primary"
                                        size="small"
                                        @click="submitPopupComment"
                                    >发送</van-button>
                                </div>
                            </div>
                        </div>
                    </van-popup>
                </div>
            </div>

            <!-- 底部操作栏 -->
            <div class="fixed bottom-0 left-0 right-0 bg-white border-t px-4 py-2 flex items-center space-x-4 bottom-wrapper">
                <div class="flex-none flex flex-col items-center gap-1 cursor-pointer active:opacity-80" @click="showCatalog = true">
                    <van-icon name="bars" class="text-lg text-gray-600" />
                    <span class="text-xs text-gray-600">课程目录</span>
                </div>
                <div class="flex-grow flex-1 min-w-0">
                    <van-field v-model="newComment" rows="1" autosize type="textarea" placeholder="请输入留言" class="bg-gray-100 rounded-lg !p-0">
                        <template #input>
                            <textarea v-model="newComment" rows="1" placeholder="请输入留言" class="w-full h-full bg-transparent outline-none resize-none" />
                        </template>
                    </van-field>
                </div>
                <van-button type="primary" size="small" @click="submitComment">发送</van-button>
            </div>
        </div>
    </div>
</template>

<script setup>
import { ref, onMounted, nextTick } from 'vue';
import { useRoute } from 'vue-router';
import { useTitle } from '@vueuse/core';
import VideoPlayer from '@/components/ui/VideoPlayer.vue';

const route = useRoute();
const course = ref(null);
const activeTab = ref('intro');
const newComment = ref('');
const showCatalog = ref(false);
const isPlaying = ref(false);
const videoPlayerRef = ref(null);
const showCommentPopup = ref(false);
const popupComment = ref('');

// 开始播放视频
const startPlay = async () => {
    isPlaying.value = true;
    await nextTick();
    if (videoPlayerRef.value) {
        videoPlayerRef.value.play();
    }
};

// 处理视频播放状态
const handleVideoPlay = () => {
    isPlaying.value = true;
};

const handleVideoPause = () => {
    // 保持视频播放器可见,只在初始状态显示封面
};

// 评论列表
const comments = ref([
    {
        id: 1,
        username: '欢乐马',
        avatar: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
        content: '教育的顶级传承,是你用什么样的心,传承智慧',
        time: '2024-12-04 18:51',
        likes: 12,
        isLiked: false
    },
    {
        id: 2,
        username: '欢乐马',
        avatar: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
        content: '不要用战术上的勤奋,掩盖战略上的懒惰',
        time: '2024-12-04 08:01',
        likes: 8,
        isLiked: true
    },
    {
        id: 3,
        username: '欢乐马',
        avatar: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
        content: '和老师积极互动,整个课堂像为你而讲',
        time: '2024-12-04 07:54',
        likes: 5,
        isLiked: false
    },
    {
        id: 1,
        username: '欢乐马',
        avatar: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
        content: '教育的顶级传承,是你用什么样的心,传承智慧',
        time: '2024-12-04 18:51',
        likes: 12,
        isLiked: false
    },
    {
        id: 2,
        username: '欢乐马',
        avatar: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
        content: '不要用战术上的勤奋,掩盖战略上的懒惰',
        time: '2024-12-04 08:01',
        likes: 8,
        isLiked: true
    },
    {
        id: 3,
        username: '欢乐马',
        avatar: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
        content: '和老师积极互动,整个课堂像为你而讲',
        time: '2024-12-04 07:54',
        likes: 5,
        isLiked: false
    }
]);

// 设置页面标题
useTitle('学习详情');

const topWrapperHeight = ref(0);
const bottomWrapperHeight = ref(0);

const handleScroll = () => {
    const introElement = document.getElementById('intro');
    const commentElement = document.getElementById('comment');
    if (!introElement || !commentElement) return;

    const scrollTop = window.scrollY;
    const commentOffset = commentElement.offsetTop - parseInt(topWrapperHeight.value);

    // 根据滚动位置更新activeTab
    if (scrollTop >= commentOffset) {
        activeTab.value = 'comments';
    } else {
        activeTab.value = 'intro';
    }
};

onMounted(() => {
    nextTick(() => {
        const topWrapper = document.querySelector('.top-wrapper');
        const bottomWrapper = document.querySelector('.bottom-wrapper');
        if (topWrapper) {
            topWrapperHeight.value = topWrapper.clientHeight + 'px';
        }
        if (bottomWrapper) {
            bottomWrapperHeight.value = bottomWrapper.clientHeight + 'px';
        }

        // 添加滚动监听
        window.addEventListener('scroll', handleScroll);
    })
    const courseId = route.params.id;
    if (courseId) {
        // TODO: 这里需要替换为实际的API调用
        // 临时使用模拟数据
        course.value = {
            id: courseId,
            title: '开学礼·止的智慧·心法老师·20241001(上)',
            videoUrl: 'https://sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',
            progress: 35,
            studyTime: 3600,
            type: '视频课程',
            studyCount: 1896,
            cover: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
            date: '2024-12-04'
        };
    }
})

// 提交评论
// 切换点赞状态
const toggleLike = (comment) => {
    comment.isLiked = !comment.isLiked;
    comment.likes += comment.isLiked ? 1 : -1;
};

const submitComment = () => {
    if (!newComment.value.trim()) return;

    comments.value.unshift({
        id: Date.now(),
        username: '当前用户',
        avatar: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
        content: newComment.value,
        time: new Date().toLocaleString(),
        likes: 0,
        isLiked: false
    });

    newComment.value = '';
};

// 处理标签页切换
const handleTabChange = (name) => {
    nextTick(() => {
        const element = document.getElementById(name === 'intro' ? 'intro' : 'comment');
        if (element) {
            const topOffset = element.offsetTop - parseInt(topWrapperHeight.value);
            window.scrollTo({
                top: topOffset,
                behavior: 'smooth'
            });
        }
    })
};


// 在组件卸载时移除滚动监听
onUnmounted(() => {
    window.removeEventListener('scroll', handleScroll);
});

// 提交弹窗中的评论
const submitPopupComment = () => {
    if (!popupComment.value.trim()) return;

    comments.value.unshift({
        id: Date.now(),
        username: '当前用户',
        avatar: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
        content: popupComment.value,
        time: new Date().toLocaleString(),
        likes: 0,
        isLiked: false
    });

    popupComment.value = '';
    showCommentPopup.value = false;
};
</script>

<style lang="less" scoped>
:deep(.van-cell.van-field) {
    background-color: rgb(244, 244, 244);
}
</style>