Compare commits
4 Commits
a3daf3f074
...
v2.0.3
| Author | SHA1 | Date | |
|---|---|---|---|
| f0137f3fa3 | |||
| b15e42891d | |||
| 214a422504 | |||
| f16f10fe82 |
@@ -125,6 +125,11 @@ class ShopController extends Controller
|
|||||||
'total_price' => $result['total_price'] ?? $item->price,
|
'total_price' => $result['total_price'] ?? $item->price,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 购买自动钓鱼卡后返回剩余分钟数,前端用于自动开启钓鱼
|
||||||
|
if ($item->type === 'auto_fishing') {
|
||||||
|
$response['auto_fishing_minutes_left'] = $this->shopService->getActiveAutoFishingMinutesLeft($user);
|
||||||
|
}
|
||||||
|
|
||||||
// ── 装扮购买:向前端透传槽位与样式信息,用于即时更新装扮状态 ──
|
// ── 装扮购买:向前端透传槽位与样式信息,用于即时更新装扮状态 ──
|
||||||
if (! empty($result['slot'])) {
|
if (! empty($result['slot'])) {
|
||||||
$response['slot'] = $result['slot'];
|
$response['slot'] = $result['slot'];
|
||||||
|
|||||||
@@ -200,34 +200,41 @@ function setFishingButton(text, disabled) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动自动钓鱼冷却倒计时。
|
* 启动自动钓鱼冷却倒计时(基于时间戳,不受浏览器后台节流影响)。
|
||||||
*
|
*
|
||||||
* @param {number} cooldown
|
* @param {number} cooldown 冷却秒数
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function startAutoFishingCooldown(cooldown) {
|
function startAutoFishingCooldown(cooldown) {
|
||||||
let remaining = cooldown;
|
const endTime = Date.now() + cooldown * 1000;
|
||||||
setFishingButton(`⏳ 冷却 ${remaining}s`, true);
|
setFishingButton(`⏳ 冷却 ${cooldown}s`, true);
|
||||||
showAutoFishStopButton(cooldown);
|
showAutoFishStopButton(cooldown);
|
||||||
|
|
||||||
|
// 基于时间戳更新倒计时 UI — 后台节流后回来也能准确显示
|
||||||
autoFishCooldownCountdown = window.setInterval(() => {
|
autoFishCooldownCountdown = window.setInterval(() => {
|
||||||
remaining -= 1;
|
const remaining = Math.max(0, Math.ceil((endTime - Date.now()) / 1000));
|
||||||
setFishingButton(`⏳ 冷却 ${remaining}s`, true);
|
setFishingButton(`⏳ 冷却 ${remaining}s`, true);
|
||||||
|
|
||||||
if (remaining <= 0) {
|
if (remaining <= 0) {
|
||||||
window.clearInterval(autoFishCooldownCountdown);
|
window.clearInterval(autoFishCooldownCountdown);
|
||||||
autoFishCooldownCountdown = null;
|
autoFishCooldownCountdown = null;
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 200);
|
||||||
|
|
||||||
autoFishCooldownTimer = window.setTimeout(() => {
|
// 基于时间戳检测冷却结束 — 后台节流后立即触发
|
||||||
autoFishCooldownTimer = null;
|
autoFishCooldownTimer = null;
|
||||||
hideAutoFishStopButton();
|
const checkEnd = () => {
|
||||||
|
if (Date.now() >= endTime) {
|
||||||
if (autoFishing) {
|
autoFishCooldownTimer = null;
|
||||||
void startFishing();
|
hideAutoFishStopButton();
|
||||||
|
if (autoFishing) {
|
||||||
|
void startFishing();
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}, cooldown * 1000);
|
autoFishCooldownTimer = window.setTimeout(checkEnd, 200);
|
||||||
|
};
|
||||||
|
autoFishCooldownTimer = window.setTimeout(checkEnd, Math.min(cooldown * 1000, 200));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -457,6 +457,23 @@ export function enqueueChatMessage(msg) {
|
|||||||
state.chatMessageFlushTimer = scheduleFlush(flushQueuedChatMessages);
|
state.chatMessageFlushTimer = scheduleFlush(flushQueuedChatMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为普通用户聊天消息(非系统/游戏通知)。
|
||||||
|
*/
|
||||||
|
function isUserChatMessage(msg) {
|
||||||
|
if (!msg || !msg.from_user) return false;
|
||||||
|
const u = msg.from_user;
|
||||||
|
if (SYSTEM_USERS.includes(u)) return false;
|
||||||
|
if (u.endsWith("播报")) return false;
|
||||||
|
if (u === "百家乐" || u === "跑马") return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 后台恢复时系统通知最多保留条数 */
|
||||||
|
const MAX_SYSTEM_BURST = 20;
|
||||||
|
/** 后台恢复时超过该时间的系统通知直接丢弃(分钟) */
|
||||||
|
const MAX_SYSTEM_AGE_MINUTES = 10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分批渲染待处理消息,给动画、输入和滚动留出主线程时间。
|
* 分批渲染待处理消息,给动画、输入和滚动留出主线程时间。
|
||||||
*/
|
*/
|
||||||
@@ -466,6 +483,55 @@ export function flushQueuedChatMessages() {
|
|||||||
|
|
||||||
state.chatMessageFlushTimer = null;
|
state.chatMessageFlushTimer = null;
|
||||||
|
|
||||||
|
// 大批量消息堆积(后台标签页恢复)时,保留所有用户聊天记录,
|
||||||
|
// 但过时的系统/游戏通知只保留最近 MAX_SYSTEM_BURST 条。
|
||||||
|
if (state.pendingChatMessages.length > MAX_SYSTEM_BURST + 30) {
|
||||||
|
const now = Date.now();
|
||||||
|
const maxAge = MAX_SYSTEM_AGE_MINUTES * 60 * 1000;
|
||||||
|
const totalSystem = state.pendingChatMessages.filter((m) => !isUserChatMessage(m)).length;
|
||||||
|
let systemSeen = 0;
|
||||||
|
let dropped = 0;
|
||||||
|
|
||||||
|
const filtered = state.pendingChatMessages.filter((msg) => {
|
||||||
|
if (isUserChatMessage(msg)) return true;
|
||||||
|
|
||||||
|
systemSeen++;
|
||||||
|
|
||||||
|
// 超过10分钟的系统通知直接丢弃
|
||||||
|
let msgTime = 0;
|
||||||
|
if (msg.sent_at) {
|
||||||
|
msgTime = new Date(msg.sent_at.replace(" ", "T")).getTime();
|
||||||
|
}
|
||||||
|
if (msgTime > 0 && now - msgTime > maxAge) {
|
||||||
|
dropped++;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从旧到新遍历,只保留最后 MAX_SYSTEM_BURST 条系统通知
|
||||||
|
const remainingAfter = totalSystem - systemSeen;
|
||||||
|
if (remainingAfter >= MAX_SYSTEM_BURST) {
|
||||||
|
dropped++;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dropped > 0) {
|
||||||
|
const container = state.container;
|
||||||
|
if (container) {
|
||||||
|
const notice = document.createElement("div");
|
||||||
|
notice.className = "msg-line msg-burst-notice";
|
||||||
|
notice.style.cssText =
|
||||||
|
"text-align:center;padding:6px 0;margin:4px 0;font-size:12px;color:#94a3b8;border-top:1px dashed #d1d5db;border-bottom:1px dashed #d1d5db;";
|
||||||
|
notice.textContent = `⏫ 省略了 ${dropped} 条系统通知`;
|
||||||
|
container.appendChild(notice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.pendingChatMessages = filtered;
|
||||||
|
}
|
||||||
|
|
||||||
const batch = state.pendingChatMessages.splice(0, CHAT_MESSAGE_FLUSH_BATCH_SIZE);
|
const batch = state.pendingChatMessages.splice(0, CHAT_MESSAGE_FLUSH_BATCH_SIZE);
|
||||||
const renderBatch = createChatMessageRenderBatch();
|
const renderBatch = createChatMessageRenderBatch();
|
||||||
batch.forEach((msg) => appendMessage(msg, renderBatch));
|
batch.forEach((msg) => appendMessage(msg, renderBatch));
|
||||||
|
|||||||
@@ -556,7 +556,7 @@ async function confirmAndBuyItem(item) {
|
|||||||
const confirmed = await confirmShopPurchase(confirmMessage);
|
const confirmed = await confirmShopPurchase(confirmMessage);
|
||||||
|
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
buyItem(item.id, item.name, item.price, "all", "", quantity);
|
buyItem(item.id, item.name, item.price, "all", "", quantity, item.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,7 +645,7 @@ export function confirmGift() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
closeGiftDialog();
|
closeGiftDialog();
|
||||||
buyItem(item.id, item.name, item.price, recipient, message);
|
buyItem(item.id, item.name, item.price, recipient, message, 1, item.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -659,7 +659,7 @@ export function confirmGift() {
|
|||||||
* @param {number} quantity 购买数量
|
* @param {number} quantity 购买数量
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
export async function buyItem(itemId, name, price, recipient, message, quantity = 1) {
|
export async function buyItem(itemId, name, price, recipient, message, quantity = 1, itemType = '') {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(getShopUrls().buy, {
|
const response = await fetch(getShopUrls().buy, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -675,7 +675,7 @@ export async function buyItem(itemId, name, price, recipient, message, quantity
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.status === "success") {
|
if (data.status === "success") {
|
||||||
handleBuySuccess(data, name);
|
handleBuySuccess(data, name, itemType);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,7 +692,7 @@ export async function buyItem(itemId, name, price, recipient, message, quantity
|
|||||||
* @param {string} itemName 商品名称
|
* @param {string} itemName 商品名称
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function handleBuySuccess(data, itemName) {
|
function handleBuySuccess(data, itemName, itemType = '') {
|
||||||
const balance = document.getElementById("shop-jjb");
|
const balance = document.getElementById("shop-jjb");
|
||||||
|
|
||||||
if (data.jjb !== undefined && balance) {
|
if (data.jjb !== undefined && balance) {
|
||||||
@@ -717,8 +717,15 @@ function handleBuySuccess(data, itemName) {
|
|||||||
shopLoaded = false;
|
shopLoaded = false;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fetchShopData();
|
fetchShopData();
|
||||||
shopLoaded = true;
|
}, 300);
|
||||||
}, 1000);
|
|
||||||
|
// 自动钓鱼卡购买后立即开启自动钓鱼
|
||||||
|
if (itemType === "auto_fishing" && typeof window.checkAndAutoStartFishing === "function") {
|
||||||
|
if (!window.chatContext) window.chatContext = {};
|
||||||
|
window.chatContext.autoFishingMinutesLeft = Number(data.auto_fishing_minutes_left || 1);
|
||||||
|
window.chatContext.fishingCooldownSeconds = 0;
|
||||||
|
setTimeout(() => window.checkAndAutoStartFishing(), 500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user