feat: 增加自定义头像上传、自动压缩与自动清理功能,统一全站头像路径读取逻辑

This commit is contained in:
2026-03-12 15:26:54 +08:00
parent ec95d69e92
commit 78564e2a1d
57 changed files with 569 additions and 350 deletions
@@ -59,14 +59,22 @@
{{-- 预览区 --}}
<div
style="padding:10px 16px; background:#f0f6ff; border-bottom:1px solid #ddd; display:flex; align-items:center; gap:12px;">
style="padding:10px 16px; background:#f0f6ff; border-bottom:1px solid #ddd; display:flex; align-items:center; gap:12px; flex-wrap:wrap;">
<span style="font-size:12px; color:#666;">当前选中:</span>
<img id="avatar-preview" src="/images/headface/{{ $user->usersf ?: '1.gif' }}"
style="width:40px; height:40px; border:2px solid #336699; border-radius:4px;">
<img id="avatar-preview" src="{{ str_starts_with($user->usersf, 'storage/') ? '/' . $user->usersf : '/images/headface/' . ($user->usersf ?: '1.gif') }}"
style="width:40px; height:40px; border:2px solid #336699; border-radius:4px; object-fit: cover;">
<span id="avatar-selected-name" style="font-size:12px; color:#333;">{{ $user->usersf ?: '未设置' }}</span>
<button id="avatar-save-btn" disabled onclick="saveAvatar()"
style="margin-left:auto; padding:5px 16px; background:#336699; color:#fff; border:none;
border-radius:3px; font-size:12px; cursor:pointer;">确定更换</button>
border-radius:3px; font-size:12px; cursor:pointer;">确定更换系统头像</button>
<div style="width:100%; height:1px; background:#ddd; margin: 4px 0;"></div>
<div style="display:flex; align-items:center; gap:8px; width:100%;">
<span style="font-size:12px; color:#666; font-weight:bold;">自定义头像上传(112x112)</span>
<input type="file" id="avatar-upload-input" accept="image/jpeg,image/png,image/gif,image/webp" style="display:none;" onchange="handleAvatarUpload(this)">
<button id="avatar-upload-btn" onclick="document.getElementById('avatar-upload-input').click()"
style="padding:5px 16px; background:#16a34a; color:#fff; border:none;
border-radius:3px; font-size:12px; cursor:pointer;">选择本地图片上传</button>
</div>
</div>
{{-- 头像网格 --}}
@@ -250,6 +258,74 @@
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 更新,成功后刷新用户列表)
*/