feat: 修复 H5 底部导航覆盖 + 更新项目进度文档

## H5 底部导航修复 (Bug #10)
- 精简 App.vue,移除重复 tabbar,仅保留全局样式
- uni-page 设置 height: calc(100% - 50px) + overflow-y: auto
- 内容区域精确停在底部导航上方,独立滚动不再叠加
- 恢复 custom-tab-bar 组件

## 项目进度文档
- PROGRESS.md 更新至 10 个 Bug 修复
- 新增 H5 底部导航修复记录
- 新增历史变更条目
This commit is contained in:
TradeMate Dev
2026-05-12 20:24:42 +08:00
parent 69e164dcae
commit 7b62c2f8b4
125 changed files with 19725 additions and 728 deletions
+89 -20
View File
@@ -1,8 +1,13 @@
from fastapi import APIRouter, HTTPException
from typing import Optional, Dict, Any
from fastapi import APIRouter, HTTPException, Response, Depends
from typing import Optional, Dict, Any, Annotated
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.services.translation import TranslationService
from app.services.tts import tts_service
from app.services.preference import UserPreferenceService
from app.core.security import decode_token
from app.api.v1.deps import get_current_user_id
router = APIRouter()
@@ -27,13 +32,10 @@ class ExtractRequest(BaseModel):
@router.post("")
async def translate_text(data: TranslateRequest, authorization: str = None):
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing token")
payload = decode_token(authorization[7:])
user_id = payload.get("sub") if payload else None
async def translate_text(
data: TranslateRequest,
user_id: str = Depends(get_current_user_id),
):
service = TranslationService()
result = await service.translate(
text=data.text,
@@ -46,9 +48,13 @@ async def translate_text(data: TranslateRequest, authorization: str = None):
@router.post("/reply")
async def generate_reply(data: ReplyRequest, authorization: str = None):
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing token")
async def generate_reply(
data: ReplyRequest,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
):
pref_service = UserPreferenceService(db)
pref_context = await pref_service.get_preference_context(user_id, "reply")
service = TranslationService()
results = await service.generate_reply(
@@ -56,25 +62,65 @@ async def generate_reply(data: ReplyRequest, authorization: str = None):
context=data.context,
tone=data.tone,
count=data.count,
preference_context=pref_context,
)
return {"suggestions": results, "inquiry": data.inquiry, "count": len(results)}
@router.post("/extract")
async def extract_info(data: ExtractRequest, authorization: str = None):
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing token")
async def extract_info(
data: ExtractRequest,
user_id: str = Depends(get_current_user_id),
):
service = TranslationService()
result = await service.extract_info(data.text, data.extract_type)
return {"extracted": result, "type": data.extract_type}
@router.post("/feedback")
async def feedback(data: dict, authorization: str = None):
if not authorization:
raise HTTPException(status_code=401, detail="Missing token")
class TTSRequest(BaseModel):
text: str
lang: str = "en"
rate: str = "0%"
pitch: str = "0Hz"
@router.post("/tts")
async def text_to_speech(
data: TTSRequest,
user_id: str = Depends(get_current_user_id),
):
audio = await tts_service.synthesize(data.text, data.lang, data.rate, data.pitch)
if not audio:
raise HTTPException(status_code=501, detail="TTS not available (edge-tts not installed or synthesis failed)")
return Response(content=audio, media_type="audio/mpeg", headers={
"Content-Disposition": f'attachment; filename="tts-{data.lang}.mp3"',
})
@router.get("/tts")
async def text_to_speech_get(
text: str = "",
lang: str = "en",
user_id: str = Depends(get_current_user_id),
):
if not text.strip():
raise HTTPException(status_code=400, detail="Text is required")
audio = await tts_service.synthesize(text, lang)
if not audio:
raise HTTPException(status_code=501, detail="TTS not available")
return Response(content=audio, media_type="audio/mpeg", headers={
"Content-Disposition": f'attachment; filename="tts-{lang}.mp3"',
})
@router.post("/feedback")
async def feedback(
data: dict,
user_id: str = Depends(get_current_user_id),
):
from app.ai.trade_corpus import TradeCorpus
corpus = TradeCorpus()
@@ -84,3 +130,26 @@ async def feedback(data: dict, authorization: str = None):
await corpus.rate_entry(entry_id, rating)
return {"status": "ok"}
public_router = APIRouter(tags=["translate-public"])
@public_router.post("/translate")
async def public_translate(data: TranslateRequest):
service = TranslationService()
result = await service.translate(
text=data.text,
target_lang=data.target_lang,
source_lang=data.source_lang,
context=data.context,
user_id=None,
)
return result
@public_router.post("/extract")
async def public_extract(data: ExtractRequest):
service = TranslationService()
result = await service.extract_info(data.text, data.extract_type)
return {"extracted": result, "type": data.extract_type}