迁移赚钱面板脚本
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
* - chat-bot.js:处理 AI 小班长发送消息和清空上下文。
|
||||
* - dialog.js:提供 window.chatDialog 全局弹窗。
|
||||
* - daily-sign-in.js:处理每日签到弹窗与补签入口。
|
||||
* - earn-panel.js:提供看视频赚钱 earnPanelData Alpine 组件和播放器加载入口。
|
||||
* - font-size.js:处理聊天输入/消息字号设置。
|
||||
* - image-upload.js:处理聊天图片上传入口。
|
||||
* - composer.js:处理聊天输入框、发送按钮和快捷操作。
|
||||
@@ -50,6 +51,7 @@ export { bindChatBanner } from "./chat-room/banner.js";
|
||||
export { bindChatBotControls, clearChatBotContext, sendToChatBot } from "./chat-room/chat-bot.js";
|
||||
export { bindGlobalDialogControls } from "./chat-room/dialog.js";
|
||||
export { bindDailySignInControls } from "./chat-room/daily-sign-in.js";
|
||||
export { bindEarnPanelControls, createEarnPanelData } from "./chat-room/earn-panel.js";
|
||||
export { applyFontSize, bindChatFontSizeControl, CHAT_FONT_SIZE_STORAGE_KEY, restoreChatFontSize } from "./chat-room/font-size.js";
|
||||
export { bindChatImageUploadControl } from "./chat-room/image-upload.js";
|
||||
export { bindChatComposerControls } from "./chat-room/composer.js";
|
||||
@@ -143,6 +145,7 @@ import { bindChatBanner } from "./chat-room/banner.js";
|
||||
import { bindChatBotControls, clearChatBotContext, sendToChatBot } from "./chat-room/chat-bot.js";
|
||||
import { bindGlobalDialogControls } from "./chat-room/dialog.js";
|
||||
import { bindDailySignInControls } from "./chat-room/daily-sign-in.js";
|
||||
import { bindEarnPanelControls, createEarnPanelData } from "./chat-room/earn-panel.js";
|
||||
import { applyFontSize, bindChatFontSizeControl, CHAT_FONT_SIZE_STORAGE_KEY, restoreChatFontSize } from "./chat-room/font-size.js";
|
||||
import { bindChatImageUploadControl } from "./chat-room/image-upload.js";
|
||||
import { bindChatComposerControls } from "./chat-room/composer.js";
|
||||
@@ -243,6 +246,8 @@ if (typeof window !== "undefined") {
|
||||
sendToChatBot,
|
||||
bindGlobalDialogControls,
|
||||
bindDailySignInControls,
|
||||
bindEarnPanelControls,
|
||||
createEarnPanelData,
|
||||
bindLotteryPanelControls,
|
||||
closeLotteryPanel,
|
||||
lotteryPanel,
|
||||
@@ -391,6 +396,7 @@ if (typeof window !== "undefined") {
|
||||
window.fetchBankRanking = fetchBankRanking;
|
||||
window.fortunePanel = fortunePanel;
|
||||
window.closeLotteryPanel = closeLotteryPanel;
|
||||
window.createEarnPanelData = createEarnPanelData;
|
||||
window.deferChatGameBootstrap = deferChatGameBootstrap;
|
||||
window.lotteryPanel = lotteryPanel;
|
||||
window.openGameHall = openGameHall;
|
||||
@@ -407,6 +413,7 @@ if (typeof window !== "undefined") {
|
||||
bindAppointmentAnnouncementControls();
|
||||
bindGlobalDialogControls();
|
||||
bindDailySignInControls();
|
||||
bindEarnPanelControls();
|
||||
bindLotteryPanelControls();
|
||||
bindChatFontSizeControl();
|
||||
bindChatImageUploadControl();
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
// 聊天室赚钱面板,注册 earnPanelData Alpine 组件并集中管理 FluidPlayer 加载。
|
||||
|
||||
const FLUID_PLAYER_SCRIPT_URL = "https://cdn.fluidplayer.com/v3/current/fluidplayer.min.js";
|
||||
const FALLBACK_VIDEO_SOURCE = "https://cdn.fluidplayer.com/videos/valerian-1080p.mkv";
|
||||
const VAST_TAG_URL = "https://s.magsrv.com/v1/vast.php?idzone=5889208";
|
||||
|
||||
let earnPanelControlsBound = false;
|
||||
let earnPanelRegistered = false;
|
||||
let fluidPlayerScriptPromise = null;
|
||||
|
||||
/**
|
||||
* 读取 CSRF Token。
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
function csrf() {
|
||||
return document.querySelector('meta[name="csrf-token"]')?.content || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示统一 Toast,避免面板代码直接依赖未初始化对象。
|
||||
*
|
||||
* @param {object} options
|
||||
* @returns {void}
|
||||
*/
|
||||
function showToast(options) {
|
||||
window.chatToast?.show?.(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按需加载 FluidPlayer CDN 脚本。
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function loadFluidPlayerScript() {
|
||||
if (typeof window.fluidPlayer === "function") {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (fluidPlayerScriptPromise) {
|
||||
return fluidPlayerScriptPromise;
|
||||
}
|
||||
|
||||
fluidPlayerScriptPromise = new Promise((resolve, reject) => {
|
||||
const existingScript = document.querySelector(`script[src="${FLUID_PLAYER_SCRIPT_URL}"]`);
|
||||
if (existingScript) {
|
||||
existingScript.addEventListener("load", () => resolve(), { once: true });
|
||||
existingScript.addEventListener("error", () => reject(new Error("FluidPlayer 加载失败")), { once: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const script = document.createElement("script");
|
||||
script.src = FLUID_PLAYER_SCRIPT_URL;
|
||||
script.async = true;
|
||||
script.addEventListener("load", () => resolve(), { once: true });
|
||||
script.addEventListener("error", () => reject(new Error("FluidPlayer 加载失败")), { once: true });
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
|
||||
return fluidPlayerScriptPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁播放器实例并吞掉第三方库的重复销毁异常。
|
||||
*
|
||||
* @param {object|null} player
|
||||
* @returns {void}
|
||||
*/
|
||||
function destroyPlayer(player) {
|
||||
if (!player) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
player.destroy();
|
||||
} catch (error) {
|
||||
// FluidPlayer 重复销毁时可能抛错,面板关闭流程应继续执行。
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重建原始 video DOM,避免 FluidPlayer 包裹后的节点影响下一次初始化。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function restoreVideoDOM() {
|
||||
const wrapper = document.getElementById("video-wrapper");
|
||||
if (!wrapper) {
|
||||
return;
|
||||
}
|
||||
|
||||
wrapper.innerHTML = `<video id="exoclick-video" style="width: 100%; height: 100%;" playsinline preload="none"><source src="${FALLBACK_VIDEO_SOURCE}" type="video/mp4" /></video>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建赚钱面板 Alpine 组件。
|
||||
*
|
||||
* @returns {object}
|
||||
*/
|
||||
export function createEarnPanelData() {
|
||||
return {
|
||||
isOpen: false,
|
||||
status: "idle",
|
||||
countdown: "获取中...",
|
||||
timer: null,
|
||||
player: null,
|
||||
videoFinished: false,
|
||||
|
||||
/**
|
||||
* 打开面板并提前加载播放器脚本。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
openPanel() {
|
||||
this.isOpen = true;
|
||||
void loadFluidPlayerScript().catch(() => {
|
||||
showToast({ title: "播放器加载失败", message: "广告播放器暂时不可用,请稍后再试。", icon: "⚠️", color: "#ef4444" });
|
||||
});
|
||||
|
||||
if (this.status !== "claimed" && this.status !== "claiming") {
|
||||
this.resetPanelState();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 关闭面板并释放播放器资源。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
closePanel() {
|
||||
this.isOpen = false;
|
||||
this.stopTimer();
|
||||
|
||||
if (this.status === "watching") {
|
||||
showToast({ title: "取消提示", message: "已取消观看,没有扣除观影次数。", icon: "ℹ️", color: "#3b82f6" });
|
||||
}
|
||||
|
||||
destroyPlayer(this.player);
|
||||
this.player = null;
|
||||
restoreVideoDOM();
|
||||
this.status = "idle";
|
||||
},
|
||||
|
||||
/**
|
||||
* 重置面板到待观看状态。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
resetPanelState() {
|
||||
this.status = "idle";
|
||||
this.countdown = "获取中...";
|
||||
this.videoFinished = false;
|
||||
this.stopTimer();
|
||||
destroyPlayer(this.player);
|
||||
this.player = null;
|
||||
restoreVideoDOM();
|
||||
},
|
||||
|
||||
/**
|
||||
* 领奖后延迟重置,给用户保留到账反馈。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
resetPanel() {
|
||||
this.closePanel();
|
||||
window.setTimeout(() => {
|
||||
this.resetPanelState();
|
||||
}, 300);
|
||||
},
|
||||
|
||||
/**
|
||||
* 标记观看完成并清理播放器。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
completeWatch() {
|
||||
this.videoFinished = true;
|
||||
this.stopTimer();
|
||||
this.status = "completed";
|
||||
destroyPlayer(this.player);
|
||||
this.player = null;
|
||||
restoreVideoDOM();
|
||||
window.EffectSounds?.ding?.();
|
||||
},
|
||||
|
||||
/**
|
||||
* 开始观看广告并启动进度轮询。
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async startWatching() {
|
||||
this.status = "watching";
|
||||
this.countdown = "请求广告...";
|
||||
this.videoFinished = false;
|
||||
this.stopTimer();
|
||||
|
||||
try {
|
||||
await loadFluidPlayerScript();
|
||||
} catch (error) {
|
||||
this.status = "idle";
|
||||
showToast({ title: "播放器加载失败", message: "广告播放器暂时不可用,请稍后再试。", icon: "⚠️", color: "#ef4444" });
|
||||
return;
|
||||
}
|
||||
|
||||
this.initPlayer();
|
||||
let elapsedTime = 0;
|
||||
|
||||
this.timer = window.setInterval(() => {
|
||||
if (this.videoFinished) {
|
||||
return;
|
||||
}
|
||||
|
||||
const video = document.getElementById("exoclick-video");
|
||||
elapsedTime += 0.5;
|
||||
|
||||
if (!video) {
|
||||
return;
|
||||
}
|
||||
|
||||
const duration = video.duration;
|
||||
const currentTime = video.currentTime;
|
||||
|
||||
if (!Number.isNaN(duration) && duration > 0) {
|
||||
const left = Math.ceil(duration - currentTime);
|
||||
this.countdown = `${left > 0 ? left : 0} 秒`;
|
||||
|
||||
if (left <= 0 && currentTime > 0) {
|
||||
this.completeWatch();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 无广告填充或缓冲过慢时,最多等待 20 秒后放行奖励。
|
||||
const fallbackLeft = Math.ceil(20 - elapsedTime);
|
||||
if (fallbackLeft > 0) {
|
||||
this.countdown = `缓冲/加载中... ${fallbackLeft}s`;
|
||||
return;
|
||||
}
|
||||
|
||||
this.completeWatch();
|
||||
}, 500);
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化 FluidPlayer 实例。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
initPlayer() {
|
||||
const video = document.getElementById("exoclick-video");
|
||||
const doInit = () => {
|
||||
try {
|
||||
this.player = window.fluidPlayer("exoclick-video", {
|
||||
layoutControls: {
|
||||
primaryColor: "#336699",
|
||||
posterImage: false,
|
||||
playButtonShowing: true,
|
||||
playPauseAnimation: false,
|
||||
fillToContainer: true,
|
||||
autoPlay: true,
|
||||
mute: false,
|
||||
},
|
||||
vastOptions: {
|
||||
allowVPAID: true,
|
||||
vastTimeout: 8000,
|
||||
adList: [
|
||||
{
|
||||
roll: "preRoll",
|
||||
vastTag: VAST_TAG_URL,
|
||||
adText: "请观看广告获取金币",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
// 第三方播放器初始化失败时保留面板,用户可关闭后重试。
|
||||
console.error("[EarnPanel] 播放器初始化未成功", error);
|
||||
}
|
||||
};
|
||||
|
||||
if (video && !video.paused) {
|
||||
video.pause();
|
||||
video.addEventListener("pause", doInit, { once: true });
|
||||
return;
|
||||
}
|
||||
|
||||
doInit();
|
||||
},
|
||||
|
||||
/**
|
||||
* 停止观看进度计时器。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
stopTimer() {
|
||||
if (!this.timer) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* 向后端领取观看奖励。
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async claimReward() {
|
||||
if (this.status !== "completed") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.status = "claiming";
|
||||
|
||||
try {
|
||||
const response = await fetch(window.chatContext.earnRewardUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-CSRF-TOKEN": csrf(),
|
||||
"Accept": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
room_id: window.chatContext?.roomId || 0,
|
||||
}),
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.success) {
|
||||
this.status = "claimed";
|
||||
showToast({ title: "奖励到账", message: data.message || "领取成功!", icon: "🎉", color: "#10b981" });
|
||||
|
||||
if (data.new_jjb !== undefined) {
|
||||
window.chatContext.userJjb = data.new_jjb;
|
||||
window.chatContext.myGold = data.new_jjb;
|
||||
window.dispatchEvent(new CustomEvent("update-user-points", { detail: { points: data.new_jjb } }));
|
||||
}
|
||||
|
||||
window.EffectSounds?.ding?.();
|
||||
|
||||
if (data.level_up) {
|
||||
showToast({
|
||||
title: "等级提升",
|
||||
message: `恭喜!您的等级提升到了 ${data.new_level_name}!`,
|
||||
icon: "🌟",
|
||||
color: "#8b5cf6",
|
||||
duration: 5000,
|
||||
});
|
||||
window.setTimeout(() => {
|
||||
window.EffectManager?.play?.("fireworks");
|
||||
}, 500);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.status = "completed";
|
||||
showToast({ title: "出错了", message: data.message || "领奖失败,请稍后再试。", icon: "❌", color: "#ef4444", duration: 4000 });
|
||||
} catch (error) {
|
||||
this.status = "completed";
|
||||
showToast({ title: "网络错误", message: "网络请求失败,请检查连接。", icon: "⚠️", color: "#ef4444" });
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册赚钱面板 Alpine 组件。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function registerEarnPanelData() {
|
||||
if (earnPanelRegistered || !window.Alpine) {
|
||||
return;
|
||||
}
|
||||
|
||||
earnPanelRegistered = true;
|
||||
window.Alpine.data("earnPanelData", createEarnPanelData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定赚钱面板入口和 Alpine 注册事件。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
export function bindEarnPanelControls() {
|
||||
if (typeof window === "undefined" || earnPanelControlsBound) {
|
||||
return;
|
||||
}
|
||||
|
||||
earnPanelControlsBound = true;
|
||||
window.createEarnPanelData = createEarnPanelData;
|
||||
window.openEarnPanel = () => {
|
||||
window.dispatchEvent(new CustomEvent("open-earn-panel"));
|
||||
};
|
||||
|
||||
document.addEventListener("alpine:init", registerEarnPanelData, { once: true });
|
||||
registerEarnPanelData();
|
||||
}
|
||||
@@ -95,248 +95,4 @@
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 引入 Fluid Player 用于播放 VAST 广告 -->
|
||||
<!-- v3 CDN 版本已内嵌样式,不能再引用已下线的 fluidplayer.min.css -->
|
||||
<script src="https://cdn.fluidplayer.com/v3/current/fluidplayer.min.js"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('earnPanelData', () => ({
|
||||
isOpen: false,
|
||||
status: 'idle', // idle, watching, completed, claiming, claimed
|
||||
countdown: '获取中...',
|
||||
timer: null,
|
||||
player: null,
|
||||
videoFinished: false,
|
||||
|
||||
openPanel() {
|
||||
this.isOpen = true;
|
||||
if (this.status !== 'claimed' && this.status !== 'claiming') {
|
||||
this.resetPanelState();
|
||||
}
|
||||
},
|
||||
|
||||
closePanel() {
|
||||
this.isOpen = false;
|
||||
this.stopTimer();
|
||||
if (this.status === 'watching') {
|
||||
window.chatToast.show({ title: '取消提示', message: '已取消观看,没有扣除观影次数。', icon: 'ℹ️', color: '#3b82f6' });
|
||||
}
|
||||
if (this.player) {
|
||||
try { this.player.destroy(); } catch(e) {}
|
||||
this.player = null;
|
||||
}
|
||||
this.restoreVideoDOM();
|
||||
this.status = 'idle';
|
||||
},
|
||||
|
||||
resetPanelState() {
|
||||
this.status = 'idle';
|
||||
this.countdown = '获取中...';
|
||||
this.videoFinished = false;
|
||||
this.stopTimer();
|
||||
if (this.player) {
|
||||
try { this.player.destroy(); } catch(e) {}
|
||||
this.player = null;
|
||||
}
|
||||
this.restoreVideoDOM();
|
||||
},
|
||||
|
||||
resetPanel() {
|
||||
this.closePanel();
|
||||
setTimeout(() => { this.resetPanelState(); }, 300);
|
||||
},
|
||||
|
||||
restoreVideoDOM() {
|
||||
// FluidPlayer 销毁后会修改/包裹 video 元素,需要重建以便下次干净初始化
|
||||
// source 必须保留,否则 VAST 失败时 FluidPlayer 的 fallback 无目标可播
|
||||
const wrapper = document.getElementById('video-wrapper');
|
||||
if (wrapper) {
|
||||
wrapper.innerHTML = `<video id="exoclick-video" style="width: 100%; height: 100%;" playsinline preload="none"><source src="https://cdn.fluidplayer.com/videos/valerian-1080p.mkv" type="video/mp4" /></video>`;
|
||||
}
|
||||
},
|
||||
|
||||
completeWatch() {
|
||||
this.videoFinished = true;
|
||||
this.stopTimer();
|
||||
this.status = 'completed';
|
||||
|
||||
if (this.player) {
|
||||
try { this.player.destroy(); } catch(e) {}
|
||||
this.player = null;
|
||||
}
|
||||
this.restoreVideoDOM();
|
||||
|
||||
if (typeof EffectSounds !== 'undefined') {
|
||||
EffectSounds.ding();
|
||||
}
|
||||
},
|
||||
|
||||
startWatching() {
|
||||
this.status = 'watching';
|
||||
this.countdown = '请求广告...';
|
||||
this.videoFinished = false;
|
||||
this.stopTimer();
|
||||
|
||||
// 同步初始化播放器,保证满足浏览器的 user gesture 直接挂钩播放
|
||||
this.initPlayer();
|
||||
|
||||
let elapsedTime = 0;
|
||||
|
||||
// 基于视频真实时长的倒计时检测与失败降级处理
|
||||
this.timer = setInterval(() => {
|
||||
if (this.videoFinished) return;
|
||||
|
||||
const v = document.getElementById('exoclick-video');
|
||||
elapsedTime += 0.5;
|
||||
|
||||
if (v) {
|
||||
const dur = v.duration;
|
||||
const cur = v.currentTime;
|
||||
|
||||
// 当系统取到真实的视频时长时(确保不是NaN)
|
||||
if (!isNaN(dur) && dur > 0) {
|
||||
const left = Math.ceil(dur - cur);
|
||||
this.countdown = (left > 0 ? left : 0) + ' 秒';
|
||||
|
||||
// 若倒计时归零并且正在播放中(保证不是误判)
|
||||
if (left <= 0 && cur > 0) {
|
||||
this.completeWatch();
|
||||
}
|
||||
} else {
|
||||
// 没有视频时长(可能正在缓冲,也可能广告没有填充)
|
||||
// 最多让用户干等 20 秒作为无广告情况下的保底奖励
|
||||
const fallbackLeft = Math.ceil(20 - elapsedTime);
|
||||
if (fallbackLeft > 0) {
|
||||
this.countdown = '缓冲/加载中... ' + fallbackLeft + 's';
|
||||
} else {
|
||||
// 20秒超时还没获得时长,则当作广告未填充或网速太慢,放行奖励
|
||||
this.completeWatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
},
|
||||
|
||||
initPlayer() {
|
||||
// 按照 Chrome 规范:确保 video 当前未在 play() 中,通过 pause() 后再初始化
|
||||
// 参考:https://developer.chrome.com/blog/play-request-was-interrupted
|
||||
const v = document.getElementById('exoclick-video');
|
||||
const doInit = () => {
|
||||
try {
|
||||
this.player = fluidPlayer(
|
||||
'exoclick-video',
|
||||
{
|
||||
layoutControls: {
|
||||
primaryColor: "#336699",
|
||||
posterImage: false,
|
||||
playButtonShowing: true,
|
||||
playPauseAnimation: false,
|
||||
fillToContainer: true,
|
||||
autoPlay: true,
|
||||
mute: false
|
||||
},
|
||||
vastOptions: {
|
||||
allowVPAID: true,
|
||||
vastTimeout: 8000,
|
||||
adList: [
|
||||
{
|
||||
roll: 'preRoll',
|
||||
vastTag: 'https://s.magsrv.com/v1/vast.php?idzone=5889208',
|
||||
adText: '请观看广告获取金币'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 注意:FluidPlayer 不支持 player.on('error'),不要注册该事件
|
||||
// VAST 失败时 FluidPlayer 会自动播放 fallback source,
|
||||
// 我们的 setInterval 轮询 currentTime/duration 会自然触发 completeWatch()
|
||||
|
||||
} catch(e) {
|
||||
console.error('[EarnPanel] 播放器初始化未成功', e);
|
||||
}
|
||||
};
|
||||
|
||||
if (v && !v.paused) {
|
||||
// 如果 video 正在播放,先等 pause 后再 init,避免 play() 被 load() 中断
|
||||
v.pause();
|
||||
v.addEventListener('pause', doInit, { once: true });
|
||||
} else {
|
||||
doInit();
|
||||
}
|
||||
},
|
||||
|
||||
stopTimer() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
},
|
||||
|
||||
async claimReward() {
|
||||
if (this.status !== 'completed') return;
|
||||
this.status = 'claiming';
|
||||
try {
|
||||
const payload = {
|
||||
room_id: window.chatContext?.roomId || 0
|
||||
};
|
||||
|
||||
const response = await fetch(window.chatContext.earnRewardUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.success) {
|
||||
this.status = 'claimed';
|
||||
window.chatToast.show({ title: '奖励到账', message: data.message || '领取成功!', icon: '🎉', color: '#10b981' });
|
||||
|
||||
// 更新本地上下文
|
||||
if (data.new_jjb !== undefined) {
|
||||
window.chatContext.userJjb = data.new_jjb;
|
||||
window.chatContext.myGold = data.new_jjb;
|
||||
// 派发更新全局 UI 的事件,类似钓鱼的更新
|
||||
window.dispatchEvent(new CustomEvent('update-user-points', { detail: { points: data.new_jjb }}));
|
||||
}
|
||||
|
||||
// 播放到账特定金币音效
|
||||
if (typeof EffectSounds !== 'undefined') {
|
||||
EffectSounds.ding();
|
||||
}
|
||||
|
||||
if (data.level_up) {
|
||||
window.chatToast.show({
|
||||
title: '等级提升',
|
||||
message: `恭喜!您的等级提升到了 ${data.new_level_name}!`,
|
||||
icon: '🌟',
|
||||
color: '#8b5cf6',
|
||||
duration: 5000
|
||||
});
|
||||
setTimeout(() => {
|
||||
// 触发烟花庆贺
|
||||
if(typeof EffectManager !== 'undefined') {
|
||||
EffectManager.play('fireworks');
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
} else {
|
||||
// 领奖失败,可能是超出次数或防刷限制
|
||||
this.status = 'completed';
|
||||
window.chatToast.show({ title: '出错了', message: data.message || '领奖失败,请稍后再试。', icon: '❌', color: '#ef4444', duration: 4000 });
|
||||
}
|
||||
} catch (error) {
|
||||
this.status = 'completed';
|
||||
window.chatToast.show({ title: '网络错误', message: '网络请求失败,请检查连接。', icon: '⚠️', color: '#ef4444' });
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
{{-- 赚钱面板 Alpine 组件与 FluidPlayer 加载已迁移到 resources/js/chat-room/earn-panel.js --}}
|
||||
|
||||
Reference in New Issue
Block a user