分销后台前端对接与结算明细接口
- 新增 GET /admin/distribution/settlement-batches/:id/items 明细查询 - distribution-admin 重构为 react-router 多页面:仪表盘/网站/规则(启停)/订单(筛选分页)/结算(创建/明细/结算)/支付通道(绑定解绑)/账号展示(上下架) - axios 统一 401 提示,引导回主站后台登录;金额统一整数分格式化 - npm run build(tsc 严格模式) 通过
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Card, Table, Typography, Space, Select, Tag, message } from 'antd'
|
||||
import { listAccountDisplays, listSites, updateAccountDisplay, type AccountDisplay, type Site } from '../api'
|
||||
|
||||
const STATUS_LABELS: Record<string, string> = {
|
||||
enabled: '展示中',
|
||||
disabled: '已下架',
|
||||
sold: '已售出',
|
||||
offline: '已离线',
|
||||
}
|
||||
|
||||
export default function Accounts() {
|
||||
const [sites, setSites] = useState<Site[]>([])
|
||||
const [siteID, setSiteID] = useState<number>()
|
||||
const [status, setStatus] = useState<string>('')
|
||||
const [items, setItems] = useState<AccountDisplay[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
async function load(site?: number, statusFilter?: string) {
|
||||
if (!site) return
|
||||
setLoading(true)
|
||||
try {
|
||||
setItems(await listAccountDisplays(site, statusFilter))
|
||||
} catch {
|
||||
message.error('账号展示加载失败')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
listSites()
|
||||
.then((rows) => {
|
||||
setSites(rows)
|
||||
if (rows.length > 0) {
|
||||
setSiteID(rows[0].id)
|
||||
void load(rows[0].id)
|
||||
}
|
||||
})
|
||||
.catch(() => message.error('站点加载失败'))
|
||||
}, [])
|
||||
|
||||
async function toggle(item: AccountDisplay) {
|
||||
const next = item.display_status === 'enabled' ? 'disabled' : 'enabled'
|
||||
try {
|
||||
await updateAccountDisplay(item.id, next)
|
||||
message.success(next === 'enabled' ? '已恢复展示' : '已下架')
|
||||
if (siteID) void load(siteID, status)
|
||||
} catch (error: unknown) {
|
||||
const respMessage = (error as { response?: { data?: { message?: string } } })?.response?.data?.message
|
||||
message.error(respMessage ?? '状态更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Space direction="vertical" className="page">
|
||||
<Typography.Title level={3}>账号展示</Typography.Title>
|
||||
<Space>
|
||||
<Select
|
||||
placeholder="分销网站"
|
||||
style={{ width: 200 }}
|
||||
value={siteID}
|
||||
options={sites.map((site) => ({ label: site.site_name, value: site.id }))}
|
||||
onChange={(value) => {
|
||||
setSiteID(value)
|
||||
void load(value, status)
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="展示状态"
|
||||
style={{ width: 140 }}
|
||||
options={Object.entries(STATUS_LABELS).map(([value, label]) => ({ label, value }))}
|
||||
onChange={(value) => {
|
||||
setStatus(value ?? '')
|
||||
void load(siteID, value ?? '')
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
<Card>
|
||||
<Table
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
dataSource={items}
|
||||
pagination={{ pageSize: 20 }}
|
||||
columns={[
|
||||
{ title: '账号 ID', dataIndex: 'account_id' },
|
||||
{ title: '商品 ID', dataIndex: 'listing_id' },
|
||||
{
|
||||
title: '展示状态',
|
||||
dataIndex: 'display_status',
|
||||
render: (v: string) => <Tag color={v === 'enabled' ? 'green' : 'default'}>{STATUS_LABELS[v] ?? v}</Tag>,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
render: (_: unknown, row: AccountDisplay) => (
|
||||
<a onClick={() => toggle(row)}>{row.display_status === 'enabled' ? '下架' : '恢复展示'}</a>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Card, Table, Typography, Statistic, Space, message } from 'antd'
|
||||
import { listOrders, listSites, formatMoney, type Site } from '../api'
|
||||
|
||||
export default function Dashboard() {
|
||||
const [sites, setSites] = useState<Site[]>([])
|
||||
const [orders, setOrders] = useState<Awaited<ReturnType<typeof listOrders>>['items']>([])
|
||||
const [total, setTotal] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([listSites(), listOrders({ pageSize: 100 })])
|
||||
.then(([siteItems, page]) => {
|
||||
setSites(siteItems)
|
||||
setOrders(page.items)
|
||||
setTotal(page.total)
|
||||
})
|
||||
.catch(() => message.error('数据加载失败'))
|
||||
}, [])
|
||||
|
||||
const netPaid = orders.reduce((sum, item) => sum + item.net_paid_amount_cent, 0)
|
||||
|
||||
return (
|
||||
<Space direction="vertical" size="large" className="page">
|
||||
<Typography.Title level={3}>分销运营概览</Typography.Title>
|
||||
<div className="stats">
|
||||
<Card><Statistic title="分销网站" value={sites.length} /></Card>
|
||||
<Card><Statistic title="分销订单" value={total} /></Card>
|
||||
<Card><Statistic title="最近净支付" value={(netPaid / 100).toFixed(2)} suffix="元" /></Card>
|
||||
</div>
|
||||
<Card title="最近订单">
|
||||
<Table
|
||||
rowKey="order_id"
|
||||
dataSource={orders.slice(0, 10)}
|
||||
pagination={false}
|
||||
columns={[
|
||||
{ title: '订单号', dataIndex: 'order_no' },
|
||||
{ title: '状态', dataIndex: 'status' },
|
||||
{ title: '净支付', dataIndex: 'net_paid_amount_cent', render: (v: number) => formatMoney(v) },
|
||||
{ title: '分成基数', dataIndex: 'split_base_amount_cent', render: (v: number) => formatMoney(v) },
|
||||
{ title: '结算', dataIndex: 'settlement_status' },
|
||||
{ title: '创建时间', dataIndex: 'created_at' },
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Card, Table, Typography, Space, Select, Tag, message } from 'antd'
|
||||
import { formatMoney, listOrders, listSites, type DistributionOrder, type Site } from '../api'
|
||||
|
||||
const ORDER_STATUSES = [
|
||||
'pending_payment',
|
||||
'pending_handoff',
|
||||
'renting',
|
||||
'overdue',
|
||||
'pending_checkout_confirm',
|
||||
'pending_checkout_accept',
|
||||
'checkout_disputing',
|
||||
'disputing',
|
||||
'abnormal',
|
||||
'completed',
|
||||
'refunded',
|
||||
'cancelled',
|
||||
]
|
||||
|
||||
export default function Orders({ kind }: { kind: 'all' | 'sold' | 'receive' }) {
|
||||
const [sites, setSites] = useState<Site[]>([])
|
||||
const [siteID, setSiteID] = useState<number>()
|
||||
const [status, setStatus] = useState<string>('')
|
||||
const [items, setItems] = useState<DistributionOrder[]>([])
|
||||
const [total, setTotal] = useState(0)
|
||||
const [page, setPage] = useState(1)
|
||||
const pageSize = 20
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const load = useCallback(async (targetPage: number) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const result = await listOrders({ kind, status, siteID, page: targetPage, pageSize })
|
||||
setItems(result.items)
|
||||
setTotal(result.total)
|
||||
setPage(result.page)
|
||||
} catch {
|
||||
message.error('订单加载失败')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [kind, status, siteID])
|
||||
|
||||
useEffect(() => {
|
||||
listSites()
|
||||
.then((rows) => {
|
||||
setSites(rows)
|
||||
if (rows.length > 0) setSiteID(rows[0].id)
|
||||
})
|
||||
.catch(() => message.error('站点加载失败'))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
void load(1)
|
||||
}, [load])
|
||||
|
||||
const title = kind === 'sold' ? '售出账号订单' : kind === 'receive' ? '分销收号订单' : '全部分销订单'
|
||||
|
||||
return (
|
||||
<Space direction="vertical" className="page">
|
||||
<Typography.Title level={3}>{title}</Typography.Title>
|
||||
<Space>
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="分销网站"
|
||||
style={{ width: 200 }}
|
||||
value={siteID}
|
||||
options={sites.map((site) => ({ label: site.site_name, value: site.id }))}
|
||||
onChange={(value) => setSiteID(value ?? undefined)}
|
||||
/>
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="订单状态"
|
||||
style={{ width: 200 }}
|
||||
options={ORDER_STATUSES.map((value) => ({ label: value, value }))}
|
||||
onChange={(value) => setStatus(value ?? '')}
|
||||
/>
|
||||
</Space>
|
||||
<Card>
|
||||
<Table
|
||||
rowKey="order_id"
|
||||
loading={loading}
|
||||
dataSource={items}
|
||||
pagination={{ current: page, pageSize, total, onChange: (p) => void load(p) }}
|
||||
columns={[
|
||||
{ title: '订单号', dataIndex: 'order_no' },
|
||||
{ title: '订单状态', dataIndex: 'status', render: (v: string) => <Tag>{v}</Tag> },
|
||||
{ title: '退款状态', dataIndex: 'refund_status' },
|
||||
{ title: '净支付', dataIndex: 'net_paid_amount_cent', render: (v: number) => formatMoney(v) },
|
||||
{ title: '手续费', dataIndex: 'payment_fee_cent', render: (v: number) => formatMoney(v) },
|
||||
{ title: '服务费', dataIndex: 'service_fee_cent', render: (v: number) => formatMoney(v) },
|
||||
{ title: '分成基数', dataIndex: 'split_base_amount_cent', render: (v: number) => formatMoney(v) },
|
||||
{ title: '尾差', dataIndex: 'rounding_adjustment_cent', render: (v: number) => formatMoney(v) },
|
||||
{ title: '结算', dataIndex: 'settlement_status' },
|
||||
{ title: '创建时间', dataIndex: 'created_at' },
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Card, Table, Typography, Space, Select, Tag, InputNumber, Button, message } from 'antd'
|
||||
import { bindPaymentChannel, listPaymentChannels, listSites, unbindPaymentChannel, type PaymentChannel, type Site } from '../api'
|
||||
|
||||
export default function Payments() {
|
||||
const [sites, setSites] = useState<Site[]>([])
|
||||
const [siteID, setSiteID] = useState<number>()
|
||||
const [channels, setChannels] = useState<PaymentChannel[]>([])
|
||||
const [configID, setConfigID] = useState<number | null>(null)
|
||||
const [priority, setPriority] = useState<number>(0)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
async function load(id: number) {
|
||||
setLoading(true)
|
||||
try {
|
||||
setChannels(await listPaymentChannels(id))
|
||||
} catch {
|
||||
message.error('支付通道加载失败')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
listSites()
|
||||
.then((items) => {
|
||||
setSites(items)
|
||||
if (items.length > 0) {
|
||||
setSiteID(items[0].id)
|
||||
void load(items[0].id)
|
||||
}
|
||||
})
|
||||
.catch(() => message.error('站点加载失败'))
|
||||
}, [])
|
||||
|
||||
async function handleBind() {
|
||||
if (!siteID || !configID) {
|
||||
message.warning('请选择站点并填写商户配置 ID')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await bindPaymentChannel(siteID, configID, priority)
|
||||
message.success('支付通道已绑定')
|
||||
setConfigID(null)
|
||||
void load(siteID)
|
||||
} catch (error: unknown) {
|
||||
const respMessage = (error as { response?: { data?: { message?: string } } })?.response?.data?.message
|
||||
message.error(respMessage ?? '绑定失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUnbind(id: number) {
|
||||
try {
|
||||
await unbindPaymentChannel(id)
|
||||
message.success('已解绑')
|
||||
if (siteID) void load(siteID)
|
||||
} catch {
|
||||
message.error('解绑失败')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Space direction="vertical" className="page">
|
||||
<Typography.Title level={3}>支付通道</Typography.Title>
|
||||
<Space>
|
||||
<Select
|
||||
placeholder="分销网站"
|
||||
style={{ width: 200 }}
|
||||
value={siteID}
|
||||
options={sites.map((site) => ({ label: site.site_name, value: site.id }))}
|
||||
onChange={(value) => {
|
||||
setSiteID(value)
|
||||
void load(value)
|
||||
}}
|
||||
/>
|
||||
<InputNumber min={1} placeholder="商户配置 ID" value={configID ?? undefined} onChange={(v) => setConfigID(v ?? null)} />
|
||||
<InputNumber min={0} placeholder="优先级" value={priority} onChange={(v) => setPriority(v ?? 0)} />
|
||||
<Button type="primary" onClick={handleBind}>绑定</Button>
|
||||
</Space>
|
||||
<Card>
|
||||
<Table
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
dataSource={channels}
|
||||
pagination={false}
|
||||
columns={[
|
||||
{ title: '商户配置 ID', dataIndex: 'config_id' },
|
||||
{ title: '优先级', dataIndex: 'priority' },
|
||||
{ title: '状态', dataIndex: 'status', render: (v: string) => <Tag color={v === 'active' ? 'green' : 'default'}>{v}</Tag> },
|
||||
{
|
||||
title: '操作',
|
||||
render: (_: unknown, row: PaymentChannel) => <a onClick={() => handleUnbind(row.id)}>解绑</a>,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Card, Table, Typography, Space, Select, Tag, message } from 'antd'
|
||||
import { listRules, listSites, updateRuleStatus, type Rule, type Site } from '../api'
|
||||
|
||||
const MODE_LABELS: Record<string, string> = {
|
||||
two_party_distributor_receive: '两方-分销收号',
|
||||
two_party_platform_receive: '两方-分销卖出',
|
||||
three_party: '三方',
|
||||
}
|
||||
|
||||
export default function Rules() {
|
||||
const [sites, setSites] = useState<Site[]>([])
|
||||
const [siteID, setSiteID] = useState<number | undefined>(undefined)
|
||||
const [rules, setRules] = useState<Rule[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
async function load(site?: number) {
|
||||
setLoading(true)
|
||||
try {
|
||||
setRules(await listRules(site))
|
||||
} catch {
|
||||
message.error('规则加载失败')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
listSites().then(setSites).catch(() => message.error('站点加载失败'))
|
||||
void load(undefined)
|
||||
}, [])
|
||||
|
||||
async function toggleStatus(rule: Rule) {
|
||||
const next = rule.status === 'active' ? 'disabled' : 'active'
|
||||
try {
|
||||
await updateRuleStatus(rule.id, next)
|
||||
message.success(`规则已${next === 'active' ? '启用' : '停用'}`)
|
||||
void load(siteID)
|
||||
} catch {
|
||||
message.error('状态更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Space direction="vertical" className="page">
|
||||
<Typography.Title level={3}>比例与税费规则</Typography.Title>
|
||||
<Space>
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="分销网站"
|
||||
style={{ width: 200 }}
|
||||
options={sites.map((site) => ({ label: site.site_name, value: site.id }))}
|
||||
onChange={(value) => {
|
||||
setSiteID(value)
|
||||
void load(value)
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
<Card>
|
||||
<Table
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
dataSource={rules}
|
||||
pagination={false}
|
||||
columns={[
|
||||
{ title: '模式', dataIndex: 'mode', render: (v: string) => MODE_LABELS[v] ?? v },
|
||||
{ title: '版本', dataIndex: 'version' },
|
||||
{ title: '状态', dataIndex: 'status', render: (v: string) => <Tag color={v === 'active' ? 'green' : 'default'}>{v}</Tag> },
|
||||
{
|
||||
title: '比例',
|
||||
render: (_: unknown, r: Rule) => {
|
||||
const parts: string[] = []
|
||||
if (r.distributor_receive_ratio > 0) parts.push(`分销收 ${r.distributor_receive_ratio}`)
|
||||
if (r.platform_sell_ratio > 0) parts.push(`主站卖 ${r.platform_sell_ratio}`)
|
||||
if (r.distributor_sell_ratio > 0) parts.push(`分销卖 ${r.distributor_sell_ratio}`)
|
||||
if (r.platform_receive_ratio > 0) parts.push(`主站收 ${r.platform_receive_ratio}`)
|
||||
if (r.distributor_a_ratio > 0) parts.push(`分销A ${r.distributor_a_ratio}`)
|
||||
if (r.distributor_b_ratio > 0) parts.push(`分销B ${r.distributor_b_ratio}`)
|
||||
return parts.join(' / ') || '-'
|
||||
},
|
||||
},
|
||||
{ title: '服务费', render: (_: unknown, r: Rule) => `${r.service_fee_value} (${r.service_fee_unit})` },
|
||||
{ title: '手续费', dataIndex: 'payment_fee_bps', render: (v: number) => `${v / 100}%` },
|
||||
{ title: '税费', render: (_: unknown, r: Rule) => (r.tax_enabled ? `${r.tax_rate_bps / 100}%` : '关闭') },
|
||||
{ title: '生效时间', dataIndex: 'effective_from' },
|
||||
{
|
||||
title: '操作',
|
||||
render: (_: unknown, r: Rule) => (
|
||||
<a onClick={() => toggleStatus(r)}>{r.status === 'active' ? '停用' : '启用'}</a>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Card, Table, Typography, Space, Select, Tag, DatePicker, Button, message, Modal } from 'antd'
|
||||
import {
|
||||
createSettlementBatch,
|
||||
formatMoney,
|
||||
listSettlementBatches,
|
||||
listSettlementItems,
|
||||
listSites,
|
||||
settleBatch,
|
||||
type SettlementBatch,
|
||||
type SettlementItem,
|
||||
type Site,
|
||||
} from '../api'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
|
||||
export default function Settlements() {
|
||||
const [sites, setSites] = useState<Site[]>([])
|
||||
const [siteID, setSiteID] = useState<number>()
|
||||
const [batches, setBatches] = useState<SettlementBatch[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [range, setRange] = useState<[Dayjs | null, Dayjs | null] | null>(null)
|
||||
const [itemsFor, setItemsFor] = useState<SettlementBatch | null>(null)
|
||||
const [items, setItems] = useState<SettlementItem[]>([])
|
||||
|
||||
async function load(id: number) {
|
||||
setLoading(true)
|
||||
try {
|
||||
setBatches(await listSettlementBatches(id))
|
||||
} catch {
|
||||
message.error('结算批次加载失败')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
listSites()
|
||||
.then((rows) => {
|
||||
setSites(rows)
|
||||
if (rows.length > 0) {
|
||||
setSiteID(rows[0].id)
|
||||
void load(rows[0].id)
|
||||
}
|
||||
})
|
||||
.catch(() => message.error('站点加载失败'))
|
||||
}, [])
|
||||
|
||||
async function handleCreate() {
|
||||
if (!siteID || !range?.[0] || !range?.[1]) {
|
||||
message.warning('请选择站点和结算周期')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const batch = await createSettlementBatch(siteID, range[0].toISOString(), range[1].toISOString())
|
||||
message.success(`批次 ${batch.batch_no} 已创建,合计 ${formatMoney(batch.total_amount_cent)}`)
|
||||
void load(siteID)
|
||||
} catch (error: unknown) {
|
||||
const respMessage = (error as { response?: { data?: { message?: string } } })?.response?.data?.message
|
||||
message.error(respMessage ?? '批次创建失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSettle(batch: SettlementBatch) {
|
||||
try {
|
||||
await settleBatch(batch.id)
|
||||
message.success(`批次 ${batch.batch_no} 已结算`)
|
||||
if (siteID) void load(siteID)
|
||||
} catch (error: unknown) {
|
||||
const respMessage = (error as { response?: { data?: { message?: string } } })?.response?.data?.message
|
||||
message.error(respMessage ?? '结算失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function showItems(batch: SettlementBatch) {
|
||||
try {
|
||||
setItemsFor(batch)
|
||||
setItems(await listSettlementItems(batch.id))
|
||||
} catch {
|
||||
message.error('结算明细加载失败')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Space direction="vertical" className="page">
|
||||
<Typography.Title level={3}>结算管理</Typography.Title>
|
||||
<Space>
|
||||
<Select
|
||||
placeholder="分销网站"
|
||||
style={{ width: 200 }}
|
||||
value={siteID}
|
||||
options={sites.map((site) => ({ label: site.site_name, value: site.id }))}
|
||||
onChange={(value) => {
|
||||
setSiteID(value)
|
||||
void load(value)
|
||||
}}
|
||||
/>
|
||||
<DatePicker.RangePicker showTime onChange={(value) => setRange(value)} />
|
||||
<Button type="primary" onClick={handleCreate}>创建批次</Button>
|
||||
</Space>
|
||||
<Card>
|
||||
<Table
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
dataSource={batches}
|
||||
pagination={false}
|
||||
columns={[
|
||||
{ title: '批次号', dataIndex: 'batch_no' },
|
||||
{ title: '周期开始', dataIndex: 'period_start' },
|
||||
{ title: '周期结束', dataIndex: 'period_end' },
|
||||
{ title: '合计金额', dataIndex: 'total_amount_cent', render: (v: number) => formatMoney(v) },
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
render: (v: string) => <Tag color={v === 'settled' ? 'green' : 'gold'}>{v === 'settled' ? '已结算' : '草稿'}</Tag>,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
render: (_: unknown, row: SettlementBatch) => (
|
||||
<Space>
|
||||
<a onClick={() => showItems(row)}>明细</a>
|
||||
{row.status !== 'settled' && <a onClick={() => handleSettle(row)}>结算</a>}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
<Modal
|
||||
open={itemsFor !== null}
|
||||
title={`结算明细 · ${itemsFor?.batch_no ?? ''}`}
|
||||
footer={null}
|
||||
onCancel={() => setItemsFor(null)}
|
||||
>
|
||||
<Table
|
||||
rowKey="id"
|
||||
dataSource={items}
|
||||
pagination={false}
|
||||
columns={[
|
||||
{ title: '订单 ID', dataIndex: 'order_id' },
|
||||
{ title: '参与方', dataIndex: 'party_role' },
|
||||
{ title: '金额', dataIndex: 'amount_cent', render: (v: number) => formatMoney(v) },
|
||||
{ title: '状态', dataIndex: 'status' },
|
||||
]}
|
||||
/>
|
||||
</Modal>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Card, Table, Typography, Space, Tag, message } from 'antd'
|
||||
import { listSites, type Site } from '../api'
|
||||
|
||||
export default function Sites() {
|
||||
const [items, setItems] = useState<Site[]>([])
|
||||
useEffect(() => {
|
||||
listSites()
|
||||
.then(setItems)
|
||||
.catch(() => message.error('站点加载失败'))
|
||||
}, [])
|
||||
return (
|
||||
<Space direction="vertical" className="page">
|
||||
<Typography.Title level={3}>分销网站</Typography.Title>
|
||||
<Card>
|
||||
<Table
|
||||
rowKey="id"
|
||||
dataSource={items}
|
||||
pagination={false}
|
||||
columns={[
|
||||
{ title: '名称', dataIndex: 'site_name' },
|
||||
{ title: '编码', dataIndex: 'site_code' },
|
||||
{ title: '域名', dataIndex: 'domain' },
|
||||
{ title: '状态', dataIndex: 'status', render: (v: string) => <Tag color={v === 'active' ? 'green' : 'default'}>{v}</Tag> },
|
||||
{
|
||||
title: '账号展示',
|
||||
dataIndex: 'display_account_enabled',
|
||||
render: (v: boolean) => (v ? <Tag color="green">开启</Tag> : <Tag>关闭</Tag>),
|
||||
},
|
||||
{
|
||||
title: '税费',
|
||||
render: (_: unknown, row: Site) => (row.tax_enabled ? `${row.tax_rate_bps / 100}%` : '关闭'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user