ソースを参照

feat: 时间线/复习汇总/首页支持文字错题展示

sequoia00 1 ヶ月 前
親
コミット
de725c7be7
4 ファイル変更、148 行追加、18 行削除
  1. 46 0
      nohup.out
  2. 16 2
      templates/index.html
  3. 33 4
      templates/review.html
  4. 53 12
      templates/timeline.html

+ 46 - 0
nohup.out

@@ -407,3 +407,49 @@ Traceback (most recent call last):
     ^^^^^^^^^^^^^^^^^^^
 jinja2.exceptions.TemplateSyntaxError: tag name expected
 INFO:     127.0.0.1:35226 - "GET / HTTP/1.1" 200 OK
+INFO:     Started server process [2083557]
+INFO:     Waiting for application startup.
+INFO:     Application startup complete.
+INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
+INFO:     219.146.255.202:55192 - "GET /subject/1 HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55193 - "GET /subject/1 HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55192 - "GET /static/uploads/e4e1cae0534f47f988212620b70e0c0e.png HTTP/1.1" 304 Not Modified
+INFO:     219.146.255.202:55213 - "GET /subject/2 HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55215 - "GET /subject/2 HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55213 - "GET /subject/4 HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55217 - "GET /subject/4 HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55213 - "GET /subject/5 HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55219 - "GET /subject/5 HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55213 - "GET /subject/1 HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55221 - "GET /subject/1 HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55225 - "GET /subjects HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55226 - "GET /subjects HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55225 - "GET /subjects HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55225 - "GET /evaluations HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55230 - "GET /evaluations HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55225 - "GET /static/uploads/eval_56fae51507aa4db89424a9ad51fdd8e4.jpg HTTP/1.1" 304 Not Modified
+INFO:     219.146.255.202:55225 - "GET /subject/1 HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55232 - "GET /subject/1 HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55225 - "GET /subject/2 HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55233 - "GET /subject/2 HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55225 - "GET /subjects HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55239 - "GET /subjects HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55225 - "GET /evaluations HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55241 - "GET /evaluations HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55225 - "GET /timeline HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55242 - "GET /timeline HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55225 - "GET /static/uploads/ HTTP/1.1" 404 Not Found
+INFO:     219.146.255.202:55246 - "GET /static/uploads/ HTTP/1.1" 404 Not Found
+INFO:     219.146.255.202:55338 - "GET /subject/1 HTTP/1.1" 200 OK
+INFO:     219.146.255.202:55339 - "GET /subject/1 HTTP/1.1" 200 OK
+INFO:     127.0.0.1:12278 - "GET / HTTP/1.1" 200 OK
+INFO:     127.0.0.1:12290 - "GET /subjects HTTP/1.1" 200 OK
+INFO:     127.0.0.1:12298 - "GET /subject/1 HTTP/1.1" 200 OK
+INFO:     127.0.0.1:12312 - "GET /timeline HTTP/1.1" 200 OK
+INFO:     127.0.0.1:12322 - "GET /review HTTP/1.1" 200 OK
+INFO:     127.0.0.1:12336 - "GET /evaluations HTTP/1.1" 200 OK
+INFO:     127.0.0.1:12352 - "GET /timeline HTTP/1.1" 200 OK
+INFO:     127.0.0.1:12362 - "GET /review HTTP/1.1" 200 OK
+INFO:     127.0.0.1:12372 - "GET / HTTP/1.1" 200 OK
+INFO:     127.0.0.1:12266 - "GET /timeline HTTP/1.1" 200 OK
+INFO:     127.0.0.1:12274 - "GET /timeline HTTP/1.1" 200 OK

+ 16 - 2
templates/index.html

@@ -71,12 +71,25 @@
           <span class="font-semibold text-gray-700 text-sm">{{ subject.name }}</span>
           <span class="ml-auto text-xs text-gray-400">{{ imgs|length }}张</span>
         </div>
+        {% set t_imgs = [] %}
+        {% set t_txts = [] %}
+        {% for img in imgs %}{% if img.filename %}{% set x = t_imgs.append(img) %}{% else %}{% set x = t_txts.append(img) %}{% endif %}{% endfor %}
         <div class="p-4 grid grid-cols-2 gap-3">
-          {% for img in imgs[:4] %}
+          {% for img in t_imgs[:4] %}
           <div class="aspect-[3/4] rounded-xl overflow-hidden bg-gray-100 group relative cursor-pointer" onclick="openLightbox('/static/uploads/{{ img.filename }}','{{ img.original_name }}',todayImg{{ 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>
           {% endfor %}
+          {% for txt in t_txts[:4 - t_imgs[:4]|length] if t_imgs[:4]|length < 4 %}
+          <div class="aspect-[3/4] rounded-xl overflow-hidden bg-violet-50 border border-violet-100 cursor-pointer hover:shadow-sm transition flex flex-col" onclick="toggleIndexAnswer(this, {{ subject_id }}, {{ loop.index0 }})">
+            <div class="p-3 flex-1">
+              <p class="text-xs font-medium text-gray-700 line-clamp-3 leading-relaxed">{{ txt.question[:40] }}</p>
+            </div>
+            <div class="index-answer hidden px-3 pb-3">
+              <p class="text-[11px] text-gray-500 leading-relaxed">{{ txt.answer }}</p>
+            </div>
+          </div>
+          {% endfor %}
         </div>
         <div class="px-5 py-2.5 bg-gray-50 border-t border-gray-100">
           <a href="/subject/{{ subject_id }}?date={{ today }}" class="text-xs text-primary font-medium hover:underline flex items-center gap-1">
@@ -135,7 +148,8 @@
 <script>
 {% for subject_id, imgs in today_images.items() %}
 const todayImg{{ subject_id }} = [
-  {% for img in imgs %}" /static/uploads/{{ img.filename }}",{% endfor %}
+  {% for img in imgs %}{% if img.filename %}" /static/uploads/{{ img.filename }}",{% endif %}
+  {% endfor %}
 ];
 {% endfor %}
 </script>

+ 33 - 4
templates/review.html

@@ -36,14 +36,18 @@
       {% set day_images = [] %}
       {% for img in data.images if img.date == date_str %}{% set x = day_images.append(img) %}{% endfor %}
       <div class="{% if not loop.last %}border-b border-gray-50{% endif %}">
+        {% set day_imgs = [] %}
+        {% set day_txts = [] %}
+        {% for img in day_images %}{% if img.filename %}{% set x = day_imgs.append(img) %}{% else %}{% set x = day_txts.append(img) %}{% endif %}{% endfor %}
         <div class="px-5 py-2.5 bg-gray-50 flex items-center gap-3">
           <i class="fa-regular fa-calendar text-gray-400 text-sm"></i>
           <span class="text-sm font-semibold text-gray-600">{{ date_str }}</span>
-          <span class="text-xs text-gray-400">{{ day_images|length }} 张</span>
+          <span class="text-xs text-gray-400">{{ day_imgs|length }} 张{% if day_txts %} · {{ day_txts|length }} 条文字{% endif %}</span>
         </div>
+        {% if day_imgs %}
         <div class="p-4 grid grid-cols-5 gap-3">
-          {% for img in day_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 }}',reviewLb{{ subject_id }}{{ loop.index0 }},{{ loop.index0 }})">
+          {% for img in day_imgs %}
+          <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 }}',reviewLb{{ 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">
@@ -52,6 +56,26 @@
           </div>
           {% endfor %}
         </div>
+        {% endif %}
+        {% if day_txts %}
+        <div class="px-4 pb-3">
+          <div class="flex items-center gap-2 mb-2">
+            <span class="text-xs text-gray-400 flex items-center gap-1"><i class="fa-solid fa-keyboard"></i>{{ day_txts|length }} 条文字记录</span>
+          </div>
+          <div class="grid grid-cols-5 gap-3">
+            {% for txt in day_txts %}
+            <div class="rounded-xl overflow-hidden bg-violet-50 border border-violet-100 cursor-pointer hover:shadow-sm transition" onclick="toggleReviewAnswer(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="review-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>
       {% endfor %}
     </div>
@@ -74,8 +98,13 @@
 <script>
 {% for subject_id, data in review_data.items() %}
 const reviewLb{{ subject_id }} = [
-  {% for img in data.images %}" /static/uploads/{{ img.filename }}",{% endfor %}
+  {% for img in data.images %}{% if img.filename %}" /static/uploads/{{ img.filename }}",{% endif %}
+  {% endfor %}
 ];
 {% endfor %}
+function toggleReviewAnswer(el) {
+  var ans = el.querySelector('.review-answer');
+  if (ans) ans.classList.toggle('hidden');
+}
 </script>
 {% endblock %}

+ 53 - 12
templates/timeline.html

@@ -40,23 +40,50 @@
 {% if timeline_data %}
 <div class="space-y-5">
   {% for item in timeline_data %}
-  <div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
+  <div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden" data-subject="{{ item.subject.id }}">
     <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 }} 张错题</span>
+      <span class="text-xs text-gray-400 ml-auto" id="count-{{ item.subject.id }}">加载中...</span>
       <a href="/subject/{{ item.subject.id }}?date={{ current_date }}" class="text-xs text-primary hover:underline ml-2 flex items-center gap-1"><i class="fa-solid fa-arrow-right"></i>进入科目</a>
     </div>
-    <div class="p-4 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 }}',lb{{ 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>
+    {% set txt_list = [] %}
+    {% set img_list = [] %}
+    {% for img in item.images %}{% if img.filename %}{% set x = img_list.append(img) %}{% else %}{% set x = txt_list.append(img) %}{% endif %}{% endfor %}
+    <div class="p-4">
+      {% if img_list %}
+      <div class="grid grid-cols-5 gap-3 mb-3">
+        {% for img in img_list %}
+        <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 }}',lbImg{{ 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>
-      {% endfor %}
+      {% endif %}
+      {% if txt_list %}
+      <div class="border-t border-gray-50 pt-3 mt-1">
+        <div class="text-xs text-gray-400 mb-2 flex items-center gap-1"><i class="fa-solid fa-keyboard"></i>{{ txt_list|length }} 条文字记录</div>
+        <div class="grid grid-cols-5 gap-3">
+          {% for txt in txt_list %}
+          <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 class="px-5 py-2 bg-gray-50 border-t border-gray-50 flex items-center gap-3">
+      <span class="text-xs text-gray-400">{{ img_list|length }} 张错题{% if txt_list %} · {{ txt_list|length }} 条文字{% endif %}</span>
     </div>
   </div>
   {% endfor %}
@@ -73,9 +100,23 @@
 {% block extra_js %}
 <script>
 {% for item in timeline_data %}
-const lb{{ item.subject.id }} = [
-  {% for img in item.images %}" /static/uploads/{{ img.filename }}",{% endfor %}
+const lbImg{{ item.subject.id }} = [
+  {% for img in item.images %}{% if img.filename %}" /static/uploads/{{ img.filename }}",{% endif %}
+  {% endfor %}
 ];
 {% endfor %}
+document.querySelectorAll('[id^="count-"]').forEach(function(el) {
+  var id = el.id.replace('count-', '');
+  var items = document.querySelectorAll('#timeline-subject-' + id + ' .timeline-entry');
+  var imgCount = document.querySelectorAll('#timeline-subject-' + id + ' .img-entry').length;
+  var txtCount = document.querySelectorAll('#timeline-subject-' + id + ' .txt-entry').length;
+  if (txtCount > 0 && imgCount > 0) el.textContent = imgCount + ' 张错题 · ' + txtCount + ' 条文字';
+  else if (txtCount > 0) el.textContent = txtCount + ' 条文字';
+  else el.textContent = imgCount + ' 张错题';
+});
+function toggleTimelineAnswer(el) {
+  var answer = el.querySelector('.timeline-answer');
+  if (answer) answer.classList.toggle('hidden');
+}
 </script>
 {% endblock %}