From a60aac4638014e4ee74e658745663892289ea968 Mon Sep 17 00:00:00 2001 From: TradeMate Dev Date: Wed, 20 May 2026 14:30:50 +0800 Subject: [PATCH] Unify frontend config, fix marketing tracking field mismatch, expose customer notes in API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- backend/app/api/v1/auth.py | 76 +++++ backend/app/main.py | 3 +- backend/app/services/customer.py | 3 + uni-app/src/App.vue | 2 - uni-app/src/config.js | 64 ++++ uni-app/src/custom-tab-bar/index.vue | 2 +- uni-app/src/pages.json | 14 +- uni-app/src/pages/index/index.vue | 149 +++++++-- uni-app/src/pages/login/login.vue | 31 +- uni-app/src/pages/marketing/marketing.vue | 6 +- uni-app/src/pages/profile/profile.vue | 390 ++++++++++++++++++++++ uni-app/src/utils/api.js | 16 +- 12 files changed, 689 insertions(+), 67 deletions(-) create mode 100644 uni-app/src/config.js create mode 100644 uni-app/src/pages/profile/profile.vue diff --git a/backend/app/api/v1/auth.py b/backend/app/api/v1/auth.py index ee44390..7dbb74a 100644 --- a/backend/app/api/v1/auth.py +++ b/backend/app/api/v1/auth.py @@ -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 diff --git a/backend/app/main.py b/backend/app/main.py index 031da08..5c4c7df 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -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__": diff --git a/backend/app/services/customer.py b/backend/app/services/customer.py index 02757ce..0b3bb82 100644 --- a/backend/app/services/customer.py +++ b/backend/app/services/customer.py @@ -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, } diff --git a/uni-app/src/App.vue b/uni-app/src/App.vue index 5ec656c..b18e9c6 100644 --- a/uni-app/src/App.vue +++ b/uni-app/src/App.vue @@ -1,10 +1,8 @@ diff --git a/uni-app/src/utils/api.js b/uni-app/src/utils/api.js index b2187e1..d8f0988 100644 --- a/uni-app/src/utils/api.js +++ b/uni-app/src/utils/api.js @@ -1,7 +1,9 @@ +import { STORAGE_KEYS, PAGES } from '@/config.js' + export const BASE_URL = '/api/v1' const getAuthHeader = () => { - const token = uni.getStorageSync('token') + const token = uni.getStorageSync(STORAGE_KEYS.TOKEN) return token ? { Authorization: `Bearer ${token}` } : {} } @@ -19,8 +21,8 @@ const request = (url, method = 'GET', data = {}) => { if (res.statusCode === 200) { resolve(res.data) } else if (res.statusCode === 401) { - uni.removeStorageSync('token') - uni.reLaunch({ url: '/pages/login/login' }) + uni.removeStorageSync(STORAGE_KEYS.TOKEN) + uni.reLaunch({ url: PAGES.LOGIN }) reject(new Error('Unauthorized')) } else { reject(new Error(res.data?.detail || 'Request failed')) @@ -60,6 +62,8 @@ export const authApi = { login: (phone, password) => request('/auth/login', 'POST', { username: phone, password }), register: (phone, password, username) => request('/auth/register', 'POST', { phone, password, username }), getUserInfo: () => request('/auth/me'), + updateProfile: (data) => request('/auth/me', 'PUT', data), + changePassword: (oldPassword, newPassword) => request('/auth/password', 'PUT', { old_password: oldPassword, new_password: newPassword }), wechatLogin: (code) => request('/auth/wechat-login', 'POST', { code }), wechatConfig: () => request('/auth/wechat/config'), guestLogin: () => requestWithoutAuth('/auth/login/guest', 'POST'), @@ -103,7 +107,7 @@ export const quotationApi = { request('/quotations/generate-from-inquiry', 'POST', { inquiry_text: inquiryText, customer_id: customerId }), importQuotations: (file) => { return new Promise((resolve, reject) => { - const token = uni.getStorageSync('token') + const token = uni.getStorageSync(STORAGE_KEYS.TOKEN) uni.uploadFile({ url: `${BASE_URL}/quotations/import`, filePath: file, @@ -132,7 +136,7 @@ export const productApi = { exportXlsx: () => `${BASE_URL}/products/export/xlsx`, importProducts: (file) => { return new Promise((resolve, reject) => { - const token = uni.getStorageSync('token') + const token = uni.getStorageSync(STORAGE_KEYS.TOKEN) uni.uploadFile({ url: `${BASE_URL}/products/import`, filePath: file, @@ -310,7 +314,7 @@ export const customerApi = { exportXlsx: () => `${BASE_URL}/customers/export/xlsx`, importCustomers: (file) => { return new Promise((resolve, reject) => { - const token = uni.getStorageSync('token') + const token = uni.getStorageSync(STORAGE_KEYS.TOKEN) uni.uploadFile({ url: `${BASE_URL}/customers/import`, filePath: file,