feat: 增加自定义头像上传、自动压缩与自动清理功能,统一全站头像路径读取逻辑
This commit is contained in:
@@ -81,7 +81,7 @@
|
||||
<td class="p-4 font-mono text-xs text-gray-500">{{ $user->id }}</td>
|
||||
<td class="p-4">
|
||||
<div class="flex items-center space-x-3">
|
||||
<img src="/images/headface/{{ $user->headface ?? '01.gif' }}"
|
||||
<img src="{{ $user->headface_url ?? '/images/headface/1.gif' }}"
|
||||
class="w-8 h-8 rounded border object-cover">
|
||||
<span class="font-bold text-gray-800">{{ $user->username }}</span>
|
||||
@if ($user->isVip())
|
||||
|
||||
@@ -415,7 +415,8 @@
|
||||
|
||||
const avatar = document.createElement('img');
|
||||
avatar.className = 'fp-avatar';
|
||||
avatar.src = '/images/headface/' + (f.headface || '1.gif');
|
||||
let hf = f.headface || '1.gif';
|
||||
avatar.src = hf.startsWith('storage/') ? '/' + hf : '/images/headface/' + hf;
|
||||
avatar.alt = f.username;
|
||||
|
||||
const name = document.createElement('span');
|
||||
@@ -456,7 +457,8 @@
|
||||
|
||||
const avatar = document.createElement('img');
|
||||
avatar.className = 'fp-avatar';
|
||||
avatar.src = '/images/headface/' + (p.headface || '1.gif');
|
||||
let hf = p.headface || '1.gif';
|
||||
avatar.src = hf.startsWith('storage/') ? '/' + hf : '/images/headface/' + hf;
|
||||
avatar.alt = p.username;
|
||||
|
||||
const name = document.createElement('span');
|
||||
|
||||
@@ -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 更新,成功后刷新用户列表)
|
||||
*/
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
item.dataset.username = username;
|
||||
|
||||
const headface = (user.headface || '1.gif').toLowerCase();
|
||||
const headImgSrc = headface.startsWith('storage/') ? '/' + headface : '/images/headface/' + headface;
|
||||
|
||||
// 徽章优先级:职务图标 > 管理员 > VIP
|
||||
let badges = '';
|
||||
@@ -224,7 +225,7 @@
|
||||
// 女生名字使用玫粉色
|
||||
const nameColor = (user.sex == 2) ? 'color:#e91e8c;' : '';
|
||||
item.innerHTML = `
|
||||
<img class="user-head" src="/images/headface/${headface}" onerror="this.src='/images/headface/1.gif'">
|
||||
<img class="user-head" src="${headImgSrc}" onerror="this.src='/images/headface/1.gif'">
|
||||
<span class="user-name" style="${nameColor}">${username}</span>${badges}
|
||||
`;
|
||||
|
||||
@@ -369,7 +370,7 @@
|
||||
const buggleUsers = ['钓鱼播报', '星海小博士', '送花播报', '系统传音', '系统公告'];
|
||||
const senderInfo = onlineUsers[msg.from_user];
|
||||
const senderHead = ((senderInfo && senderInfo.headface) || '1.gif').toLowerCase();
|
||||
let headImgSrc = `/images/headface/${senderHead}`;
|
||||
let headImgSrc = senderHead.startsWith('storage/') ? '/' + senderHead : `/images/headface/${senderHead}`;
|
||||
if (msg.from_user === 'AI小班长') {
|
||||
headImgSrc = '/images/ai_bot.png';
|
||||
} else if (buggleUsers.includes(msg.from_user)) {
|
||||
|
||||
@@ -597,7 +597,7 @@
|
||||
<div class="modal-body">
|
||||
<div class="profile-row">
|
||||
<img class="profile-avatar" x-show="userInfo.headface"
|
||||
:src="'/images/headface/' + (userInfo.headface || '1.gif').toLowerCase()"
|
||||
:src="(userInfo.headface || '1.gif').toLowerCase().startsWith('storage/') ? '/' + (userInfo.headface || '1.gif').toLowerCase() : '/images/headface/' + (userInfo.headface || '1.gif').toLowerCase()"
|
||||
x-on:error="$el.style.display='none'">
|
||||
<div class="profile-info">
|
||||
<h4>
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
@foreach ($position->activeUserPositions as $up)
|
||||
<div
|
||||
class="flex items-center gap-2 px-3 py-1.5 bg-gray-50 border border-gray-100 rounded-xl">
|
||||
<img src="/images/headface/{{ strtolower($up->user?->headface ?? '1.gif') }}"
|
||||
<img src="{{ $up->user?->headface_url ?? '/images/headface/1.gif' }}"
|
||||
class="w-7 h-7 rounded-full border border-purple-100 object-cover bg-white"
|
||||
onerror="this.src='/images/headface/1.gif'">
|
||||
<div>
|
||||
@@ -210,7 +210,7 @@
|
||||
|
||||
{{-- 成员 --}}
|
||||
<div class="col-span-4 flex items-center gap-3">
|
||||
<img src="/images/headface/{{ strtolower($row->user?->headface ?? '1.gif') }}"
|
||||
<img src="{{ $row->user?->headface_url ?? '/images/headface/1.gif' }}"
|
||||
class="w-9 h-9 rounded-full border-2 border-purple-100 object-cover bg-white"
|
||||
onerror="this.src='/images/headface/1.gif'">
|
||||
<div>
|
||||
@@ -266,7 +266,7 @@
|
||||
<span class="text-xs font-bold text-gray-300">{{ $i + 1 }}</span>
|
||||
@endif
|
||||
</div>
|
||||
<img src="/images/headface/{{ strtolower($row->user?->headface ?? '1.gif') }}"
|
||||
<img src="{{ $row->user?->headface_url ?? '/images/headface/1.gif' }}"
|
||||
class="w-8 h-8 rounded-full border border-purple-100 object-cover bg-white shrink-0"
|
||||
onerror="this.src='/images/headface/1.gif'">
|
||||
<div class="flex-1 min-w-0">
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
<div class="col-span-1 flex justify-center">
|
||||
<div
|
||||
class="w-10 h-10 rounded-md overflow-hidden bg-white border border-gray-200 shadow-sm shrink-0">
|
||||
<img src="/images/headface/{{ strtolower($inviter->headface ?: '1.gif') }}"
|
||||
<img src="{{ $inviter->headface_url ?? '/images/headface/1.gif' }}"
|
||||
onerror="this.style.display='none'" class="w-full h-full object-cover">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
@auth
|
||||
<div class="flex items-center space-x-3 ml-2 pl-2 border-l border-indigo-700">
|
||||
<div class="flex items-center space-x-2">
|
||||
<img src="/images/headface/{{ Auth::user()->headface ?? '01.gif' }}"
|
||||
<img src="{{ Auth::user()->headface_url ?? '/images/headface/1.gif' }}"
|
||||
class="w-7 h-7 rounded border border-indigo-500 object-cover bg-white">
|
||||
<span class="font-bold hidden sm:inline">{{ Auth::user()->username }}</span>
|
||||
<span
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 truncate">
|
||||
<img class="w-8 h-8 rounded border object-cover shrink-0"
|
||||
src="/images/headface/{{ $user->headface ?? '01.gif' }}" alt="">
|
||||
src="{{ $user->headface_url }}" alt="">
|
||||
<div class="flex flex-col truncate">
|
||||
<span class="text-sm font-bold text-gray-800 truncate" title="{{ $user->username }}">
|
||||
{{ $user->username }}
|
||||
|
||||
@@ -289,7 +289,7 @@
|
||||
<label class="block text-sm font-bold text-gray-700 mb-2">头像选择 (01.gif - 50.gif)</label>
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-12 h-12 rounded bg-gray-200 shrink-0 overflow-hidden border">
|
||||
<img :src="'/images/headface/' + profileData.headface"
|
||||
<img :src="profileData.headface.startsWith('storage/') ? '/' + profileData.headface : '/images/headface/' + profileData.headface"
|
||||
@@error="$el.style.display='none'"
|
||||
class="w-full h-full object-cover">
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user