feat: 首页仪表盘改造为深色科技风格 + Apple Health 步数同步
UI 改造: - 首页采用深色科技风格 (Dark Tech) - Tailwind CSS + Glass Morphism 毛玻璃效果 - 左侧边栏导航 (PC端) / 底部导航 (移动端) - Material Symbols 图标 + Space Grotesk 字体 - Chart.js 深色主题适配 新功能: - Apple Health 步数同步 API (/api/steps) - 设置页面添加 iOS 快捷指令配置说明 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -153,6 +153,9 @@ class DeepSeekVisionAnalyzer:
|
||||
| GET | `/api/meals/nutrition` | 营养统计 |
|
||||
| POST | `/api/exercise` | 添加运动记录 |
|
||||
| GET | `/api/exercises` | 查询运动记录 |
|
||||
| POST | `/api/steps` | 同步步数(iOS 快捷指令调用) |
|
||||
| GET | `/api/steps` | 查询步数记录 |
|
||||
| GET | `/api/steps/today` | 获取今日步数 |
|
||||
| POST | `/api/sleep` | 添加睡眠记录 |
|
||||
| POST | `/api/weight` | 添加体重记录 |
|
||||
| GET | `/api/config` | 获取用户配置 |
|
||||
@@ -216,6 +219,9 @@ weight (id, user_id, date, weight, body_fat, notes)
|
||||
-- 阅读记录
|
||||
reading (id, user_id, date, book_title, pages, duration, notes)
|
||||
|
||||
-- 步数记录
|
||||
steps (id, user_id, date, steps, distance, calories, source, synced_at)
|
||||
|
||||
-- 用户配置
|
||||
user_config (id, user_id, height, weight, age, gender, activity_level, goal)
|
||||
|
||||
@@ -258,6 +264,11 @@ invite (id, code, created_by, used_by, used_at, expires_at)
|
||||
|
||||
## 最近更新
|
||||
|
||||
- **Apple Health 步数同步**:支持通过 iOS 快捷指令同步 Apple Health 步数数据
|
||||
- 数据库:新增 `steps` 表存储每日步数
|
||||
- API:`/api/steps` (POST 同步, GET 查询), `/api/steps/today` (今日步数)
|
||||
- 前端:运动页面显示今日步数卡片,设置页面提供 iOS 快捷指令配置说明
|
||||
|
||||
- **文字 AI 识别功能**:在饮食页面添加"文字AI识别"按钮,支持输入文字描述后自动识别卡路里
|
||||
- 前端:`web/app.py` (第 5041 行按钮,第 5166 行函数)
|
||||
- 后端:`/api/meal/recognize` 支持 `text` 参数
|
||||
|
||||
@@ -200,6 +200,22 @@ def init_db():
|
||||
)
|
||||
""")
|
||||
|
||||
# 步数记录表
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS steps (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INT DEFAULT 1,
|
||||
date DATE NOT NULL,
|
||||
steps INT NOT NULL DEFAULT 0,
|
||||
distance FLOAT,
|
||||
calories INT,
|
||||
source VARCHAR(50) DEFAULT 'apple_health',
|
||||
synced_at DATETIME NOT NULL,
|
||||
UNIQUE KEY unique_user_date (user_id, date),
|
||||
INDEX idx_steps_date (date)
|
||||
)
|
||||
""")
|
||||
|
||||
# 创建默认管理员用户(仅在用户表为空时)
|
||||
cursor.execute("SELECT COUNT(*) as count FROM users")
|
||||
if cursor.fetchone()["count"] == 0:
|
||||
@@ -277,6 +293,74 @@ def delete_exercise(exercise_id: int):
|
||||
cursor.execute("DELETE FROM exercise WHERE id = %s", (exercise_id,))
|
||||
|
||||
|
||||
# ===== 步数记录 =====
|
||||
|
||||
def add_or_update_steps(user_id: int, record_date: date, steps: int, distance: float = None, calories: int = None, source: str = "apple_health") -> int:
|
||||
"""添加或更新步数记录(同一天只保留一条记录)"""
|
||||
with get_connection() as (conn, cursor):
|
||||
cursor.execute("""
|
||||
INSERT INTO steps (user_id, date, steps, distance, calories, source, synced_at)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
steps = VALUES(steps),
|
||||
distance = VALUES(distance),
|
||||
calories = VALUES(calories),
|
||||
source = VALUES(source),
|
||||
synced_at = VALUES(synced_at)
|
||||
""", (
|
||||
user_id,
|
||||
record_date.isoformat(),
|
||||
steps,
|
||||
distance,
|
||||
calories,
|
||||
source,
|
||||
datetime.now().isoformat(),
|
||||
))
|
||||
return cursor.lastrowid
|
||||
|
||||
|
||||
def get_steps(start_date: Optional[date] = None, end_date: Optional[date] = None, user_id: Optional[int] = None) -> list[dict]:
|
||||
"""查询步数记录"""
|
||||
with get_connection() as (conn, cursor):
|
||||
query = "SELECT * FROM steps WHERE 1=1"
|
||||
params = []
|
||||
|
||||
if user_id:
|
||||
query += " AND user_id = %s"
|
||||
params.append(user_id)
|
||||
if start_date:
|
||||
query += " AND date >= %s"
|
||||
params.append(start_date.isoformat())
|
||||
if end_date:
|
||||
query += " AND date <= %s"
|
||||
params.append(end_date.isoformat())
|
||||
|
||||
query += " ORDER BY date DESC"
|
||||
cursor.execute(query, params)
|
||||
|
||||
return [
|
||||
{
|
||||
"id": row["id"],
|
||||
"date": _parse_date(row["date"]).isoformat() if isinstance(row["date"], str) else row["date"].isoformat(),
|
||||
"steps": row["steps"],
|
||||
"distance": row["distance"],
|
||||
"calories": row["calories"],
|
||||
"source": row["source"],
|
||||
"synced_at": row["synced_at"].isoformat() if row["synced_at"] else None,
|
||||
}
|
||||
for row in cursor.fetchall()
|
||||
]
|
||||
|
||||
|
||||
def get_today_steps(user_id: int) -> dict:
|
||||
"""获取今日步数"""
|
||||
today = date.today()
|
||||
records = get_steps(start_date=today, end_date=today, user_id=user_id)
|
||||
if records:
|
||||
return records[0]
|
||||
return {"date": today.isoformat(), "steps": 0, "distance": None, "calories": None}
|
||||
|
||||
|
||||
# ===== 饮食记录 =====
|
||||
|
||||
def add_meal(meal: Meal, user_id: int = 1) -> int:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user