const { requireLogin } = require("../../utils/auth"); const { del, get, post } = require("../../utils/api"); const { getEnabledModules, getActiveClassId, showError } = require("../../utils/page-helpers"); const { isModuleEnabled } = require("../../utils/modules"); function initialOf(name) { return String(name || "班").slice(0, 1); } function formatTime(value) { return String(value || "").replace("T", " ").slice(0, 16); } function sameId(left, right) { return Number(left) === Number(right); } function datasetBoolean(value) { return value === true || value === "true" || value === 1 || value === "1"; } function normalizeTimeline(item, comments = [], currentUserId = null) { return { ...item, author_initial: initialOf(item.author_name), created_at_text: formatTime(item.created_at), like_action_text: item.has_liked ? "已赞" : "赞", like_action_class: item.has_liked ? "active" : "", comments: comments.map((comment) => ({ ...comment, created_at_text: formatTime(comment.created_at), can_delete: sameId(currentUserId, comment.author_id) })) }; } Page({ data: { timelines: [], timelineEnabled: false, commentPostId: null, commentText: "", replyToName: "", commentPlaceholder: "写评论", commentInputFocus: false, keyboardOpen: false, loading: false, likingId: null, commenting: false }, onShow() { if (!requireLogin()) return; this.load(); }, async load() { const enabledModules = getEnabledModules(); const classId = getActiveClassId(); const timelineEnabled = isModuleEnabled("timeline", enabledModules); if (!timelineEnabled) { this.setData({ timelines: [], timelineEnabled: false }); return; } this.setData({ loading: true }); try { const currentUser = getApp().globalData.user || wx.getStorageSync("auth_user") || {}; const res = await get("/api/timeline/", { page_size: 20, class_id: classId }); const commentResults = await Promise.all( (res.items || []).map((item) => get(`/api/timeline/${item.id}/comments`, { page_size: 6 }).catch(() => ({ items: [] })) ) ); const timelines = (res.items || []).map((item, index) => ( normalizeTimeline(item, commentResults[index].items || [], currentUser.id) )); this.setData({ timelines, timelineEnabled: true }); } catch (error) { showError(error); } finally { this.setData({ loading: false }); } }, openTimeline(event) { wx.navigateTo({ url: `/pages/timeline-detail/index?id=${event.currentTarget.dataset.id}` }); }, openCompose() { wx.navigateTo({ url: "/pages/timeline-create/index" }); }, previewImage(event) { const current = event.currentTarget.dataset.src; const postId = Number(event.currentTarget.dataset.postId); const postItem = this.data.timelines.find((item) => item.id === postId); const urls = postItem && postItem.image_urls ? postItem.image_urls : []; if (!current || !urls.length) return; wx.previewImage({ current, urls }); }, async toggleLike(event) { const id = Number(event.currentTarget.dataset.id); if (!id || this.data.likingId) return; this.setData({ likingId: id }); try { const result = await post(`/api/timeline/${id}/like`); const timelines = this.data.timelines.map((item) => { if (item.id !== id) return item; return normalizeTimeline({ ...item, has_liked: Boolean(result.liked), like_count: Number(result.like_count || 0) }, item.comments || [], (getApp().globalData.user || wx.getStorageSync("auth_user") || {}).id); }); this.setData({ timelines }); } catch (error) { showError(error, "操作失败"); } finally { this.setData({ likingId: null }); } }, openComment(event) { const id = Number(event.currentTarget.dataset.id); const postItem = this.data.timelines.find((item) => item.id === id); this.setData({ commentPostId: id, commentText: "", replyToName: "", commentPlaceholder: postItem ? `评论 ${postItem.author_name}` : "写评论", commentInputFocus: false }, () => { this.focusCommentInput(); }); }, replyComment(event) { const postId = Number(event.currentTarget.dataset.postId); const commentId = Number(event.currentTarget.dataset.commentId); const name = event.currentTarget.dataset.name || ""; const canDelete = datasetBoolean(event.currentTarget.dataset.canDelete); if (canDelete && commentId) { this.openOwnCommentActions(postId, commentId, name); return; } this.startReply(postId, name); }, startReply(postId, name) { this.setData({ commentPostId: postId, commentText: "", replyToName: name, commentPlaceholder: name ? `回复 ${name}` : "写评论", commentInputFocus: false }, () => { this.focusCommentInput(); }); }, closeComment() { if (this.data.commenting) return; this.setData({ commentPostId: null, commentText: "", replyToName: "", commentInputFocus: false, keyboardOpen: false }); }, noop() {}, onCommentInput(event) { this.setData({ commentText: event.detail.value }); }, onCommentFocus(event) { const height = event.detail.height || 0; this.setData({ commentInputFocus: true, keyboardOpen: height > 0 }); }, onKeyboardHeightChange(event) { const height = event.detail.height || 0; this.setData({ keyboardOpen: height > 0 }); }, onCommentBlur() { this.setData({ commentInputFocus: false, keyboardOpen: false }); }, focusCommentInput() { const focus = () => { if (this.data.commentPostId) { this.setData({ commentInputFocus: true }); } }; if (wx.nextTick) { wx.nextTick(() => setTimeout(focus, 120)); return; } setTimeout(focus, 120); }, async submitComment() { const postId = this.data.commentPostId; const text = this.data.commentText.trim(); if (!postId || !text) { wx.showToast({ title: "请输入评论", icon: "none" }); return; } const content = this.data.replyToName ? `回复 @${this.data.replyToName}:${text}` : text; this.setData({ commenting: true }); try { await post(`/api/timeline/${postId}/comments`, { content }); const detail = await get(`/api/timeline/${postId}`); const currentUser = getApp().globalData.user || wx.getStorageSync("auth_user") || {}; const timelines = this.data.timelines.map((item) => ( item.id === postId ? normalizeTimeline(detail, detail.comments || [], currentUser.id) : item )); this.setData({ timelines, commentPostId: null, commentText: "", replyToName: "", commentInputFocus: false, keyboardOpen: false }); wx.showToast({ title: "已评论", icon: "success" }); } catch (error) { showError(error, "评论失败"); } finally { this.setData({ commenting: false }); } }, deleteComment(postId, commentId) { wx.showModal({ title: "删除评论", content: "确认删除这条评论?", confirmText: "删除", confirmColor: "#b42318", success: async (res) => { if (!res.confirm) return; try { await del(`/api/timeline/comments/${commentId}`); const detail = await get(`/api/timeline/${postId}`); const currentUser = getApp().globalData.user || wx.getStorageSync("auth_user") || {}; const timelines = this.data.timelines.map((item) => ( item.id === postId ? normalizeTimeline(detail, detail.comments || [], currentUser.id) : item )); this.setData({ timelines }); wx.showToast({ title: "已删除", icon: "success" }); } catch (error) { showError(error, "删除失败"); } } }); }, openOwnCommentActions(postId, commentId, name) { wx.showActionSheet({ itemList: ["回复", "删除评论"], itemColor: "#6b1f2b", success: (res) => { if (res.tapIndex === 0) { this.startReply(postId, name); } if (res.tapIndex === 1) { this.deleteComment(postId, commentId); } } }); } });