mobile . themes

This commit is contained in:
2026-03-05 22:03:30 +08:00
parent c325007897
commit 2d3163a1a0
15 changed files with 236 additions and 471 deletions

View File

@@ -187,7 +187,7 @@
<div id="friendListForGroup" class="max-h-48 overflow-y-auto space-y-1"></div>
</div>
<div class="px-4 py-3 border-t border-white/10">
<button onclick="createGroup()" class="btn-futuristic w-full px-4 py-2 text-sm">创建</button>
<button id="groupActionBtn" onclick="createGroup()" class="btn-futuristic w-full px-4 py-2 text-sm">创建</button>
</div>
</div>
</div>
@@ -203,8 +203,9 @@
</div>
</div>
<div id="membersList" class="p-4 overflow-y-auto max-h-96 space-y-2"></div>
<div id="inviteSection" class="px-4 py-3 border-t border-white/10 hidden">
<div id="inviteSection" class="px-4 py-3 border-t border-white/10 hidden space-y-2">
<button onclick="showInvite()" class="btn-futuristic w-full px-4 py-2 text-sm">邀请好友</button>
<button id="dissolveBtn" onclick="dissolveGroup()" class="w-full px-4 py-2 text-sm rounded-lg bg-red-500/20 text-red-400 hover:bg-red-500/30 border border-red-500/30 transition-colors hidden">解散群聊</button>
</div>
</div>
</div>
@@ -943,7 +944,7 @@ async function showCreateGroup() {
modal.classList.add('flex');
// 重置弹窗标题和按钮(防止被邀请好友功能覆盖)
document.querySelector('#createGroupModal h3').textContent = '创建群聊';
const createBtn = document.querySelector('#createGroupModal .bg-primary');
const createBtn = document.getElementById('groupActionBtn');
createBtn.textContent = '创建';
createBtn.onclick = createGroup;
document.getElementById('groupName').value = '';
@@ -1036,6 +1037,7 @@ async function showMembers() {
</div>`;
}).join('');
document.getElementById('inviteSection').classList.toggle('hidden', !isGroup);
document.getElementById('dissolveBtn').classList.toggle('hidden', !isCreator);
}
function hideMembers() {
@@ -1044,11 +1046,27 @@ function hideMembers() {
modal.classList.remove('flex');
}
async function dissolveGroup() {
if (!confirm('确定要解散该群聊?所有聊天记录将被删除,此操作不可撤销。')) return;
const res = await fetch(`/api/chat/rooms/${currentRoomId}/dissolve`, {method:'POST'});
const data = await res.json();
if (data.success) {
hideMembers();
currentRoomId = null;
document.getElementById('chat-header-info').innerHTML = '';
document.getElementById('messages').innerHTML = '';
document.getElementById('chat-input-area').classList.add('hidden');
await loadRooms();
} else {
alert(data.message || '操作失败');
}
}
async function showInvite() {
hideMembers();
showCreateGroup();
document.querySelector('#createGroupModal h3').textContent = '邀请好友';
const btn = document.querySelector('#createGroupModal .bg-primary');
const btn = document.getElementById('groupActionBtn');
btn.textContent = '邀请';
btn.onclick = async () => {
const ids = [...document.querySelectorAll('.friend-check:checked')].map(c => parseInt(c.value));

View File

@@ -491,7 +491,7 @@ function renderPosts(posts) {
<div class="mt-auto flex flex-col sm:flex-row sm:items-center justify-between gap-3 pt-4 border-t border-slate-100/60">
<div class="flex items-center gap-3">
<span class="text-sm font-bold text-slate-800 hover:text-indigo-600 transition-colors cursor-pointer" onclick="event.stopPropagation();showProfile('${p.author_id}')">${esc(p.author)}</span>
${!p.is_official ? `<span class="text-[10px] text-slate-400 flex items-center gap-1 bg-slate-50 px-2 py-0.5 rounded-md border border-slate-100"><svg class="w-3 h-3 text-amber-400" fill="currentColor" viewBox="0 0 20 20"><path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/></svg>Lv.${randomLv}</span>` : ''}
${!p.is_official ? `<span class="text-[10px] text-slate-400 flex items-center gap-1 bg-slate-50 px-2 py-0.5 rounded-md border border-slate-100"><svg class="w-3 h-3 text-amber-400" fill="currentColor" viewBox="0 0 20 20"><path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/></svg>Lv.${p.author_level || 1}</span>` : ''}
<span class="text-xs text-slate-400 bg-slate-50 px-2 py-1 rounded-md">${p.created_at}</span>
</div>

View File

@@ -2,15 +2,16 @@
{% block title %}登录 - 智联青云{% endblock %}
{% block navbar %}{% endblock %}
{% block content %}
<div class="min-h-screen bg-slate-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
<canvas id="meteor-canvas" style="position:fixed;top:0;left:0;width:100%;height:100%;z-index:0;pointer-events:none;"></canvas>
<div class="min-h-screen flex flex-col justify-center py-12 sm:px-6 lg:px-8 relative" style="z-index:1;">
<div class="sm:mx-auto sm:w-full sm:max-w-md">
<h2 class="mt-6 text-center text-3xl font-extrabold bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent animate-pulse">登录您的账户</h2>
<p class="mt-2 text-center text-sm text-slate-600">
<p class="mt-2 text-center text-sm text-slate-300">
或者 <a href="/register" class="font-medium text-primary hover:text-blue-500 transition-all duration-300 hover:scale-110 inline-block">注册新账户</a>
</p>
</div>
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div class="futuristic-card py-8 px-4 sm:px-10">
<div class="futuristic-card py-8 px-4 sm:px-10" style="background:rgba(255,255,255,0.92);backdrop-filter:blur(12px);">
<div class="flex border-b border-slate-200 mb-6">
<button id="tab-phone" onclick="switchTab('phone')" class="flex-1 pb-4 text-sm font-medium text-center text-primary border-b-2 border-primary transition-all duration-300 hover:scale-105">手机验证码登录</button>
<button id="tab-email" onclick="switchTab('email')" class="flex-1 pb-4 text-sm font-medium text-center text-slate-500 hover:text-slate-700 transition-all duration-300 hover:scale-105">邮箱密码登录</button>
@@ -143,5 +144,109 @@ async function handleEmailLogin(e) {
}
fetchCaptcha('phone');
// 流星粒子背景
(function() {
const canvas = document.getElementById('meteor-canvas');
const ctx = canvas.getContext('2d');
let W, H;
window._mouseX = -9999;
window._mouseY = -9999;
document.addEventListener('mousemove', function(e) { window._mouseX = e.clientX; window._mouseY = e.clientY; });
document.addEventListener('mouseleave', function() { window._mouseX = -9999; window._mouseY = -9999; });
function resize() {
W = canvas.width = window.innerWidth;
H = canvas.height = window.innerHeight;
}
resize();
window.addEventListener('resize', resize);
const COUNT = 120;
const MOUSE_R = 150;
const REPEL = 8;
const particles = [];
function rand(a, b) { return Math.random() * (b - a) + a; }
function spawn(warm) {
const angle = rand(Math.PI * 0.6, Math.PI * 0.8);
const speed = rand(2, 5);
const p = {
x: rand(0, W * 1.2),
y: warm ? rand(-H * 0.3, H) : rand(-H * 0.3, 0),
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
len: rand(15, 40),
size: rand(0.8, 2),
alpha: rand(0.3, 0.9),
hue: rand(190, 260),
life: 0,
maxLife: rand(120, 300)
};
if (warm) p.life = rand(0, p.maxLife);
return p;
}
for (let i = 0; i < COUNT; i++) particles.push(spawn(true));
function loop() {
ctx.clearRect(0, 0, W, H);
const bg = ctx.createRadialGradient(W/2, H/2, 0, W/2, H/2, Math.max(W, H) * 0.7);
bg.addColorStop(0, '#0f172a');
bg.addColorStop(1, '#020617');
ctx.fillStyle = bg;
ctx.fillRect(0, 0, W, H);
const mx = window._mouseX, my = window._mouseY;
for (let i = 0; i < particles.length; i++) {
const p = particles[i];
p.life++;
const dx = p.x - mx, dy = p.y - my;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < MOUSE_R && dist > 0) {
const f = (1 - dist / MOUSE_R) * REPEL;
p.x += (dx / dist) * f;
p.y += (dy / dist) * f;
}
p.x += p.vx;
p.y += p.vy;
let a = p.alpha;
if (p.life < 20) a *= p.life / 20;
if (p.life > p.maxLife - 30) a *= (p.maxLife - p.life) / 30;
const spd = Math.sqrt(p.vx * p.vx + p.vy * p.vy);
const tx = p.x - (p.vx / spd) * p.len * 0.6;
const ty = p.y - (p.vy / spd) * p.len * 0.6;
const g = ctx.createLinearGradient(tx, ty, p.x, p.y);
g.addColorStop(0, `hsla(${p.hue},80%,70%,0)`);
g.addColorStop(1, `hsla(${p.hue},80%,70%,${a})`);
ctx.beginPath();
ctx.moveTo(tx, ty);
ctx.lineTo(p.x, p.y);
ctx.strokeStyle = g;
ctx.lineWidth = p.size;
ctx.lineCap = 'round';
ctx.stroke();
ctx.beginPath();
ctx.arc(p.x, p.y, p.size * 0.8, 0, Math.PI * 2);
ctx.fillStyle = `hsla(${p.hue},90%,85%,${a})`;
ctx.fill();
if (p.life >= p.maxLife || p.y > H + 50 || p.x < -100) {
particles[i] = spawn(false);
}
}
requestAnimationFrame(loop);
}
loop();
})();
</script>
{% endblock %}

View File

@@ -277,23 +277,23 @@ async function loadFriends() {
if (!data.success) throw new Error(data.message);
if (data.friends.length === 0) {
container.innerHTML = '<div class="col-span-2 text-center py-4 text-slate-400">暂无好友</div>';
return;
} else {
let html = '';
data.friends.forEach(f => {
html += `
<div class="flex items-center space-x-3 p-3 border border-slate-100 rounded-xl bg-white">
<div class="w-10 h-10 bg-slate-200 rounded-full flex items-center justify-center text-slate-600 text-sm font-bold overflow-hidden">
${f.avatar ? `<img src="${f.avatar}" class="w-full h-full object-cover">` : f.name.charAt(0).toUpperCase()}
</div>
<div class="flex-1 min-w-0">
<div class="text-sm font-medium text-slate-900 truncate">${f.name}</div>
<div class="text-xs text-slate-400">好友 · ${f.created_at}</div>
</div>
<a href="/chat?dm=${f.id}" class="btn-futuristic px-3 py-1.5 text-xs rounded-lg transition-colors font-medium">私聊</a>
</div>`;
});
container.innerHTML = html;
}
let html = '';
data.friends.forEach(f => {
html += `
<div class="flex items-center space-x-3 p-3 border border-slate-100 rounded-xl bg-white">
<div class="w-10 h-10 bg-slate-200 rounded-full flex items-center justify-center text-slate-600 text-sm font-bold overflow-hidden">
${f.avatar ? `<img src="${f.avatar}" class="w-full h-full object-cover">` : f.name.charAt(0).toUpperCase()}
</div>
<div class="flex-1 min-w-0">
<div class="text-sm font-medium text-slate-900 truncate">${f.name}</div>
<div class="text-xs text-slate-400">好友 · ${f.created_at}</div>
</div>
<a href="/chat?dm=${f.id}" class="btn-futuristic px-3 py-1.5 text-xs rounded-lg transition-colors font-medium">私聊</a>
</div>`;
});
container.innerHTML = html;
} catch (e) {
container.innerHTML = '<div class="col-span-2 text-center py-4 text-red-500">加载失败</div>';
}