feat(distribution): 完善分销收号资产双视图、商户提现、自主加价、品牌装修与数据大盘

This commit is contained in:
yml2213
2026-09-08 16:05:30 +08:00
parent fd4e490ed3
commit 2c4e87475a
19 changed files with 3145 additions and 108 deletions
@@ -0,0 +1,293 @@
import { useEffect, useState, useCallback } from 'react'
import {
Card,
Typography,
Space,
Select,
message,
Button,
Form,
Input,
Row,
Col,
Divider,
Image,
Alert,
Tag,
} from 'antd'
import {
SaveOutlined,
PictureOutlined,
NotificationOutlined,
EyeOutlined,
} from '@ant-design/icons'
import {
listSites,
updateSiteBranding,
type Site,
type SiteBrandingPayload,
} from '../api'
export default function SiteSettings() {
const [sites, setSites] = useState<Site[]>([])
const [siteID, setSiteID] = useState<number>()
const [loading, setLoading] = useState(false)
const [saving, setSaving] = useState(false)
const [currentSite, setCurrentSite] = useState<Site | null>(null)
const [form] = Form.useForm()
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)
form.setFieldsValue({
logo_url: found.logo_url || '',
favicon_url: found.favicon_url || '',
banners_json: found.banners_json || '[]',
announcements_json: found.announcements_json || '[]',
footer_info_json: found.footer_info_json || '{}',
})
}
} catch {
message.error('加载站点设置失败')
} finally {
setLoading(false)
}
}, [form])
useEffect(() => {
listSites()
.then((rows) => {
setSites(rows)
if (rows.length > 0) {
setSiteID(rows[0].id)
void loadSite(rows[0].id)
}
})
.catch(() => message.error('站点加载失败'))
}, [loadSite])
async function handleSave(values: SiteBrandingPayload) {
if (!siteID) return
// Validate JSON formatting
try {
if (values.banners_json) JSON.parse(values.banners_json)
if (values.announcements_json) JSON.parse(values.announcements_json)
if (values.footer_info_json) JSON.parse(values.footer_info_json)
} catch {
message.error('轮播图、公告或页脚配置的 JSON 格式不正确,请检查!')
return
}
setSaving(true)
try {
const updated = await updateSiteBranding(siteID, values)
setCurrentSite(updated)
message.success('分销站点品牌与装修配置保存成功!')
} catch (error: unknown) {
const respMessage = (error as { response?: { data?: { message?: string } } })?.response?.data?.message
message.error(respMessage || '保存装修配置失败')
} finally {
setSaving(false)
}
}
const logoUrl = Form.useWatch('logo_url', form)
const bannersJsonStr = Form.useWatch('banners_json', form)
const announcementsJsonStr = Form.useWatch('announcements_json', form)
let banners: { image_url: string; title?: string }[] = []
try {
if (bannersJsonStr) banners = JSON.parse(bannersJsonStr)
} catch {
// ignore json error in preview
}
let announcements: { title: string; content?: string }[] = []
try {
if (announcementsJsonStr) announcements = JSON.parse(announcementsJsonStr)
} catch {
// ignore
}
return (
<Space direction="vertical" className="page" style={{ width: '100%' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<Typography.Title level={3} style={{ marginBottom: 4 }}>
品牌与站点装修
</Typography.Title>
<Typography.Text type="secondary">
自定义当前分销站前台 Logo、浏览器图标、轮播横幅、站点公告及页脚备案信息。
</Typography.Text>
</div>
<Space>
<Select
placeholder="分销网站"
style={{ width: 220 }}
value={siteID}
options={sites.map((site) => ({ label: `${site.site_name} (${site.site_code})`, value: site.id }))}
onChange={(value) => {
setSiteID(value)
void loadSite(value)
}}
/>
{currentSite?.site_code && (
<Button
icon={<EyeOutlined />}
onClick={() => {
window.open(`http://${window.location.hostname}:5189/?site=${currentSite.site_code}`, '_blank')
}}
>
预览分销前台
</Button>
)}
</Space>
</div>
<Row gutter={24}>
<Col span={14}>
<Card title="装修配置表单" loading={loading}>
<Form form={form} layout="vertical" onFinish={handleSave}>
<Row gutter={16}>
<Col span={12}>
<Form.Item
label="站点 Logo 图片地址"
name="logo_url"
extra="支持 PNG / SVG / JPG 格式的公开图片链接"
>
<Input placeholder="https://.../logo.png" allowClear />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="浏览器 Favicon 图标地址"
name="favicon_url"
extra="浏览器标签页小图标 (ICO / PNG)"
>
<Input placeholder="https://.../favicon.ico" allowClear />
</Form.Item>
</Col>
</Row>
<Divider style={{ margin: '12px 0' }} />
<Form.Item
label={
<Space>
<PictureOutlined />
<span>首页轮播图配置 (Banners JSON)</span>
</Space>
}
name="banners_json"
extra='格式:[{"image_url": "图片URL", "title": "标题", "link": "点击跳转链接"}]'
>
<Input.TextArea rows={4} placeholder='[{"image_url":"https://.../banner1.jpg","title":"暑期特惠"}]' />
</Form.Item>
<Form.Item
label={
<Space>
<NotificationOutlined />
<span>站点跑马灯公告 (Announcements JSON)</span>
</Space>
}
name="announcements_json"
extra='格式:[{"title": "公告标题", "content": "详细内容"}]'
>
<Input.TextArea rows={3} placeholder='[{"title":"本站三角洲账号货源充足,上号快!"}]' />
</Form.Item>
<Form.Item
label="页脚信息配置 (Footer Info JSON)"
name="footer_info_json"
extra='格式:{"copyright": "© 2026 分销站点", "icp": "京ICP备XXXXX号", "contact": "客服QQ: 123456"}'
>
<Input.TextArea rows={3} placeholder='{"copyright":"© 2026 演示分销网","icp":"浙ICP备000000号"}' />
</Form.Item>
<Form.Item style={{ marginBottom: 0 }}>
<Button type="primary" htmlType="submit" icon={<SaveOutlined />} loading={saving} size="large">
保存装修配置
</Button>
</Form.Item>
</Form>
</Card>
</Col>
<Col span={10}>
<Card title="效果实时预览" style={{ background: '#f5f7fa' }}>
<div style={{ background: '#fff', borderRadius: 8, padding: 16, border: '1px solid #e8e8e8' }}>
{/* Header Preview */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderBottom: '1px solid #eee', paddingBottom: 12 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
{logoUrl ? (
<Image src={logoUrl} height={28} preview={false} style={{ objectFit: 'contain' }} fallback="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='28'%3E%3Ctext x='10' y='20' fill='%23999'%3ELogo 加载中%3C/text%3E%3C/svg%3E" />
) : (
<Typography.Text strong style={{ fontSize: 16, color: '#1677ff' }}>
{currentSite?.site_name || '分销站点名称'}
</Typography.Text>
)}
</div>
<Tag color="cyan" style={{ margin: 0 }}>分销前台顶栏</Tag>
</div>
{/* Announcement Preview */}
{announcements.length > 0 && (
<div style={{ marginTop: 12 }}>
<Alert
message={
<Space>
<NotificationOutlined style={{ color: '#fa8c16' }} />
<span>{announcements[0].title}</span>
</Space>
}
type="warning"
banner
style={{ borderRadius: 4, padding: '4px 12px' }}
/>
</div>
)}
{/* Banner Preview */}
<div style={{ marginTop: 12 }}>
<div style={{ fontSize: 12, color: '#888', marginBottom: 4 }}>轮播图预览:</div>
{banners.length > 0 && banners[0].image_url ? (
<div style={{ width: '100%', height: 120, borderRadius: 6, overflow: 'hidden', background: '#000' }}>
<img
src={banners[0].image_url}
alt="Banner"
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
onError={(e) => {
(e.target as HTMLImageElement).src = 'https://images.unsplash.com/photo-1542751371-adc38448a05e?w=600&auto=format&fit=crop&q=60'
}}
/>
</div>
) : (
<div style={{ width: '100%', height: 90, background: '#f0f2f5', borderRadius: 6, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#999', fontSize: 12 }}>
暂无轮播图配置
</div>
)}
</div>
{/* Content Placeholder */}
<div style={{ marginTop: 12, padding: 12, background: '#fafafa', borderRadius: 4, textAlign: 'center', color: '#999', fontSize: 12 }}>
[ 游戏商品列表展示区域 ]
</div>
{/* Footer Preview */}
<div style={{ marginTop: 16, borderTop: '1px solid #eee', paddingTop: 12, textAlign: 'center', fontSize: 11, color: '#999' }}>
<div>{currentSite?.site_name || '分销站点'} · 技术支持由 HFB 平台提供</div>
</div>
</div>
</Card>
</Col>
</Row>
</Space>
)
}