feat(header): 新增头部组件功能与样式优化
- 添加默认头像图片资源 - 扩展监听文件同步配置 - 重构头像展示样式,增加背景和尺寸控制 - 完善签到功能逻辑和样式交互 - 新增头部组件HTML模板和JS实现 - 调整搜索框和历史记录显示逻辑 - 优化页面布局和响应式设计
This commit is contained in:
275
component/head-top-web/head-top-web.js
Normal file
275
component/head-top-web/head-top-web.js
Normal file
@@ -0,0 +1,275 @@
|
||||
// 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;
|
||||
44
component/head-top-web/head-top-web.txt
Normal file
44
component/head-top-web/head-top-web.txt
Normal file
@@ -0,0 +1,44 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user