Files
PC-Light-Forum/component/head-top-web/head-top-web.js
DESKTOP-RQ919RC\Pc c63bf6c611 feat(header): 新增头部组件功能与样式优化
- 添加默认头像图片资源
- 扩展监听文件同步配置
- 重构头像展示样式,增加背景和尺寸控制
- 完善签到功能逻辑和样式交互
- 新增头部组件HTML模板和JS实现
- 调整搜索框和历史记录显示逻辑
- 优化页面布局和响应式设计
2025-12-01 19:58:21 +08:00

276 lines
14 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 = `<div class="head-top flexacenter"> <a href="/" class="flexacenter" target="_blank"> <img class="logo" src="https://oss.gter.net/logo" alt="" /> </a> <div class="flex1"></div> <div class="input-box flexacenter" :class="{'pitch': searchInputState}"> <div class="placeholder" v-if="!searchInputState && !input"> <div class="placeholder-box" :style="{transform: 'translateY(-' + currentIndex * 36 + 'px)', transition: 'transform .3s ease'}"> <div class="item one-line-display" v-for="(item,index) in hotSearchWords" :key="index">大家都在搜:{{ item.keyword }}</div> <div class="item one-line-display" v-for="(item,index) in hotSearchWords.slice(0, 2)" :key="'copy-' + index">大家都在搜:{{ item.keyword }}</div> </div> </div> <input class="input flex1" type="text" @keyup.enter="searchEvent()" v-model="input" @focus="searchInputFocus" @blur="searchInputBlur" maxlength="140" /> <img class="icon" src="/img/search-icon.svg" @click="searchEvent()" /> <div class="search-box-history" v-if="searchHistoryShowState"> <div class="search-box-history-title">历史搜索</div> <div class="search-box-history-list"> <div class="search-box-history-item one-line-display" v-for="(item,index) in historySearchList " :key="index" @click="searchEvent(item)">{{ item }}</div> </div> </div> </div> <div class="post-list flexacenter" v-if="page == 'details'"><a href="/publish" target="_blank" style="margin-right: 10px"> <img class="post-item" src="/img/post-thread.png" /> </a> <a href="https://offer.gter.net/post" target="_blank" style="margin-right: 10px"> <img class="post-item" src="/img/post-offer.png" /> </a> <a href="https://offer.gter.net/post/summary" target="_blank" style="margin-right: 10px"> <img class="post-item" src="/img/post-summary.png" /> </a> <a href="https://interviewexperience.gter.net/publish" target="_blank" style="margin-right: 10px"> <img class="post-item" src="/img/post-mj.png" /> </a> <a href="https://vote.gter.net/publish" target="_blank" style="margin-right: 10px"> <img class="post-item" src="/img/post-vote.png" /> </a> </div> <template v-else> <div class="sign-in flexacenter" :class="{'sign-in-already': state == 1, 'sign-in-no': state == 0}" @click="signIn()" v-cloak> <div class="sign-in-no-box"> <img class="sign-in-bj" src="/img/sign-in-bj.svg" /> <img class="coin-bj" src="/img/coin-bj.svg" /> <img class="coin-icon" src="/img/coin-icon.png" /> <span class="text flex1">签到领寄托币</span> <div class="sign-go flexcenter"> <img class="sign-go-bj" src="/img/sign-go.svg" /> GO </div> <img class="petal1" src="/img/petal1.png" /> <img class="petal2" src="/img/petal2.png" /> <img class="petal3" src="/img/petal3.png" /> </div> <div class="sign-in-already-box"> <img class="sign-icon" src="/img/sign-icon.png" /> <span>已签到,明天再来</span> </div> </div> </template></div> `;
// 2. 定义组件类
class HeadTopWeb extends HTMLElement {
constructor() {
super();
}
// 监听的属性列表
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.headTopWebComponent) {
window.headTopWebComponent.init(type);
return true;
}
console.warn("head-top-web组件尚未加载或挂载");
return false;
}
// 生命周期:组件挂载
connectedCallback() {
if (this._mounted) return;
this.innerHTML = "";
this.appendChild(template.content.cloneNode(true));
this.coins = this.getAttribute("coins") || this.coins || "";
this.token = this.getAttribute("token") || this.token || "";
this.pagetpye = this.getAttribute("pagetpye") || this.pagetpye || "";
this.displaytips = this.getAttribute("displaytips") || this.displaytips || "";
this.input = this.querySelector(".input") || this.input;
window.headTopWebComponent = this;
this._mounted = true;
}
// 生命周期:组件卸载
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. 注册组件(确保只注册一次) head-top-web
if (!customElements.get("head-top-web")) customElements.define("head-top-web", HeadTopWeb);
// 4. 导出组件类,以便在外部直接调用
window.HeadTopWebComponent = HeadTopWeb;