Files
PC-Light-Forum/component/bi/bi.js
DESKTOP-RQ919RC\Pc c9e229a5cd fix(bi): 添加投币数量必须大于0的校验
feat(item-bottom): 实现二维码弹窗自适应右侧边界
添加判断逻辑使二维码弹窗在靠近右侧边界时向左弹出

refactor(public.js): 优化ajax请求配置和登录跳转逻辑
统一设置axios默认配置,提取登录跳转函数

style(public.css): 调整QRcode-box.right的定位
修复二维码弹窗靠近右侧时的显示问题

fix(details.js): 修复粗体标记正则匹配多行内容
使用[\s\S]*?匹配可能的多行内容

refactor(index.js): 优化列表加载和滚动逻辑
移除模拟数据,添加加载状态,调整滚动加载阈值

chore: 更新html模板中的唯一标识和广告类名
2025-11-19 19:27:43 +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; } .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;