hku-class/miniprogram/pages/interact/index.js
2026-05-16 23:59:13 +08:00

280 lines
8.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}
});
}
});