- 添加签到组件到详情页 - 修改签到初始化逻辑,使用SignInComponent代替原有方法 - 优化签到弹窗样式和交互 - 移除调试用的console.log - 更新资源路径为绝对路径
1200 lines
47 KiB
JavaScript
1200 lines
47 KiB
JavaScript
const { createApp, ref, onMounted, nextTick, onUnmounted, computed, watch, provide } = Vue;
|
||
import { itemForum } from "../component/item-forum/item-forum.js";
|
||
import { itemOffer } from "../component/item-offer/item-offer.js";
|
||
import { itemSummary } from "../component/item-summary/item-summary.js";
|
||
import { itemVote } from "../component/item-vote/item-vote.js";
|
||
import { itemMj } from "../component/item-mj/item-mj.js";
|
||
import { itemTenement } from "../component/item-tenement/item-tenement.js";
|
||
import { latestList } from "../component/latest-list/latest-list.js";
|
||
import { slideshowBox } from "../component/slideshow-box/slideshow-box.js";
|
||
import { like } from "../component/like/like.js";
|
||
import { report } from "../component/report/report.js";
|
||
import { headTop } from "../component/head-top/head-top.js";
|
||
|
||
const appSectionIndex = createApp({
|
||
setup() {
|
||
onMounted(() => {
|
||
getUserInfoWin();
|
||
setTimeout(() => {
|
||
SignInComponent.initComponent();
|
||
}, 3000);
|
||
});
|
||
|
||
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 = '如果你热爱古典文献,又希望在现代职场大展身手——这个项目可能就是你的“本命”!作为香港最正统的中国语言文学项目,它既传承经典,又为你打跨境传播等全新赛道!\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>");
|
||
|
||
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 inputTextarea = ref("");
|
||
|
||
// 打开 Emoji
|
||
const openEmoji = (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 { 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");
|