Files
PC-Light-Forum/component/bi/bi.js
DESKTOP-RQ919RC\Pc 460450c339 feat(editor): 集成富文本编辑器并优化响应式布局
1. 添加wangEditor富文本编辑器替换原有简易编辑器
2. 新增编辑器相关CSS样式和功能按钮
3. 优化详情页和编辑页的响应式布局
4. 调整评论区域样式结构
5. 添加移动端适配样式
2025-11-25 19:12:29 +08:00

302 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

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

// 1. 创建组件模板和样式(内置到 JS 中,无需外部 HTML/Template
const template = document.createElement("template");
template.innerHTML = `<style> .flexflex { display: flex; } .flexcenter { display: flex; justify-content: center; align-items: center; } .flexjcenter { display: flex; justify-content: center; } .flexacenter { display: flex; align-items: center; } .flex1 { flex: 1; } .flexcolumn { display: flex; flex-direction: column; } .one-line-display { word-break: keep-all; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .two-line-display { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; text-overflow: ellipsis; } .coins-area { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 10004; display: none; } .coins-area * { box-sizing: border-box; } .coins-area a { text-decoration: none; } .coins-area .coins-box { width: 624px; background-color: #ffffff; border: 1px solid #e9eef2; border-radius: 11px; flex-direction: column; padding: 40px 30px 35px; position: relative; display: none; max-width: calc(100vw - 10px); } .coins-area .coins-box .fork { width: 12px; height: 12px; position: absolute; top: 20px; right: 20px; cursor: pointer; } .coins-area .coins-box .coins-head { margin-bottom: 32px; } .coins-area .coins-box .coins-head .icon { width: 50px; height: 60px; margin-right: 16px; } .coins-area .coins-box .coins-head .text { font-family: "PingFangSC-Regular", "PingFang SC", sans-serif; font-weight: 400; color: #7f7f7f; font-size: 14px; } .coins-area .coins-box .coins-head .text .sum { font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif; font-weight: 650; font-size: 18px; color: #000000; margin: 0 5px; } .coins-area .coins-box .coins-input { width: 333px; height: 36px; background-color: #ffffff; border: 1px solid #d7d7d7; border-radius: 8px; overflow: hidden; margin-bottom: 31px; } .coins-area .coins-box .coins-input .input { height: 100%; border: none; outline: none; font-size: 14px; padding: 0 10px; } .coins-area .coins-box .coins-input .btn { width: 84px; height: 100%; line-height: 36px; text-align: center; background-color: #50e3c2; cursor: pointer; } .coins-area .coins-box .coins-info { color: #555555; font-size: 14px; margin-bottom: 43px; } .coins-area .coins-box .coins-info .icon { width: 16px; height: 16px; margin-right: 6px; } .coins-area .coins-box .coins-info .sum { font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif; font-weight: 650; color: #000000; margin: 0 5px; } .coins-area .coins-box .coins-info .strategy { margin-left: 5px; color: #026277; cursor: pointer; text-decoration: none; } .coins-area .coins-box .coins-list-area { max-height: 347px; background-color: #fbfbfb; border-radius: 16px; width: 100%; /* padding: 20px 20px 0; */ flex-direction: column; } .coins-area .coins-box .coins-list-area .coins-total { color: #7f7f7f; font-size: 14px; margin: 20px 20px 0; } .coins-area .coins-box .coins-list-area .coins-total .sum { font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif; font-weight: 650; color: #000000; margin: 0 5px; } .coins-area .coins-box .coins-list-area .list { overflow: auto; margin: 0 20px; } .coins-area .coins-box .coins-list-area .list .item { height: 65px; padding: 0 35px; } .coins-area .coins-box .coins-list-area .list .item:not(:last-child) { border-bottom: 1px solid #f2f2f2; } .coins-area .coins-box .coins-list-area .list .item .serial { font-family: "Arial-BoldMT", "Arial Bold", "Arial", sans-serif; font-weight: 700; font-style: normal; color: #ffb600; margin-right: 114px; } .coins-area .coins-box .coins-list-area .list .item .user { color: #555555; font-size: 13px; cursor: pointer; } .coins-area .coins-box .coins-list-area .list .item .user .avatar { width: 32px; height: 32px; margin-right: 10px; border-radius: 50%; } .coins-area .coins-box .coins-list-area .list .item .amount { color: #000000; font-size: 16px; } .coins-area .coins-box .coins-list-area .list .item .amount .text { font-size: 13px; margin-left: 2px; } .unlock-insertcoins-box { width: 520px; border: 1px solid #e5e5e5; background-color: #fff; border-radius: 11px; flex-direction: column; display: flex; -webkit-box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.21); box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.21); display: none; } .unlock-insertcoins-box .unlock-insertcoins-close { width: 16px; height: 16px; padding: 10px; align-self: self-end; box-sizing: content-box; cursor: pointer; } .unlock-insertcoins-box .unlock-insertcoins-head { font-size: 14px; color: #555555; margin: 10px auto 35px; } .unlock-insertcoins-box .unlock-insertcoins-head .bi-icon { width: 40px; height: 48px; } .unlock-insertcoins-box .unlock-insertcoins-btn { width: 175px; height: 43px; border-radius: 45px; background-color: #50e3c2; cursor: pointer; } .unlock-insertcoins-box .in-all { color: #555555; font-size: 13px; margin-top: 17px; margin-bottom: 54px; } .unlock-insertcoins-box .in-all span { font-weight: 650; } .no-jituobi-pop-box { width: 520px; flex-direction: column; border: 1px solid #e5e5e5; background-color: #fff; border-radius: 11px; display: flex; -webkit-box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.21); box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.21); padding-bottom: 55px; display: none; } .no-jituobi-pop-box .no-jituobi-close { width: 16px; height: 16px; padding: 10px; align-self: flex-end; cursor: pointer; box-sizing: content-box; } .no-jituobi-pop-box .no-jituobi-head { font-size: 16px; color: #333; margin: 23px auto 42px; } .no-jituobi-pop-box .no-jituobi-head .bi-icon { width: 50px; height: 60px; } .no-jituobi-pop-box .strategy-btn { width: 198px; height: 43px; color: #000; font-size: 16px; border-radius: 100px; margin: 0 auto; cursor: pointer; } .no-jituobi-pop-box .strategy-btn .strategy-icon { width: 16px; height: 16px; margin-left: 8px; } .greenBj { background-color: #50e3c2; border-color: #50e3c2 !important; }</style> <div class="coins-area flexcenter"> <div class="coins-box flexcenter"> <img class="fork closeCoinBox" src="https://app.gter.net/image/gter/commonCom/bi/img/fork-icon.png" /> <div class="coins-head flexacenter"> <img class="icon" src="https://app.gter.net/image/gter/commonCom/bi/img/bi.png" /> <div class="text flexacenter"> 该<span class="pagetpyeText"></span>已获得 <div class="sum"></div> 个寄托币 </div> </div> <div class="coins-input flexacenter"> <input class="input flex1" type="number" placeholder="输入投币数" /> <div class="btn coinSubmit">投币</div> </div> <div class="coins-info flexacenter"> <img class="icon" src="https://app.gter.net/image/gter/commonCom/bi/img/bi-black-icon.png" /> 你当前共有 <div class="sum"></div> 寄托币 <a class="strategy" target="_blank" href="">[挣币攻略]</a> </div> <div class="coins-list-area flexflex"></div> </div> <!-- <div class="unlock-insertcoins-box flexcenter" v-if="coinMybalance >= defaultcoinnum"> --> <div class="unlock-insertcoins-box flexcenter"> <img class="unlock-insertcoins-close closeCoinBox" src="https://app.gter.net/image/gter/commonCom/bi/img/fork-icon.png" /> <div class="unlock-insertcoins-head flexacenter"> <img class="bi-icon" src="https://app.gter.net/image/gter/commonCom/bi/img/bi.png" style="margin-right: 11px;" /> 作者设置了阅读限制,解锁所有内容仅需 <span style="font-size: 20px;font-weight: 650;margin: 0 4px;" class="coinNum"></span> 寄托币 </div> <div class="unlock-insertcoins-btn flexcenter">投币解锁</div> <div class="in-all">你共有 <span class="balance"></span> 寄托币</div> </div> <!-- <div class="no-jituobi-pop-box" v-else-if="coinMybalance <= 0 || coinMybalance < defaultcoinnum"> --> <div class="no-jituobi-pop-box"> <img class="no-jituobi-close closeCoinBox" src="https://app.gter.net/image/gter/commonCom/bi/img/fork-icon.png" /> <div class="no-jituobi-head flexacenter"> <img class="bi-icon" src="https://app.gter.net/image/gter/commonCom/bi/img/bi.png" style="margin-right: 12px;"> <span style="margin-top: 10px;">你的寄托币不够,快去发帖挣币吧</span> </div> <a href="https://bbs.gter.net/thread-2543548-1-1.html" target="_blank"> <div class="strategy-btn greenBj flexcenter"> 攒币指南 <img class="strategy-icon" src="https://app.gter.net/image/gter/commonCom/bi/img/u1045.svg" /> </div> </a> </div></div>`;
// 2. 定义组件类
class BiCard extends HTMLElement {
constructor() {
super();
this.coins = 0;
this.token = "";
this.pagetpye = "";
this.displaytips = "";
// 创建 Shadow DOM 并挂载模板
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(template.content.cloneNode(true));
this.strategyEl = this.shadowRoot.querySelector(".coins-info .strategy");
this.mybalanceEl = this.shadowRoot.querySelector(".coins-info .sum");
this.coinsAreaEl = this.shadowRoot.querySelector(".coins-area");
this.coinsEl = this.shadowRoot.querySelector(".coins-area .coins-head .sum");
this.coinSubmitEl = this.shadowRoot.querySelector(".coins-input .coinSubmit");
this.closeCoinBoxEl = this.shadowRoot.querySelectorAll(".coins-area .closeCoinBox");
this.closeCoinBoxEl.forEach((item) => {
item.addEventListener("click", () => this.closeCoinBox());
});
this.coinSubmitEl.addEventListener("click", () => this.coinSubmit());
this.input = this.shadowRoot.querySelector(".coins-input .input");
this.coinListAreaEl = this.shadowRoot.querySelector(".coins-list-area");
this.defaultcoinnum = 0;
// 将实例存储到全局变量中,以便外部调用
window.biComponent = this;
this.pagetpyeText = this.shadowRoot.querySelector(".pagetpyeText");
this.coinsBoxEl = this.shadowRoot.querySelector(".coins-box");
// 解锁插入币弹窗
this.coinsUnlock = this.shadowRoot.querySelector(".unlock-insertcoins-box");
this.coinsNoBi = this.shadowRoot.querySelector(".no-jituobi-pop-box");
}
// 监听的属性列表
static get observedAttributes() {
return ["coins", "token", "pagetpye", "displaytips"];
}
// 属性变化回调
attributeChangedCallback(attrName, oldVal, newVal) {
this[attrName] = newVal; // 同步属性值到变量
}
init(type) {
this.fetchGetData(`https://api.gter.net/v2/api/forum/getCoinConfig`).then((res) => {
const data = res.data;
const strategy = data.config.strategy.url || 0;
const mybalance = data.mybalance || 0;
const defaultcoinnum = data.defaultcoinnum || 0;
// 替换策略链接
this.strategyEl.href = "https://bbs.gter.net/account.php?a=credit&op=rule";
this.mybalanceEl.textContent = mybalance;
this.coinsEl.textContent = this.coins;
this.defaultcoinnum = defaultcoinnum;
this.input.placeholder = `输入投币数,默认${defaultcoinnum}个币`;
this.coinsAreaEl.style.display = "flex";
if (type == "unlock") {
if (mybalance >= defaultcoinnum) {
this.coinsBoxEl.style.display = "none";
this.coinsNoBi.style.display = "none";
this.coinsUnlock.style.display = "flex";
this.coinsUnlock.querySelector(".coinNum").textContent = defaultcoinnum;
this.coinsUnlock.querySelector(".balance").textContent = mybalance;
this.coinsUnlock.querySelector(".unlock-insertcoins-btn").addEventListener("click", () => this.coinSubmit(type));
} else {
this.coinsBoxEl.style.display = "none";
this.coinsUnlock.style.display = "none";
this.coinsNoBi.style.display = "flex";
}
} else {
this.coinsUnlock.style.display = "none";
this.coinsNoBi.style.display = "none";
this.coinsBoxEl.style.display = "flex";
}
});
if (!this.coinListAreaEl.querySelector(".list")) this.getCoinRankList();
const obj = {
offer: "Offer",
summary: "总结",
mj: "面经",
thread: "帖子",
vote: "投票",
};
console.log("obj[this.pagetpye]", this.pagetpye);
this.pagetpyeText.textContent = obj[this.pagetpye];
}
getCoinRankList() {
this.fetchGetData(`https://api.gter.net/v2/api/forum/getCoinRankList?token=${this.token}&limit=1000`).then((res) => {
const data = res.data;
this.coinListAreaEl.innerHTML = "";
const coinNubmer = data.nubmer;
if (coinNubmer == 0) return;
const total = `<div class="coins-total flexacenter">共<div class="sum">${coinNubmer}</div>人参与投币:</div>`;
const coinList = data.data;
let list = coinList
.map((item) => {
return `<div class="item flexacenter" =>
<div class="serial">${item.rank}</div>
<a class="user flex1 flexacenter" href="https://f.gter.net/u/${item.user.uniqid}" target="_blank">
<img class="avatar" src="${item.user.avatar}" alt="" />
<div class="username one-line-display flex1">${item.user.nickname || "匿名用户"}</div>
</a>
<div class="amount flexacenter">
${item.coins}
<div class="text">币</div>
</div>
</div>`;
})
.join("");
list = `<div class="list flex1">${list}</div>`;
const listHTML = total + list;
this.coinListAreaEl.innerHTML = listHTML;
});
}
coinSubmit() {
const num = Number(this.input.value || this.defaultcoinnum) || 0;
if (num <= 0) {
creationAlertBox("error", "投币数量必须大于0");
return;
}
this.fetchData(`https://api.gter.net/v2/api/forum/postTopicCoin`, {
token: this.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;
if (this.displaytips == "coindisplayuser") {
setTimeout(() => window.location.reload(), 1000);
return;
}
this.mybalanceEl.textContent = Number(this.mybalanceEl.textContent) - num || 0;
this.input.value = "";
const coins = data.coins;
this.coinsEl.textContent = coins || 0;
if (this.pagetpye == "thread") document.querySelector(".action-bar-item.coins .text").textContent = coins || 0;
if (this.pagetpye == "vote") document.querySelector(".coinText").textContent = coins || 0;
if (this.pagetpye == "mj") document.querySelector(".coinText").textContent = coins || 0;
if (this.pagetpye == "offer") document.querySelector(".broadside-text.cursorpointer.coinText").textContent = (coins || 0) + " 寄托币";
if (this.pagetpye == "summary") document.querySelector(".broadside-text.cursorpointer.coinText").textContent = (coins || 0) + " 寄托币";
this.getCoinRankList();
});
}
closeCoinBox() {
this.coinsAreaEl.style.display = "none";
}
// 静态方法,用于在外部调用组件初始化 type normal 展示正常的投币和列表 unlock 解锁插入币弹窗 或者 币不足
static initComponent(type = "normal") {
if (window.biComponent) {
window.biComponent.init(type);
return true;
}
console.warn("bi组件尚未加载或挂载");
return false;
}
// 生命周期:组件挂载
connectedCallback() {}
// 生命周期:组件卸载
disconnectedCallback() {
console.log(`用户卡片 ${this.getAttribute("username")} 已卸载`);
}
fetchData(url, data) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.responseType = "json";
xhr.withCredentials = true;
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type", "application/json");
if (["127.0.0.1", "localhost", "192.168.18.219"].includes(location.hostname)) xhr.setRequestHeader("Authorization", "3b01343c65e3b2fa3ce32ae26feb3a9b");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
let response = xhr.response;
resolve(response);
}
};
xhr.send(JSON.stringify(data));
});
}
fetchGetData(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open("GET", url, true);
if (["127.0.0.1", "localhost", "192.168.18.219"].includes(location.hostname)) xhr.setRequestHeader("Authorization", "3b01343c65e3b2fa3ce32ae26feb3a9b");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
let response = xhr.response;
resolve(JSON.parse(response));
}
};
xhr.send();
});
}
$ajax(url) {
var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
return new Promise(function (resolve, reject) {
axios
.post(url, data, {
emulateJSON: true,
withCredentials: true,
})
.then(function (res) {
var data = typeof res.data == "string" ? JSON.parse(res.data) : res.data;
if (data.code == 401) {
// 需要登录
showWindow("login", "https://passport.gter.net/login/ajax", "get", -1, {
cover: true,
});
reject();
}
resolve(data);
})
.catch((err) => {
if (err.response.status == 401)
showWindow("login", "https://passport.gter.net/login/ajax", "get", -1, {
cover: true,
});
});
});
}
$ajaxget(url) {
var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
return new Promise(function (resolve, reject) {
axios
.get(
url,
{},
{
emulateJSON: true,
withCredentials: true,
}
)
.then(function (res) {
var data = typeof res.data == "string" ? JSON.parse(res.data) : res.data;
if (data.code == 401) {
// 需要登录
showWindow("login", "https://passport.gter.net/login/ajax", "get", -1, {
cover: true,
});
reject();
}
resolve(data);
})
.catch((error) => {
reject(error);
});
});
}
}
// 3. 注册组件(确保只注册一次)
if (!customElements.get("bi-card")) customElements.define("bi-card", BiCard);
// 4. 导出组件类,以便在外部直接调用
window.BiComponent = BiCard;