修复:服务市场接口全线snake_case转camelCase,修复服务方信息加载
- 添加_to_camel通用转换函数,将所有snake_case字段转为camelCase - 服务列表接口:添加LEFT JOIN talent_profiles,组装talent对象(userId/displayName/avatar/headline/bio) - 服务详情接口:添加contentHtml字段转换,组装talent对象 - myServices接口:返回完整字段(category/description/contentHtml/paymentMethods等),支持编辑功能 - 修复前端无法正确读取opcName/publisherType/salesCount等camelCase字段的问题
This commit is contained in:
@@ -11,7 +11,7 @@ import time
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, Request, UploadFile
|
||||
from sqlalchemy import text
|
||||
|
||||
from ..dependencies import Database, get_current_user, get_db, optional_current_user
|
||||
@@ -52,6 +52,19 @@ def _parse_json(val: str | None, default: Any) -> Any:
|
||||
return default
|
||||
|
||||
|
||||
def _to_camel(d: dict) -> dict:
|
||||
"""将 dict 中的 snake_case key 转换为 camelCase。"""
|
||||
result = {}
|
||||
for k, v in d.items():
|
||||
if "_" in k and not k.startswith("_"):
|
||||
parts = k.split("_")
|
||||
camel_key = parts[0] + "".join(p.capitalize() for p in parts[1:])
|
||||
result[camel_key] = v
|
||||
else:
|
||||
result[k] = v
|
||||
return result
|
||||
|
||||
|
||||
# ── 服务分类 ──────────────────────────────────────────────────────────
|
||||
|
||||
SERVICE_CATEGORIES = [
|
||||
@@ -132,12 +145,15 @@ async def service_list(
|
||||
# 分页查询
|
||||
offset = (page - 1) * page_size
|
||||
list_sql = f"""
|
||||
SELECT id, opc_id, opc_name, title, category, description, cover,
|
||||
price, delivery_days, tags, status, rating, order_count,
|
||||
sales_count, favorite_count, view_count, publisher_type,
|
||||
payment_methods, region, service_mode, is_recommended, is_hot,
|
||||
pricing_mode, created_at
|
||||
FROM opc_services
|
||||
SELECT s.id, s.opc_id, s.opc_name, s.title, s.category, s.description, s.cover,
|
||||
s.price, s.delivery_days, s.tags, s.status, s.rating, s.order_count,
|
||||
s.sales_count, s.favorite_count, s.view_count, s.publisher_type,
|
||||
s.payment_methods, s.region, s.service_mode, s.is_recommended, s.is_hot,
|
||||
s.pricing_mode, s.created_at,
|
||||
t.display_name as talent_name, t.avatar as talent_avatar,
|
||||
t.headline as talent_headline, t.bio as talent_bio
|
||||
FROM opc_services s
|
||||
LEFT JOIN talent_profiles t ON s.opc_id = t.user_id
|
||||
WHERE {where}
|
||||
ORDER BY {order_by}
|
||||
LIMIT :limit OFFSET :offset
|
||||
@@ -149,8 +165,17 @@ async def service_list(
|
||||
items = []
|
||||
for row in rows:
|
||||
d = _row_to_dict(row)
|
||||
d["paymentMethods"] = _parse_json(d.get("payment_methods"), [])
|
||||
d["gallery"] = _parse_json(d.get("gallery_json"), [])
|
||||
d = _to_camel(d)
|
||||
d["paymentMethods"] = _parse_json(d.get("paymentMethods") or d.get("payment_methods"), [])
|
||||
d["gallery"] = _parse_json(d.get("galleryJson") or d.get("gallery_json"), [])
|
||||
# 组装 talent 对象
|
||||
d["talent"] = {
|
||||
"userId": d.get("opcId"),
|
||||
"displayName": d.get("talentName") or d.get("opcName"),
|
||||
"avatar": d.get("talentAvatar"),
|
||||
"headline": d.get("talentHeadline"),
|
||||
"bio": d.get("talentBio"),
|
||||
}
|
||||
items.append(d)
|
||||
|
||||
result = {
|
||||
@@ -165,6 +190,19 @@ async def service_list(
|
||||
return result
|
||||
|
||||
|
||||
# ── 文件上传(服务封面/图集) ─────────────────────────────────────────
|
||||
|
||||
@router.post("/upload", summary="上传服务封面/图集图片")
|
||||
async def upload_service_media(
|
||||
file: UploadFile = File(...),
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""上传服务封面或图集图片,返回 OSS/CDN 直链 URL。普通登录用户即可使用。"""
|
||||
from ...services import media_upload
|
||||
url = await media_upload.save_media(file, dir="services")
|
||||
return {"ok": True, "url": url}
|
||||
|
||||
|
||||
# ── 服务详情(增强版:浏览量自增、评价统计、收藏状态) ───────────────
|
||||
|
||||
@router.get("/services/{service_id}", summary="服务详情(增强版:浏览量/评价统计/收藏状态)")
|
||||
@@ -184,13 +222,26 @@ async def service_detail(
|
||||
if row is None:
|
||||
raise HTTPException(status_code=404, detail="服务不存在")
|
||||
item = _row_to_dict(row)
|
||||
item = _to_camel(item)
|
||||
if item.get("status") != "published":
|
||||
raise HTTPException(status_code=404, detail="服务不存在或已下架")
|
||||
|
||||
# 解析 JSON 字段
|
||||
item["paymentMethods"] = _parse_json(item.get("payment_methods"), [])
|
||||
item["gallery"] = _parse_json(item.get("gallery_json"), [])
|
||||
item["skus"] = _parse_json(item.get("skus_json"), [])
|
||||
item["paymentMethods"] = _parse_json(item.get("paymentMethods") or item.get("payment_methods"), [])
|
||||
item["gallery"] = _parse_json(item.get("galleryJson") or item.get("gallery_json"), [])
|
||||
item["skus"] = _parse_json(item.get("skusJson") or item.get("skus_json"), [])
|
||||
|
||||
# 字段转换:snake_case -> camelCase
|
||||
item["contentHtml"] = item.get("contentHtml") or item.get("content_html") or ""
|
||||
|
||||
# 组装 talent 对象
|
||||
item["talent"] = {
|
||||
"userId": item.get("opcId"),
|
||||
"displayName": item.get("talentName") or item.get("opcName"),
|
||||
"avatar": item.get("talentAvatar"),
|
||||
"headline": item.get("talentHeadline"),
|
||||
"bio": item.get("talentBio"),
|
||||
}
|
||||
|
||||
# 浏览量自增(同步执行,避免异步任务导致会话状态冲突)
|
||||
try:
|
||||
@@ -1061,8 +1112,11 @@ async def my_services(
|
||||
|
||||
rows = (await db.session.execute(
|
||||
text(f"""
|
||||
SELECT id, title, cover, price, status, rating, sales_count,
|
||||
view_count, favorite_count, publisher_type, created_at, updated_at
|
||||
SELECT id, opc_id, opc_name, title, category, description, cover,
|
||||
price, delivery_days, tags, status, rating, sales_count,
|
||||
view_count, favorite_count, publisher_type, payment_methods,
|
||||
content_html, service_mode, pricing_mode, region, refund_policy,
|
||||
created_at, updated_at
|
||||
FROM opc_services
|
||||
WHERE {where}
|
||||
ORDER BY created_at DESC
|
||||
@@ -1071,8 +1125,16 @@ async def my_services(
|
||||
{**params, "limit": page_size, "offset": offset},
|
||||
)).fetchall()
|
||||
|
||||
items = []
|
||||
for row in rows:
|
||||
d = _row_to_dict(row)
|
||||
d = _to_camel(d)
|
||||
d["paymentMethods"] = _parse_json(d.get("paymentMethods") or d.get("payment_methods"), [])
|
||||
d["contentHtml"] = d.get("contentHtml") or d.get("content_html") or ""
|
||||
items.append(d)
|
||||
|
||||
return {
|
||||
"items": [_row_to_dict(r) for r in rows],
|
||||
"items": items,
|
||||
"total": count_row[0] if count_row else 0,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
|
||||
Reference in New Issue
Block a user