迁移头像设置脚本

This commit is contained in:
2026-04-25 14:51:07 +08:00
parent 585a6fbf5f
commit 66a9e8ad23
4 changed files with 668 additions and 507 deletions
+2
View File
@@ -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>