import { useEffect, useState, useCallback } from 'react' import { Card, Typography, Space, Select, message, Button, Form, Input, InputNumber, Row, Col, Divider, Image, Tag, Tabs, Collapse, Switch, Alert, Popconfirm, } from 'antd' import { SaveOutlined, PictureOutlined, NotificationOutlined, EyeOutlined, PlusOutlined, DeleteOutlined, InfoCircleOutlined, SkinOutlined, GlobalOutlined, MessageOutlined, SendOutlined, } from '@ant-design/icons' import { listSites, updateSiteBranding, updateSite, getMe, fetchSiteSMSConfig, updateSiteSMSConfig, testSiteSMSConfig, type Site, type SiteBrandingPayload, type SiteSMSConfig, type UpdateSiteSMSConfigPayload, type TestSiteSMSConfigPayload, } from '../api' import { useMember } from '../MemberContext' interface BannerItem { image_url: string title?: string link?: string } interface AnnouncementItem { title: string content?: string } interface FooterInfo { copyright?: string icp?: string contact?: string } export default function SiteSettings() { const member = useMember() const [sites, setSites] = useState([]) const [siteID, setSiteID] = useState(member?.site_id) const [loading, setLoading] = useState(false) const [saving, setSaving] = useState(false) const [currentSite, setCurrentSite] = useState(null) // Visual state for customizer const [logoUrl, setLogoUrl] = useState('') const [faviconUrl, setFaviconUrl] = useState('') const [banners, setBanners] = useState([]) const [announcements, setAnnouncements] = useState([]) const [footerInfo, setFooterInfo] = useState({ copyright: '', icp: '', contact: '', }) const [receiveCostDelta, setReceiveCostDelta] = useState(0) const [savingRatio, setSavingRatio] = useState(false) // SMS config state const [smsConfig, setSmsConfig] = useState({ enabled: false, provider: 'aliyun', access_key_id: '', masked_secret: '', has_secret: false, endpoint: 'dysmsapi.aliyuncs.com', sign_name: '', login_template_code: '', }) const [smsSecretInput, setSmsSecretInput] = useState('') const [savingSMS, setSavingSMS] = useState(false) const [testPhone, setTestPhone] = useState('') const [testingSMS, setTestingSMS] = useState(false) const [testCooldown, setTestCooldown] = useState(0) useEffect(() => { if (testCooldown <= 0) return const timer = setInterval(() => { setTestCooldown((prev) => (prev > 0 ? prev - 1 : 0)) }, 1000) return () => clearInterval(timer) }, [testCooldown]) const loadSite = useCallback(async (id: number) => { setLoading(true) try { const rows = await listSites() setSites(rows) const found = rows.find((s) => s.id === id) if (found) { setCurrentSite(found) setLogoUrl(found.logo_url || '') setFaviconUrl(found.favicon_url || '') try { setBanners(found.banners_json ? JSON.parse(found.banners_json) : []) } catch { setBanners([]) } try { setAnnouncements(found.announcements_json ? JSON.parse(found.announcements_json) : []) } catch { setAnnouncements([]) } try { setFooterInfo(found.footer_info_json ? JSON.parse(found.footer_info_json) : {}) } catch { setFooterInfo({}) } setReceiveCostDelta(Math.max(0, found.receive_cost_delta || 0)) try { const sms = await fetchSiteSMSConfig(id) setSmsConfig(sms) setSmsSecretInput('') } catch { // ignore } } } catch { message.error('加载站点设置失败') } finally { setLoading(false) } }, []) useEffect(() => { async function init() { let currentMember = member if (!currentMember) { try { currentMember = await getMe() } catch { // ignore } } const activeSiteID = currentMember?.site_id if (activeSiteID) { setSiteID(activeSiteID) void loadSite(activeSiteID) } if (currentMember?.is_superadmin) { try { const rows = await listSites() setSites(rows) if (!activeSiteID && rows.length > 0) { setSiteID(rows[0].id) void loadSite(rows[0].id) } } catch { // ignore } } } void init() }, [member, loadSite]) function handleAddBanner() { setBanners([ ...banners, { image_url: 'https://images.unsplash.com/photo-1542751371-adc38448a05e?w=1200&q=80', title: '热门推荐活动', link: '', }, ]) } function handleUpdateBanner(index: number, field: keyof BannerItem, value: string) { const next = [...banners] next[index] = { ...next[index], [field]: value } setBanners(next) } function handleDeleteBanner(index: number) { setBanners(banners.filter((_, i) => i !== index)) } function handleAddAnnouncement() { setAnnouncements([ ...announcements, { title: '平台全新上线精选商品,免押畅玩!', content: '', }, ]) } function handleUpdateAnnouncement(index: number, field: keyof AnnouncementItem, value: string) { const next = [...announcements] next[index] = { ...next[index], [field]: value } setAnnouncements(next) } function handleDeleteAnnouncement(index: number) { setAnnouncements(announcements.filter((_, i) => i !== index)) } async function handleSaveReceiveRatio() { if (!siteID) return setSavingRatio(true) try { const updated = await updateSite(siteID, { receive_cost_delta: receiveCostDelta }) setCurrentSite(updated) setReceiveCostDelta(Math.max(0, updated.receive_cost_delta || 0)) message.success('收号压价已保存,本站收号价比主站更低,售价与主站一致') } catch (error: unknown) { const respMessage = (error as { response?: { data?: { message?: string } } })?.response?.data?.message message.error(respMessage || '保存收号比例失败') } finally { setSavingRatio(false) } } async function handleSaveSMS() { if (!siteID) return if (smsConfig.enabled) { if (!smsConfig.access_key_id?.trim()) { message.warning('启用独立短信时,阿里云 AccessKey ID 不能为空') return } if (!smsConfig.has_secret && !smsSecretInput.trim()) { message.warning('启用独立短信时,阿里云 AccessKey Secret 不能为空') return } if (!smsConfig.sign_name?.trim()) { message.warning('启用独立短信时,短信签名不能为空') return } if (!smsConfig.login_template_code?.trim()) { message.warning('启用独立短信时,登录验证码模板 Code 不能为空') return } } setSavingSMS(true) try { const payload: UpdateSiteSMSConfigPayload = { enabled: smsConfig.enabled, provider: smsConfig.provider || 'aliyun', access_key_id: smsConfig.access_key_id.trim(), endpoint: smsConfig.endpoint.trim() || 'dysmsapi.aliyuncs.com', sign_name: smsConfig.sign_name.trim(), login_template_code: smsConfig.login_template_code.trim(), } if (smsSecretInput.trim()) { payload.access_key_secret = smsSecretInput.trim() } const updated = await updateSiteSMSConfig(siteID, payload) setSmsConfig(updated) setSmsSecretInput('') message.success('短信服务配置保存成功') } catch (err: unknown) { const msg = err && typeof err === 'object' && 'response' in err ? (err as { response?: { data?: { message?: string } } }).response?.data?.message : '保存短信配置失败' message.error(msg || '保存短信配置失败') } finally { setSavingSMS(false) } } async function handleTestSMS() { if (!siteID) return const phone = testPhone.trim() if (!phone) { message.warning('请输入接收测试短信的手机号') return } if (!/^1[3-9]\d{9}$/.test(phone)) { message.warning('手机号格式不正确,请输入有效的11位手机号码') return } if (!smsConfig.access_key_id?.trim()) { message.warning('请先填写阿里云 AccessKey ID') return } if (!smsConfig.has_secret && !smsSecretInput.trim()) { message.warning('请先填写阿里云 AccessKey Secret') return } if (!smsConfig.sign_name?.trim()) { message.warning('请先填写短信签名') return } if (!smsConfig.login_template_code?.trim()) { message.warning('请先填写登录验证码模板 Code') return } setTestingSMS(true) try { const payload: TestSiteSMSConfigPayload = { phone, enabled: true, provider: smsConfig.provider || 'aliyun', access_key_id: smsConfig.access_key_id.trim(), endpoint: smsConfig.endpoint.trim() || 'dysmsapi.aliyuncs.com', sign_name: smsConfig.sign_name.trim(), login_template_code: smsConfig.login_template_code.trim(), } if (smsSecretInput.trim()) { payload.access_key_secret = smsSecretInput.trim() } await testSiteSMSConfig(siteID, payload) message.success('测试短信发送成功,请查收手机短信') setTestCooldown(60) } catch (err: unknown) { const msg = err && typeof err === 'object' && 'response' in err ? (err as { response?: { data?: { message?: string } } }).response?.data?.message : '测试短信发送失败' message.error(msg || '测试短信发送失败') } finally { setTestingSMS(false) } } async function handleSave() { if (!siteID) return setSaving(true) try { const payload: SiteBrandingPayload = { logo_url: logoUrl.trim(), favicon_url: faviconUrl.trim(), banners_json: JSON.stringify(banners), announcements_json: JSON.stringify(announcements), footer_info_json: JSON.stringify(footerInfo), } const updated = await updateSiteBranding(siteID, payload) setCurrentSite(updated) message.success('分销站点品牌与装修配置保存成功!') } catch (error: unknown) { const respMessage = (error as { response?: { data?: { message?: string } } })?.response?.data?.message message.error(respMessage || '保存装修配置失败') } finally { setSaving(false) } } return (
品牌与站点可视化装修 可视化配置当前分销独立站的 Logo、首页轮播海报、跑马灯公告及页脚客服信息。
{member?.is_superadmin && sites.length > 1 && ( setSmsConfig({ ...smsConfig, provider: val })} options={[{ label: '阿里云短信服务 (Aliyun Dysms)', value: 'aliyun' }]} /> setSmsConfig({ ...smsConfig, sign_name: e.target.value })} /> setSmsConfig({ ...smsConfig, login_template_code: e.target.value })} /> setSmsConfig({ ...smsConfig, access_key_id: e.target.value })} /> setSmsSecretInput(e.target.value)} /> setSmsConfig({ ...smsConfig, endpoint: e.target.value })} /> setTestPhone(e.target.value)} /> 0} onConfirm={() => void handleTestSMS()} > {/* Left Visual Form Customizer */}
站点 Logo 图片链接: setLogoUrl(e.target.value)} allowClear style={{ marginTop: 6 }} /> 建议尺寸 240x60 像素,透明底色 PNG 或 SVG 格式
浏览器标签页 Favicon 图标: setFaviconUrl(e.target.value)} allowClear style={{ marginTop: 6 }} /> 浏览器选项卡显示的小图标,建议 32x32 像素
), }, { key: 'banners', label: `首页轮播横幅 (${banners.length})`, children: (
为分销商城首页配置吸睛的促销横幅(支持自动轮播展示)。
{banners.length === 0 ? (
暂未添加轮播横幅,点击上方按钮新增
) : ( banners.map((item, idx) => (
横幅 #{idx + 1}
{item.image_url ? ( ) : (
无图片
)}
handleUpdateBanner(idx, 'image_url', e.target.value)} /> handleUpdateBanner(idx, 'title', e.target.value)} /> handleUpdateBanner(idx, 'link', e.target.value)} />
)) )}
), }, { key: 'announcements', label: `跑马灯公告 (${announcements.length})`, children: (
在商城顶部轮播滚动的最新活动或服务保障公告。
{announcements.length === 0 ? (
暂无公告,点击上方按钮新增
) : ( announcements.map((item, idx) => (
公告 #{idx + 1}
handleUpdateAnnouncement(idx, 'title', e.target.value)} /> handleUpdateAnnouncement(idx, 'content', e.target.value)} />
)) )}
), }, { key: 'footer', label: '页脚与客服备案', children: (
商城版权文案: setFooterInfo({ ...footerInfo, copyright: e.target.value })} style={{ marginTop: 6 }} />
ICP 备案编号: setFooterInfo({ ...footerInfo, icp: e.target.value })} style={{ marginTop: 6 }} />
官方客服微信 / QQ: setFooterInfo({ ...footerInfo, contact: e.target.value })} style={{ marginTop: 6 }} />
), }, ]} /> {/* Advanced Raw JSON Inspector (Collapsed) */} {JSON.stringify( { banners, announcements, footer_info: footerInfo, }, null, 2 )} ), }, ]} />
{/* Right Live Visual Simulation Card */}
{/* Simulated Browser Bar */}
https://{currentSite?.domain || `${currentSite?.site_code || 'demo'}.hfb.com`}
{/* Simulated Header Nav */}
{logoUrl ? ( Logo ) : ( {currentSite?.site_name || 'HFB 分销商城'} )} 分销前台
{/* Simulated Announcements Marquee */} {announcements.length > 0 && (
{announcements[0]?.title}
)} {/* Simulated Banner Carousel */}
{banners.length > 0 && banners[0]?.image_url ? ( Banner ) : (
暂无轮播横幅,请在左侧添加
)} {banners.length > 0 && banners[0]?.title && (
{banners[0].title}
)}
{/* Simulated Account Listings Grid Preview */}
精选租号货架
三角洲全金典藏号
¥35.00 /小时
无畏契约全套幻化
¥28.00 /小时
{/* Simulated Footer */}
{footerInfo.copyright || `© 2026 ${currentSite?.site_name || '分销网'}`}
{footerInfo.icp &&
{footerInfo.icp}
} {footerInfo.contact &&
{footerInfo.contact}
}
) }