mobile base exam_grade forum themes
This commit is contained in:
@@ -352,7 +352,7 @@
|
||||
</script>
|
||||
|
||||
<!-- 浮动聊天气泡 -->
|
||||
<div id="chatBubble" class="fixed bottom-6 right-6 z-40">
|
||||
<div id="chatBubble" class="fixed bottom-6 right-6 z-30">
|
||||
<button onclick="toggleMiniChat()" class="w-14 h-14 bg-gradient-to-tr from-primary to-blue-400 text-white rounded-full shadow-lg hover:shadow-xl hover:bg-blue-600 flex items-center justify-center relative transition-all duration-300 hover:scale-110">
|
||||
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/></svg>
|
||||
<span id="bubbleBadge" class="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center hidden">0</span>
|
||||
@@ -360,7 +360,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 迷你聊天窗口 -->
|
||||
<div id="miniChat" class="fixed bottom-24 right-6 w-[380px] h-[500px] bg-white rounded-xl shadow-2xl border border-slate-200 z-40 hidden flex flex-col overflow-hidden">
|
||||
<div id="miniChat" class="fixed bottom-24 right-6 w-[380px] h-[500px] bg-white rounded-xl shadow-2xl border border-slate-200 z-30 hidden flex flex-col overflow-hidden">
|
||||
<div class="px-4 py-3 bg-primary text-white flex justify-between items-center rounded-t-xl">
|
||||
<span class="font-medium text-sm" id="miniTitle">消息</span>
|
||||
<div class="flex items-center gap-3">
|
||||
@@ -385,7 +385,14 @@
|
||||
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
|
||||
<script>
|
||||
(function(){
|
||||
if (document.getElementById('chatApp')) return; // chat.html 已有自己的socket
|
||||
// 在聊天页面隐藏浮动气泡(已有完整聊天界面)
|
||||
if (document.getElementById('chatApp')) {
|
||||
var bubble = document.getElementById('chatBubble');
|
||||
if (bubble) bubble.style.display = 'none';
|
||||
var miniC = document.getElementById('miniChat');
|
||||
if (miniC) miniC.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
const bubbleSocket = io();
|
||||
let miniRoomId = null;
|
||||
let miniRooms = [];
|
||||
@@ -422,6 +429,11 @@
|
||||
});
|
||||
|
||||
window.toggleMiniChat = function() {
|
||||
// 移动端直接跳转到完整聊天页面
|
||||
if (window.innerWidth < 640) {
|
||||
window.location.href = '/chat';
|
||||
return;
|
||||
}
|
||||
const mc = document.getElementById('miniChat');
|
||||
mc.classList.toggle('hidden');
|
||||
if (!mc.classList.contains('hidden')) {
|
||||
|
||||
@@ -117,6 +117,9 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="flex justify-between items-center bg-white shadow-sm rounded-lg p-6 border border-slate-200 sticky bottom-4">
|
||||
<div class="text-lg font-medium text-slate-900">总分:<span id="total-score" class="text-primary">0</span> / {{ exam.total_score }}</div>
|
||||
|
||||
@@ -7,9 +7,6 @@
|
||||
.toast{position:fixed;top:20px;right:20px;z-index:9999;padding:12px 20px;border-radius:8px;color:#fff;font-size:14px;animation:slideIn .3s ease;box-shadow:0 4px 12px rgba(0,0,0,.15)}
|
||||
.toast-success{background:#10b981}.toast-error{background:#ef4444}.toast-info{background:#3b82f6}
|
||||
@keyframes slideIn{from{transform:translateX(100%);opacity:0}to{transform:translateX(0);opacity:1}}
|
||||
.reaction-btn{transition:all .2s;cursor:pointer;padding:2px 8px;border-radius:20px;border:1px solid #e2e8f0;font-size:13px;display:inline-flex;align-items:center;gap:3px}
|
||||
.reaction-btn:hover{transform:scale(1.15);border-color:#93c5fd;background:#eff6ff}
|
||||
.reaction-btn.active{background:#dbeafe;border-color:#60a5fa}
|
||||
.poll-bar{height:28px;border-radius:6px;background:#f1f5f9;overflow:hidden;position:relative;margin:4px 0}
|
||||
.poll-fill{height:100%;border-radius:6px;background:linear-gradient(90deg,#3b82f6,#60a5fa);transition:width .6s ease;display:flex;align-items:center;padding:0 10px;font-size:12px;color:#fff;font-weight:500;min-width:fit-content}
|
||||
.poll-bar.voted .poll-fill{background:linear-gradient(90deg,#10b981,#34d399)}
|
||||
@@ -307,7 +304,7 @@
|
||||
<script>
|
||||
const CU = {{ user | tojson if user else 'null' }};
|
||||
let curTag = '全部';
|
||||
const REACTIONS = {like:'👍',love:'❤️',haha:'😂',wow:'😮',sad:'😢',angry:'😡'};
|
||||
const REACTIONS = {};
|
||||
const TAG_ICONS = {'官方公告':'📢','题目讨论':'📐','经验分享':'💡','求助答疑':'🙋','闲聊灌水':'☕'};
|
||||
const TAG_COLORS = {'官方公告':'red','题目讨论':'blue','经验分享':'green','求助答疑':'yellow','闲聊灌水':'purple'};
|
||||
|
||||
@@ -443,12 +440,7 @@ function renderPosts(posts) {
|
||||
posts.forEach(p => {
|
||||
const tc = TAG_COLORS[p.tag] || 'slate';
|
||||
const ti = TAG_ICONS[p.tag] || '📋';
|
||||
const reactions = p.reactions || {};
|
||||
let reactHtml = '';
|
||||
for (const [k, v] of Object.entries(reactions)) {
|
||||
if (v > 0) reactHtml += `<span class="inline-flex items-center gap-1 text-xs bg-slate-50 border border-slate-100 px-2 py-0.5 rounded-md text-slate-600">${REACTIONS[k]||k} ${v}</span>`;
|
||||
}
|
||||
|
||||
|
||||
// 生成纯文本内容用于预览
|
||||
let cleanContent = p.content.replace(/\[img:[^\]]+\]/g, '[图片]');
|
||||
|
||||
@@ -513,7 +505,6 @@ function renderPosts(posts) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${reactHtml ? `<div class="mt-4 pt-3 border-t border-slate-100/60 flex flex-wrap gap-2">${reactHtml}</div>` : ''}
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
@@ -569,12 +560,6 @@ async function openPost(pid) {
|
||||
const isTeacher = CU && (CU.role === 'teacher' || CU.role === 'admin');
|
||||
const reactions = p.reactions || {};
|
||||
|
||||
let reactBtns = '';
|
||||
for (const [k, emoji] of Object.entries(REACTIONS)) {
|
||||
const cnt = reactions[k] || 0;
|
||||
reactBtns += `<button onclick="reactPost(${p.id},'${k}',this)" class="reaction-btn${cnt>0?' active':''}">${emoji} <span>${cnt}</span></button>`;
|
||||
}
|
||||
|
||||
let pollHtml = '';
|
||||
if (p.has_poll) {
|
||||
try {
|
||||
@@ -635,7 +620,6 @@ async function openPost(pid) {
|
||||
</div>
|
||||
<div class="prose prose-sm max-w-none text-slate-800 whitespace-pre-wrap border-b border-slate-100 pb-5 mb-4">${renderContent(p.content)}</div>
|
||||
${pollHtml}
|
||||
<div class="flex items-center gap-2 flex-wrap mb-4">${reactBtns}</div>
|
||||
<div class="flex items-center gap-3 mb-6 border-b border-slate-100 pb-4">
|
||||
<button onclick="toggleLikeModal(${p.id})" id="ml-btn" class="flex items-center gap-1.5 px-3 py-1.5 rounded-full border ${p.liked?'border-red-300 text-red-500 bg-red-50':'border-slate-200 text-slate-500'} hover:bg-red-50 text-sm">❤️ <span id="ml-cnt">${p.likes}</span></button>
|
||||
<button onclick="toggleBmModal(${p.id})" id="mb-btn" class="flex items-center gap-1.5 px-3 py-1.5 rounded-full border ${p.bookmarked?'border-yellow-300 text-yellow-500 bg-yellow-50':'border-slate-200 text-slate-500'} hover:bg-yellow-50 text-sm">${p.bookmarked?'⭐ 已收藏':'☆ 收藏'}</button>
|
||||
@@ -732,10 +716,7 @@ async function toggleBmModal(pid) {
|
||||
}
|
||||
|
||||
async function reactPost(pid, reaction, btn) {
|
||||
if (!CU) { toast('请先登录','error'); return; }
|
||||
const res = await fetch(`/api/posts/${pid}/react`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({reaction})});
|
||||
const d = await res.json();
|
||||
if (d.success) openPost(pid);
|
||||
// removed
|
||||
}
|
||||
|
||||
async function votePoll(pid) {
|
||||
|
||||
Reference in New Issue
Block a user