瀏覽代碼

提交docker版本,解决日期更新问题

sequoia00 2 周之前
父節點
當前提交
83e71421c6
共有 37 個文件被更改,包括 350 次插入 和 6 次删除
  1. 5 0
      .dockerignore
  2. 7 0
      .env
  3. 18 0
      Dockerfile
  4. 16 0
      README.md
  5. 二進制
      __pycache__/app.cpython-311.pyc
  6. 二進制
      __pycache__/app.cpython-313.pyc
  7. 58 6
      app.py
  8. 10 0
      docker-compose.yml
  9. 48 0
      nohup.out
  10. 6 0
      requirements.txt
  11. 二進制
      static/uploads/43d9870c9b944a97b328b828d391ee1f.jpg
  12. 二進制
      static/uploads/44a329d2e0fa476180b007d96e2549e1.jpg
  13. 二進制
      static/uploads/61d74c330fa74fc8a03e451ae51b9ec9.jpg
  14. 二進制
      static/uploads/7099cb5a4e804367b386a33da3231fce.jpg
  15. 二進制
      static/uploads/d1c6e5189c7f44b3981b5a77bdaed2d5.jpg
  16. 6 0
      templates/base.html
  17. 5 0
      templates/subject_detail.html
  18. 171 0
      templates/timeline_new.html
  19. 二進制
      vendor/annotated_types-0.8.0-py3-none-any.whl
  20. 二進制
      vendor/anyio-4.15.1-py3-none-any.whl
  21. 二進制
      vendor/click-8.5.0-py3-none-any.whl
  22. 二進制
      vendor/fastapi-0.115.5-py3-none-any.whl
  23. 二進制
      vendor/greenlet-3.5.6-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
  24. 二進制
      vendor/h11-0.16.0-py3-none-any.whl
  25. 二進制
      vendor/idna-3.20-py3-none-any.whl
  26. 二進制
      vendor/jinja2-3.1.4-py3-none-any.whl
  27. 二進制
      vendor/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
  28. 二進制
      vendor/pydantic-2.13.5-py3-none-any.whl
  29. 二進制
      vendor/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
  30. 二進制
      vendor/pymysql-1.1.2-py3-none-any.whl
  31. 二進制
      vendor/python_multipart-0.0.10-py3-none-any.whl
  32. 二進制
      vendor/sqlalchemy-2.0.54-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
  33. 二進制
      vendor/sqlmodel-0.0.42-py3-none-any.whl
  34. 二進制
      vendor/starlette-0.41.3-py3-none-any.whl
  35. 二進制
      vendor/typing_extensions-4.16.0-py3-none-any.whl
  36. 二進制
      vendor/typing_inspection-0.4.4-py3-none-any.whl
  37. 二進制
      vendor/uvicorn-0.32.0-py3-none-any.whl

+ 5 - 0
.dockerignore

@@ -0,0 +1,5 @@
+.git
+__pycache__
+nohup.out
+*.db
+.env

+ 7 - 0
.env

@@ -0,0 +1,7 @@
+# MySQL 连接配置(host 网络模式下 127.0.0.1 即本机 MySQL)
+DB_HOST=127.0.0.1
+DB_PORT=3306
+DB_USER=root
+DB_PASS=792199Zhao*
+DB_NAME=cuowuben
+APP_PORT=8000

+ 18 - 0
Dockerfile

@@ -0,0 +1,18 @@
+FROM python:3.13-slim
+
+WORKDIR /app
+
+# 离线安装:wheel 预先下载到 vendor/(pip download -r requirements.txt -d vendor/)
+COPY requirements.txt ./
+COPY vendor/ ./vendor/
+RUN pip install --no-cache-dir --no-index --find-links vendor -r requirements.txt
+
+COPY . .
+
+# host 网络模式下 127.0.0.1 即宿主机 MySQL
+ENV DB_HOST=127.0.0.1 \
+    DB_PORT=3306
+
+EXPOSE 8000
+
+CMD ["sh", "-c", "uvicorn app:app --host 0.0.0.0 --port ${APP_PORT:-8000}"]

+ 16 - 0
README.md

@@ -32,3 +32,19 @@ mysql -u root -p < setup_mysql.sql
 - 点击"科目管理"可收起/展开其下的科目子列表
 - 点击左上角箭头按钮可收起整个侧边栏
 - 收起状态通过 localStorage 记忆,刷新页面保持状态
+
+## Docker 一键运行
+
+前提:本机已运行 MySQL,并执行过 `setup_mysql.sql` 初始化 `cuowuben` 库。
+
+```bash
+cd /home/myprojector/manyi_review
+docker compose up -d --build
+```
+
+- 容器使用 host 网络模式,`DB_HOST=127.0.0.1` 直接连接**本机 MySQL**,无需端口映射
+- 数据库连接参数在 `.env` 中配置(DB_HOST / DB_PORT / DB_USER / DB_PASS / DB_NAME)
+- 服务端口由 `.env` 中 `APP_PORT` 控制,默认 8000
+- 依赖通过 `vendor/` 目录离线安装(容器网络不可达 pypi);升级依赖后执行:
+  `pip download -r requirements.txt -d vendor/`
+- 停止/查看:`docker compose down`、`docker logs -f manyi-review`

二進制
__pycache__/app.cpython-311.pyc


二進制
__pycache__/app.cpython-313.pyc


+ 58 - 6
app.py

@@ -4,7 +4,7 @@ from fastapi.templating import Jinja2Templates
 from fastapi.responses import HTMLResponse, RedirectResponse
 from sqlmodel import Session
 from pathlib import Path
-import uuid, os
+import uuid, os, calendar
 from datetime import datetime
 
 from database import engine, init_db, get_session
@@ -71,12 +71,10 @@ async def subject_detail(
     if not subject:
         raise HTTPException(404, "科目不存在")
     dates = crud.get_dates_for_subject(session, subject_id)
+    today = datetime.now().strftime("%Y-%m-%d")
     if not date:
-        date = dates[0] if dates else datetime.now().strftime("%Y-%m-%d")
+        date = today
     images = crud.get_subject_images(session, subject_id, date)
-    if not images and dates:
-        date = dates[0]
-        images = crud.get_subject_images(session, subject_id, date)
     prev_date = None
     next_date = None
     if dates and date in dates:
@@ -87,7 +85,7 @@ async def subject_detail(
             next_date = dates[idx - 1]
     return templates.TemplateResponse("subject_detail.html", {
         "request": request, "subject": subject, "dates": dates,
-        "current_date": date, "images": images,
+        "current_date": date, "today": today, "images": images,
         "prev_date": prev_date, "next_date": next_date,
         "subjects": crud.get_all_subjects(session)
     })
@@ -165,6 +163,60 @@ async def timeline(request: Request, date: str | None = None, session: Session =
     })
 
 # ── 复习汇总 ────────────────────────────────────────────
+# ── 时间线 New(对比样式) ───────────────────────────
+@app.get("/timeline-new", response_class=HTMLResponse)
+async def timeline_new(request: Request, date: str | None = None, session: Session = Depends(get_session)):
+    subjects = crud.get_all_subjects(session)
+    all_dates = crud.get_all_dates(session)
+    if not date:
+        date = all_dates[0] if all_dates else datetime.now().strftime("%Y-%m-%d")
+
+    nodes = []
+    for d in all_dates:
+        items = []
+        for s in subjects:
+            imgs = crud.get_subject_images(session, s.id, d)
+            if imgs:
+                image_items = [img for img in imgs if img.filename]
+                text_items = [img for img in imgs if not img.filename]
+                items.append({"subject": s, "images": image_items, "texts": text_items})
+        if not items:
+            continue
+        nodes.append({
+            "date": d,
+            "active": d == date,
+            "total": sum(len(it["images"]) + len(it["texts"]) for it in items),
+            "image_count": sum(len(it["images"]) for it in items),
+            "text_count": sum(len(it["texts"]) for it in items),
+            "subject_count": len(items),
+            "preview": next((img for it in items for img in it["images"]), None),
+            "preview_text": next((txt for it in items for txt in it["texts"]), None),
+            "items": items,
+            "week": "周" + "一二三四五六日"[calendar.weekday(*[int(x) for x in d.split("-")])],
+        })
+
+    active_node = next((n for n in nodes if n["active"]), None)
+    total_records = sum(n["total"] for n in nodes)
+    total_images = sum(n["image_count"] for n in nodes)
+    total_texts = sum(n["text_count"] for n in nodes)
+
+    prev_date = None
+    next_date = None
+    if all_dates and date in all_dates:
+        idx = all_dates.index(date)
+        if idx < len(all_dates) - 1:
+            prev_date = all_dates[idx + 1]
+        if idx > 0:
+            next_date = all_dates[idx - 1]
+
+    return templates.TemplateResponse("timeline_new.html", {
+        "request": request, "subjects": subjects, "all_dates": all_dates,
+        "current_date": date, "nodes": nodes,
+        "active_node": active_node, "total_records": total_records,
+        "total_images": total_images, "total_texts": total_texts,
+        "prev_date": prev_date, "next_date": next_date
+    })
+
 @app.get("/review", response_class=HTMLResponse)
 async def review_page(request: Request, session: Session = Depends(get_session)):
     data = crud.get_review_data(session)

+ 10 - 0
docker-compose.yml

@@ -0,0 +1,10 @@
+services:
+  app:
+    build: .
+    image: manyi-review:latest
+    container_name: manyi-review
+    restart: unless-stopped
+    # host 网络模式:容器直接使用宿主机网络,可连接本机 MySQL
+    network_mode: host
+    env_file:
+      - .env

File diff suppressed because it is too large
+ 48 - 0
nohup.out


+ 6 - 0
requirements.txt

@@ -0,0 +1,6 @@
+fastapi==0.115.5
+uvicorn==0.32.0
+sqlmodel==0.0.42
+PyMySQL==1.1.2
+Jinja2==3.1.4
+python-multipart==0.0.10

二進制
static/uploads/43d9870c9b944a97b328b828d391ee1f.jpg


二進制
static/uploads/44a329d2e0fa476180b007d96e2549e1.jpg


二進制
static/uploads/61d74c330fa74fc8a03e451ae51b9ec9.jpg


二進制
static/uploads/7099cb5a4e804367b386a33da3231fce.jpg


二進制
static/uploads/d1c6e5189c7f44b3981b5a77bdaed2d5.jpg


+ 6 - 0
templates/base.html

@@ -113,6 +113,12 @@
           <span class="sidebar-text text-sm font-medium">知识评价</span>
         </a>
 
+        <!-- 时间线 New -->
+        <a href="/timeline-new" 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-new' in request.url.path %}active{% endif %}">
+          <i class="fa-solid fa-timeline w-5 text-center text-base flex-shrink-0"></i>
+          <span class="sidebar-text text-sm font-medium">时间线 New</span>
+        </a>
+
         <!-- 时间线 -->
         <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>

+ 5 - 0
templates/subject_detail.html

@@ -24,6 +24,11 @@
     </span>
     {% endif %}
 
+    {% if today not in dates %}
+    <a href="?date={{ today }}" class="date-chip flex-shrink-0 px-3 py-1.5 rounded-xl text-xs font-medium transition {% if current_date == today %}bg-primary text-white shadow-sm{% else %}bg-gray-50 text-gray-500 hover:bg-gray-100{% endif %}">
+      今日
+    </a>
+    {% endif %}
     {% for d in dates %}
     <a href="?date={{ d }}" class="date-chip flex-shrink-0 px-3 py-1.5 rounded-xl text-xs font-medium transition {% if d == current_date %}bg-primary text-white shadow-sm{% else %}bg-gray-50 text-gray-500 hover:bg-gray-100{% endif %}">
       {{ d }}

+ 171 - 0
templates/timeline_new.html

@@ -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 %}

二進制
vendor/annotated_types-0.8.0-py3-none-any.whl


二進制
vendor/anyio-4.15.1-py3-none-any.whl


二進制
vendor/click-8.5.0-py3-none-any.whl


二進制
vendor/fastapi-0.115.5-py3-none-any.whl


二進制
vendor/greenlet-3.5.6-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl


二進制
vendor/h11-0.16.0-py3-none-any.whl


二進制
vendor/idna-3.20-py3-none-any.whl


二進制
vendor/jinja2-3.1.4-py3-none-any.whl


二進制
vendor/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl


二進制
vendor/pydantic-2.13.5-py3-none-any.whl


二進制
vendor/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl


二進制
vendor/pymysql-1.1.2-py3-none-any.whl


二進制
vendor/python_multipart-0.0.10-py3-none-any.whl


二進制
vendor/sqlalchemy-2.0.54-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl


二進制
vendor/sqlmodel-0.0.42-py3-none-any.whl


二進制
vendor/starlette-0.41.3-py3-none-any.whl


二進制
vendor/typing_extensions-4.16.0-py3-none-any.whl


二進制
vendor/typing_inspection-0.4.4-py3-none-any.whl


二進制
vendor/uvicorn-0.32.0-py3-none-any.whl


Some files were not shown because too many files changed in this diff