Files
PC-Light-Forum/js/details.js
DESKTOP-RQ919RC\Pc 2a227a806d fix(editor): 修复编辑器样式和功能问题
修复编辑器容器高度设置问题,统一h2标签为h1
调整图片和视频的显示样式,修复表格背景色
优化编辑器工具栏功能,修复链接插入逻辑
2025-11-27 19:23:49 +08:00

1238 lines
50 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { createApp, ref, onMounted, nextTick, onUnmounted, computed, watch, provide } = Vue;
const ASSET_VERSION = window.__ASSET_VERSION__ || "20251126";
const withVer = (p) => `${p}?v=${ASSET_VERSION}`;
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 { 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();
});
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.attachments = {
images: [
{
aid: 708161,
url: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG7Y2l-S_pItc37qqsgFptxhXa6RWi26P-BuTQYWFOfCsdkb8LQ0NDI5",
},
],
files: [],
videos: [
{
aid: 1009770,
posterid: 1009849,
posterurl: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_polfXuP1NFX9ddrB_WbUGy8P79gQxdHR-HKts0V7NkzNDQyOQ~~",
url: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_polcniG1NFX9ddrB_WbUGy8P79gQxcSFbqQ78MV7NkzNDQyOQ~~",
},
],
};
// targetInfo.content = '<p style="text-align: center;">红红火火<strong>恍恍惚惚</strong></p>[b]红红火火恍恍惚惚有[/b]<p>\n</p><p>\n</p><p>[attach]1009770[/attach]</p><p>\n</p><p>\n</p><p style="text-align: center;">[img=96]708161[/img]</p><p style="text-align: center;">65456456456456465&nbsp;<a href="11111" target="_blank" contenteditable="false">111</a>&nbsp;</p><p style="text-align: center;">\n</p>';
targetInfo.content = '如果你热爱古典文献,又希望在现代职场大展身手——这个项目可能就是你的“本命”!作为香港最正统的中国语言文学项目,它既传承经典,又为你打跨境传播等全新赛道!\n\n<b>🌟 项目核心亮点</b>权威认证:中国语言文学专业认证,考公考编无障碍\n古今结合深耕古典文献与理论同时对接AI内容创作等新兴领域\n语言友好全程中文授课普通话+粤语),无语言适应压力\n规模可观每年录取150+,机会相对较多\n\n点击前往 [港校项目库] 查看 \n<a href="https://program.gter.net/details/tf1yFYIBSda7Y5k7s9iHeLVSxDiuYTljNA~~" target="_blank" contenteditable="false">中国语言文学</a>\n手机扫码查看\n[attachimg]1008942[/attachimg]\n\n<b>🎯 谁最适合申请?</b>中文系、汉语言、古代文学等对口专业背景\n希望在教育、传媒、AI内容或国际中文教育领域发展\n看重学校声誉与专业正统性的同学\n<b>💼 毕业出路超多元</b>除了教师、公务员等传统路径,毕业生还活跃于:\n✔ 跨境文化传播\n✔ AI内容策划与生成\n✔ 国际中文教育\n✔ 出版与编辑行业\n<b>📌 申请指南</b>专业背景:严格限定中文相关专业,暂不接受跨专业申请\n成绩要求985/211同学建议86+\n语言成绩雅思7.0小分5.5)即可\n面试体验氛围轻松专业问题较少\n<b>💡 内部消息参考</b>前几轮拿到面试邀请的同学基本都能录取\n985背景优势明显建议尽早提交申请\n双非同学如背景特别匹配也可尝试\n<b>🤝 欢迎交流</b>你对中国文学在AI时代的发展有什么想法或者对哪个就业方向申请问题欢迎在评论区分享交流\n欢迎加入寄托香港群交流\n\n[attachimg]969489[/attachimg]';
// 替换换行
targetInfo.content = targetInfo.content?.replace(/\n/g, "<br>") || "";
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) => {
const imageList = attachments?.images || [];
const filesList = attachments?.files || [];
const videosList = attachments?.videos || [];
let html = formattedText;
html = html.replaceAll("<b>", "[b]");
html = html.replaceAll("</b>", "[/b]");
// 1. 还原换行符为<br>标签
html = html.replace(/\n/g, "<br>");
// 2. 还原块级标签的换行标记
html = html.replace(/<br><div>/g, "<div>");
html = html.replace(/<\/div><br>/g, "</div>");
// 3. 还原标签标记为span.blue
html = html.replace(/\[tag\]([^[]+)\[\/tag\]/gi, '<a class="blue" href="/tag/$1" target="_blank">#$1</a> <span class="fill"></span> ');
// 4. 还原粗体标记为h2标签
html = html.replace(/\[b\]([\s\S]*?)\[\/b\]/gi, "<h2>$1</h2>");
// 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 `<img src="${image.url}" ${style}>`;
});
console.log(html);
// 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 `<img src="${item.url}" data-aid="${aid}"><br/>`;
}
if (item.type === "file") {
return `<div class="flexacenter"><a href="${item.url}" download="${item.filename}">${item.filename}</a>【点击下载附件】</div>`;
}
return `<video src="${item.url}" width="400" height="400" preload="none" poster="${item.posterurl}" controls></video><br/>`;
});
// 6. 还原填充标签
html = html.replace(/(<span class="blue">[^<]+<\/span>)\s+/gi, '$1 <span class="fill"></span> ');
// 7. 清理多余的<br>标签
// html = html.replace(/<br><br>/g, "<br>");
if (type != "comment") {
byAid.forEach((item, aid) => {
if (item.type === "image") html += `<img src="${item.url}" data-aid="${aid}"><br/>`;
else if (item.type === "file") html += `<div class="flexacenter"><a href="${item.url}" download="${item.name || item.filename}">${item.name || item.filename}</a>【点击下载附件】</div><br/>`;
else html += `<video src="${item.url}" width="400" height="400" preload="none" poster="${item.posterurl}" controls></video><br/>`;
});
}
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 (!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") {
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 emojiBottomDistance = ref(0);
let inputTextarea = ref("");
// 打开 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;
};
// 关闭 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"] = "";
commentList.value[index]["child"][i]["commentInput"] += key;
} else if (index != undefined) {
if (!commentList.value[index]["commentInput"]) commentList.value[index]["commentInput"] = "";
commentList.value[index]["commentInput"] += key;
} else inputTextarea.value += key;
};
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;
};
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"] = [];
});
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 { emojiBottomDistance, 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");