Add word-book practice, iOS app shell, and fix embedded WebView blank screen.

Ship dual-track learning (daily accumulation vs textbook),沪教/商务词书 APIs and UI, native iOS wrapper with bundled H5, and production book import on deploy.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-06-06 21:09:27 +08:00
parent 2fae90b597
commit e76fa586f1
76 changed files with 47488 additions and 445 deletions
+17 -1
View File
@@ -6,6 +6,8 @@ from sqlalchemy.orm import Session
from models import QuizRecord, Word, User
ACCUMULATION_BOOK_ID = 0
def utc_now_iso() -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
@@ -20,10 +22,12 @@ def calc_mastery_score(correct: int, wrong: int) -> int:
class WordService:
def create_word(self, db: Session, user: User, data: dict) -> Word:
book_id = data.get("book_id") or ACCUMULATION_BOOK_ID
existing = (
db.query(Word)
.filter(
Word.user_id == user.id,
Word.book_id == book_id,
Word.source_text == data["source_text"],
Word.target_text == data["target_text"],
)
@@ -34,6 +38,8 @@ class WordService:
word = Word(
user_id=user.id,
book_id=book_id,
book_entry_id=data.get("book_entry_id"),
source_text=data["source_text"],
target_text=data["target_text"],
source_lang=data["source_lang"],
@@ -54,8 +60,18 @@ class WordService:
db.refresh(word)
return word
def list_words(self, db: Session, user: User, status: Optional[str] = None) -> list[Word]:
def list_words(
self,
db: Session,
user: User,
status: Optional[str] = None,
book_id: Optional[int] = None,
) -> list[Word]:
q = db.query(Word).filter(Word.user_id == user.id)
if book_id is None:
q = q.filter(Word.book_id == ACCUMULATION_BOOK_ID)
elif book_id > 0:
q = q.filter(Word.book_id == book_id)
if status:
q = q.filter(Word.status == status)
return q.order_by(Word.created_at.desc()).all()