统一用户信息付费查看

This commit is contained in:
2026-04-26 11:31:46 +08:00
parent f0269c7c17
commit af772350c9
13 changed files with 987 additions and 36 deletions
+88 -1
View File
@@ -222,6 +222,76 @@ export function bankShowMsg(message, success) {
}, 3000);
}
/**
* 确认是否扣费查看别人银行存款。
*
* @param {number} cost 查看费用
* @returns {Promise<boolean>}
*/
async function confirmRevealBankBalance(cost) {
const message = `查看 TA 的银行存款需扣除 ${Number(cost || 0).toLocaleString()} 金币,是否继续?`;
if (typeof window.chatDialog?.confirm === "function") {
return Boolean(await window.chatDialog.confirm(message, "信息查看付费", "#d97706", "扣费查看", "取消"));
}
return window.confirm(message);
}
/**
* 付费查看排行榜里的指定用户存款,并只更新当前行展示。
*
* @param {HTMLElement} trigger 触发查看的按钮
* @returns {Promise<void>}
*/
async function revealRankingBankBalance(trigger) {
const userId = Number.parseInt(trigger.getAttribute("data-bank-reveal-user-id") || "0", 10);
const cost = Number.parseInt(trigger.getAttribute("data-bank-reveal-cost") || "1000", 10);
if (!userId || trigger.getAttribute("aria-disabled") === "true") {
return;
}
const confirmed = await confirmRevealBankBalance(cost);
if (!confirmed) {
return;
}
trigger.setAttribute("aria-disabled", "true");
try {
const response = await fetch("/user/reveal-info", {
method: "POST",
headers: {
"X-CSRF-TOKEN": csrf(),
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({ user_id: userId, asset: "bank_jjb" }),
});
const data = await response.json();
if (data.status !== "success") {
await window.chatDialog?.alert?.(data.message || "查看失败,请稍后重试。", "查看失败", "#cc4444");
return;
}
const valueWrap = trigger.closest(".bank-rank-val");
if (valueWrap) {
valueWrap.innerHTML = `🏦 ${Number(data.value || 0).toLocaleString()}`;
}
if (window.chatContext && data.jjb !== undefined) {
// 同步流通金币缓存,避免后续购买或游戏继续使用扣费前余额。
window.chatContext.myGold = data.jjb;
}
bankShowMsg(data.message || "存款金额已显示。", true);
} catch (error) {
await window.chatDialog?.alert?.("网络异常,请稍后重试。", "查看失败", "#cc4444");
} finally {
trigger.removeAttribute("aria-disabled");
}
}
/**
* 切换排行榜排序并回到第一页。
*
@@ -330,6 +400,16 @@ function renderRankingRow(user, index) {
const username = escapeHtml(user.username || "");
const sexSymbol = user.sex === "女" ? "♀" : "♂";
const bankBalanceHtml = user.can_reveal
? `<button type="button"
data-bank-reveal-user-id="${escapeHtml(user.id)}"
data-bank-reveal-cost="${escapeHtml(user.reveal_cost || 1000)}"
title="点击查看存款,需扣除 ${Number(user.reveal_cost || 1000).toLocaleString()} 金币"
style="border:none;background:transparent;color:#059669;font-weight:bold;cursor:pointer;padding:0;">
****** 👁️
</button>`
: `🏦 ${Number(user.bank_jjb || 0).toLocaleString()}`;
return `<div class="bank-rank-item">
<div class="bank-rank-num ${rankClass}">${absoluteRank}</div>
<img src="${avatarUrl}" onerror="this.src='/images/headface/1.gif'" style="width:32px; height:32px; border-radius:50%; object-fit:cover; border:1px solid #d0e4f5;">
@@ -337,7 +417,7 @@ function renderRankingRow(user, index) {
<div style="font-weight:bold; color:#1e3a8a;">${username} <span style="font-size:12px;">${sexSymbol}</span></div>
</div>
<div class="bank-rank-val">
🏦 ${Number(user.bank_jjb || 0).toLocaleString()}
${bankBalanceHtml}
</div>
</div>`;
}
@@ -412,6 +492,13 @@ export function bindBankControls() {
return;
}
const revealButton = event.target.closest("[data-bank-reveal-user-id]");
if (revealButton instanceof HTMLElement) {
event.preventDefault();
void revealRankingBankBalance(revealButton);
return;
}
const pageButton = event.target.closest("[data-bank-rank-page-delta]");
if (pageButton) {
event.preventDefault();
+9 -3
View File
@@ -23,9 +23,11 @@ function getDialogElement(id) {
* @param {string} options.color
* @param {string} options.type
* @param {string} [options.defaultVal]
* @param {string} [options.confirmText]
* @param {string} [options.cancelText]
* @returns {void}
*/
function openDialog({ message, title, color, type, defaultVal }) {
function openDialog({ message, title, color, type, defaultVal, confirmText, cancelText }) {
currentDialogType = type;
const header = getDialogElement("global-dialog-header");
@@ -44,6 +46,8 @@ function openDialog({ message, title, color, type, defaultVal }) {
header.style.background = color;
messageBox.textContent = message;
confirmButton.style.background = color;
confirmButton.textContent = confirmText || "确定";
cancelButton.textContent = cancelText || "取消";
if (type === "prompt") {
inputElement.value = defaultVal ?? "";
@@ -105,12 +109,14 @@ function createChatDialogApi() {
* @param {string} message
* @param {string} title
* @param {string} color
* @param {string} confirmText
* @param {string} cancelText
* @returns {Promise<boolean>}
*/
confirm(message, title = "请确认", color = "#cc4444") {
confirm(message, title = "请确认", color = "#cc4444", confirmText = "确定", cancelText = "取消") {
return new Promise((resolve) => {
dialogResolve = resolve;
openDialog({ message, title, color, type: "confirm" });
openDialog({ message, title, color, type: "confirm", confirmText, cancelText });
});
},
+184
View File
@@ -121,6 +121,190 @@ export function userCardComponent() {
return operatorPositionRank >= targetPositionRank;
},
/** 返回名片资产字段的中文名称。 */
assetValueLabel(asset) {
return {
exp_num: "经验",
jjb: "金币",
meili: "魅力",
}[asset] || "资产";
},
/** 判断名片资产字段是否处于可付费查看的隐藏状态。 */
canRevealAssetValue(asset) {
return Boolean(
this.userInfo.asset_numbers_can_reveal
&& this.userInfo.asset_numbers_masked
&& this.userInfo[asset] === "******"
&& this.userInfo.asset_reveal_user_id
);
},
/** 格式化名片里的经验、金币、魅力显示。 */
displayAssetValue(asset) {
if (this.canRevealAssetValue(asset)) {
return "****** 👁️";
}
return Number(this.userInfo[asset] || 0).toLocaleString();
},
/** 返回名片资产字段的悬停提示。 */
assetValueTitle(asset) {
if (this.canRevealAssetValue(asset)) {
const cost = Number(this.userInfo.asset_numbers_reveal_cost || 1000).toLocaleString();
return `点击查看${this.assetValueLabel(asset)},需扣除 ${cost} 金币`;
}
return this.assetValueLabel(asset);
},
/** 返回名片资产字段的点击态样式。 */
assetValueStyle(asset, color) {
const clickable = this.canRevealAssetValue(asset);
return [
"font-weight: 700",
`color: ${color}`,
"font-size: 14px",
clickable ? "cursor: pointer" : "cursor: default",
"text-decoration: none",
].join("; ");
},
/** 付费查看当前名片里的经验、金币或魅力。 */
async revealAssetValue(asset) {
if (!this.canRevealAssetValue(asset)) {
return;
}
const cost = Number(this.userInfo.asset_numbers_reveal_cost || 1000);
const label = this.assetValueLabel(asset);
const confirmed = await this.$confirm(
`查看 TA 的${label}需扣除 ${cost.toLocaleString()} 金币,是否继续?`,
"信息查看付费",
"#d97706",
"扣费查看",
"取消"
);
if (!confirmed) {
return;
}
try {
const response = await fetch("/user/reveal-info", {
method: "POST",
headers: this._headers(),
body: JSON.stringify({
user_id: this.userInfo.asset_reveal_user_id,
asset,
}),
});
const data = await response.json();
if (data.status !== "success") {
this.$alert(data.message || "查看失败,请稍后重试。", "查看失败", "#cc4444");
return;
}
// 只解锁当前点击的资产字段,其他隐藏字段仍保持星号并可单独付费查看。
this.userInfo[asset] = data.value;
if (window.chatContext && data.jjb !== undefined) {
window.chatContext.myGold = data.jjb;
}
this.$alert(data.message || `${label}已显示。`, "查看成功", "#16a34a");
} catch (e) {
this.$alert("网络异常,请稍后重试。", "查看失败", "#cc4444");
}
},
/** 格式化名片里的银行存款显示。 */
displayBankBalance() {
if (this.userInfo.bank_jjb_can_reveal && this.userInfo.bank_jjb_masked) {
return "****** 👁️";
}
return Number(this.userInfo.bank_jjb || 0).toLocaleString();
},
/** 返回银行存款字段的悬停提示。 */
bankBalanceTitle() {
if (this.userInfo.bank_jjb_can_reveal && this.userInfo.bank_jjb_masked) {
const cost = Number(this.userInfo.bank_jjb_reveal_cost || 1000).toLocaleString();
return `点击查看存款,需扣除 ${cost} 金币`;
}
return "银行存款";
},
/** 返回银行存款字段的点击态样式。 */
bankBalanceStyle() {
const clickable = this.userInfo.bank_jjb_can_reveal && this.userInfo.bank_jjb_masked;
return [
"font-weight: 700",
"color: #059669",
"font-size: 14px",
clickable ? "cursor: pointer" : "cursor: default",
"text-decoration: none",
].join("; ");
},
/** 付费查看当前名片用户的银行存款。 */
async revealBankBalance() {
if (!this.userInfo.bank_jjb_can_reveal || !this.userInfo.bank_jjb_masked || !this.userInfo.bank_reveal_user_id) {
return;
}
const cost = Number(this.userInfo.bank_jjb_reveal_cost || 1000);
const confirmed = await this.$confirm(
`查看 TA 的银行存款需扣除 ${cost.toLocaleString()} 金币,是否继续?`,
"信息查看付费",
"#d97706",
"扣费查看",
"取消"
);
if (!confirmed) {
return;
}
try {
const response = await fetch("/user/reveal-info", {
method: "POST",
headers: this._headers(),
body: JSON.stringify({
user_id: this.userInfo.bank_reveal_user_id,
asset: "bank_jjb",
}),
});
const data = await response.json();
if (data.status !== "success") {
this.$alert(data.message || "查看失败,请稍后重试。", "查看失败", "#cc4444");
return;
}
// 仅解锁当前名片这一处显示,重新打开名片时仍以后端返回的遮罩状态为准。
this.userInfo.bank_jjb = data.value;
this.userInfo.bank_jjb_masked = false;
this.userInfo.bank_jjb_can_reveal = false;
if (window.chatContext && data.jjb !== undefined) {
window.chatContext.myGold = data.jjb;
}
this.$alert(data.message || "存款金额已显示。", "查看成功", "#16a34a");
} catch (e) {
this.$alert("网络异常,请稍后重试。", "查看失败", "#cc4444");
}
},
/** 切换好友关系(加好友 / 删好友) */
async toggleFriend() {
if (this.friendLoading) return;
@@ -3,7 +3,7 @@
提供全局 JS API
- window.chatDialog.alert(message, title?, color?) Promise<void>
- window.chatDialog.confirm(message, title?, color?) Promise<boolean>
- window.chatDialog.confirm(message, title?, color?, confirmText?, cancelText?) Promise<boolean>
- window.chatDialog.prompt(message, defaultVal?, title?, color?) Promise<string|null>
任何 JS 代码(Alpine.js 组件、toolbar、scripts 等)均可直接调用,
@@ -106,35 +106,43 @@
</div>
{{-- 详细信息区:外层 x-show 控制显隐,内层单独写 flex 避免被 Alpine 覆盖 --}}
<div x-show="userInfo.exp_num !== undefined" style="margin-top: 12px;">
<div x-show="userInfo.username" style="margin-top: 12px;">
<div style="display: flex; flex-direction: row; gap: 8px;">
<!-- 经验 -->
<div
style="flex: 1; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; padding: 6px 0; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<span style="color: #64748b; font-size: 11px; margin-bottom: 2px;">经验</span>
<span x-text="userInfo.exp_num"
style="font-weight: 700; color: #4f46e5; font-size: 14px;"></span>
<span x-text="displayAssetValue('exp_num')"
x-on:click="revealAssetValue('exp_num')"
:title="assetValueTitle('exp_num')"
:style="assetValueStyle('exp_num', '#4f46e5')"></span>
</div>
<!-- 金币 -->
<div
style="flex: 1; background: #fdfae8; border: 1px solid #fef08a; border-radius: 6px; padding: 6px 0; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<span style="color: #b45309; font-size: 11px; margin-bottom: 2px;">金币</span>
<span x-text="userInfo.jjb"
style="font-weight: 700; color: #d97706; font-size: 14px;"></span>
<span x-text="displayAssetValue('jjb')"
x-on:click="revealAssetValue('jjb')"
:title="assetValueTitle('jjb')"
:style="assetValueStyle('jjb', '#d97706')"></span>
</div>
<!-- 存款 -->
<div
style="flex: 1; background: #ecfdf5; border: 1px solid #a7f3d0; border-radius: 6px; padding: 6px 0; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<span style="color: #047857; font-size: 11px; margin-bottom: 2px;">存款</span>
<span x-text="userInfo.bank_jjb"
style="font-weight: 700; color: #059669; font-size: 14px;"></span>
<span x-text="displayBankBalance()"
x-on:click="revealBankBalance()"
:title="bankBalanceTitle()"
:style="bankBalanceStyle()"></span>
</div>
<!-- 魅力 -->
<div
style="flex: 1; background: #fdf2f8; border: 1px solid #fbcfe8; border-radius: 6px; padding: 6px 0; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<span style="color: #be185d; font-size: 11px; margin-bottom: 2px;">魅力</span>
<span x-text="userInfo.meili"
style="font-weight: 700; color: #db2777; font-size: 14px;"></span>
<span x-text="displayAssetValue('meili')"
x-on:click="revealAssetValue('meili')"
:title="assetValueTitle('meili')"
:style="assetValueStyle('meili', '#db2777')"></span>
</div>
</div>
</div>