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
+59 -1
View File
@@ -1,4 +1,4 @@
from sqlalchemy import Column, ForeignKey, Integer, String, Text
from sqlalchemy import Column, ForeignKey, Integer, String, Text, UniqueConstraint
from database import Base
@@ -12,11 +12,54 @@ class User(Base):
created_at = Column(String(32), nullable=False)
class WordBook(Base):
"""全局词书目录(部署时导入)。"""
__tablename__ = "word_books"
id = Column(Integer, primary_key=True, autoincrement=True)
slug = Column(String(64), unique=True, nullable=False, index=True)
title = Column(String(128), nullable=False)
description = Column(Text, nullable=True)
level = Column(String(32), nullable=False)
word_count = Column(Integer, nullable=False, default=0)
unit_count = Column(Integer, nullable=False, default=0)
sort_order = Column(Integer, nullable=False, default=0)
daily_target = Column(Integer, nullable=False, default=15)
master_required_count = Column(Integer, nullable=False, default=4)
weak_wrong_threshold = Column(Integer, nullable=False, default=2)
learn_mode = Column(String(32), nullable=False, default="sequential")
is_published = Column(Integer, nullable=False, default=1)
class WordBookEntry(Base):
__tablename__ = "word_book_entries"
__table_args__ = (UniqueConstraint("book_id", "lemma_en", name="uq_book_lemma"),)
id = Column(Integer, primary_key=True, autoincrement=True)
book_id = Column(Integer, ForeignKey("word_books.id"), nullable=False, index=True)
unit = Column(Integer, nullable=False, default=1)
unit_title = Column(String(128), nullable=True)
sort_index = Column(Integer, nullable=False, index=True)
lemma_en = Column(String(128), nullable=False)
zh = Column(String(256), nullable=False)
phonetic = Column(String(128), nullable=True)
example_en = Column(Text, nullable=True)
example_cn = Column(Text, nullable=True)
class Word(Base):
__tablename__ = "words"
__table_args__ = (
UniqueConstraint(
"user_id", "book_id", "source_text", "target_text", name="uq_user_book_word"
),
)
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
book_id = Column(Integer, nullable=False, default=0, index=True)
book_entry_id = Column(Integer, ForeignKey("word_book_entries.id"), nullable=True)
source_text = Column(String(500), nullable=False)
target_text = Column(String(500), nullable=False)
source_lang = Column(String(8), nullable=False)
@@ -58,6 +101,21 @@ class UserSettings(Base):
daily_target = Column(Integer, nullable=False, default=20)
master_required_count = Column(Integer, nullable=False, default=3)
weak_wrong_threshold = Column(Integer, nullable=False, default=3)
active_book_id = Column(Integer, ForeignKey("word_books.id"), nullable=True)
class UserBookSettings(Base):
"""用户对某本词书的练习参数(每本书独立保存)。"""
__tablename__ = "user_book_settings"
__table_args__ = (UniqueConstraint("user_id", "book_id", name="uq_user_book_settings"),)
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
book_id = Column(Integer, ForeignKey("word_books.id"), nullable=False, index=True)
daily_target = Column(Integer, nullable=False, default=12)
master_required_count = Column(Integer, nullable=False, default=3)
weak_wrong_threshold = Column(Integer, nullable=False, default=2)
class DictionaryEntry(Base):