|
@@ -33,48 +33,101 @@
|
|
|
#lightbox img { max-height: 85vh; max-width: 90vw; object-fit: contain; }
|
|
#lightbox img { max-height: 85vh; max-width: 90vw; object-fit: contain; }
|
|
|
.date-chip { transition: all 0.15s; }
|
|
.date-chip { transition: all 0.15s; }
|
|
|
.date-chip:hover { transform: translateY(-1px); }
|
|
.date-chip:hover { transform: translateY(-1px); }
|
|
|
|
|
+ /* Sidebar collapse */
|
|
|
|
|
+ .sidebar { transition: width 0.25s ease; }
|
|
|
|
|
+ .sidebar.collapsed { width: 5rem; }
|
|
|
|
|
+ .sidebar.collapsed .sidebar-text { display: none; }
|
|
|
|
|
+ .sidebar.collapsed .sidebar-sub { display: none; }
|
|
|
|
|
+ .sidebar.collapsed .brand-text { display: none; }
|
|
|
|
|
+ .sidebar.collapsed .collapse-btn i { transform: rotate(180deg); }
|
|
|
|
|
+ .sidebar.collapsed main { margin-left: 5rem !important; }
|
|
|
|
|
+ .sidebar-sub { overflow: hidden; transition: max-height 0.25s ease, opacity 0.2s; max-height: 500px; opacity: 1; }
|
|
|
|
|
+ .sidebar-sub.collapsed { max-height: 0 !important; opacity: 0 !important; }
|
|
|
|
|
+ .subject-toggle { transition: transform 0.2s; display: inline-block; }
|
|
|
|
|
+ .subject-toggle.open { transform: rotate(90deg); }
|
|
|
|
|
+ .subject-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.4rem 0.75rem; border-radius: 0.5rem; font-size: 0.8rem; color: #6b7280; text-decoration: none; transition: all 0.15s; cursor: pointer; white-space: nowrap; }
|
|
|
|
|
+ .subject-item:hover { background: #f3f4f6; color: #374151; }
|
|
|
|
|
+ .subject-item.active { background: rgba(99,102,241,0.10); color: #6366f1; }
|
|
|
|
|
+ .sidebar-section-label { font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.08em; color: #9ca3af; padding: 0.5rem 1rem 0.25rem; font-weight: 600; }
|
|
|
|
|
+ .sidebar.collapsed .sidebar-section-label { display: none; }
|
|
|
</style>
|
|
</style>
|
|
|
{% block extra_css %}{% endblock %}
|
|
{% block extra_css %}{% endblock %}
|
|
|
</head>
|
|
</head>
|
|
|
<body class="bg-gray-50 min-h-screen">
|
|
<body class="bg-gray-50 min-h-screen">
|
|
|
<div class="flex min-h-screen">
|
|
<div class="flex min-h-screen">
|
|
|
<!-- Sidebar -->
|
|
<!-- Sidebar -->
|
|
|
- <aside class="w-56 bg-white shadow-lg fixed h-full z-20 flex flex-col">
|
|
|
|
|
- <div class="p-5 border-b border-gray-100">
|
|
|
|
|
- <div class="flex items-center gap-3">
|
|
|
|
|
- <div class="w-9 h-9 rounded-xl bg-primary flex items-center justify-center text-white text-base">
|
|
|
|
|
- <i class="fa-solid fa-book-open"></i>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div>
|
|
|
|
|
- <h1 class="font-bold text-gray-800 text-sm leading-tight">满意错题本</h1>
|
|
|
|
|
- <p class="text-[10px] text-gray-400 mt-0.5">在线复习助手</p>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <aside class="sidebar w-56 bg-white shadow-lg fixed h-full z-20 flex flex-col border-r border-gray-100">
|
|
|
|
|
+ <!-- Header -->
|
|
|
|
|
+ <div class="px-4 py-4 flex items-center gap-3">
|
|
|
|
|
+ <div class="w-9 h-9 rounded-xl bg-primary flex items-center justify-center text-white text-base flex-shrink-0">
|
|
|
|
|
+ <i class="fa-solid fa-book-open"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="brand-text overflow-hidden">
|
|
|
|
|
+ <h1 class="font-bold text-gray-800 text-sm leading-tight">满意错题本</h1>
|
|
|
|
|
+ <p class="text-[10px] text-gray-400 mt-0.5">在线复习助手</p>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <button id="collapseBtn" class="collapse-btn ml-auto w-7 h-7 rounded-lg flex items-center justify-center text-gray-400 hover:bg-gray-100 hover:text-gray-600 transition flex-shrink-0" title="收起/展开">
|
|
|
|
|
+ <i class="fa-solid fa-angles-left text-xs"></i>
|
|
|
|
|
+ </button>
|
|
|
</div>
|
|
</div>
|
|
|
- <nav class="flex-1 py-3">
|
|
|
|
|
- <a href="/" class="sidebar-link flex items-center gap-3 px-5 py-2.5 text-gray-500 hover:bg-gray-50 transition {% if request.url.path == '/' %}active{% endif %}">
|
|
|
|
|
- <i class="fa-solid fa-house w-5 text-center text-base"></i><span class="text-sm font-medium">首页</span>
|
|
|
|
|
- </a>
|
|
|
|
|
- <a href="/subjects" class="sidebar-link flex items-center gap-3 px-5 py-2.5 text-gray-500 hover:bg-gray-50 transition {% if '/subjects' in request.url.path %}active{% endif %}">
|
|
|
|
|
- <i class="fa-solid fa-folder w-5 text-center text-base"></i><span class="text-sm font-medium">科目管理</span>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Nav -->
|
|
|
|
|
+ <nav class="flex-1 py-2 px-3 overflow-y-auto">
|
|
|
|
|
+ <!-- 首页 -->
|
|
|
|
|
+ <a href="/" class="sidebar-link flex items-center gap-3 px-3 py-2.5 rounded-xl text-gray-500 hover:bg-gray-50 transition mb-1 {% if request.url.path == '/' %}active{% endif %}">
|
|
|
|
|
+ <i class="fa-solid fa-house w-5 text-center text-base flex-shrink-0"></i>
|
|
|
|
|
+ <span class="sidebar-text text-sm font-medium">首页</span>
|
|
|
</a>
|
|
</a>
|
|
|
- <a href="/timeline" class="sidebar-link flex items-center gap-3 px-5 py-2.5 text-gray-500 hover:bg-gray-50 transition {% if '/timeline' in request.url.path %}active{% endif %}">
|
|
|
|
|
- <i class="fa-solid fa-clock-rotate-left w-5 text-center text-base"></i><span class="text-sm font-medium">时间线</span>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 科目管理 + 子菜单 -->
|
|
|
|
|
+ <div class="mb-1">
|
|
|
|
|
+ <a href="javascript:void(0)" id="subjectToggleBtn" class="sidebar-link flex items-center gap-3 px-3 py-2.5 rounded-xl text-gray-500 hover:bg-gray-50 transition {% if '/subjects' in request.url.path or '/subject/' in request.url.path %}active{% endif %}">
|
|
|
|
|
+ <i class="fa-solid fa-folder w-5 text-center text-base flex-shrink-0"></i>
|
|
|
|
|
+ <span class="sidebar-text text-sm font-medium">科目管理</span>
|
|
|
|
|
+ <i class="fa-solid fa-chevron-right text-[10px] text-gray-400 ml-auto subject-toggle" id="subjectToggleIcon"></i>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ <div id="subjectSubmenu" class="sidebar-sub collapsed mt-1 ml-4 pl-3 border-l border-gray-100">
|
|
|
|
|
+ <div class="sidebar-section-label sidebar-text px-1">全部科目</div>
|
|
|
|
|
+ {% for s in subjects %}
|
|
|
|
|
+ <a href="/subject/{{ s.id }}" class="subject-item {% if request.url.path == '/subject/' ~ s.id|string %}active{% endif %}" style="{% if '/subject/' ~ s.id|string ~ '?' in request.url.path %}margin-top:0{% endif %}">
|
|
|
|
|
+ <span class="w-2.5 h-2.5 rounded-full flex-shrink-0" style="background-color:{{ s.color }};"></span>
|
|
|
|
|
+ <span class="sidebar-text truncate">{{ s.name }}</span>
|
|
|
|
|
+ <span class="sidebar-text ml-auto text-[10px] text-gray-300">{{ s.error_images|length }}</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ {% endfor %}
|
|
|
|
|
+ {% if not subjects %}
|
|
|
|
|
+ <div class="sidebar-text text-xs text-gray-300 px-3 py-2">暂无科目</div>
|
|
|
|
|
+ {% endif %}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 时间线 -->
|
|
|
|
|
+ <a href="/timeline" class="sidebar-link flex items-center gap-3 px-3 py-2.5 rounded-xl text-gray-500 hover:bg-gray-50 transition mb-1 {% if '/timeline' in request.url.path %}active{% endif %}">
|
|
|
|
|
+ <i class="fa-solid fa-clock-rotate-left w-5 text-center text-base flex-shrink-0"></i>
|
|
|
|
|
+ <span class="sidebar-text text-sm font-medium">时间线</span>
|
|
|
</a>
|
|
</a>
|
|
|
- <a href="/review" class="sidebar-link flex items-center gap-3 px-5 py-2.5 text-gray-500 hover:bg-gray-50 transition {% if '/review' in request.url.path %}active{% endif %}">
|
|
|
|
|
- <i class="fa-solid fa-graduation-cap w-5 text-center text-base"></i><span class="text-sm font-medium">复习汇总</span>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 复习汇总 -->
|
|
|
|
|
+ <a href="/review" class="sidebar-link flex items-center gap-3 px-3 py-2.5 rounded-xl text-gray-500 hover:bg-gray-50 transition mb-1 {% if '/review' in request.url.path %}active{% endif %}">
|
|
|
|
|
+ <i class="fa-solid fa-graduation-cap w-5 text-center text-base flex-shrink-0"></i>
|
|
|
|
|
+ <span class="sidebar-text text-sm font-medium">复习汇总</span>
|
|
|
</a>
|
|
</a>
|
|
|
- <a href="/evaluations" class="sidebar-link flex items-center gap-3 px-5 py-2.5 text-gray-500 hover:bg-gray-50 transition {% if '/evaluations' in request.url.path %}active{% endif %}">
|
|
|
|
|
- <i class="fa-solid fa-clipboard-check w-5 text-center text-base"></i><span class="text-sm font-medium">知识评价</span>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 知识评价 -->
|
|
|
|
|
+ <a href="/evaluations" class="sidebar-link flex items-center gap-3 px-3 py-2.5 rounded-xl text-gray-500 hover:bg-gray-50 transition mb-1 {% if '/evaluations' in request.url.path %}active{% endif %}">
|
|
|
|
|
+ <i class="fa-solid fa-clipboard-check w-5 text-center text-base flex-shrink-0"></i>
|
|
|
|
|
+ <span class="sidebar-text text-sm font-medium">知识评价</span>
|
|
|
</a>
|
|
</a>
|
|
|
</nav>
|
|
</nav>
|
|
|
- <div class="p-4 border-t border-gray-100">
|
|
|
|
|
- <p class="text-[10px] text-gray-300 text-center">© 2025 满意错题本</p>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Footer -->
|
|
|
|
|
+ <div class="px-4 py-3 border-t border-gray-100">
|
|
|
|
|
+ <p class="sidebar-text text-[10px] text-gray-300 text-center">© 2025 满意错题本</p>
|
|
|
</div>
|
|
</div>
|
|
|
</aside>
|
|
</aside>
|
|
|
|
|
|
|
|
<!-- Main content -->
|
|
<!-- Main content -->
|
|
|
- <main class="flex-1 ml-56">
|
|
|
|
|
|
|
+ <main class="flex-1 ml-56 transition-margin">
|
|
|
<header class="bg-white/80 backdrop-blur border-b border-gray-100 px-8 py-3.5 flex items-center justify-between sticky top-0 z-10">
|
|
<header class="bg-white/80 backdrop-blur border-b border-gray-100 px-8 py-3.5 flex items-center justify-between sticky top-0 z-10">
|
|
|
<h2 class="text-base font-semibold text-gray-700">{% block page_title %}{% endblock %}</h2>
|
|
<h2 class="text-base font-semibold text-gray-700">{% block page_title %}{% endblock %}</h2>
|
|
|
<div class="text-xs text-gray-400" id="current-date"></div>
|
|
<div class="text-xs text-gray-400" id="current-date"></div>
|
|
@@ -103,6 +156,62 @@
|
|
|
<script>
|
|
<script>
|
|
|
document.getElementById('current-date').textContent = new Date().toLocaleDateString('zh-CN', {year:'numeric',month:'long',day:'numeric',weekday:'long'});
|
|
document.getElementById('current-date').textContent = new Date().toLocaleDateString('zh-CN', {year:'numeric',month:'long',day:'numeric',weekday:'long'});
|
|
|
|
|
|
|
|
|
|
+ // Sidebar collapse
|
|
|
|
|
+ (function(){
|
|
|
|
|
+ var sidebar = document.querySelector('.sidebar');
|
|
|
|
|
+ var main = document.querySelector('main');
|
|
|
|
|
+ var btn = document.getElementById('collapseBtn');
|
|
|
|
|
+ var icon = btn ? btn.querySelector('i') : null;
|
|
|
|
|
+ var collapsed = localStorage.getItem('sidebarCollapsed') === 'true';
|
|
|
|
|
+ if(collapsed){
|
|
|
|
|
+ sidebar.classList.add('collapsed');
|
|
|
|
|
+ main.style.marginLeft = '5rem';
|
|
|
|
|
+ if(icon) icon.className = 'fa-solid fa-angles-right text-xs';
|
|
|
|
|
+ }
|
|
|
|
|
+ if(btn){
|
|
|
|
|
+ btn.addEventListener('click', function(){
|
|
|
|
|
+ sidebar.classList.toggle('collapsed');
|
|
|
|
|
+ if(sidebar.classList.contains('collapsed')){
|
|
|
|
|
+ main.style.marginLeft = '5rem';
|
|
|
|
|
+ if(icon) icon.className = 'fa-solid fa-angles-right text-xs';
|
|
|
|
|
+ localStorage.setItem('sidebarCollapsed', 'true');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ main.style.marginLeft = '14rem';
|
|
|
|
|
+ if(icon) icon.className = 'fa-solid fa-angles-left text-xs';
|
|
|
|
|
+ localStorage.setItem('sidebarCollapsed', 'false');
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ })();
|
|
|
|
|
+
|
|
|
|
|
+ // Subject submenu toggle (collapsed by default)
|
|
|
|
|
+ (function(){
|
|
|
|
|
+ var submenu = document.getElementById('subjectSubmenu');
|
|
|
|
|
+ var icon = document.getElementById('subjectToggleIcon');
|
|
|
|
|
+ var btn = document.getElementById('subjectToggleBtn');
|
|
|
|
|
+ var saved = localStorage.getItem('subjectMenuOpen');
|
|
|
|
|
+ var isOpen = saved === 'true';
|
|
|
|
|
+ if(isOpen){
|
|
|
|
|
+ submenu.classList.remove('collapsed');
|
|
|
|
|
+ if(icon) icon.classList.add('open');
|
|
|
|
|
+ }
|
|
|
|
|
+ if(btn){
|
|
|
|
|
+ btn.addEventListener('click', function(e){
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ e.stopPropagation();
|
|
|
|
|
+ isOpen = !isOpen;
|
|
|
|
|
+ if(isOpen){
|
|
|
|
|
+ submenu.classList.remove('collapsed');
|
|
|
|
|
+ if(icon) icon.classList.add('open');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ submenu.classList.add('collapsed');
|
|
|
|
|
+ if(icon) icon.classList.remove('open');
|
|
|
|
|
+ }
|
|
|
|
|
+ localStorage.setItem('subjectMenuOpen', String(isOpen));
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ })();
|
|
|
|
|
+
|
|
|
// Lightbox
|
|
// Lightbox
|
|
|
let lbImages = [];
|
|
let lbImages = [];
|
|
|
let lbIndex = 0;
|
|
let lbIndex = 0;
|
|
@@ -112,14 +221,12 @@
|
|
|
updateLightbox();
|
|
updateLightbox();
|
|
|
const lb = document.getElementById('lightbox');
|
|
const lb = document.getElementById('lightbox');
|
|
|
lb.style.display='flex';
|
|
lb.style.display='flex';
|
|
|
-
|
|
|
|
|
document.body.style.overflow = 'hidden';
|
|
document.body.style.overflow = 'hidden';
|
|
|
}
|
|
}
|
|
|
function closeLightbox(e) {
|
|
function closeLightbox(e) {
|
|
|
if (e && e.target.tagName === 'IMG') return;
|
|
if (e && e.target.tagName === 'IMG') return;
|
|
|
const lb = document.getElementById('lightbox');
|
|
const lb = document.getElementById('lightbox');
|
|
|
lb.style.display='none';
|
|
lb.style.display='none';
|
|
|
-
|
|
|
|
|
document.body.style.overflow = '';
|
|
document.body.style.overflow = '';
|
|
|
}
|
|
}
|
|
|
function navigateLightbox(dir, e) {
|
|
function navigateLightbox(dir, e) {
|
|
@@ -130,7 +237,7 @@
|
|
|
}
|
|
}
|
|
|
function updateLightbox() {
|
|
function updateLightbox() {
|
|
|
document.getElementById('lb-img').src = lbImages[lbIndex];
|
|
document.getElementById('lb-img').src = lbImages[lbIndex];
|
|
|
- document.getElementById('lb-caption').textContent = lbImages[lbIndex + 1] ? `${lbIndex+1}/${lbImages.length} · ${document.getElementById('lb-img').dataset.caption || ''}` : '';
|
|
|
|
|
|
|
+ document.getElementById('lb-caption').textContent = lbImages[lbIndex + 1] ? `${lbIndex+1}/${lbImages.length}` : '';
|
|
|
document.getElementById('lb-prev').classList.toggle('hidden', lbImages.length <= 1);
|
|
document.getElementById('lb-prev').classList.toggle('hidden', lbImages.length <= 1);
|
|
|
document.getElementById('lb-next').classList.toggle('hidden', lbImages.length <= 1);
|
|
document.getElementById('lb-next').classList.toggle('hidden', lbImages.length <= 1);
|
|
|
}
|
|
}
|