const { createApp, ref, onMounted, nextTick, onUnmounted, computed, watch, provide, defineAsyncComponent } = Vue; (async function () { const { itemForum } = await import(withVer("../component/item-forum/item-forum.js")); const { itemOffer } = await import(withVer("../component/item-offer/item-offer.js")); const { itemSummary } = await import(withVer("../component/item-summary/item-summary.js")); const { itemVote } = await import(withVer("../component/item-vote/item-vote.js")); const { itemMj } = await import(withVer("../component/item-mj/item-mj.js")); const { itemTenement } = await import(withVer("../component/item-tenement/item-tenement.js")); // const itemForum = defineAsyncComponent(() => import(withVer("../component/item-forum/item-forum.js")).then(m => m.itemForum)); // const itemOffer = defineAsyncComponent(() => import(withVer("../component/item-offer/item-offer.js")).then(m => m.itemOffer)); // const itemSummary = defineAsyncComponent(() => import(withVer("../component/item-summary/item-summary.js")).then(m => m.itemSummary)); // const itemVote = defineAsyncComponent(() => import(withVer("../component/item-vote/item-vote.js")).then(m => m.itemVote)); // const itemMj = defineAsyncComponent(() => import(withVer("../component/item-mj/item-mj.js")).then(m => m.itemMj)); // const itemTenement = defineAsyncComponent(() => import(withVer("../component/item-tenement/item-tenement.js")).then(m => m.itemTenement)); // const latestList = defineAsyncComponent(() => import(withVer("../component/latest-list/latest-list.js")).then(m => m.latestList)); // const slideshowBox = defineAsyncComponent(() => import(withVer("../component/slideshow-box/slideshow-box.js")).then(m => m.slideshowBox)); // const like = defineAsyncComponent(() => import(withVer("../component/like/like.js")).then(m => m.like)); // const report = defineAsyncComponent(() => import(withVer("../component/report/report.js")).then(m => m.report)); // const headTop = defineAsyncComponent(() => import(withVer("../component/head-top/head-top.js")).then(m => m.headTop)); // 判断 ismobile 为 true 是 不加载 latestList 和 slideshowBox // if (!window.isMobile) { // const { latestList } = await import(withVer("../component/latest-list/latest-list.js")); // const { slideshowBox } = await import(withVer("../component/slideshow-box/slideshow-box.js")); // } let latestList, slideshowBox; if (!window.isMobile) { ({ latestList } = await import(withVer("../component/latest-list/latest-list.js"))); ({ slideshowBox } = await import(withVer("../component/slideshow-box/slideshow-box.js"))); } else { latestList = { name: "latestList", template: "
" }; slideshowBox = { name: "slideshowBox", template: "
" }; } // const { latestList } = await import(withVer("../component/latest-list/latest-list.js")); // const { slideshowBox } = await import(withVer("../component/slideshow-box/slideshow-box.js")); const { like } = await import(withVer("../component/like/like.js")); const { report } = await import(withVer("../component/report/report.js")); const { headTop } = await import(withVer("../component/head-top/head-top.js")); const appSectionIndex = createApp({ setup() { onMounted(() => { getUserInfoWin(); const preLoader = document.getElementById("pre-loader"); if (preLoader) preLoader.style.display = "none"; document.querySelectorAll(".vuehide").forEach((item) => { item.style.display = "none"; }); }); let isLogin = ref(false); let realname = ref(0); // 是否已经实名 let userInfoWin = ref({}); let permissions = ref([]); const getUserInfoWin = () => { const checkUser = () => { const user = window.userInfoWin; if (!user) return; document.removeEventListener("getUser", checkUser); realname.value = user.realname; userInfoWin.value = user; if (user?.uin > 0 || user?.uid > 0) isLogin.value = true; permissions.value = user?.authority || []; ismanager.value = permissions.value.indexOf("topic:manager") >= 0; }; document.addEventListener("getUser", checkUser); }; const openAttest = () => { const handleAttestClose = () => { document.removeEventListener("closeAttest", handleAttestClose); realname.value = window.userInfoWin?.realname || 0; }; // 启动认证流程时添加监听 document.addEventListener("closeAttest", handleAttestClose); loadAttest(2); }; // 跳转登录 const goLogin = () => { if (typeof window === "undefined") return; if (window["userInfoWin"] && Object.keys(window["userInfoWin"]).length !== 0) { if (window["userInfoWin"]["uid"]) isLogin.value = true; else ajax_login(); } else ajax_login(); }; provide("isLogin", isLogin); provide("userInfoWin", userInfoWin); provide("realname", realname); provide("openAttest", openAttest); provide("goLogin", goLogin); let authorInfo = ref({}); let info = ref({}); let ismyself = ref(false); let timestamp = ref(""); let updatedTime = ref(""); let token = ""; let tokentoken = ref(""); let uniqid = ""; let sectionn = ref([]); let tags = ref([]); let uniqidRef = ref(null); onMounted(() => { uniqid = uniqidRef.value.innerText; init(); window.addEventListener("scroll", handleScroll); checkWConfig(); }); const checkWConfig = () => { const wConfig = JSON.parse(localStorage.getItem("wConfig")) || {}; if (wConfig.time) { const time = new Date(wConfig.time); const now = new Date(); if (now - time > 24 * 60 * 60 * 1000) getWConfig(); else { const config = wConfig.config || {}; maxPicture.value = config.topic_image_count; } } else { getWConfig(); } }; const getWConfig = () => { ajaxGet("/v2/api/config/website").then((res) => { if (res.code == 200) { let data = res["data"] || {}; const config = data.config || {}; maxPicture.value = config.topic_image_count; data.time = new Date().toISOString(); localStorage.setItem("wConfig", JSON.stringify(data)); } }); }; const init = () => { ajaxGet(`/v2/api/forum/getTopicDetails?uniqid=${uniqid}`).then((res) => { if (res.code != 200) { creationAlertBox("error", res.message || "主题不存在"); // setTimeout(() => redirectToExternalWebsite(`/`), 3000); return; } const data = res.data; let targetInfo = data.info; if (!targetInfo.hidden) targetInfo.hidden = 0; // 替换换行 targetInfo.content = targetInfo.content?.replace(/\n/g, "
") || ""; if (!targetInfo.content) { targetInfo.content = targetInfo.title; targetInfo.title = ""; } authorInfo.value = Array.isArray(data.authorInfo) ? null : data.authorInfo; ismyself.value = data.ismyself || false; const sectionNameSet = new Set(targetInfo.sectionn.map((item) => item.name)); const newTag = targetInfo.tags.filter((tagName) => !sectionNameSet.has(tagName)); sectionn.value = targetInfo.sectionn; tags.value = newTag; timestamp.value = strtimeago(targetInfo.release_at, 4); updatedTime.value = targetInfo.updated_at ? strtimeago(targetInfo.updated_at, 4) : null; if (targetInfo.content) targetInfo.content = restoreHtml(targetInfo.content, targetInfo.attachments); info.value = targetInfo; token = data.token; tokentoken.value = data.token; if (info.value["anonymous"] == 0) getAuthorInfo(); isLogin.value = data.islogin; if (isLogin.value) getTopicOperation(); getRelatedTopics(); getComment(); getQrcode(); }); }; const restoreHtml = (formattedText, attachments, type) => { let imageList = attachments?.images || []; if (imageList && typeof imageList === "object" && !Array.isArray(imageList)) imageList = Object.values(imageList); let filesList = attachments?.files || []; if (filesList && typeof filesList === "object" && !Array.isArray(filesList)) filesList = Object.values(filesList); let videosList = attachments?.videos || []; if (videosList && typeof videosList === "object" && !Array.isArray(videosList)) videosList = Object.values(videosList); // const filesList = attachments?.files || []; // const videosList = attachments?.videos || []; let html = formattedText; html = html.replaceAll("", "[b]"); html = html.replaceAll("", "[/b]"); // 1. 还原换行符为
标签 html = html.replace(/\n/g, "
"); // 2. 还原块级标签的换行标记 html = html.replace(/
/g, "
"); html = html.replace(/<\/div>
/g, "
"); // 3. 还原标签标记为span.blue html = html.replace(/\[tag\]([^[]+)\[\/tag\]/gi, '#$1 '); // 4. 还原粗体标记为h2标签 html = html.replace(/\[b\]([\s\S]*?)\[\/b\]/gi, "

$1

"); // 5. 还原【新增图片格式】[img=width,height]aid[/img] 或 [img]aid[/img] html = html.replace(/\[img(?:=([0-9]+(?:\.[0-9]+)?)(?:,([0-9]+(?:\.[0-9]+)?))?)?\](\d+)\[\/img\]/gi, (match, width, height, aid) => { const image = imageList.find((img) => String(img.aid) === String(aid)); // 统一字符串比较,避免类型问题 if (!image) return match; // 从列表中移除已匹配的图片(避免重复使用) const index = imageList.findIndex((img) => String(img.aid) === String(aid)); if (index > -1) imageList.splice(index, 1); // 拼接img标签(带宽高样式,宽高为0则不设置) let style = ""; const w = width ? Number(width) : 0; const h = height ? Number(height) : 0; if (w > 0 && h > 0) style = `style="width: ${w}px; height: ${h}px;"`; else if (w > 0) style = `style="width: ${w}px;"`; return ``; }); // 5. 统一在单次遍历中按出现顺序替换 attach/attachimg const byAid = new Map(); imageList.forEach((e) => byAid.set(Number(e.aid), { type: "image", ...e })); filesList.forEach((e) => byAid.set(Number(e.aid), { type: "file", ...e })); videosList.forEach((e) => byAid.set(Number(e.aid), { type: "video", ...e })); html = html.replace(/\[(attachimg|attach)\](\d+)\[\/\1\]/gi, (match, tag, aidStr) => { const aid = Number(aidStr); const item = byAid.get(aid); if (!item) return match; byAid.delete(aid); if (item.type === "image") { return `
`; } if (item.type === "file") { return `
${item.filename}【点击下载附件】
`; } return `
`; }); // 6. 还原填充标签 html = html.replace(/([^<]+<\/span>)\s+/gi, '$1 '); // 7. 清理多余的
标签 // html = html.replace(/

/g, "
"); if (type != "comment") { byAid.forEach((item, aid) => { if (item.type === "image") html += `
`; else if (item.type === "file") html += `
${item.name || item.filename}【点击下载附件】

`; else html += `
`; }); } return html; }; let QRcode = ref(""); const getQrcode = () => { ajaxGet(`/v2/api/forum/getQrcode?token=${token}`).then((res) => { if (res.code != 200) return; const data = res.data || []; QRcode.value = data.url || ""; }); }; let count = ref(0); let medal = ref([]); const getAuthorInfo = () => { ajaxGet(`/v2/api/forum/getSpaceDetail?uid=${authorInfo.value.uid || 0}&uin=${authorInfo.value.uin || 0}`).then((res) => { const data = res.data; const countList = data.count || []; count.value = countList.reduce((sum, item) => { const currentCount = Number(item.count) || 0; return sum + currentCount; }, 0); medal.value = data.medal || []; authorInfo.value = data.info; getCreationList(data.token); }); }; let recentlyList = ref([]); const getCreationList = (token) => { ajaxGet(`/v2/api/forum/getSpaceTopicList?token=${token}&simple=1`).then((res) => { const data = res.data; recentlyList.value = data.data || []; recentlyList.value = recentlyList.value.slice(0, 8); }); }; let islike = ref(0); let iscollect = ref(0); const getTopicOperation = () => { ajax(`/v2/api/forum/getTopicOperation`, { token, actions: ["like", "collection"], }) .then((res) => { const data = res.data; const like = data.like; const collection = data.collection; islike.value = like.status; iscollect.value = collection.status; }) .catch((err) => { console.log("err", err); }); }; let isLikeGif = ref(false); const likeClick = () => { if (realname.value == 0 && userInfoWin.value?.uin > 0) { openAttest(); return; } if (!isLogin.value) { goLogin(); return; } ajax(`/v2/api/forum/postTopicLike`, { token, }).then((res) => { if (res.code != 200) { creationAlertBox("error", res.message); return; } const data = res.data; islike.value = data.status; info.value.likes = data.likes; if (data.status) { isLikeGif.value = true; setTimeout(() => (isLikeGif.value = false), 2000); } }); }; const collectClick = () => { ajax(`/v2/api/forum/postTopicCollect`, { token, }).then((res) => { if (res.code != 200) { creationAlertBox("error", res.message); return; } const data = res.data; iscollect.value = data.status; info.value.collections = data.collections; }); }; let strategy = ref(""); let mybalance = ref(0); let defaultcoinnum = 0; const getCoinConfig = () => { if (!isLogin.value) { goLogin(); return; } ajaxGet(`/v2/api/forum/getCoinConfig`).then((res) => { const data = res.data; strategy.value = data.config.strategy.url || 0; mybalance.value = data.mybalance || 0; defaultcoinnum = data.defaultcoinnum || 0; // openCoinBox(); }); }; let coinsState = ref(false); const openCoinBox = () => { BiComponent.initComponent(); // getCoinConfig(); // coinsState.value = true; // document.body.style.overflow = "hidden"; // if (!coinListRequest) getCoinRankList(); }; const closeCoinBox = () => { coinsState.value = false; document.body.style.overflow = "auto"; }; let coinAmount = ref(""); const coinSubmit = () => { const num = Number(coinAmount.value || defaultcoinnum) || 0; ajax(`/v2/api/forum/postTopicCoin`, { token, num, }) .then((res) => { if (res.code == 200) creationAlertBox("success", res.message); else creationAlertBox("error", res.message); if (res.code != 200) return; let data = res.data; mybalance.value = mybalance.value - num || 0; coinAmount.value = ""; info.value.coins = data.coins || 0; coinNubmer.value = 0; coinList.value = []; getCoinRankList(); }) .finally(() => { // wx.hideLoading(); }); }; let coinNubmer = ref(0); let coinList = ref([]); let coinListRequest = false; // 控制请求次数 const getCoinRankList = () => { ajaxGet(`/v2/api/forum/getCoinRankList?token=${token}&limit=1000`).then((res) => { const data = res.data; coinNubmer.value = data.nubmer; coinList.value = data.data; coinListRequest = true; }); }; let relatedList = ref([]); let relatedTime = ref(""); const getRelatedTopics = () => { ajaxGet(`/v2/api/forum/getRelatedTopics?uniqid=${uniqid}&limit=8`).then((res) => { const data = res.data; relatedTime.value = data.updated_at || ""; relatedList.value = data.list || []; }); }; let alreadyCommentIdList = []; let commentPage = ref(1); let isgetCommentSate = false; let commentList = ref([]); let commentTotalCount = ref(0); const getComment = () => { if (commentPage.value == 0 || isgetCommentSate || !token) return; isgetCommentSate = true; ajaxGet(`/v2/api/forum/getCommentList?token=${token}&page=${commentPage.value}&limit=20`) .then((res) => { if (res.code != 200) { creationAlertBox("error", res.message || ""); return; } let data = res.data; data.data.forEach((element, index) => { element.timestamp = strtimeago(element.created_at, 4); element["picture"] = []; element["isReplyBoxShow"] = 0; if (element["content"]) element["content"] = restoreHtml(element["content"], element.attachments, "comment"); if (element.child.length > 0) { element.child.forEach((el) => { el["picture"] = []; el.timestamp = strtimeago(el.created_at, 4); el["isReplyBoxShow"] = 0; if (el["content"]) el["content"] = restoreHtml(el["content"], el.attachments, "comment"); }); } }); // if (commentPage.value > 1) { // for (let index = 0; index < data.data.length; index++) { // if (alreadyCommentIdList.includes(data.data[index].id)) { // data.data.splice(index, 1); // index--; // } // } // } commentList.value = commentList.value.concat(data.data); commentTotalCount.value = data.commentcount; commentPage.value = data.count > commentList.value.length ? commentPage.value + 1 : 0; }) .finally(() => { isgetCommentSate = false; }); }; let picture = ref([]); const openUserInfo = (index, i) => { if (i != undefined && (commentList.value[index].child[i].user["uin"] > 0 || commentList.value[index].child[i].user["uid"] > 0)) commentList.value[index].child[i]["avatarState"] = true; if (i == undefined && index != undefined && (commentList.value[index].user["uin"] > 0 || commentList.value[index].user["uid"] > 0)) commentList.value[index]["avatarState"] = true; }; const closeUserInfo = (index, i) => { if (i != undefined) commentList.value[index].child[i]["avatarState"] = false; else if (index != undefined) commentList.value[index]["avatarState"] = false; }; let isReplyBoxShow = ref(true); // 打开 回答-评论 的子评论 const openAnswerCommentsChild = (index, i) => { if (realname.value == 0 && userInfoWin.value?.uin > 0) { openAttest(); return; } if (!isLogin.value) { goLogin(); return; } closeAnswerCommentsChild(); if (i == null) commentList.value[index]["childState"] = true; else commentList.value[index].child[i]["childState"] = true; isReplyBoxShow.value = false; }; // 关闭 回答-评论 的子评论 const closeAnswerCommentsChild = () => { commentList.value.forEach((ele) => { ele["childState"] = false; ele["commentInput"] = ""; // 删除原本输入值 if (ele["child"] && ele["child"].length != 0) { ele["child"].forEach((el) => { el["childState"] = false; el["commentInput"] = ""; }); } }); isReplyBoxShow.value = true; }; const handleAnswerText = (e) => { if (e.target.tagName === "IMG") { // 检查点击的图片是否被a标签包裹 const anchorTag = e.target.closest("a"); // 如果被a标签包裹,则不执行图片预览,让链接正常跳转 if (anchorTag) { return; } // 否则,执行图片预览 var src = e.target.getAttribute("src"); previewImage.initComponent(src); } }; // 回答-评论 点赞 const operateAnswerCommentsLike = (token, index, i) => { if (realname.value == 0 && userInfoWin.value?.uin > 0) { openAttest(); return; } if (!isLogin.value) { goLogin(); return; } ajax("/v2/api/forum/likeComment", { token, }).then((res) => { if (res.code != 200) { creationAlertBox("error", res.message || "操作成功"); return; } let data = res.data; if (i != undefined) { commentList.value[index].child[i]["islike"] = data["status"]; commentList.value[index].child[i]["likenum"] = data["count"]; } else { commentList.value[index]["islike"] = data["status"]; commentList.value[index]["likenum"] = data["count"]; } creationAlertBox("success", res.message || "操作成功"); if (data["status"]) { isLikeGif.value = true; setTimeout(() => (isLikeGif.value = false), 2000); } }); }; let emojiState = ref(false); let emojiMaskState = ref(false); let inputTextarea = ref(""); const inputTextareaRef = ref(null); // 打开 Emoji const openEmoji = (event, index, i) => { if (!isLogin.value) { goLogin(); return; } if (i != undefined) commentList.value[index].child[i]["emojiState"] = true; else if (index != undefined) commentList.value[index]["emojiState"] = true; else { closeEmoji(); closeAnswerCommentsChild(); emojiState.value = true; } emojiMaskState.value = true; let emojiBottomDistance = 0; const doc = document.documentElement; try { const targetEl = (event && (event.currentTarget || event.target)) || null; const rect = targetEl && targetEl.getBoundingClientRect ? targetEl.getBoundingClientRect() : null; if (rect) { const elementBottomDocY = rect.bottom + window.scrollY; emojiBottomDistance = Math.max(doc.scrollHeight - elementBottomDocY, 0); } else { const pageY = event && (event.pageY != null ? event.pageY : event.clientY != null ? event.clientY + window.scrollY : 0); emojiBottomDistance = Math.max(doc.scrollHeight - pageY, 0); } const itemEl = targetEl && targetEl.closest ? targetEl.closest(".item") : null; const boxEl = itemEl ? itemEl.querySelector(".emoji-box") : null; if (boxEl) { if (emojiBottomDistance < 500) boxEl.classList.add("top"); else boxEl.classList.remove("top"); } } catch (e) {} }; // 关闭 Emoji const closeEmoji = (index, i) => { commentList.value.forEach((ele) => { ele["emojiState"] = false; if (ele["child"] && ele["child"].length != 0) { ele["child"].forEach((el) => { el["emojiState"] = false; }); } }); emojiState.value = false; emojiMaskState.value = false; editEmojiState.value = false; }; const TAHomePage = (token) => goHomePage(token); const sendMessage = (token) => goSendMessage(token); let emojiData = ref(["😀", "😁", "😆", "😅", "😂", "😉", "😍", "🥰", "😘", "🤥", "😪", "😵‍💫", "🤓", "🥺", "😋", "😜", "🤪", "😎", "🤩", "🥳", "😔", "🙁", "😭", "😡", "😳", "🤗", "🤔", "🤭", "🤫", "😯", "😵", "🙄", "🥴", "🤢", "🤑", "🤠", "👌", "✌️", "🤟", "🤘", "🤙", "👍", "👎", "✊", "👏", "🤝", "🙏", "💪", "❎️", "✳️", "✴️", "❇️", "#️⃣", "*️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟", "🆗", "🈶", "🉐", "🉑", "🌹", "🥀", "🌸", "🌺", "🌷", "🌲", "☘️", "🍀", "🍁", "🌙", "⭐", "🌍", "☀️", "⭐️", "🌟", "☁️", "🌈", "☂️", "❄️", "☃️", "☄️", "🔥", "💧", "🍎", "🍐", "🍊", "🍉", "🍓", "🍑", "🍔", "🍟", "🍕", "🥪", "🍜", "🍡", "🍨", "🍦", "🎂", "🍰", "🍭", "🍿", "🍩", "🧃", "🍹", "🍒", "🥝", "🥒", "🥦", "🥨", "🌭", "🥘", "🍱", "🍢", "🥮", "🍩", "🍪", "🧁", "🍵", "🍶", "🍻", "🥂", "🧋", "🎉", "🎁", "🧧", "🎃", "🎄", "🧨", "✨️", "🎈", "🎊", "🎋", "🎍", "🎀", "🎖️", "🏆️", "🏅", "💌", "📬", "🚗", "🚕", "🚲", "🛵", "🚀", "🚁", "⛵", "🚢", "🔮", "🧸", "🀄️"]); const handleEditFile = () => { editEmojiState.value = false; judgeLogin(); }; // 选择 Emoji const selectEmoji = (key, index, i) => { closeEmoji(); if (i != undefined) { if (!commentList.value[index]["child"][i]["commentInput"]) commentList.value[index]["child"][i]["commentInput"] = ""; const id = commentList.value[index]["child"][i]?.id; const textarea = document.querySelector(`.input-textarea-${id}`); if (textarea) { const currentValue = textarea.value; const startPos = textarea.selectionStart || 0; const endPos = textarea.selectionEnd || 0; const newValue = currentValue.substring(0, startPos) + key + currentValue.substring(endPos); commentList.value[index]["child"][i]["commentInput"] = newValue; nextTick(() => { textarea.focus(); textarea.selectionStart = textarea.selectionEnd = startPos + key.length; }); } else commentList.value[index]["child"][i]["commentInput"] += key; } else if (index != undefined) { if (!commentList.value[index]["commentInput"]) commentList.value[index]["commentInput"] = ""; const id = commentList.value[index]?.id; const textarea = document.querySelector(`.input-textarea-${id}`); if (textarea) { const currentValue = textarea.value; const startPos = textarea.selectionStart || 0; const endPos = textarea.selectionEnd || 0; const newValue = currentValue.substring(0, startPos) + key + currentValue.substring(endPos); commentList.value[index]["commentInput"] = newValue; nextTick(() => { textarea.focus(); textarea.selectionStart = textarea.selectionEnd = startPos + key.length; }); } else commentList.value[index]["commentInput"] += key; } else { const textarea = inputTextareaRef.value; if (!textarea) return; const currentValue = textarea.value; const startPos = textarea.selectionStart || 0; const endPos = textarea.selectionEnd || 0; const newValue = currentValue.substring(0, startPos) + key + currentValue.substring(endPos); inputTextarea.value = newValue; nextTick(() => { textarea.focus(); textarea.selectionStart = textarea.selectionEnd = startPos + key.length; }); } }; const judgeLogin = () => { if (!isLogin.value) goLogin(); }; let maxPicture = ref(10); const handleFileUpload = (event, index, i) => { closeEmoji(); const file = event.target.files[0]; // 获取选择的文件 if (!file) return; if (file.size > maxSize) { creationAlertBox("error", "文件大小不能超过 20MB"); return; } let target = []; if (editCommentState.value) target = editPicture.value; else { if (i != undefined) target = commentList.value[index].child[i]["picture"]; else if (index != undefined) target = commentList.value[index]["picture"]; else target = picture.value; } if (target.length >= maxPicture.value) { creationAlertBox("error", `最多只能上传 ${maxPicture.value} 张图片`); return; } const reader = new FileReader(); reader.onload = (e) => { const base64 = e.target.result; uploadImg(file).then((res) => { const obj = { aid: res.aid || "", url: res.url || "", }; target.push(obj); if (editCommentState.value) editPicture.value = target; else { if (i != undefined) commentList.value[index].child[i]["picture"] = target; else if (index != undefined) commentList.value[index]["picture"] = target; else picture.value = target; } creationAlertBox("success", "上传成功"); }); }; reader.readAsDataURL(file); }; let uploadConfig = null; const maxSize = 20 * 1024 * 1024; // 20MB // 上传图片 获取图片url const uploadImg = (file) => { return new Promise((resolve, reject) => { const upload = () => { let config = uploadConfig; const formData = new FormData(); formData.append(config.requestName, file); // 文件数据 formData.append("name", file.name); // 文件名 formData.append("type", "image"); // 文件名 formData.append("data", config.params.data); // 文件名 ajax(config.url, formData).then((res) => { if (res.code != 200) { creationAlertBox("error", "上传失败"); return; } let data = res.data; resolve(data); }); }; if (uploadConfig) upload(); else getUploadConfig().then(() => upload()); }); }; const getUploadConfig = () => { return new Promise((resolve, reject) => { ajaxGet("/v2/api/config/upload?type=comment").then((res) => { let data = res.data; uploadConfig = data; resolve(); }); }); }; // 删除上传的图片 const closeFileUpload = (aid, index, i) => { let target = []; if (i != undefined) target = [...commentList.value[index].child[i]["picture"]]; else if (index != undefined) target = [...commentList.value[index]["picture"]]; else target = [...picture.value]; let sub = target.findIndex((item) => item.aid == aid); if (sub != -1) target.splice(sub, 1); if (i != undefined) commentList.value[index].child[i]["picture"] = target; else if (index != undefined) commentList.value[index]["picture"] = target; else picture.value = target; }; const closePictureUpload = (index) => picture.value.splice(index, 1); // 提交回答-评论 const submitAnswerComments = (index, i) => { if (realname.value == 0 && userInfoWin.value?.uin > 0) { openAttest(); return; } if (!isLogin.value) { goLogin(); return; } let content = ""; let parentid = null; // let token = this.token; let image = []; if (i != null) { content = commentList.value[index]["child"][i]["commentInput"]; parentid = commentList.value[index]["child"][i]["id"]; image = [...commentList.value[index]["child"][i]["picture"]]; } else if (index != null) { content = commentList.value[index]["commentInput"]; parentid = commentList.value[index]["id"]; image = [...commentList.value[index]["picture"]]; } else { content = inputTextarea.value; image = [...picture.value]; } // this.detailLoading = true; const attachments = { images: image, }; ajax("/v2/api/forum/postComment", { content, token, attachments, replyid: parentid, }) .then((res) => { if (res.code != 200) { creationAlertBox("error", res.message || "操作成功"); return; } let data = res.data; const timestamp = strtimeago(new Date()); if (i != null) { let targetData = { id: data["commentid"], content, isauthor: 1, islike: 0, likenum: 0, reply: { nickname: commentList.value[index]["child"][i]["nickname"], }, ...data, attachments, picture: [], timestamp, user: { ...userInfoWin.value }, }; commentList.value[index]["child"].push(targetData); commentList.value[index]["childnum"]++; } else if (index != null) { let targetData = { id: data["commentid"], content, isauthor: 1, islike: 0, likenum: 0, reply: [], ...data, attachments, picture: [], timestamp, user: { ...userInfoWin.value }, }; commentList.value[index]["child"].unshift(targetData); commentList.value[index]["childnum"]++; } else { let targetData = { id: data["commentid"], content, isauthor: 1, islike: 0, likenum: 0, ...data, child: [], attachments, picture: [], timestamp, user: { ...userInfoWin.value }, }; commentList.value.unshift(targetData); inputTextarea.value = ""; picture.value = []; } commentTotalCount.value = data.count || 0; // if (!inputTextarea.value) { // const textarea = this.$refs["input-textarea"] // textarea.style.height = "80px" // } closeAnswerCommentsChild(); creationAlertBox("success", res.message || "操作成功"); }) .finally(() => { // this.detailLoading = false; }); }; let editCommentState = ref(false); let editToken = ref(""); let editPicture = ref([]); let editInput = ref(""); let editEmojiState = ref(false); const openEdit = (token, index, i) => { const list = JSON.parse(JSON.stringify(commentList.value)); let target = {}; if (i != null) target = list[index]["child"][i]; else target = list[index]; editToken.value = target.token || ""; editInput.value = target.content || ""; editPicture.value = target.attachments?.images || []; editCommentState.value = true; nextTick(() => { editInputRef.value.style.height = "auto"; // 重置高度 editInputRef.value.style.height = `${editInputRef.value.scrollHeight}px`; // 设置为内容高度 }); }; const closeEdit = () => { editPicture.value = {}; editToken.value = ""; editInput.value = ""; editCommentState.value = false; }; // 打开 Emoji const openEditEmoji = (index, i) => { if (!isLogin.value) { goLogin(); return; } editEmojiState.value = true; }; const closeEditEmoji = () => { editEmojiState.value = false; }; const selectEditEmoji = (key) => { closeEmoji(); editInput.value += key; }; const postEditComment = () => { if (!isLogin.value) { goLogin(); return; } const image = editPicture.value; const attachments = { images: image, }; ajax("/v2/api/forum/postCommentEdit", { content: editInput.value, token: editToken.value, attachments, }).then((res) => { if (res.code != 200) { creationAlertBox("error", res.message || "操作失败"); return; } commentList.value.forEach((element) => { if (element.token == editToken.value) { element["content"] = editInput.value; element["attachments"] = attachments; } element.child && element.child.forEach((ele) => { if (ele.token == editToken.value) { ele["content"] = editInput.value; ele["attachments"] = attachments; } }); }); editPicture.value = []; editToken.value = ""; editCommentState.value = false; editEmojiState.value = false; creationAlertBox("success", res.message || "操作成功"); }); }; const closeEditFileUpload = (aid) => { let target = [...editPicture.value]; let sub = target.findIndex((item) => item.aid == aid); if (sub != -1) target.splice(sub, 1); editPicture.value = target; }; const handleInputPaste = (event, index, ii) => { const items = event.clipboardData.items; // 获取粘贴的内容 for (let i = 0; i < items.length; i++) { const item = items[i]; if (item.type.startsWith("image/")) { event.preventDefault(); const file = item.getAsFile(); // 获取文件 if (file.size > maxSize) { creationAlertBox("error", "文件大小不能超过 20MB"); return; } let target = []; if (editCommentState.value) target = editPicture.value; else { if (ii != undefined) target = commentList.value[index].child[ii]["picture"]; else if (index != undefined) target = commentList.value[index]["picture"]; else target = picture.value; } if (target.length >= maxPicture.value) { creationAlertBox("error", `最多只能上传 ${maxPicture.value} 张图片`); return; } const reader = new FileReader(); reader.onload = (e) => { const base64 = e.target.result; uploadImg(file).then((res) => { const obj = { aid: res.aid || "", url: res.url || "", }; target.push(obj); if (editCommentState.value) editPicture.value = target; else { if (ii != undefined) commentList.value[index].child[ii]["picture"] = target; else if (index != undefined) commentList.value[index]["picture"] = target; else picture.value = target; } creationAlertBox("success", "上传成功"); }); }; reader.readAsDataURL(file); } } }; const alsoCommentsData = (index, i) => { if (!isLogin.value) { goLogin(); return; } const parentid = commentList.value[index]["id"]; ajax("/v2/api/forum/childrenList", { token, parentid, limit: 2000, page: 1, childlimit: 3, }).then((res) => { if (res.code != 200) { creationAlertBox("error", res.message || "操作成功"); return; } let data = res.data; data.data.forEach((element, index) => { element.timestamp = strtimeago(element.created_at, 4); element["isReplyBoxShow"] = 0; element["picture"] = []; if (element["content"]) element["content"] = restoreHtml(element["content"], element.attachments, "comment"); }); let merged = [...commentList.value[index]["child"], ...data.data.filter((item2) => !commentList.value[index]["child"].find((item1) => item1.id == item2.id))]; commentList.value[index]["child"] = merged; }); }; // 自动输入框增高 const autoResize = (e) => { e.target.style.height = "auto"; // 重置高度 e.target.style.height = `${e.target.scrollHeight}px`; // 设置为内容高度 }; let commemtDelete = {}; // 点击删除 const commentDelete = (token, index, i) => { const post = () => { ajax("/v2/api/forum/deleteComment", { token, }).then((res) => { if (res.code != 200) { creationAlertBox("error", res.message); return; } if (i >= 0) { commentList.value[index].child.splice(i, 1); commentList.value[index].childnum -= 1; } else { commentList.value.splice(index, 1); } commentTotalCount.value -= 1; creationAlertBox("success", res.message || "操作成功"); }); }; const isConfirmed = confirm(`确定要删除讨论吗?`); if (isConfirmed) post(); }; const openDiscuss = () => { let dom = document.querySelector(".answer-discuss"); if (!dom) return; const rect = dom.getBoundingClientRect(); const scrollPosition = window.pageYOffset + rect.top - 50; window.scrollTo({ top: scrollPosition, behavior: "smooth", }); }; let show = ref(false); let ismanager = ref(false); const cutShow = () => { show.value = !show.value; // 修改为切换显示状态 }; let reportState = ref(false); let reportToken = ref(""); provide("reportState", reportState); // 举报 const report = (token) => { cutShow(); reportState.value = true; reportToken.value = token; }; // 隐藏 const hide = () => { const target = info.value; managerHide(token, target.hidden, "thread").then((value) => { target.hidden = value; info.value = target; cutShow(); }); }; // 推荐 const recommend = () => { const target = info.value; managerRecommend(token, target.recommend).then((value) => { target.recommend = value; info.value = target; cutShow(); }); }; // 精华 const essence = () => { const target = info.value; managerEssence(token, target.best).then((value) => { target.best = value; info.value = target; cutShow(); }); }; const copyLinkClick = () => { copyForumUid(location.href); }; const goPersonalHomepage = (token) => { if (!token) return; redirectToExternalWebsite(`/u/${token}`); }; let searchInput = ref(""); let defaultSearchText = ref("屯特"); const goSearch = () => { const searchText = searchInput.value || defaultSearchText.value; redirectToExternalWebsite("/search/" + searchText); }; const edit = () => { redirectToExternalWebsite(`/publish?uniqid=${info.value.uniqid}`); }; let pitchInputState = ref(false); const sidebarFixed = ref(false); const handleScroll = () => { matterHeight.value = -(matterRef.value.offsetHeight - window.innerHeight); sidebarHeight.value = -(sidebarRef.value.offsetHeight - window.innerHeight); if (matterHeight.value > 0) matterHeight.value = 12; if (sidebarHeight.value > 0) sidebarHeight.value = 12; const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight; const clientHeight = window.innerHeight; // 列表下 滑动到底部 获取新数据 if (scrollTop + clientHeight >= scrollHeight - 200) getComment(); }; const matterRef = ref(null); const sidebarRef = ref(null); const deleteItem = () => { managerDelete(token) .then(() => redirectToExternalWebsite("/", "_self")) .finally(() => cutShow()); }; let sidebarHeight = ref(0); let matterHeight = ref(0); const share = () => { ajax(`/v2/api/forum/postTopicShare`, { token }); }; return { inputTextareaRef, uniqidRef, share, reportToken, isReplyBoxShow, matterHeight, sidebarHeight, deleteItem, maxPicture, sidebarFixed, matterRef, sidebarRef, pitchInputState, ismyself, edit, searchInput, defaultSearchText, goSearch, goPersonalHomepage, QRcode, alsoCommentsData, copyLinkClick, reportState, tokentoken, essence, recommend, hide, report, cutShow, ismanager, show, openDiscuss, commentDelete, handleInputPaste, autoResize, editCommentState, selectEditEmoji, closeEditEmoji, openEditEmoji, closeEdit, openEdit, closeEditFileUpload, postEditComment, submitAnswerComments, closePictureUpload, closeFileUpload, picture, editToken, editPicture, editInput, editEmojiState, handleFileUpload, inputTextarea, judgeLogin, handleEditFile, selectEmoji, emojiData, emojiMaskState, emojiState, closeEmoji, openEmoji, closeAnswerCommentsChild, openAnswerCommentsChild, handleAnswerText, sendMessage, TAHomePage, operateAnswerCommentsLike, closeUserInfo, openUserInfo, permissions, commentList, commentPage, commentTotalCount, picture, userInfoWin, relatedList, relatedTime, coinNubmer, coinList, coinAmount, coinSubmit, strategy, mybalance, coinsState, openCoinBox, closeCoinBox, isLikeGif, likeClick, collectClick, islike, iscollect, recentlyList, medal, count, sectionn, tags, authorInfo, info, timestamp, updatedTime }; }, }); appSectionIndex.component("itemForum", itemForum); appSectionIndex.component("itemOffer", itemOffer); appSectionIndex.component("itemSummary", itemSummary); appSectionIndex.component("itemVote", itemVote); appSectionIndex.component("itemMj", itemMj); appSectionIndex.component("itemTenement", itemTenement); appSectionIndex.component("latestList", latestList); appSectionIndex.component("slideshowBox", slideshowBox); appSectionIndex.component("like", like); appSectionIndex.component("report", report); appSectionIndex.component("headTop", headTop); appSectionIndex.mount("#details"); })();