From 9b870122483cc78634049cc314ddbafee0ab7907 Mon Sep 17 00:00:00 2001 From: QihuAI Design Date: Mon, 13 Jul 2026 20:01:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=E9=A6=96=E9=A1=B5=E5=B9=BF?= =?UTF-8?q?=E5=91=8A=E4=BD=8D=E7=BD=91=E6=A0=BC=E6=BA=A2=E5=87=BA=E6=A0=B9?= =?UTF-8?q?=E6=B2=BB(minmax(0,1fr)=20minmax(0,1.2fr)+min-width:0)=20+=20?= =?UTF-8?q?=E7=B2=BE=E9=80=89=E6=8E=A8=E8=8D=90=E5=AF=B9=E6=8E=A5=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E5=B9=BF=E5=91=8A=E6=8E=A5=E5=8F=A3=20GET=20/app/api/?= =?UTF-8?q?market/ads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/python/app/api/market.py | 30 ++++++++++++++++++++- frontend/src/pages/front/LandingPage.css | 18 ++++++++++--- frontend/src/pages/front/LandingPage.tsx | 33 ++++++++++++++++++++++-- 3 files changed, 74 insertions(+), 7 deletions(-) diff --git a/backend/python/app/api/market.py b/backend/python/app/api/market.py index 33c55df..fa6a0b1 100644 --- a/backend/python/app/api/market.py +++ b/backend/python/app/api/market.py @@ -9,7 +9,7 @@ from datetime import datetime from ..database import get_db from ..models import ( User, MarketApp, AppPurchase, AppReview, AppInstallation, Order, - gen_uuid + AdBanner, gen_uuid ) from ..auth import get_current_user, get_current_user_id, get_current_user_id_optional from ..middleware.log_service import LogService @@ -17,6 +17,34 @@ from ..middleware.log_service import LogService router = APIRouter() +@router.get("/ads") +def list_public_ads(db: Session = Depends(get_db)): + """ + 前台首页「精选推荐」广告位:返回已上架(active)广告,按排序升序,无需登录。 + 供 LandingPage 轮播调用,无鉴权依赖。 + """ + ads = ( + db.query(AdBanner) + .filter(AdBanner.status == "active") + .order_by(AdBanner.sort_order.asc()) + .all() + ) + return [ + { + "id": a.id, + "title": a.title, + "image_url": a.image_url, + "link_app_id": a.link_app_id, + "link_app_name": a.link_app_name, + "sort_order": a.sort_order, + "click_count": a.click_count, + "status": a.status, + "created_at": a.created_at.isoformat() if a.created_at else None, + } + for a in ads + ] + + class UploadAppRequest(BaseModel): name: str = Field(min_length=1, max_length=200) description: Optional[str] = None diff --git a/frontend/src/pages/front/LandingPage.css b/frontend/src/pages/front/LandingPage.css index 3afdfc3..397850a 100644 --- a/frontend/src/pages/front/LandingPage.css +++ b/frontend/src/pages/front/LandingPage.css @@ -73,8 +73,13 @@ } .lp-hero-grid { max-width: 1200px; margin: 0 auto; padding: 0 var(--space-2xl); - display: grid; grid-template-columns: 1fr 2fr; gap: 0; align-items: center; + display: grid; + /* minmax(0,..) 锁死轨道最小宽=0,防止右侧轮播的 flex 内容把列撑破而"超出免费注册边框" */ + grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); + gap: var(--space-4xl); align-items: center; } +/* 左右两列都允许收缩,杜绝内容撑破网格导致的水平溢出 */ +.lp-hero-text { min-width: 0; } .lp-eyebrow { display: inline-block; font-size: var(--text-sm); font-weight: 600; color: var(--color-accent); background: var(--color-accent-light); padding: 4px 12px; border-radius: var(--radius-full); margin-bottom: var(--space-lg); @@ -89,12 +94,17 @@ .lp-stat-num { font-size: var(--text-xl); font-weight: 800; color: var(--color-text-primary); } .lp-stat-label { font-size: var(--text-sm); color: var(--color-text-tertiary); margin-top: 2px; } -.lp-hero-ad { display: flex; flex-direction: column; padding-right: var(--space-3xl); } +/* 右侧广告位:min-width:0 让其严格贴合网格右轨道,右边框与顶栏"免费注册"垂直对齐、绝不外溢 */ +.lp-hero-ad { display: flex; flex-direction: column; min-width: 0; } .lp-ad-head { display: flex; align-items: baseline; gap: var(--space-md); margin-bottom: var(--space-lg); flex-wrap: wrap; } .lp-ad-title { font-size: var(--text-lg); font-weight: 700; color: var(--color-text-primary); } .lp-ad-sub { font-size: var(--text-sm); color: var(--color-text-tertiary); } -.lp-hero-ad .lp-carousel-card { min-height: 300px; } -.lp-hero-ad .lp-cc-content { max-width: 64%; } +.lp-hero-ad .lp-carousel { width: 100%; } +.lp-hero-ad .lp-carousel-card { min-height: 340px; } +.lp-hero-ad .lp-cc-content { max-width: 62%; } +.lp-hero-ad .lp-cc-cover { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 0; } +.lp-hero-ad .lp-cc-cover + .lp-cc-content { color: #fff; } +.lp-hero-ad .lp-cc-scrim { position: absolute; inset: 0; z-index: 1; background: linear-gradient(90deg, rgba(0,0,0,.55) 0%, rgba(0,0,0,.15) 60%, rgba(0,0,0,0) 100%); } /* ===== 轮播广告位 ===== */ .lp-band { padding: var(--space-4xl) 0; } diff --git a/frontend/src/pages/front/LandingPage.tsx b/frontend/src/pages/front/LandingPage.tsx index 828a5f9..ffd2634 100644 --- a/frontend/src/pages/front/LandingPage.tsx +++ b/frontend/src/pages/front/LandingPage.tsx @@ -79,7 +79,9 @@ const LandingPage: React.FC = () => { const [isPaused, setIsPaused] = useState(false); const [mobileMenu, setMobileMenu] = useState(false); - const banners = mockAdBanners.filter(b => b.status === 'active'); + const [banners, setBanners] = useState( + mockAdBanners.filter(b => b.status === 'active') + ); const publishedApps = mockApps.filter(a => a.status === 'published'); const topSkills = [...publishedApps].sort((a, b) => b.installCount - a.installCount).slice(0, 5); @@ -94,6 +96,32 @@ const LandingPage: React.FC = () => { return () => clearInterval(timer); }, [banners.length, isPaused, nextSlide]); + // 精选推荐:对接平台「广告位」能力——拉取后台已上架(active)的真实广告轮播; + // 无数据或接口异常时回退到内置示例,保证首屏不空。 + useEffect(() => { + let alive = true; + fetch('/app/api/market/ads') + .then(r => (r.ok ? r.json() : Promise.reject(r.status))) + .then((rows: any[]) => { + if (!alive || !Array.isArray(rows) || rows.length === 0) return; + const mapped: AdBanner[] = rows.map(a => ({ + id: a.id, + title: a.title, + imageUrl: a.image_url || '', + linkAppId: a.link_app_id || undefined, + linkAppName: a.link_app_name || undefined, + sortOrder: a.sort_order ?? 0, + clickCount: a.click_count ?? 0, + status: (a.status as 'active' | 'inactive') || 'active', + createdAt: a.created_at || '', + })); + setBanners(mapped); + setCurrentSlide(0); + }) + .catch(() => { /* 静默失败,保留 mock 回退 */ }); + return () => { alive = false; }; + }, []); + // SEO / GEO:动态写入标题与描述 useEffect(() => { document.title = '七虎AI · 个人体验 AI、办公提效与 AI 创收的一站式平台'; @@ -214,13 +242,14 @@ const LandingPage: React.FC = () => { {banners.map(b => (
+ {b.imageUrl && (<>{b.title})}
推荐

{b.title}

{b.linkAppName &&

立即体验 {b.linkAppName}

}
-
+ {!b.imageUrl &&
}
))}