迁移头像设置脚本
This commit is contained in:
@@ -33,7 +33,7 @@
|
||||
* - bank-modal.js:处理银行弹窗、转账、排行和标签切换。
|
||||
* - fishing.js:处理钓鱼抛竿、收竿、浮漂和自动钓鱼循环。
|
||||
* - fortune-panel.js:提供神秘占卜 fortunePanel Alpine 组件。
|
||||
* - profile-controls.js:处理用户资料和资料相关按钮。
|
||||
* - profile-controls.js:处理头像选择、个人资料、密码、邮箱验证码和微信绑定入口。
|
||||
* - shop-controls.js:处理商店弹窗的基础按钮事件。
|
||||
* - slot-machine.js:提供老虎机 slotPanel/slotFab Alpine 组件。
|
||||
* - vip-controls.js:处理 VIP 中心弹窗、会员数据渲染、支付跳转和专属进退场设置。
|
||||
@@ -114,7 +114,24 @@ export {
|
||||
} from "./chat-room/bank-modal.js";
|
||||
export { bindFishingControls, checkAndAutoStartFishing, createBobber, reelFish, removeBobber, resetFishingBtn, startFishing, stopAutoFishing } from "./chat-room/fishing.js";
|
||||
export { bindFortunePanelControls, fortunePanel } from "./chat-room/fortune-panel.js";
|
||||
export { bindProfileControls } from "./chat-room/profile-controls.js";
|
||||
export {
|
||||
bindProfileControls,
|
||||
closeAvatarPicker,
|
||||
closeSettingsModal,
|
||||
copyWechatBindCode,
|
||||
generateWechatBindCode,
|
||||
handleAvatarUpload,
|
||||
loadHeadfaces,
|
||||
openAvatarPicker,
|
||||
openSettingsModal,
|
||||
saveAvatar,
|
||||
savePassword,
|
||||
saveSettings,
|
||||
selectAvatar,
|
||||
sendEmailCode,
|
||||
showInlineMsg,
|
||||
unbindWechat,
|
||||
} from "./chat-room/profile-controls.js";
|
||||
export { bindShopControls } from "./chat-room/shop-controls.js";
|
||||
export { bindSlotMachineControls, slotFab, slotPanel } from "./chat-room/slot-machine.js";
|
||||
export { bindVipControls, buyVip, closeVipModal, openVipModal, saveVipPresenceSettings, switchVipTab } from "./chat-room/vip-controls.js";
|
||||
@@ -214,7 +231,24 @@ import {
|
||||
} from "./chat-room/bank-modal.js";
|
||||
import { bindFishingControls, checkAndAutoStartFishing, createBobber, reelFish, removeBobber, resetFishingBtn, startFishing, stopAutoFishing } from "./chat-room/fishing.js";
|
||||
import { bindFortunePanelControls, fortunePanel } from "./chat-room/fortune-panel.js";
|
||||
import { bindProfileControls } from "./chat-room/profile-controls.js";
|
||||
import {
|
||||
bindProfileControls,
|
||||
closeAvatarPicker,
|
||||
closeSettingsModal,
|
||||
copyWechatBindCode,
|
||||
generateWechatBindCode,
|
||||
handleAvatarUpload,
|
||||
loadHeadfaces,
|
||||
openAvatarPicker,
|
||||
openSettingsModal,
|
||||
saveAvatar,
|
||||
savePassword,
|
||||
saveSettings,
|
||||
selectAvatar,
|
||||
sendEmailCode,
|
||||
showInlineMsg,
|
||||
unbindWechat,
|
||||
} from "./chat-room/profile-controls.js";
|
||||
import { bindShopControls } from "./chat-room/shop-controls.js";
|
||||
import { bindSlotMachineControls, slotFab, slotPanel } from "./chat-room/slot-machine.js";
|
||||
import { bindVipControls, buyVip, closeVipModal, openVipModal, saveVipPresenceSettings, switchVipTab } from "./chat-room/vip-controls.js";
|
||||
@@ -340,6 +374,21 @@ if (typeof window !== "undefined") {
|
||||
fortunePanel,
|
||||
bindMarriageStatusControls,
|
||||
bindProfileControls,
|
||||
closeAvatarPicker,
|
||||
closeSettingsModal,
|
||||
copyWechatBindCode,
|
||||
generateWechatBindCode,
|
||||
handleAvatarUpload,
|
||||
loadHeadfaces,
|
||||
openAvatarPicker,
|
||||
openSettingsModal,
|
||||
saveAvatar,
|
||||
savePassword,
|
||||
saveSettings,
|
||||
selectAvatar,
|
||||
sendEmailCode,
|
||||
showInlineMsg,
|
||||
unbindWechat,
|
||||
bindShopControls,
|
||||
bindSlotMachineControls,
|
||||
slotFab,
|
||||
@@ -448,6 +497,21 @@ if (typeof window !== "undefined") {
|
||||
window.switchBankTab = switchBankTab;
|
||||
window.toggleBankRankSort = toggleBankRankSort;
|
||||
window.applyFontSize = applyFontSize;
|
||||
window.closeAvatarPicker = closeAvatarPicker;
|
||||
window.closeSettingsModal = closeSettingsModal;
|
||||
window.copyWechatBindCode = copyWechatBindCode;
|
||||
window.generateWechatBindCode = generateWechatBindCode;
|
||||
window.handleAvatarUpload = handleAvatarUpload;
|
||||
window.loadHeadfaces = loadHeadfaces;
|
||||
window.openAvatarPicker = openAvatarPicker;
|
||||
window.openSettingsModal = openSettingsModal;
|
||||
window.saveAvatar = saveAvatar;
|
||||
window.savePassword = savePassword;
|
||||
window.saveSettings = saveSettings;
|
||||
window.selectAvatar = selectAvatar;
|
||||
window.sendEmailCode = sendEmailCode;
|
||||
window.showInlineMsg = showInlineMsg;
|
||||
window.unbindWechat = unbindWechat;
|
||||
|
||||
// 页面加载后立即注册事件委托,具体业务逻辑仍由各子模块负责。
|
||||
bindChatBanner();
|
||||
|
||||
@@ -1,26 +1,572 @@
|
||||
// 头像选择与个人设置弹窗事件绑定,替代 toolbar 中的内联 onclick/onchange。
|
||||
// 聊天室头像选择和个人设置模块,负责头像 API、密码修改、邮箱验证码和微信绑定操作。
|
||||
|
||||
let profileControlEventsBound = false;
|
||||
let avatarPickerLoaded = false;
|
||||
|
||||
/**
|
||||
* 触发存量全局函数,保持当前头像与设置业务逻辑不变。
|
||||
* 读取 CSRF Token。
|
||||
*
|
||||
* @param {string} functionName 全局函数名
|
||||
* @param {...unknown} args 参数
|
||||
* @returns {string}
|
||||
*/
|
||||
function csrf() {
|
||||
return document.querySelector('meta[name="csrf-token"]')?.content || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 ID 获取 DOM 节点。
|
||||
*
|
||||
* @param {string} id
|
||||
* @returns {HTMLElement|null}
|
||||
*/
|
||||
function element(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示内联操作结果提示。
|
||||
*
|
||||
* @param {string} elementId
|
||||
* @param {string} message
|
||||
* @param {boolean} success
|
||||
* @returns {void}
|
||||
*/
|
||||
function callGlobal(functionName, ...args) {
|
||||
// 头像与设置的大段业务仍在 Blade 内,模块阶段先集中桥接旧全局函数。
|
||||
if (typeof window[functionName] === "function") {
|
||||
window[functionName](...args);
|
||||
export function showInlineMsg(elementId, message, success) {
|
||||
const target = element(elementId);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
target.style.background = success ? "#f0fdf4" : "#fff5f5";
|
||||
target.style.border = success ? "1px solid #86efac" : "1px solid #fecaca";
|
||||
target.style.color = success ? "#16a34a" : "#dc2626";
|
||||
target.textContent = message;
|
||||
target.style.display = "block";
|
||||
target.style.opacity = "1";
|
||||
target.style.transition = "opacity .4s";
|
||||
|
||||
window.clearTimeout(target._hideTimer);
|
||||
target._hideTimer = window.setTimeout(() => {
|
||||
target.style.opacity = "0";
|
||||
window.setTimeout(() => {
|
||||
target.style.display = "none";
|
||||
}, 420);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开头像选择弹窗并懒加载头像列表。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
export function openAvatarPicker() {
|
||||
const modal = element("avatar-picker-modal");
|
||||
if (!modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
modal.style.display = "flex";
|
||||
|
||||
if (!avatarPickerLoaded) {
|
||||
void loadHeadfaces();
|
||||
avatarPickerLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭头像选择弹窗。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
export function closeAvatarPicker() {
|
||||
const modal = element("avatar-picker-modal");
|
||||
if (modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开个人设置弹窗。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
export function openSettingsModal() {
|
||||
const modal = element("settings-modal");
|
||||
if (modal) {
|
||||
modal.style.display = "flex";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭个人设置弹窗。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
export function closeSettingsModal() {
|
||||
const modal = element("settings-modal");
|
||||
if (modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载系统头像列表。
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function loadHeadfaces() {
|
||||
const grid = element("avatar-grid");
|
||||
if (!grid) {
|
||||
return;
|
||||
}
|
||||
|
||||
grid.innerHTML = '<div style="text-align:center;padding:20px;color:#999;">加载中...</div>';
|
||||
|
||||
try {
|
||||
const response = await fetch("/headface/list", {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
grid.innerHTML = "";
|
||||
|
||||
for (const file of data.headfaces || []) {
|
||||
const image = document.createElement("img");
|
||||
image.src = `/images/headface/${file}`;
|
||||
image.className = "avatar-option";
|
||||
image.title = file;
|
||||
image.dataset.file = file;
|
||||
image.dataset.avatarFile = file;
|
||||
image.onerror = () => {
|
||||
image.style.display = "none";
|
||||
};
|
||||
grid.appendChild(image);
|
||||
}
|
||||
} catch (error) {
|
||||
grid.innerHTML = '<div style="text-align:center;padding:20px;color:red;">加载失败</div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 选中头像并刷新预览。
|
||||
*
|
||||
* @param {string} file
|
||||
* @param {HTMLElement} imageElement
|
||||
* @returns {void}
|
||||
*/
|
||||
export function selectAvatar(file, imageElement) {
|
||||
document.querySelectorAll(".avatar-option.selected").forEach((item) => item.classList.remove("selected"));
|
||||
imageElement.classList.add("selected");
|
||||
element("avatar-preview").src = `/images/headface/${file}`;
|
||||
element("avatar-selected-name").textContent = file;
|
||||
|
||||
const saveButton = element("avatar-save-btn");
|
||||
saveButton.disabled = false;
|
||||
saveButton.dataset.file = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步当前用户头像到在线名单。
|
||||
*
|
||||
* @param {string} headface
|
||||
* @returns {void}
|
||||
*/
|
||||
function syncOnlineUserHeadface(headface) {
|
||||
const username = window.chatContext?.username;
|
||||
|
||||
// 在线名单仍由存量聊天室脚本维护为全局词法变量,这里保留同名访问兼容。
|
||||
if (username && typeof onlineUsers !== "undefined" && onlineUsers[username]) {
|
||||
onlineUsers[username].headface = headface;
|
||||
}
|
||||
|
||||
if (typeof renderUserList === "function") {
|
||||
renderUserList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理本地头像上传。
|
||||
*
|
||||
* @param {HTMLInputElement} input
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function handleAvatarUpload(input) {
|
||||
if (!input.files || !input.files[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const file = input.files[0];
|
||||
if (file.size > 2 * 1024 * 1024) {
|
||||
window.chatDialog?.alert?.("图片大小不可超过 2MB", "上传失败", "#cc4444");
|
||||
input.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const button = element("avatar-upload-btn");
|
||||
button.disabled = true;
|
||||
button.textContent = "上传中...";
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
try {
|
||||
const response = await fetch("/headface/upload", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-CSRF-TOKEN": csrf(),
|
||||
"Accept": "application/json",
|
||||
},
|
||||
body: formData,
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.status === "success") {
|
||||
window.chatDialog?.alert?.("自定义头像上传成功!", "提示", "#16a34a");
|
||||
const relativeUrl = `/${data.headface}`;
|
||||
element("avatar-preview").src = relativeUrl;
|
||||
element("avatar-selected-name").textContent = data.headface;
|
||||
syncOnlineUserHeadface(data.headface);
|
||||
document.querySelectorAll(".avatar-option.selected").forEach((item) => item.classList.remove("selected"));
|
||||
element("avatar-save-btn").disabled = true;
|
||||
closeAvatarPicker();
|
||||
} else {
|
||||
window.chatDialog?.alert?.(data.message || "上传失败", "操作失败", "#cc4444");
|
||||
}
|
||||
} catch (error) {
|
||||
window.chatDialog?.alert?.("网络错误,上传失败", "网络异常", "#cc4444");
|
||||
}
|
||||
|
||||
button.disabled = false;
|
||||
button.textContent = "选择本地图片上传";
|
||||
input.value = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存当前选中的系统头像。
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function saveAvatar() {
|
||||
const button = element("avatar-save-btn");
|
||||
const file = button?.dataset.file || "";
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
button.disabled = true;
|
||||
button.textContent = "保存中...";
|
||||
|
||||
try {
|
||||
const response = await fetch("/headface/change", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-CSRF-TOKEN": csrf(),
|
||||
"Accept": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ headface: file }),
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === "success") {
|
||||
window.chatDialog?.alert?.("头像修改成功!", "提示", "#16a34a");
|
||||
syncOnlineUserHeadface(data.headface);
|
||||
closeAvatarPicker();
|
||||
} else {
|
||||
window.chatDialog?.alert?.(data.message || "修改失败", "操作失败", "#cc4444");
|
||||
}
|
||||
} catch (error) {
|
||||
window.chatDialog?.alert?.("网络错误", "网络异常", "#cc4444");
|
||||
}
|
||||
|
||||
button.disabled = false;
|
||||
button.textContent = "确定更换";
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存登录密码。
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function savePassword() {
|
||||
const oldPassword = element("set-old-pwd").value;
|
||||
const newPassword = element("set-new-pwd").value;
|
||||
const confirmPassword = element("set-new-pwd2").value;
|
||||
|
||||
if (!oldPassword || !newPassword) {
|
||||
showInlineMsg("pwd-inline-msg", "⚠️ 请填写旧密码和新密码", false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword.length < 6) {
|
||||
showInlineMsg("pwd-inline-msg", "⚠️ 新密码最少6位!", false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
showInlineMsg("pwd-inline-msg", "⚠️ 两次输入的新密码不一致!", false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("/user/password", {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"X-CSRF-TOKEN": csrf(),
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
old_password: oldPassword,
|
||||
new_password: newPassword,
|
||||
new_password_confirmation: confirmPassword,
|
||||
}),
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.status === "success") {
|
||||
showInlineMsg("pwd-inline-msg", "🔒 密码修改成功!", true);
|
||||
element("set-old-pwd").value = "";
|
||||
element("set-new-pwd").value = "";
|
||||
element("set-new-pwd2").value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
showInlineMsg("pwd-inline-msg", `❌ ${data.message || "请输入正确的旧密码"}`, false);
|
||||
} catch (error) {
|
||||
showInlineMsg("pwd-inline-msg", "🌐 网络异常,请稍后重试", false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存个人资料和密保设置。
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function saveSettings() {
|
||||
const profileData = {
|
||||
sex: element("set-sex").value,
|
||||
email: element("set-email").value,
|
||||
email_code: element("set-email-code")?.value || "",
|
||||
question: element("set-question").value,
|
||||
answer: element("set-answer").value,
|
||||
headface: window.chatContext?.profileHeadface || "1.gif",
|
||||
sign: window.chatContext?.profileSign || "",
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch("/user/profile", {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"X-CSRF-TOKEN": csrf(),
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
body: JSON.stringify(profileData),
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.status === "success") {
|
||||
showInlineMsg("settings-inline-msg", "✅ 资料保存成功!", true);
|
||||
return;
|
||||
}
|
||||
|
||||
showInlineMsg("settings-inline-msg", `❌ ${data.message || "输入有误"}`, false);
|
||||
} catch (error) {
|
||||
showInlineMsg("settings-inline-msg", "🌐 网络异常,请稍后重试", false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮箱验证码并启动按钮倒计时。
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function sendEmailCode() {
|
||||
const email = element("set-email").value.trim();
|
||||
if (!email) {
|
||||
window.chatDialog?.alert?.("请先填写邮箱地址后再获取验证码!", "提示", "#d97706");
|
||||
return;
|
||||
}
|
||||
|
||||
const button = element("btn-send-code");
|
||||
button.disabled = true;
|
||||
button.innerText = "正在发送...";
|
||||
button.style.opacity = "0.6";
|
||||
button.style.cursor = "not-allowed";
|
||||
|
||||
try {
|
||||
const response = await fetch("/user/send-email-code", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-CSRF-TOKEN": csrf(),
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.status === "success") {
|
||||
window.chatDialog?.alert?.(data.message || "验证码发送成功,请前往邮箱查收!(有效期5分钟)", "发送成功", "#16a34a");
|
||||
startEmailCodeCountdown(button);
|
||||
return;
|
||||
}
|
||||
|
||||
window.chatDialog?.alert?.(`发送失败:${data.message || "系统繁忙"}`, "发送失败", "#dc2626");
|
||||
resetEmailCodeButton(button);
|
||||
} catch (error) {
|
||||
window.chatDialog?.alert?.("网络异常,验证码发送失败,请稍后重试。", "错误", "#6b7280");
|
||||
resetEmailCodeButton(button);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动邮箱验证码按钮 60 秒倒计时。
|
||||
*
|
||||
* @param {HTMLButtonElement} button
|
||||
* @returns {void}
|
||||
*/
|
||||
function startEmailCodeCountdown(button) {
|
||||
let remaining = 60;
|
||||
button.innerText = `${remaining}s 后重试`;
|
||||
|
||||
const timer = window.setInterval(() => {
|
||||
remaining -= 1;
|
||||
|
||||
if (remaining <= 0) {
|
||||
window.clearInterval(timer);
|
||||
resetEmailCodeButton(button);
|
||||
return;
|
||||
}
|
||||
|
||||
button.innerText = `${remaining}s 后重试`;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复邮箱验证码按钮状态。
|
||||
*
|
||||
* @param {HTMLButtonElement} button
|
||||
* @returns {void}
|
||||
*/
|
||||
function resetEmailCodeButton(button) {
|
||||
button.innerText = "获取验证码";
|
||||
button.disabled = false;
|
||||
button.style.opacity = "1";
|
||||
button.style.cursor = "pointer";
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成微信绑定验证码。
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function generateWechatBindCode() {
|
||||
const button = element("btn-generate-bind-code");
|
||||
const input = element("wechat-bind-code");
|
||||
const tip = element("bind-code-tip");
|
||||
|
||||
button.disabled = true;
|
||||
button.innerText = "生成中...";
|
||||
|
||||
try {
|
||||
const response = await fetch("/user/generate-wechat-code", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-CSRF-TOKEN": csrf(),
|
||||
"Accept": "application/json",
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.status === "success") {
|
||||
input.value = data.code;
|
||||
tip.style.display = "block";
|
||||
element("btn-copy-bind-code").style.display = "inline-block";
|
||||
showInlineMsg("settings-inline-msg", "✅ 绑定代码生成成功,请在5分钟内发送给机器人", true);
|
||||
} else {
|
||||
showInlineMsg("settings-inline-msg", `❌ 生成失败:${data.message || "未知错误"}`, false);
|
||||
}
|
||||
} catch (error) {
|
||||
showInlineMsg("settings-inline-msg", "🌐 网络异常,请稍后重试", false);
|
||||
}
|
||||
|
||||
button.disabled = false;
|
||||
button.innerText = "重新生成";
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制微信绑定验证码。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
export function copyWechatBindCode() {
|
||||
const input = element("wechat-bind-code");
|
||||
if (!input.value || input.value === "点击生成" || input.value === "生成中...") {
|
||||
return;
|
||||
}
|
||||
|
||||
input.select();
|
||||
input.setSelectionRange(0, 99999);
|
||||
|
||||
try {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
void navigator.clipboard.writeText(input.value);
|
||||
} else {
|
||||
document.execCommand("copy");
|
||||
}
|
||||
|
||||
const button = element("btn-copy-bind-code");
|
||||
const originalText = button.innerText;
|
||||
button.innerText = "已复制";
|
||||
window.setTimeout(() => {
|
||||
button.innerText = originalText;
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
showInlineMsg("settings-inline-msg", "❌ 复制失败,请手动复制", false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解除微信绑定。
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function unbindWechat() {
|
||||
const confirmed = typeof window.chatDialog?.confirm === "function"
|
||||
? await window.chatDialog.confirm("确定要解除微信绑定吗?解除后将无法接收任何机器人推送通知。", "解除微信绑定")
|
||||
: window.confirm("确定要解除微信绑定吗?解除后将无法接收任何机器人推送通知。");
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("/user/unbind-wechat", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-CSRF-TOKEN": csrf(),
|
||||
"Accept": "application/json",
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.status === "success") {
|
||||
window.chatDialog?.alert?.("✅ 解绑成功!请刷新页面获取最新状态。", "提示", "#16a34a");
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
window.chatDialog?.alert?.(`❌ 解绑失败:${data.message || "未知错误"}`, "操作失败", "#cc4444");
|
||||
} catch (error) {
|
||||
window.chatDialog?.alert?.("网络异常,解绑失败", "网络异常", "#cc4444");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理动态头像项选择。
|
||||
* 头像列表由 Blade 旧脚本异步生成,Vite 模块只通过 data-avatar-file 接管点击入口。
|
||||
*
|
||||
* @param {Element} target 点击目标
|
||||
* @param {Element} target
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function handleAvatarOptionClick(target) {
|
||||
@@ -34,17 +580,46 @@ function handleAvatarOptionClick(target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
callGlobal("selectAvatar", file, avatarOption);
|
||||
selectAvatar(file, avatarOption);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 挂载头像与设置全局兼容函数。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function exposeProfileGlobals() {
|
||||
window.openAvatarPicker = openAvatarPicker;
|
||||
window.closeAvatarPicker = closeAvatarPicker;
|
||||
window.openSettingsModal = openSettingsModal;
|
||||
window.closeSettingsModal = closeSettingsModal;
|
||||
window.loadHeadfaces = loadHeadfaces;
|
||||
window.selectAvatar = selectAvatar;
|
||||
window.handleAvatarUpload = handleAvatarUpload;
|
||||
window.saveAvatar = saveAvatar;
|
||||
window.showInlineMsg = showInlineMsg;
|
||||
window.savePassword = savePassword;
|
||||
window.saveSettings = saveSettings;
|
||||
window.sendEmailCode = sendEmailCode;
|
||||
window.generateWechatBindCode = generateWechatBindCode;
|
||||
window.copyWechatBindCode = copyWechatBindCode;
|
||||
window.unbindWechat = unbindWechat;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定头像选择器和个人设置弹窗事件。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
export function bindProfileControls() {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
exposeProfileGlobals();
|
||||
|
||||
if (profileControlEventsBound || typeof document === "undefined") {
|
||||
return;
|
||||
}
|
||||
@@ -55,10 +630,9 @@ export function bindProfileControls() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 头像选择器包含静态按钮和动态头像项,统一用 data-* 事件代理承接。
|
||||
if (event.target.closest("[data-avatar-picker-close]")) {
|
||||
event.preventDefault();
|
||||
callGlobal("closeAvatarPicker");
|
||||
closeAvatarPicker();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -69,57 +643,55 @@ export function bindProfileControls() {
|
||||
|
||||
if (event.target.closest("[data-avatar-save]")) {
|
||||
event.preventDefault();
|
||||
callGlobal("saveAvatar");
|
||||
void saveAvatar();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target.closest("[data-avatar-upload-trigger]")) {
|
||||
event.preventDefault();
|
||||
// 上传按钮只负责打开隐藏的 file input,上传校验仍由存量 handleAvatarUpload 负责。
|
||||
document.getElementById("avatar-upload-input")?.click();
|
||||
element("avatar-upload-input")?.click();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target.closest("[data-settings-modal-close]")) {
|
||||
event.preventDefault();
|
||||
callGlobal("closeSettingsModal");
|
||||
closeSettingsModal();
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置弹窗的保存、验证码、微信绑定仍走原请求函数,避免迁移中改变接口行为。
|
||||
if (event.target.closest("[data-settings-save-password]")) {
|
||||
event.preventDefault();
|
||||
callGlobal("savePassword");
|
||||
void savePassword();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target.closest("[data-settings-send-email-code]")) {
|
||||
event.preventDefault();
|
||||
callGlobal("sendEmailCode");
|
||||
void sendEmailCode();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target.closest("[data-settings-copy-wechat-code]")) {
|
||||
event.preventDefault();
|
||||
callGlobal("copyWechatBindCode");
|
||||
copyWechatBindCode();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target.closest("[data-settings-generate-wechat-code]")) {
|
||||
event.preventDefault();
|
||||
callGlobal("generateWechatBindCode");
|
||||
void generateWechatBindCode();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target.closest("[data-settings-unbind-wechat]")) {
|
||||
event.preventDefault();
|
||||
callGlobal("unbindWechat");
|
||||
void unbindWechat();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target.closest("[data-settings-save-profile]")) {
|
||||
event.preventDefault();
|
||||
callGlobal("saveSettings");
|
||||
void saveSettings();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -128,9 +700,8 @@ export function bindProfileControls() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 只在点击设置弹窗遮罩时关闭,避免内容区点击被误判。
|
||||
if (event.target.closest("[data-settings-modal-overlay]")) {
|
||||
callGlobal("closeSettingsModal");
|
||||
closeSettingsModal();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -139,7 +710,6 @@ export function bindProfileControls() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 保留原上传函数的文件大小校验、接口请求和在线名单刷新逻辑。
|
||||
callGlobal("handleAvatarUpload", event.target);
|
||||
void handleAvatarUpload(event.target);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -108,6 +108,8 @@
|
||||
dailySignInMakeupUrl: @json(\Illuminate\Support\Facades\Route::has('daily-sign-in.makeup') ? route('daily-sign-in.makeup') : null),
|
||||
userJjb: {{ (int) $user->jjb }}, // 当前用户金币(求婚前金额预检查用)
|
||||
myGold: {{ (int) $user->jjb }}, // 赠金币面板显示余额用(赠送成功后前端更新)
|
||||
profileHeadface: @json(Auth::user()->usersf ?: '1.gif'),
|
||||
profileSign: @json(Auth::user()->sign ?? ''),
|
||||
chatPreferences: @json($user->chat_preferences ?? []),
|
||||
currentDailyStatus: @json($activeDailyStatus),
|
||||
dailyStatusCatalog: @json($dailyStatusCatalog),
|
||||
|
||||
@@ -235,482 +235,7 @@
|
||||
{{-- ═══════════ 娱乐游戏大厅弹窗(games/ 子目录)═══════════ --}}
|
||||
@include('chat.partials.games.game-hall')
|
||||
|
||||
{{-- ═══════════ 工具条相关 JS 函数 ═══════════ --}}
|
||||
<script>
|
||||
// ── 头像选择器(与上方 #avatar-picker-modal DOM 对应)──────────────
|
||||
let avatarPickerLoaded = false;
|
||||
|
||||
/**
|
||||
* 打开头像选择弹窗
|
||||
*/
|
||||
function openAvatarPicker() {
|
||||
const modal = document.getElementById('avatar-picker-modal');
|
||||
modal.style.display = 'flex';
|
||||
|
||||
if (!avatarPickerLoaded) {
|
||||
loadHeadfaces();
|
||||
avatarPickerLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭头像选择弹窗
|
||||
*/
|
||||
function closeAvatarPicker() {
|
||||
document.getElementById('avatar-picker-modal').style.display = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开个人设置弹窗。
|
||||
*/
|
||||
function openSettingsModal() {
|
||||
document.getElementById('settings-modal').style.display = 'flex';
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭个人设置弹窗。
|
||||
*/
|
||||
function closeSettingsModal() {
|
||||
document.getElementById('settings-modal').style.display = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载头像列表(懒加载,首次打开时请求)
|
||||
*/
|
||||
async function loadHeadfaces() {
|
||||
const grid = document.getElementById('avatar-grid');
|
||||
grid.innerHTML = '<div style="text-align:center;padding:20px;color:#999;">加载中...</div>';
|
||||
|
||||
try {
|
||||
const res = await fetch('/headface/list');
|
||||
const data = await res.json();
|
||||
grid.innerHTML = '';
|
||||
|
||||
data.headfaces.forEach(file => {
|
||||
const img = document.createElement('img');
|
||||
img.src = '/images/headface/' + file;
|
||||
img.className = 'avatar-option';
|
||||
img.title = file;
|
||||
img.dataset.file = file;
|
||||
img.dataset.avatarFile = file;
|
||||
img.onerror = () => img.style.display = 'none';
|
||||
grid.appendChild(img);
|
||||
});
|
||||
} catch (e) {
|
||||
grid.innerHTML = '<div style="text-align:center;padding:20px;color:red;">加载失败</div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 选中一个头像(高亮选中状态)
|
||||
*
|
||||
* @param {string} file 头像文件名
|
||||
* @param {HTMLElement} imgEl 被点击的 img 元素
|
||||
*/
|
||||
function selectAvatar(file, imgEl) {
|
||||
document.querySelectorAll('.avatar-option.selected').forEach(el => el.classList.remove('selected'));
|
||||
imgEl.classList.add('selected');
|
||||
document.getElementById('avatar-preview').src = '/images/headface/' + file;
|
||||
document.getElementById('avatar-selected-name').textContent = file;
|
||||
document.getElementById('avatar-save-btn').disabled = false;
|
||||
document.getElementById('avatar-save-btn').dataset.file = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理本地头像上传
|
||||
*/
|
||||
async function handleAvatarUpload(input) {
|
||||
if (!input.files || !input.files[0]) return;
|
||||
|
||||
const file = input.files[0];
|
||||
|
||||
// 简单的前端校验
|
||||
if (file.size > 2 * 1024 * 1024) {
|
||||
window.chatDialog.alert('图片大小不可超过 2MB', '上传失败', '#cc4444');
|
||||
input.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = document.getElementById('avatar-upload-btn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = '上传中...';
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
try {
|
||||
const res = await fetch('/headface/upload', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (res.ok && data.status === 'success') {
|
||||
window.chatDialog.alert('自定义头像上传成功!', '提示', '#16a34a');
|
||||
|
||||
// 更新预览图和显示名称
|
||||
const previewImg = document.getElementById('avatar-preview');
|
||||
const relativeUrl = '/' + data.headface;
|
||||
previewImg.src = relativeUrl;
|
||||
document.getElementById('avatar-selected-name').textContent = data.headface;
|
||||
|
||||
// 同步在线列表自己
|
||||
const myName = window.chatContext.username;
|
||||
if (typeof onlineUsers !== 'undefined' && onlineUsers[myName]) {
|
||||
onlineUsers[myName].headface = data.headface;
|
||||
}
|
||||
if (typeof renderUserList === 'function') {
|
||||
renderUserList();
|
||||
}
|
||||
|
||||
// 清除系统头像选中状态
|
||||
document.querySelectorAll('.avatar-option.selected').forEach(el => el.classList.remove('selected'));
|
||||
document.getElementById('avatar-save-btn').disabled = true;
|
||||
|
||||
closeAvatarPicker();
|
||||
} else {
|
||||
window.chatDialog.alert(data.message || '上传失败', '操作失败', '#cc4444');
|
||||
}
|
||||
} catch (e) {
|
||||
window.chatDialog.alert('网络错误,上传失败', '网络异常', '#cc4444');
|
||||
}
|
||||
|
||||
btn.disabled = false;
|
||||
btn.textContent = '选择本地图片上传';
|
||||
input.value = ''; // 清空 file input,允许重复选中同一文件
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存选中的头像(调用 API 更新,成功后刷新用户列表)
|
||||
*/
|
||||
async function saveAvatar() {
|
||||
const btn = document.getElementById('avatar-save-btn');
|
||||
const file = btn.dataset.file;
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = '保存中...';
|
||||
|
||||
try {
|
||||
const res = await fetch('/headface/change', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
headface: file
|
||||
})
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (data.status === 'success') {
|
||||
window.chatDialog.alert('头像修改成功!', '提示', '#16a34a');
|
||||
// 同步更新内存中的在线用户头像,避免重新渲染前闪烁旧图
|
||||
const myName = window.chatContext.username;
|
||||
if (typeof onlineUsers !== 'undefined' && onlineUsers[myName]) {
|
||||
onlineUsers[myName].headface = data.headface;
|
||||
}
|
||||
if (typeof renderUserList === 'function') {
|
||||
renderUserList();
|
||||
}
|
||||
closeAvatarPicker();
|
||||
} else {
|
||||
window.chatDialog.alert(data.message || '修改失败', '操作失败', '#cc4444');
|
||||
}
|
||||
} catch (e) {
|
||||
window.chatDialog.alert('网络错误', '网络异常', '#cc4444');
|
||||
}
|
||||
|
||||
btn.disabled = false;
|
||||
btn.textContent = '确定更换';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 保存密码(调用修改密码 API)
|
||||
*/
|
||||
/**
|
||||
* 显示内联操作结果提示(仿百家乐「已押注」卡片风格,3s 后自动消失)
|
||||
*
|
||||
* @param {string} elId 目标元素 ID
|
||||
* @param {string} message 提示内容
|
||||
* @param {boolean} success 是否成功(决定颜色)
|
||||
*/
|
||||
function showInlineMsg(elId, message, success) {
|
||||
const el = document.getElementById(elId);
|
||||
if (!el) return;
|
||||
if (success) {
|
||||
el.style.background = '#f0fdf4';
|
||||
el.style.border = '1px solid #86efac';
|
||||
el.style.color = '#16a34a';
|
||||
} else {
|
||||
el.style.background = '#fff5f5';
|
||||
el.style.border = '1px solid #fecaca';
|
||||
el.style.color = '#dc2626';
|
||||
}
|
||||
el.textContent = message;
|
||||
el.style.display = 'block';
|
||||
el.style.opacity = '1';
|
||||
el.style.transition = 'opacity .4s';
|
||||
clearTimeout(el._hideTimer);
|
||||
el._hideTimer = setTimeout(() => {
|
||||
el.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
el.style.display = 'none';
|
||||
}, 420);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存密码(调用修改密码 API)
|
||||
*/
|
||||
async function savePassword() {
|
||||
const oldPwd = document.getElementById('set-old-pwd').value;
|
||||
const newPwd = document.getElementById('set-new-pwd').value;
|
||||
const newPwd2 = document.getElementById('set-new-pwd2').value;
|
||||
if (!oldPwd || !newPwd) {
|
||||
showInlineMsg('pwd-inline-msg', '⚠️ 请填写旧密码和新密码', false);
|
||||
return;
|
||||
}
|
||||
if (newPwd.length < 6) {
|
||||
showInlineMsg('pwd-inline-msg', '⚠️ 新密码最少6位!', false);
|
||||
return;
|
||||
}
|
||||
if (newPwd !== newPwd2) {
|
||||
showInlineMsg('pwd-inline-msg', '⚠️ 两次输入的新密码不一致!', false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('{{ route('user.update_password') }}', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
old_password: oldPwd,
|
||||
new_password: newPwd,
|
||||
new_password_confirmation: newPwd2
|
||||
})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok && data.status === 'success') {
|
||||
showInlineMsg('pwd-inline-msg', '🔒 密码修改成功!', true);
|
||||
document.getElementById('set-old-pwd').value = '';
|
||||
document.getElementById('set-new-pwd').value = '';
|
||||
document.getElementById('set-new-pwd2').value = '';
|
||||
} else {
|
||||
showInlineMsg('pwd-inline-msg', '❌ ' + (data.message || '请输入正确的旧密码'), false);
|
||||
}
|
||||
} catch (e) {
|
||||
showInlineMsg('pwd-inline-msg', '🌐 网络异常,请稍后重试', false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存个人资料和密保设置
|
||||
*/
|
||||
async function saveSettings() {
|
||||
const profileData = {
|
||||
sex: document.getElementById('set-sex').value,
|
||||
email: document.getElementById('set-email').value,
|
||||
email_code: document.getElementById('set-email-code') ? document.getElementById('set-email-code')
|
||||
.value : '',
|
||||
question: document.getElementById('set-question').value,
|
||||
answer: document.getElementById('set-answer').value,
|
||||
headface: @json(Auth::user()->usersf ?: '1.gif'),
|
||||
sign: @json(Auth::user()->sign ?? '')
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch('{{ route('user.update_profile') }}', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(profileData)
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok && data.status === 'success') {
|
||||
showInlineMsg('settings-inline-msg', '✅ 资料保存成功!', true);
|
||||
} else {
|
||||
showInlineMsg('settings-inline-msg', '❌ ' + (data.message || '输入有误'), false);
|
||||
}
|
||||
} catch (e) {
|
||||
showInlineMsg('settings-inline-msg', '🌐 网络异常,请稍后重试', false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮箱验证码 (带有 60s 倒计时机制防灌水)
|
||||
*/
|
||||
async function sendEmailCode() {
|
||||
const emailInput = document.getElementById('set-email').value.trim();
|
||||
if (!emailInput) {
|
||||
window.chatDialog.alert('请先填写邮箱地址后再获取验证码!', '提示', '#d97706');
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = document.getElementById('btn-send-code');
|
||||
btn.disabled = true;
|
||||
btn.innerText = '正在发送...';
|
||||
btn.style.opacity = '0.6';
|
||||
btn.style.cursor = 'not-allowed';
|
||||
|
||||
try {
|
||||
const res = await fetch('{{ route('user.send_email_code') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: emailInput
|
||||
})
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (res.ok && data.status === 'success') {
|
||||
window.chatDialog.alert(data.message || '验证码发送成功,请前往邮箱查收!(有效期5分钟)', '发送成功', '#16a34a');
|
||||
|
||||
// 开始 60 秒防暴力点击倒计时
|
||||
let count = 60;
|
||||
btn.innerText = count + 's 后重试';
|
||||
const timer = setInterval(() => {
|
||||
count--;
|
||||
if (count <= 0) {
|
||||
clearInterval(timer);
|
||||
btn.innerText = '获取验证码';
|
||||
btn.disabled = false;
|
||||
btn.style.opacity = '1';
|
||||
btn.style.cursor = 'pointer';
|
||||
} else {
|
||||
btn.innerText = count + 's 后重试';
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
} else {
|
||||
window.chatDialog.alert('发送失败:' + (data.message || '系统繁忙'), '发送失败', '#dc2626');
|
||||
// 失败了立刻解除禁用以重新尝试
|
||||
btn.innerText = '获取验证码';
|
||||
btn.disabled = false;
|
||||
btn.style.opacity = '1';
|
||||
btn.style.cursor = 'pointer';
|
||||
}
|
||||
} catch (e) {
|
||||
window.chatDialog.alert('网络异常,验证码发送失败,请稍后重试。', '错误', '#6b7280');
|
||||
btn.innerText = '获取验证码';
|
||||
btn.disabled = false;
|
||||
btn.style.opacity = '1';
|
||||
btn.style.cursor = 'pointer';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成微信绑定验证码
|
||||
*/
|
||||
async function generateWechatBindCode() {
|
||||
const btn = document.getElementById('btn-generate-bind-code');
|
||||
const input = document.getElementById('wechat-bind-code');
|
||||
const tip = document.getElementById('bind-code-tip');
|
||||
|
||||
btn.disabled = true;
|
||||
btn.innerText = '生成中...';
|
||||
|
||||
try {
|
||||
const res = await fetch('{{ route('user.generate_wechat_code') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (res.ok && data.status === 'success') {
|
||||
input.value = data.code;
|
||||
tip.style.display = 'block';
|
||||
document.getElementById('btn-copy-bind-code').style.display = 'inline-block';
|
||||
showInlineMsg('settings-inline-msg', '✅ 绑定代码生成成功,请在5分钟内发送给机器人', true);
|
||||
} else {
|
||||
showInlineMsg('settings-inline-msg', '❌ 生成失败:' + (data.message || '未知错误'), false);
|
||||
}
|
||||
} catch (e) {
|
||||
showInlineMsg('settings-inline-msg', '🌐 网络异常,请稍后重试', false);
|
||||
}
|
||||
|
||||
btn.disabled = false;
|
||||
btn.innerText = '重新生成';
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制微信绑定验证码
|
||||
*/
|
||||
function copyWechatBindCode() {
|
||||
const input = document.getElementById('wechat-bind-code');
|
||||
if (input.value && input.value !== '点击生成' && input.value !== '生成中...') {
|
||||
input.select();
|
||||
input.setSelectionRange(0, 99999);
|
||||
|
||||
try {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(input.value);
|
||||
} else {
|
||||
document.execCommand('copy');
|
||||
}
|
||||
const btn = document.getElementById('btn-copy-bind-code');
|
||||
const originalText = btn.innerText;
|
||||
btn.innerText = '已复制';
|
||||
setTimeout(() => {
|
||||
btn.innerText = originalText;
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
showInlineMsg('settings-inline-msg', '❌ 复制失败,请手动复制', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解除微信绑定
|
||||
*/
|
||||
async function unbindWechat() {
|
||||
if (!confirm('确定要解除微信绑定吗?解除后将无法接收任何机器人推送通知。')) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await fetch('{{ route('user.unbind_wechat') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok && data.status === 'success') {
|
||||
alert('✅ 解绑成功!请刷新页面获取最新状态。');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('❌ 解绑失败:' + (data.message || '未知错误'));
|
||||
}
|
||||
} catch (e) {
|
||||
alert('网络异常,解绑失败');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{{-- 头像选择和个人设置脚本已迁移到 resources/js/chat-room/profile-controls.js --}}
|
||||
|
||||
{{-- ═══════════ 商店弹窗 ═══════════ --}}
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user