useStudyComments.js 7.56 KB
import { ref, watch } from 'vue';
import { getGroupCommentListAPI, addGroupCommentAPI, addGroupCommentLikeAPI, delGroupCommentLikeAPI } from '@/api/course';

/**
 * 学习评论功能封装
 * @module useStudyComments
 * @description
 * 管理课程/学习资料的评论列表、发表评论、点赞以及弹窗内的评论加载。
 * 
 * @param {import('vue').Ref<Object>} course - 课程对象响应式引用
 * @returns {Object} 评论相关状态和方法
 */
export const useStudyComments = (course) => {
    // ==================== 状态定义 ====================
    
    /** @type {import('vue').Ref<number>} 评论总数 */
    const commentCount = ref(0);
    /** @type {import('vue').Ref<Array>} 评论列表(主页面显示) */
    const commentList = ref([]);
    /** @type {import('vue').Ref<string>} 新评论内容(主页面输入框) */
    const newComment = ref('');

    // 弹窗相关状态
    /** @type {import('vue').Ref<boolean>} 是否显示评论弹窗 */
    const showCommentPopup = ref(false);
    /** @type {import('vue').Ref<string>} 弹窗内的新评论内容 */
    const popupComment = ref('');
    /** @type {import('vue').Ref<Array>} 弹窗内的评论列表 */
    const popupCommentList = ref([]);
    /** @type {import('vue').Ref<boolean>} 弹窗加载中状态 */
    const popupLoading = ref(false);
    /** @type {import('vue').Ref<boolean>} 弹窗是否已加载全部 */
    const popupFinished = ref(false);
    /** @type {import('vue').Ref<number>} 弹窗每页加载数量 */
    const popupLimit = ref(5);
    /** @type {import('vue').Ref<number>} 弹窗当前页码 */
    const popupPage = ref(0);

    // ==================== 方法定义 ====================

    /**
     * 刷新主页面评论列表
     * @description
     * 数据流:
     * 1. 检查 course.value 是否包含 group_id 和 schedule_id
     * 2. 调用 getGroupCommentListAPI 获取最新评论
     * 3. 更新 commentList 和 commentCount
     */
    const refreshComments = async () => {
        if (!course.value?.group_id || !course.value?.id) return;

        const comment = await getGroupCommentListAPI({
            group_id: course.value.group_id,
            schedule_id: course.value.id
        });
        if (comment.code === 1) {
            commentList.value = comment.data.comment_list;
            commentCount.value = comment.data.comment_count;
        }
    };

    /**
     * 切换点赞状态
     * @param {Object} comment - 评论对象
     * @description
     * 乐观更新:先调用 API,成功后更新本地状态
     * 1. 如果当前未点赞 -> 调用 addGroupCommentLikeAPI -> 成功则 is_like=true, count+1
     * 2. 如果当前已点赞 -> 调用 delGroupCommentLikeAPI -> 成功则 is_like=false, count-1
     */
    const toggleLike = async (comment) => {
        try {
            if (!comment.is_like) {
                const { code } = await addGroupCommentLikeAPI({ i: comment.id });
                if (code === 1) {
                    comment.is_like = true;
                    comment.like_count += 1;
                }
            } else {
                const { code } = await delGroupCommentLikeAPI({ i: comment.id });
                if (code === 1) {
                    comment.is_like = false;
                    comment.like_count -= 1;
                }
            }
        } catch (error) {
            console.error('点赞操作失败:', error);
        }
    };

    /**
     * 提交主页面评论
     * @description
     * 1. 校验内容非空
     * 2. 调用 addGroupCommentAPI
     * 3. 成功后刷新评论列表并清空输入框
     */
    const submitComment = async () => {
        if (!newComment.value.trim()) return;
        if (!course.value?.group_id || !course.value?.id) return;

        try {
            const { code } = await addGroupCommentAPI({
                group_id: course.value.group_id,
                schedule_id: course.value.id,
                note: newComment.value
            });

            if (code === 1) {
                await refreshComments();
                newComment.value = '';
            }
        } catch (error) {
            console.error('提交评论失败:', error);
        }
    };

    /**
     * 弹窗加载更多评论(分页)
     * @description
     * 用于 Vant List 组件的 @load 事件
     * 1. 计算下一页页码
     * 2. 调用 API 获取分页数据
     * 3. 过滤重复数据并追加到 popupCommentList
     * 4. 判断是否已加载全部数据 (finished)
     */
    const onPopupLoad = async () => {
        if (!course.value?.group_id || !course.value?.id) {
            popupLoading.value = false;
            return;
        }

        const nextPage = popupPage.value;
        try {
            const res = await getGroupCommentListAPI({
                group_id: course.value.group_id,
                schedule_id: course.value.id,
                limit: popupLimit.value,
                page: nextPage
            });
            if (res.code === 1) {
                const newComments = res.data.comment_list;
                // 去重处理,防止页码错乱导致的数据重复
                const existingIds = new Set(popupCommentList.value.map(item => item.id));
                const uniqueNewComments = newComments.filter(item => !existingIds.has(item.id));
                
                popupCommentList.value = [...popupCommentList.value, ...uniqueNewComments];
                // 如果返回数量小于限制,说明已无更多数据
                popupFinished.value = res.data.comment_list.length < popupLimit.value;
                popupPage.value = nextPage + 1;
            }
        } catch (error) {
            console.error('加载评论失败:', error);
        }
        popupLoading.value = false;
    };

    /**
     * 提交弹窗内的评论
     * @description
     * 1. 调用 API 提交
     * 2. 成功后重置弹窗列表状态(清空列表、页码归零)
     * 3. 重新加载第一页数据
     * 4. 同步更新外部评论总数
     */
    const submitPopupComment = async () => {
        if (!popupComment.value.trim()) return;
        if (!course.value?.group_id || !course.value?.id) return;

        try {
            const { code, data } = await addGroupCommentAPI({
                group_id: course.value.group_id,
                schedule_id: course.value.id,
                note: popupComment.value
            });

            if (code === 1) {
                // 重置列表状态以重新加载
                popupCommentList.value = [];
                popupPage.value = 0;
                popupFinished.value = false;
                await onPopupLoad();

                commentCount.value = data.comment_count;
                await refreshComments(); // 同步刷新主列表
                popupComment.value = '';
            }
        } catch (error) {
            console.error('提交评论失败:', error);
        }
    };

    // 监听弹窗打开,初始化数据
    watch(showCommentPopup, async (newVal) => {
        if (!newVal) return;
        if (!course.value?.group_id || !course.value?.id) return;

        popupCommentList.value = [];
        popupPage.value = 0;
        popupFinished.value = false;
        popupLoading.value = true;

        await refreshComments();
        onPopupLoad();
    });

    return {
        commentCount,
        commentList,
        newComment,
        showCommentPopup,
        popupComment,
        popupCommentList,
        popupLoading,
        popupFinished,
        refreshComments,
        toggleLike,
        submitComment,
        onPopupLoad,
        submitPopupComment
    };
};