Unify frontend config, fix marketing tracking field mismatch, expose customer notes in API
Centralizes all hardcoded page paths, storage keys, external URLs, and branding into a single uni-app/src/config.js. Fixes trackMarketingEffect sending wrong field names (action/content_preview -> event_type/content) that silently dropped tracking data. Adds notes, estimated_value, next_followup_at to Customer response. Removes '翻译' from bottom tab nav (5 tabs now), adds quick translate card on home page. Makes profile page header color consistent with app theme (#1890ff).
This commit is contained in:
@@ -189,6 +189,16 @@ async def get_me(
|
||||
}
|
||||
|
||||
|
||||
class ProfileUpdate(BaseModel):
|
||||
username: str = None
|
||||
email: str = None
|
||||
|
||||
|
||||
class PasswordChange(BaseModel):
|
||||
old_password: str
|
||||
new_password: str
|
||||
|
||||
|
||||
class SettingsUpdate(BaseModel):
|
||||
preferred_translate_provider: str = None
|
||||
reply_tone: str = None
|
||||
@@ -202,6 +212,72 @@ class WeChatLoginRequest(BaseModel):
|
||||
iv: str = ""
|
||||
|
||||
|
||||
@router.put("/me")
|
||||
async def update_me(
|
||||
data: ProfileUpdate,
|
||||
authorization: Optional[str] = Header(None, alias="Authorization"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if not authorization or not authorization.startswith("Bearer "):
|
||||
raise HTTPException(status_code=401, detail="Missing token")
|
||||
|
||||
payload = decode_token(authorization[7:])
|
||||
if not payload:
|
||||
raise HTTPException(status_code=401, detail="Invalid token")
|
||||
|
||||
if payload.get("is_guest"):
|
||||
raise HTTPException(status_code=403, detail="Guests cannot update profile")
|
||||
|
||||
result = await db.execute(select(User).where(User.id == payload["sub"]))
|
||||
user = result.scalar_one_or_none()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
if data.username is not None:
|
||||
user.username = data.username
|
||||
if data.email is not None:
|
||||
user.email = data.email
|
||||
|
||||
await db.flush()
|
||||
return {
|
||||
"id": str(user.id),
|
||||
"phone": user.phone,
|
||||
"username": user.username,
|
||||
"email": user.email,
|
||||
"tier": user.tier,
|
||||
"role": user.role,
|
||||
}
|
||||
|
||||
|
||||
@router.put("/password")
|
||||
async def change_password(
|
||||
data: PasswordChange,
|
||||
authorization: Optional[str] = Header(None, alias="Authorization"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if not authorization or not authorization.startswith("Bearer "):
|
||||
raise HTTPException(status_code=401, detail="Missing token")
|
||||
|
||||
payload = decode_token(authorization[7:])
|
||||
if not payload:
|
||||
raise HTTPException(status_code=401, detail="Invalid token")
|
||||
|
||||
if payload.get("is_guest"):
|
||||
raise HTTPException(status_code=403, detail="Guests cannot change password")
|
||||
|
||||
result = await db.execute(select(User).where(User.id == payload["sub"]))
|
||||
user = result.scalar_one_or_none()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
if not verify_password(data.old_password, user.password_hash):
|
||||
raise HTTPException(status_code=400, detail="旧密码不正确")
|
||||
|
||||
user.password_hash = hash_password(data.new_password)
|
||||
await db.flush()
|
||||
return {"message": "密码修改成功"}
|
||||
|
||||
|
||||
@router.get("/wechat/config")
|
||||
async def wechat_config():
|
||||
from app.config import settings
|
||||
|
||||
+2
-1
@@ -54,7 +54,7 @@ async def health():
|
||||
return {"status": "ok", "app": settings.APP_NAME, "version": "1.0.0"}
|
||||
|
||||
|
||||
from app.api.v1 import auth, marketing, translate, customer, quotation, whatsapp, product, exchange, push, admin, analytics, teams, onboarding, notification, feedback, payment, interaction, silent_pattern, training, followup
|
||||
from app.api.v1 import auth, marketing, translate, customer, quotation, whatsapp, product, exchange, push, admin, analytics, teams, onboarding, notification, feedback, payment, interaction, silent_pattern, training, followup, ai_assistant
|
||||
|
||||
app.include_router(auth.router, prefix="/api/v1/auth", tags=["auth"])
|
||||
app.include_router(marketing.router, prefix="/api/v1/marketing", tags=["marketing"])
|
||||
@@ -77,6 +77,7 @@ app.include_router(interaction.router, prefix="/api/v1/interaction", tags=["inte
|
||||
app.include_router(silent_pattern.router, prefix="/api/v1/silent-pattern", tags=["silent-pattern"])
|
||||
app.include_router(training.router, prefix="/api/v1/training", tags=["training"])
|
||||
app.include_router(followup.router, prefix="/api/v1/followup", tags=["followup"])
|
||||
app.include_router(ai_assistant.router, prefix="/api/v1/ai", tags=["ai-assistant"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -197,8 +197,11 @@ class CustomerService:
|
||||
"whatsapp_id": c.whatsapp_id,
|
||||
"source": c.source,
|
||||
"tags": c.tags,
|
||||
"notes": c.notes,
|
||||
"status": c.status,
|
||||
"estimated_value": c.estimated_value,
|
||||
"last_contact_at": c.last_contact_at.isoformat() if c.last_contact_at else None,
|
||||
"silence_days": (datetime.utcnow() - c.last_contact_at).days if c.last_contact_at else 0,
|
||||
"next_followup_at": c.next_followup_at.isoformat() if c.next_followup_at else None,
|
||||
"created_at": c.created_at.isoformat() if c.created_at else None,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user