添加`DetailRecord`数据库模型、迁移脚本及完整的CRUD管理接口,将路线详情从玩法路线表解耦,实现独立存储。 在Admin UI的路线编辑器中集成详情配置面板,支持编辑详情文案、图片等展示内容。 完善MiniAPP路线详情页,新增导航函数、API调用及数据归一化逻辑,并添加对应单元测试。 更新所有相关文档,明确领域边界、联调流程及接口契约规范。 调整首页和玩法页的点击跳转逻辑,优先跳转路线详情页而非原需求页。 新增数值格式化工具函数优化内容展示效果。
178 lines
6.4 KiB
Python
178 lines
6.4 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import Session, selectinload
|
|
from .shared import site_config
|
|
from ..auth import create_customer_token, require_customer
|
|
from ..database import get_db
|
|
from ..models import ConciergeAdvisor, Customer, DetailRecord, HomeExperience, HomeTeamBuilding, HomeWanfaRecommendation, HomeWildArchive, Lead, WanfaCategory
|
|
from ..schemas import LeadCreateIn, PhoneLoginIn
|
|
from ..serializers import (
|
|
public_concierge_advisor_dict,
|
|
public_home_experience_dict,
|
|
public_home_team_building_dict,
|
|
public_home_team_building_detail_dict,
|
|
public_home_wanfa_recommendation_dict,
|
|
public_home_wild_archive_detail_dict,
|
|
public_home_wild_archive_dict,
|
|
public_detail_dict,
|
|
public_wanfa_category_dict,
|
|
)
|
|
from ..wechat import WechatApiError, WechatConfigError, exchange_phone_code
|
|
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def mask_phone(phone: str) -> str:
|
|
normalized = phone.strip()
|
|
if len(normalized) <= 7:
|
|
return "***"
|
|
return f"{normalized[:3]}****{normalized[-4:]}"
|
|
|
|
|
|
def public_customer_dict(customer: Customer) -> dict:
|
|
return {"id": customer.id, "phoneMasked": mask_phone(customer.phone)}
|
|
|
|
|
|
@router.get("/health")
|
|
def health():
|
|
return {"ok": True, "service": "miniapp-api"}
|
|
|
|
|
|
@router.get("/api/public/site-config")
|
|
def get_site_config(db: Session = Depends(get_db)):
|
|
return site_config(db, active_only=True)
|
|
|
|
|
|
@router.get("/api/public/wanfa/categories")
|
|
def get_public_wanfa_categories(db: Session = Depends(get_db)):
|
|
categories = db.scalars(
|
|
select(WanfaCategory)
|
|
.options(selectinload(WanfaCategory.routes))
|
|
.order_by(WanfaCategory.sortOrder.asc())
|
|
).all()
|
|
return {"categories": [public_wanfa_category_dict(category) for category in categories]}
|
|
|
|
|
|
@router.get("/api/public/details/{detail_key}")
|
|
def get_public_detail(detail_key: str, db: Session = Depends(get_db)):
|
|
detail = db.scalar(
|
|
select(DetailRecord).where(
|
|
DetailRecord.key == detail_key,
|
|
DetailRecord.isActive.is_(True),
|
|
)
|
|
)
|
|
if not detail:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="详情不存在")
|
|
return public_detail_dict(detail)
|
|
|
|
|
|
@router.get("/api/public/concierge/advisors")
|
|
def get_public_concierge_advisors(db: Session = Depends(get_db)):
|
|
advisors = db.scalars(
|
|
select(ConciergeAdvisor)
|
|
.where(ConciergeAdvisor.isActive.is_(True))
|
|
.order_by(ConciergeAdvisor.sortOrder.asc())
|
|
).all()
|
|
return {"advisors": [public_concierge_advisor_dict(advisor) for advisor in advisors]}
|
|
|
|
|
|
@router.get("/api/public/home")
|
|
def get_public_home(db: Session = Depends(get_db)):
|
|
experiences = db.scalars(
|
|
select(HomeExperience)
|
|
.where(HomeExperience.isActive.is_(True))
|
|
.order_by(HomeExperience.sortOrder.asc())
|
|
).all()
|
|
team_buildings = db.scalars(
|
|
select(HomeTeamBuilding)
|
|
.where(HomeTeamBuilding.isActive.is_(True))
|
|
.order_by(HomeTeamBuilding.sortOrder.asc())
|
|
).all()
|
|
wild_archives = db.scalars(
|
|
select(HomeWildArchive)
|
|
.where(HomeWildArchive.isActive.is_(True))
|
|
.order_by(HomeWildArchive.sortOrder.asc())
|
|
).all()
|
|
play_recommendations = db.scalars(
|
|
select(HomeWanfaRecommendation)
|
|
.options(selectinload(HomeWanfaRecommendation.category).selectinload(WanfaCategory.routes))
|
|
.where(HomeWanfaRecommendation.isActive.is_(True))
|
|
.order_by(HomeWanfaRecommendation.sortOrder.asc())
|
|
).all()
|
|
return {
|
|
"experiences": [public_home_experience_dict(item) for item in experiences],
|
|
"teamBuildings": [public_home_team_building_dict(item) for item in team_buildings],
|
|
"wildArchives": [public_home_wild_archive_dict(item) for item in wild_archives],
|
|
"playRecommendations": [public_home_wanfa_recommendation_dict(item) for item in play_recommendations],
|
|
}
|
|
|
|
|
|
@router.get("/api/public/home/team-buildings/{team_building_id}")
|
|
def get_public_team_building(team_building_id: str, db: Session = Depends(get_db)):
|
|
team_building = db.scalar(
|
|
select(HomeTeamBuilding).where(
|
|
HomeTeamBuilding.id == team_building_id,
|
|
HomeTeamBuilding.isActive.is_(True),
|
|
)
|
|
)
|
|
if not team_building:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="团队共创不存在")
|
|
return public_home_team_building_detail_dict(team_building)
|
|
|
|
|
|
@router.get("/api/public/home/wild-archives")
|
|
def list_public_wild_archives(db: Session = Depends(get_db)):
|
|
archives = db.scalars(
|
|
select(HomeWildArchive)
|
|
.where(HomeWildArchive.isActive.is_(True))
|
|
.order_by(HomeWildArchive.sortOrder.asc())
|
|
).all()
|
|
return {"items": [public_home_wild_archive_dict(item) for item in archives]}
|
|
|
|
|
|
@router.get("/api/public/home/wild-archives/{archive_id}")
|
|
def get_public_wild_archive(archive_id: str, db: Session = Depends(get_db)):
|
|
archive = db.scalar(
|
|
select(HomeWildArchive).where(
|
|
HomeWildArchive.id == archive_id,
|
|
HomeWildArchive.isActive.is_(True),
|
|
)
|
|
)
|
|
if not archive:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="客片案例不存在")
|
|
return public_home_wild_archive_detail_dict(archive)
|
|
|
|
|
|
@router.post("/api/public/auth/phone-login")
|
|
def phone_login(body: PhoneLoginIn, db: Session = Depends(get_db)):
|
|
try:
|
|
phone = exchange_phone_code(body.code)
|
|
except WechatConfigError as exc:
|
|
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=str(exc)) from exc
|
|
except WechatApiError as exc:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
|
|
|
|
customer = db.scalar(select(Customer).where(Customer.phone == phone))
|
|
if not customer:
|
|
customer = Customer(phone=phone)
|
|
db.add(customer)
|
|
db.flush()
|
|
db.commit()
|
|
db.refresh(customer)
|
|
return {"token": create_customer_token(customer), "customer": public_customer_dict(customer)}
|
|
|
|
|
|
@router.get("/api/public/auth/me")
|
|
def current_customer(customer: Customer = Depends(require_customer)):
|
|
return public_customer_dict(customer)
|
|
|
|
|
|
@router.post("/api/public/leads", status_code=status.HTTP_201_CREATED)
|
|
def create_lead(body: LeadCreateIn, db: Session = Depends(get_db)):
|
|
lead = Lead(**body.model_dump(exclude_none=True), status="new")
|
|
db.add(lead)
|
|
db.commit()
|
|
db.refresh(lead)
|
|
return {"id": lead.id, "status": lead.status}
|