feat(distribution): 完善分销收号资产双视图、商户提现、自主加价、品牌装修与数据大盘
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user