|
@@ -0,0 +1,171 @@
|
|
|
|
|
+{% extends "base.html" %}
|
|
|
|
|
+{% block title %}时间线 New - 满意错题本{% endblock %}
|
|
|
|
|
+{% block page_title %}时间线 New{% endblock %}
|
|
|
|
|
+
|
|
|
|
|
+{% block extra_css %}
|
|
|
|
|
+<style>
|
|
|
|
|
+ .tl-node { position: relative; width: 7.5rem; flex-shrink: 0; display: flex; flex-direction: column; align-items: center; text-decoration: none; }
|
|
|
|
|
+ .tl-thumb { width: 4.5rem; height: 5rem; border-radius: 0.75rem; overflow: hidden; background: #f3f4f6; border: 1px solid #e5e7eb; margin-bottom: 0.65rem; position: relative; flex-shrink: 0; display: flex; align-items: center; justify-content: center; transition: box-shadow 0.15s, border-color 0.15s; }
|
|
|
|
|
+ .tl-thumb img { width: 100%; height: 100%; object-fit: cover; }
|
|
|
|
|
+ .tl-thumb-text { font-size: 10px; color: #6b7280; padding: 0.4rem 0.5rem; line-height: 1.4; text-align: center; word-break: break-word; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
|
|
|
|
|
+ .tl-badge { position: absolute; top: 4px; right: 4px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 9999px; background: rgba(99,102,241,0.9); color: #fff; font-size: 9px; line-height: 16px; text-align: center; z-index: 2; }
|
|
|
|
|
+ .tl-dot { width: 14px; height: 14px; border-radius: 9999px; background: #fff; border: 3px solid #d1d5db; z-index: 1; flex-shrink: 0; transition: all 0.15s; }
|
|
|
|
|
+ .tl-line { position: absolute; left: 0; right: 0; top: calc(5rem + 0.65rem + 7px - 1px); height: 2px; background: #e5e7eb; pointer-events: none; }
|
|
|
|
|
+ .tl-date { margin-top: 0.5rem; }
|
|
|
|
|
+ .tl-node:hover .tl-dot { border-color: #6366f1; }
|
|
|
|
|
+ .tl-node:hover .tl-thumb { border-color: #c7d2fe; }
|
|
|
|
|
+ .tl-node.active .tl-dot { background: #6366f1; border-color: #6366f1; box-shadow: 0 0 0 4px rgba(99,102,241,0.15); }
|
|
|
|
|
+ .tl-node.active .tl-thumb { border-color: #6366f1; box-shadow: 0 4px 14px rgba(99,102,241,0.25); }
|
|
|
|
|
+ .tl-node.active .tl-week { color: #4f46e5; font-weight: 700; }
|
|
|
|
|
+</style>
|
|
|
|
|
+{% endblock %}
|
|
|
|
|
+
|
|
|
|
|
+{% block content %}
|
|
|
|
|
+<!-- 统计 + 跳转 -->
|
|
|
|
|
+<div class="bg-white rounded-2xl border border-gray-100 p-4 mb-4 flex flex-wrap items-center gap-3">
|
|
|
|
|
+ <div class="flex flex-wrap items-center gap-2 text-xs text-gray-500">
|
|
|
|
|
+ <span class="px-2.5 py-1 rounded-lg bg-gray-50 border border-gray-100"><b class="text-gray-700">{{ nodes|length }}</b> 天</span>
|
|
|
|
|
+ <span class="px-2.5 py-1 rounded-lg bg-gray-50 border border-gray-100"><b class="text-gray-700">{{ total_records }}</b> 条记录</span>
|
|
|
|
|
+ <span class="px-2.5 py-1 rounded-lg bg-gray-50 border border-gray-100"><b class="text-gray-700">{{ total_images }}</b> 张错题</span>
|
|
|
|
|
+ <span class="px-2.5 py-1 rounded-lg bg-gray-50 border border-gray-100"><b class="text-gray-700">{{ total_texts }}</b> 条文字</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="ml-auto flex items-center gap-2">
|
|
|
|
|
+ <label class="text-xs text-gray-400">跳转日期</label>
|
|
|
|
|
+ <input type="date" value="{{ current_date }}" onchange="location.href='/timeline-new?date='+this.value"
|
|
|
|
|
+ class="border border-gray-200 rounded-xl px-3 py-1.5 text-xs focus:outline-none focus:ring-2 focus:ring-primary/30 text-gray-600">
|
|
|
|
|
+ <a href="/timeline" class="text-xs text-gray-400 hover:text-primary px-2.5 py-1.5 rounded-lg hover:bg-gray-50 transition flex items-center gap-1"><i class="fa-solid fa-clock-rotate-left"></i>旧时间线</a>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</div>
|
|
|
|
|
+
|
|
|
|
|
+{% if nodes %}
|
|
|
|
|
+<!-- 时间轴 -->
|
|
|
|
|
+<div class="bg-white rounded-2xl border border-gray-100 overflow-hidden mb-6">
|
|
|
|
|
+ <div class="px-5 pt-4 pb-2 flex items-center justify-between">
|
|
|
|
|
+ <h3 class="text-sm font-semibold text-gray-700">时间轴</h3>
|
|
|
|
|
+ <span class="text-xs text-gray-400">点击节点查看该日记录</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="tl-scroll overflow-x-auto">
|
|
|
|
|
+ <div class="tl-track relative min-w-max px-8 py-5">
|
|
|
|
|
+ <div class="tl-line"></div>
|
|
|
|
|
+ <div class="relative flex gap-2">
|
|
|
|
|
+ {% for node in nodes|reverse %}
|
|
|
|
|
+ <a href="/timeline-new?date={{ node.date }}" class="tl-node {% if node.active %}active{% endif %}">
|
|
|
|
|
+ <div class="tl-thumb">
|
|
|
|
|
+ {% if node.preview %}
|
|
|
|
|
+ <img src="/static/uploads/{{ node.preview.filename }}" alt="{{ node.preview.original_name }}" loading="lazy">
|
|
|
|
|
+ {% elif node.preview_text %}
|
|
|
|
|
+ <span class="tl-thumb-text">{{ node.preview_text.question[:24] }}</span>
|
|
|
|
|
+ {% endif %}
|
|
|
|
|
+ {% if node.total %}
|
|
|
|
|
+ <span class="tl-badge">{{ node.total }}</span>
|
|
|
|
|
+ {% endif %}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="tl-dot"></div>
|
|
|
|
|
+ <div class="tl-date">
|
|
|
|
|
+ <div class="text-xs font-medium text-gray-600">{{ node.date[5:] }}</div>
|
|
|
|
|
+ <div class="tl-week text-[10px] text-gray-400 mt-0.5">{{ node.week }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ {% endfor %}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</div>
|
|
|
|
|
+
|
|
|
|
|
+<!-- 当前节点详情 -->
|
|
|
|
|
+{% if active_node %}
|
|
|
|
|
+<section class="mb-6">
|
|
|
|
|
+ <div class="flex flex-wrap items-center gap-2 mb-4">
|
|
|
|
|
+ <i class="fa-solid fa-location-dot text-primary text-sm"></i>
|
|
|
|
|
+ <h3 class="text-sm font-semibold text-gray-700">{{ active_node.date }}({{ active_node.week }})记录</h3>
|
|
|
|
|
+ <span class="text-xs text-gray-400">共 {{ active_node.total }} 条</span>
|
|
|
|
|
+ <div class="ml-auto flex items-center gap-2 text-xs">
|
|
|
|
|
+ {% if prev_date %}<a href="/timeline-new?date={{ prev_date }}" class="px-2 py-1 rounded-lg bg-gray-50 text-gray-500 hover:bg-gray-100 transition">前一天</a>{% endif %}
|
|
|
|
|
+ {% if next_date %}<a href="/timeline-new?date={{ next_date }}" class="px-2 py-1 rounded-lg bg-gray-50 text-gray-500 hover:bg-gray-100 transition">后一天</a>{% endif %}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="space-y-5">
|
|
|
|
|
+ {% for item in active_node['items'] %}
|
|
|
|
|
+ <div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
|
|
|
|
+ <div class="px-5 py-3.5 flex items-center gap-3 border-b border-gray-50">
|
|
|
|
|
+ <span class="w-3 h-3 rounded-full flex-shrink-0" style="background-color:{{ item.subject.color }};box-shadow:0 0 0 3px {{ item.subject.color }}20"></span>
|
|
|
|
|
+ <span class="font-bold text-gray-800 text-sm">{{ item.subject.name }}</span>
|
|
|
|
|
+ <span class="text-xs text-gray-400 ml-auto">{{ item.images|length }} 张错题 · {{ item.texts|length }} 条文字</span>
|
|
|
|
|
+ <a href="/subject/{{ item.subject.id }}?date={{ active_node.date }}" class="text-xs text-primary hover:underline ml-2 flex items-center gap-1 flex-shrink-0"><i class="fa-solid fa-arrow-right"></i>进入科目</a>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="p-4">
|
|
|
|
|
+ {% if item.images %}
|
|
|
|
|
+ <div class="grid grid-cols-5 gap-3">
|
|
|
|
|
+ {% for img in item.images %}
|
|
|
|
|
+ <div class="group relative aspect-[3/4] rounded-xl overflow-hidden bg-gray-100 cursor-pointer" onclick="openLightbox('/static/uploads/{{ img.filename }}','{{ img.original_name }}',lbImgNew{{ item.subject.id }},{{ loop.index0 }})">
|
|
|
|
|
+ <img src="/static/uploads/{{ img.filename }}" class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" loading="lazy">
|
|
|
|
|
+ <div class="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition"></div>
|
|
|
|
|
+ <div class="absolute bottom-1.5 left-1.5 right-1.5">
|
|
|
|
|
+ <p class="text-[10px] text-white/80 truncate px-1 py-0.5 bg-black/30 rounded">{{ img.original_name }}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ {% endfor %}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ {% endif %}
|
|
|
|
|
+ {% if item.texts %}
|
|
|
|
|
+ <div class="{% if item.images %}border-t border-gray-50 pt-3 mt-3{% endif %}">
|
|
|
|
|
+ <div class="text-xs text-gray-400 mb-2 flex items-center gap-1"><i class="fa-solid fa-keyboard"></i>{{ item.texts|length }} 条文字记录</div>
|
|
|
|
|
+ <div class="grid grid-cols-5 gap-3">
|
|
|
|
|
+ {% for txt in item.texts %}
|
|
|
|
|
+ <div class="rounded-xl overflow-hidden bg-violet-50 border border-violet-100 cursor-pointer hover:shadow-sm transition" onclick="toggleTimelineAnswer(this)">
|
|
|
|
|
+ <div class="p-3">
|
|
|
|
|
+ <p class="text-xs font-medium text-gray-700 line-clamp-2 leading-relaxed">{{ txt.question[:40] }}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="timeline-answer hidden px-3 pb-3">
|
|
|
|
|
+ <p class="text-[11px] text-gray-500 leading-relaxed">{{ txt.answer }}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ {% endfor %}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ {% endif %}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ {% endfor %}
|
|
|
|
|
+ </div>
|
|
|
|
|
+</section>
|
|
|
|
|
+{% else %}
|
|
|
|
|
+<div class="bg-white rounded-2xl border border-gray-100 p-12 text-center">
|
|
|
|
|
+ <div class="text-5xl text-gray-200 mb-4"><i class="fa-regular fa-calendar-xmark"></i></div>
|
|
|
|
|
+ <p class="text-gray-400 mb-2">该日期暂无错题记录</p>
|
|
|
|
|
+ <p class="text-sm text-gray-300">点击上方时间轴节点或跳转其他日期</p>
|
|
|
|
|
+</div>
|
|
|
|
|
+{% endif %}
|
|
|
|
|
+{% else %}
|
|
|
|
|
+<div class="bg-white rounded-2xl border border-gray-100 p-16 text-center">
|
|
|
|
|
+ <div class="text-6xl text-gray-200 mb-4"><i class="fa-regular fa-calendar-xmark"></i></div>
|
|
|
|
|
+ <p class="text-gray-400 mb-2">暂无错题记录</p>
|
|
|
|
|
+ <p class="text-sm text-gray-300">前往科目页上传错题后,时间轴将自动显示节点</p>
|
|
|
|
|
+</div>
|
|
|
|
|
+{% endif %}
|
|
|
|
|
+{% endblock %}
|
|
|
|
|
+
|
|
|
|
|
+{% block extra_js %}
|
|
|
|
|
+<script>
|
|
|
|
|
+{% if active_node %}
|
|
|
|
|
+{% for item in active_node['items'] %}
|
|
|
|
|
+var lbImgNew{{ item.subject.id }} = [
|
|
|
|
|
+ {% for img in item.images %}"/static/uploads/{{ img.filename }}",{% endfor %}
|
|
|
|
|
+];
|
|
|
|
|
+{% endfor %}
|
|
|
|
|
+{% endif %}
|
|
|
|
|
+function toggleTimelineAnswer(el) {
|
|
|
|
|
+ var answer = el.querySelector('.timeline-answer');
|
|
|
|
|
+ if (answer) answer.classList.toggle('hidden');
|
|
|
|
|
+}
|
|
|
|
|
+// 加载时将当前选中节点滚动到视野中央
|
|
|
|
|
+(function(){
|
|
|
|
|
+ var wrap = document.querySelector('.tl-scroll');
|
|
|
|
|
+ var active = document.querySelector('.tl-node.active');
|
|
|
|
|
+ if (wrap && active) {
|
|
|
|
|
+ var target = active.offsetLeft + active.offsetWidth / 2 - wrap.clientWidth / 2;
|
|
|
|
|
+ wrap.scrollTo({ left: Math.max(0, target), behavior: 'smooth' });
|
|
|
|
|
+ }
|
|
|
|
|
+})();
|
|
|
|
|
+</script>
|
|
|
|
|
+{% endblock %}
|