import React, { useState, useEffect } from 'react'; import { adminAPI, marketAPI } from '@/api'; import type { AdBanner, MarketApp } from '@/types'; import './Ads.css'; const SVGIcon: React.FC<{ children: React.ReactNode; size?: number }> = ({ children, size = 16 }) => ( {children} ); const typeLabels: Record = { skill: 'Skill', agent: 'Agent', mcp: 'MCP' }; const Ads: React.FC = () => { const [banners, setBanners] = useState([]); const [appOptions, setAppOptions] = useState([]); const [loading, setLoading] = useState(true); const [editingBanner, setEditingBanner] = useState(null); const [showModal, setShowModal] = useState(false); const [isNew, setIsNew] = useState(false); const [uploading, setUploading] = useState(false); const [generating, setGenerating] = useState(false); const [genPrompt, setGenPrompt] = useState(''); const emptyForm: AdBanner = { id: '', title: '', imageUrl: '', linkAppId: '', linkAppName: '', sortOrder: banners.length + 1, clickCount: 0, status: 'active', createdAt: new Date().toISOString(), }; const [form, setForm] = useState(emptyForm); const loadBanners = async () => { try { setLoading(true); const res: any = await adminAPI.getAds(); const data = Array.isArray(res) ? res : (res.data || []); setBanners(data.map((a: any) => ({ id: a.id, title: a.title, imageUrl: a.image_url || a.imageUrl || '', linkAppId: a.link_app_id || a.linkAppId || '', linkAppName: a.link_app_name || a.linkAppName || '', sortOrder: a.sort_order ?? a.sortOrder ?? 0, clickCount: a.click_count ?? a.clickCount ?? 0, status: a.status || 'active', createdAt: a.created_at || a.createdAt || new Date().toISOString(), }))); } catch (e) { console.error('加载广告失败:', e); } finally { setLoading(false); } }; useEffect(() => { loadBanners(); // 加载应用列表用于关联选择 marketAPI.getApps({}).then((res: any) => { const items = res.data?.items || []; setAppOptions(items); }).catch(() => {}); }, []); const handleCreate = () => { setIsNew(true); setForm({ ...emptyForm, sortOrder: banners.length + 1 }); setEditingBanner(null); setGenPrompt(''); setShowModal(true); }; const handleEdit = (banner: AdBanner) => { setIsNew(false); setEditingBanner(banner); setForm({ ...banner }); setGenPrompt(''); setShowModal(true); }; const handleSave = async () => { try { if (isNew) { await adminAPI.addAd({ title: form.title, image_url: form.imageUrl || undefined, link_app_id: form.linkAppId || undefined, link_app_name: form.linkAppName || undefined, sort_order: form.sortOrder, status: form.status, }); } else if (editingBanner) { await adminAPI.updateAd(editingBanner.id, { title: form.title, image_url: form.imageUrl || undefined, link_app_id: form.linkAppId || undefined, link_app_name: form.linkAppName || undefined, sort_order: form.sortOrder, status: form.status, }); } setShowModal(false); setEditingBanner(null); loadBanners(); } catch (err: any) { alert('保存失败:' + (err.detail || err.message)); } }; const handleDelete = async (id: string) => { if (!window.confirm('确定要删除此广告吗?')) return; try { await adminAPI.deleteAd(id); loadBanners(); } catch (err: any) { alert('删除失败:' + (err.detail || err.message)); } }; const toggleStatus = async (id: string, currentStatus: string) => { const newStatus = currentStatus === 'active' ? 'inactive' : 'active'; try { await adminAPI.updateAd(id, { status: newStatus }); loadBanners(); } catch (err: any) { alert('切换状态失败:' + (err.detail || err.message)); } }; const handleAppSelect = (appId: string) => { const app = appOptions.find(a => a.id === appId); setForm(prev => ({ ...prev, linkAppId: appId, linkAppName: app ? app.name : '', })); }; // 真实图片上传 const handleImageUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setUploading(true); try { const formData = new FormData(); formData.append('file', file); const token = localStorage.getItem('aiflow_token'); const res = await fetch('/app/api/admin/ads/upload-image', { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, body: formData, }); if (!res.ok) { const err = await res.json().catch(() => ({ detail: '上传失败' })); throw new Error(err.detail || '上传失败'); } const data = await res.json(); setForm({ ...form, imageUrl: data.image_url }); } catch (err: any) { alert('图片上传失败:' + err.message); } finally { setUploading(false); } }; // AI生成图片 const handleAIGenerate = async () => { setGenerating(true); try { const token = localStorage.getItem('aiflow_token'); const prompt = genPrompt || form.title || 'AI智能平台广告横幅'; const res = await fetch(`/app/api/admin/ads/generate-image?prompt=${encodeURIComponent(prompt)}`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, }); if (!res.ok) { const err = await res.json().catch(() => ({ detail: '生成失败' })); throw new Error(err.detail || '生成失败'); } const data = await res.json(); setForm({ ...form, imageUrl: data.image_url }); } catch (err: any) { alert('AI绘图失败:' + err.message); } finally { setGenerating(false); } }; // 生成渐变色预览色块 const getPreviewGradient = (appId?: string): string => { const gradients: Record = { a1: 'linear-gradient(135deg, #667eea, #764ba2)', a2: 'linear-gradient(135deg, #f093fb, #f5576c)', a3: 'linear-gradient(135deg, #4facfe, #00f2fe)', a4: 'linear-gradient(135deg, #43e97b, #38f9d7)', a5: 'linear-gradient(135deg, #fa709a, #fee140)', a6: 'linear-gradient(135deg, #a18cd1, #fbc2eb)', a8: 'linear-gradient(135deg, #ffecd2, #fcb69f)', }; return gradients[appId || ''] || 'linear-gradient(135deg, #5B5FED, #A78BFA)'; }; return (

广告位管理

{/* Banner List */} {loading ? (
加载中...
) : banners.length === 0 ? (
暂无广告位,点击"新增广告位"按钮创建
) : (

轮播图广告位列表

共 {banners.length} 个广告位,活跃 {banners.filter(b => b.status === 'active').length} 个
{banners.sort((a, b) => a.sortOrder - b.sortOrder).map(banner => ( ))}
预览 标题 关联应用 排序 点击次数 状态 创建时间 操作
{banner.title} {banner.linkAppName ? ( {banner.linkAppName} ) : ( 未关联 )} {banner.sortOrder} {banner.clickCount.toLocaleString()} toggleStatus(banner.id, banner.status)} > {banner.status === 'active' ? '投放中' : '已暂停'} {new Date(banner.createdAt).toLocaleDateString()}
)} {/* Edit/Create Modal */} {showModal && (
{ setShowModal(false); setEditingBanner(null); }} >
e.stopPropagation()}>

{isNew ? '新增广告位' : `编辑 - ${editingBanner?.title}`}

{/* Preview */}
推荐

{form.title || '广告标题'}

{form.linkAppName && (

关联应用: {form.linkAppName}

)}
{/* Title */}
setForm({ ...form, title: e.target.value })} placeholder="输入轮播图展示标题" />
{/* Image URL + Upload + AIGC */}
setForm({ ...form, imageUrl: e.target.value })} placeholder="图片URL / 上传后自动填入 / 留空使用渐变色" />
setGenPrompt(e.target.value)} placeholder="AI绘图提示词..." />
{form.imageUrl && ( )}
{form.imageUrl && (
预览 { (e.target as HTMLImageElement).style.display = 'none'; }} />
)}
{/* Link App */}
{/* Sort Order */}
setForm({ ...form, sortOrder: Number(e.target.value) })} min={1} />
)}
); }; export default Ads;