1249 lines
47 KiB
TypeScript
1249 lines
47 KiB
TypeScript
import { useEffect, useState, useMemo } from 'react'
|
||
import {
|
||
Card,
|
||
Table,
|
||
Typography,
|
||
Space,
|
||
Select,
|
||
Tag,
|
||
message,
|
||
Button,
|
||
Input,
|
||
Modal,
|
||
InputNumber,
|
||
Empty,
|
||
Alert,
|
||
Tooltip,
|
||
Divider,
|
||
Radio,
|
||
Row,
|
||
Col,
|
||
} from 'antd'
|
||
import {
|
||
SyncOutlined,
|
||
SearchOutlined,
|
||
CheckCircleOutlined,
|
||
StopOutlined,
|
||
EditOutlined,
|
||
DollarCircleOutlined,
|
||
ReloadOutlined,
|
||
FilterOutlined,
|
||
ShopOutlined,
|
||
ThunderboltOutlined,
|
||
} from '@ant-design/icons'
|
||
import {
|
||
listAccountDisplays,
|
||
listSites,
|
||
updateAccountDisplay,
|
||
syncSiteAccounts,
|
||
updateAccountMarkup,
|
||
batchUpdateAccountMarkup,
|
||
formatMoney,
|
||
getMe,
|
||
type AccountDisplay,
|
||
type Site,
|
||
} from '../api'
|
||
import { useMember } from '../MemberContext'
|
||
|
||
const SOURCE_OPTIONS = [
|
||
{ label: '全部货源', value: 'all' },
|
||
{ label: '主站自营 (平台共享池)', value: 'main' },
|
||
{ label: '本站收号 (独家货源)', value: 'self' },
|
||
{ label: '合作分站 (跨站共享)', value: 'collab' },
|
||
]
|
||
|
||
const MARKUP_OPTIONS = [
|
||
{ label: '全部提价状态', value: 'all' },
|
||
{ label: '已自主提价 (赚差价)', value: 'marked' },
|
||
{ label: '未加价 (基准销售价)', value: 'unmarked' },
|
||
]
|
||
|
||
const DISPLAY_OPTIONS = [
|
||
{ label: '全部展示状态', value: 'all' },
|
||
{ label: '展示中 (在售货架)', value: 'enabled' },
|
||
{ label: '已下架 (隐藏货架)', value: 'disabled' },
|
||
]
|
||
|
||
const TX_OPTIONS = [
|
||
{ label: '全部租借状态', value: 'all' },
|
||
{ label: '待租中 (空闲可租)', value: 'idle' },
|
||
{ label: '出租中 (租赁履约)', value: 'rented' },
|
||
]
|
||
|
||
const MAIN_LISTING_STATUS_MAP: Record<string, { label: string; color: string }> = {
|
||
published: { label: '主站在售', color: 'green' },
|
||
rented: { label: '在租中', color: 'cyan' },
|
||
completed: { label: '已售出/结单', color: 'blue' },
|
||
offline: { label: '主站下架', color: 'default' },
|
||
closed: { label: '已关闭', color: 'error' },
|
||
abnormal: { label: '异常冻结', color: 'error' },
|
||
sealed: { label: '已封存', color: 'magenta' },
|
||
draft: { label: '草稿', color: 'warning' },
|
||
}
|
||
|
||
function formatGameName(name?: string) {
|
||
if (!name) return '默认游戏'
|
||
const lower = name.toLowerCase().trim()
|
||
if (
|
||
lower === 'delta_force' ||
|
||
lower === 'deltaforce' ||
|
||
lower === 'delta' ||
|
||
lower.includes('delta_force') ||
|
||
lower.includes('delta') ||
|
||
name.includes('三角洲')
|
||
) {
|
||
return '三角洲'
|
||
}
|
||
if (lower.includes('honor') || name.includes('王者')) {
|
||
return '王者荣耀'
|
||
}
|
||
if (lower.includes('genshin') || name.includes('原神')) {
|
||
return '原神'
|
||
}
|
||
if (lower.includes('cs') || lower.includes('csgo')) {
|
||
return 'CS2'
|
||
}
|
||
return name
|
||
}
|
||
|
||
function formatLoginPlatform(platform?: string): string {
|
||
if (!platform) return ''
|
||
const lower = platform.toLowerCase().trim()
|
||
if (lower === 'qq') return 'QQ'
|
||
if (lower === 'wechat' || lower === 'wx') return '微信'
|
||
if (lower === 'steam') return 'Steam'
|
||
if (lower === 'epic') return 'Epic'
|
||
if (lower === 'wegame') return 'WeGame'
|
||
if (lower === 'mobile') return '手游端'
|
||
if (lower === 'pc') return 'PC端'
|
||
return platform
|
||
}
|
||
|
||
export default function Accounts() {
|
||
const member = useMember()
|
||
const [sites, setSites] = useState<Site[]>([])
|
||
const [siteID, setSiteID] = useState<number | undefined>(member?.site_id)
|
||
const [items, setItems] = useState<AccountDisplay[]>([])
|
||
const [loading, setLoading] = useState(false)
|
||
const [syncing, setSyncing] = useState(false)
|
||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([])
|
||
|
||
// Shelf product filter states
|
||
const [sourceFilter, setSourceFilter] = useState<string>('all')
|
||
const [markupFilter, setMarkupFilter] = useState<string>('all')
|
||
const [displayFilter, setDisplayFilter] = useState<string>('all')
|
||
const [txFilter, setTxFilter] = useState<string>('all')
|
||
const [keyword, setKeyword] = useState<string>('')
|
||
|
||
// Multi-mode Pricing modal state
|
||
const [markupModalVisible, setMarkupModalVisible] = useState(false)
|
||
const [targetAccount, setTargetAccount] = useState<AccountDisplay | null>(null)
|
||
const [pricingMode, setPricingMode] = useState<'target_price' | 'add_amount' | 'percent' | 'ratio_delta'>('target_price')
|
||
const [targetPriceYuan, setTargetPriceYuan] = useState<number>(0)
|
||
const [addAmountYuan, setAddAmountYuan] = useState<number>(0)
|
||
const [percentMarkup, setPercentMarkup] = useState<number>(0)
|
||
const [markupRatio, setMarkupRatio] = useState<number>(0)
|
||
const [markupSaving, setMarkupSaving] = useState(false)
|
||
|
||
// Batch pricing state
|
||
const [batchMode, setBatchMode] = useState<'percent' | 'add_amount' | 'ratio_delta'>('percent')
|
||
const [batchValue, setBatchValue] = useState<number>(10)
|
||
|
||
async function load(site?: number) {
|
||
if (!site) return
|
||
setLoading(true)
|
||
try {
|
||
// Fetch full shelf goods to allow fast client-side multidimensional filtering
|
||
const rows = await listAccountDisplays(site, '')
|
||
setItems(rows)
|
||
} catch {
|
||
message.error('账号展示货架加载失败')
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
// Initialize site context:
|
||
// Normal distribution merchant is strictly bound to member.site_id
|
||
// Only superadmin can fetch sites and switch between tenants
|
||
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 load(activeSiteID)
|
||
}
|
||
|
||
if (currentMember?.is_superadmin) {
|
||
try {
|
||
const rows = await listSites()
|
||
setSites(rows)
|
||
if (!activeSiteID && rows.length > 0) {
|
||
setSiteID(rows[0].id)
|
||
void load(rows[0].id)
|
||
}
|
||
} catch {
|
||
// ignore
|
||
}
|
||
}
|
||
}
|
||
void init()
|
||
}, [member])
|
||
|
||
async function handleSync() {
|
||
if (!siteID) {
|
||
message.warning('无法获取当前分销站点信息')
|
||
return
|
||
}
|
||
setSyncing(true)
|
||
try {
|
||
const res = await syncSiteAccounts(siteID)
|
||
message.success(
|
||
`同步完成!成功同步新增商品 ${res.synced_count} 个,已在货架 ${res.skipped_count} 个(主站共享池在售共 ${res.total_active} 个)`,
|
||
5,
|
||
)
|
||
await load(siteID)
|
||
} catch (error: unknown) {
|
||
const respMessage = (error as { response?: { data?: { message?: string } } })?.response?.data?.message
|
||
message.error(respMessage ?? '同步主站商品失败')
|
||
} finally {
|
||
setSyncing(false)
|
||
}
|
||
}
|
||
|
||
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)
|
||
} catch (error: unknown) {
|
||
const respMessage = (error as { response?: { data?: { message?: string } } })?.response?.data?.message
|
||
message.error(respMessage ?? '状态更新失败')
|
||
}
|
||
}
|
||
|
||
async function handleBatchUpdate(targetStatus: 'enabled' | 'disabled') {
|
||
if (selectedRowKeys.length === 0) {
|
||
message.warning('请先勾选需要批量操作的账号')
|
||
return
|
||
}
|
||
const actionLabel = targetStatus === 'enabled' ? '批量恢复展示' : '批量下架'
|
||
Modal.confirm({
|
||
title: `确认${actionLabel}?`,
|
||
content: `即将对选中的 ${selectedRowKeys.length} 个账号执行${actionLabel}操作。`,
|
||
onOk: async () => {
|
||
let successCount = 0
|
||
for (const id of selectedRowKeys) {
|
||
try {
|
||
await updateAccountDisplay(Number(id), targetStatus)
|
||
successCount++
|
||
} catch {
|
||
// continue next
|
||
}
|
||
}
|
||
message.success(`已完成 ${successCount}/${selectedRowKeys.length} 个账号的${actionLabel}`)
|
||
setSelectedRowKeys([])
|
||
if (siteID) void load(siteID)
|
||
},
|
||
})
|
||
}
|
||
|
||
function roundRatio(value: number): number {
|
||
if (value <= 0 || isNaN(value) || !isFinite(value)) return 0
|
||
return Math.round(value * 10) / 10
|
||
}
|
||
|
||
function calcDeltaFromTargetPrice(hafCoinAmount: number, basePriceCent: number, targetPriceYuan: number): number {
|
||
if (basePriceCent <= 0 || targetPriceYuan <= 0) return 0
|
||
const coinWan = hafCoinAmount / 10000
|
||
const basePriceYuan = basePriceCent / 100
|
||
if (targetPriceYuan <= basePriceYuan) return 0
|
||
const baseRatio = roundRatio(coinWan / basePriceYuan)
|
||
if (baseRatio <= 0) return 0
|
||
const siteRatio = roundRatio(coinWan / targetPriceYuan)
|
||
const delta = roundRatio(baseRatio - siteRatio)
|
||
return Math.max(0, delta)
|
||
}
|
||
|
||
function calcPreviewPrice(hafCoinAmount: number, basePriceCent: number, delta: number): { priceCent: number; siteRatio: number; markupCent: number } {
|
||
const coinWan = hafCoinAmount / 10000
|
||
const basePriceYuan = basePriceCent / 100
|
||
const baseRatio = roundRatio(coinWan / basePriceYuan)
|
||
if (delta <= 0 || baseRatio <= 0) {
|
||
return { priceCent: basePriceCent, siteRatio: baseRatio, markupCent: 0 }
|
||
}
|
||
let siteRatio = roundRatio(baseRatio - delta)
|
||
if (siteRatio <= 0.01) siteRatio = 0.01
|
||
const priceYuan = coinWan / siteRatio
|
||
const priceCent = Math.max(basePriceCent, Math.round(priceYuan * 100))
|
||
return {
|
||
priceCent,
|
||
siteRatio,
|
||
markupCent: priceCent - basePriceCent,
|
||
}
|
||
}
|
||
|
||
function handleOpenMarkup(item?: AccountDisplay) {
|
||
if (item) {
|
||
setTargetAccount(item)
|
||
const currentDelta = item.shelf_ratio_delta ?? 0
|
||
setMarkupRatio(currentDelta)
|
||
const basePriceYuan = (item.price_cent || 0) / 100
|
||
const currentPriceYuan = (item.site_price_cent || item.price_cent || 0) / 100
|
||
setTargetPriceYuan(currentPriceYuan)
|
||
setAddAmountYuan(Math.max(0, Number((currentPriceYuan - basePriceYuan).toFixed(2))))
|
||
setPercentMarkup(
|
||
basePriceYuan > 0
|
||
? Number((((currentPriceYuan - basePriceYuan) / basePriceYuan) * 100).toFixed(1))
|
||
: 0
|
||
)
|
||
setPricingMode('target_price')
|
||
} else {
|
||
if (selectedRowKeys.length === 0) {
|
||
message.warning('请先勾选需要批量加价的账号商品')
|
||
return
|
||
}
|
||
setTargetAccount(null)
|
||
setBatchMode('percent')
|
||
setBatchValue(10)
|
||
}
|
||
setMarkupModalVisible(true)
|
||
}
|
||
|
||
function onTargetPriceChange(val: number | null) {
|
||
const nextTarget = Math.max(0, val ?? 0)
|
||
setTargetPriceYuan(nextTarget)
|
||
if (!targetAccount) return
|
||
const basePriceYuan = (targetAccount.price_cent || 0) / 100
|
||
const addYuan = Math.max(0, Number((nextTarget - basePriceYuan).toFixed(2)))
|
||
setAddAmountYuan(addYuan)
|
||
setPercentMarkup(basePriceYuan > 0 ? Number(((addYuan / basePriceYuan) * 100).toFixed(1)) : 0)
|
||
const delta = calcDeltaFromTargetPrice(targetAccount.haf_coin_amount || 0, targetAccount.price_cent || 0, nextTarget)
|
||
setMarkupRatio(delta)
|
||
}
|
||
|
||
function onAddAmountChange(val: number | null) {
|
||
const nextAdd = Math.max(0, val ?? 0)
|
||
setAddAmountYuan(nextAdd)
|
||
if (!targetAccount) return
|
||
const basePriceYuan = (targetAccount.price_cent || 0) / 100
|
||
const nextTarget = Number((basePriceYuan + nextAdd).toFixed(2))
|
||
setTargetPriceYuan(nextTarget)
|
||
setPercentMarkup(basePriceYuan > 0 ? Number(((nextAdd / basePriceYuan) * 100).toFixed(1)) : 0)
|
||
const delta = calcDeltaFromTargetPrice(targetAccount.haf_coin_amount || 0, targetAccount.price_cent || 0, nextTarget)
|
||
setMarkupRatio(delta)
|
||
}
|
||
|
||
function onPercentMarkupChange(val: number | null) {
|
||
const nextPct = Math.max(0, val ?? 0)
|
||
setPercentMarkup(nextPct)
|
||
if (!targetAccount) return
|
||
const basePriceYuan = (targetAccount.price_cent || 0) / 100
|
||
const nextTarget = Number((basePriceYuan * (1 + nextPct / 100)).toFixed(2))
|
||
setTargetPriceYuan(nextTarget)
|
||
setAddAmountYuan(Number((nextTarget - basePriceYuan).toFixed(2)))
|
||
const delta = calcDeltaFromTargetPrice(targetAccount.haf_coin_amount || 0, targetAccount.price_cent || 0, nextTarget)
|
||
setMarkupRatio(delta)
|
||
}
|
||
|
||
function onMarkupRatioChange(val: number | null) {
|
||
const nextDelta = Math.max(0, val ?? 0)
|
||
setMarkupRatio(nextDelta)
|
||
if (!targetAccount) return
|
||
const preview = calcPreviewPrice(targetAccount.haf_coin_amount || 0, targetAccount.price_cent || 0, nextDelta)
|
||
const previewYuan = Number((preview.priceCent / 100).toFixed(2))
|
||
const basePriceYuan = (targetAccount.price_cent || 0) / 100
|
||
setTargetPriceYuan(previewYuan)
|
||
const addYuan = Number((preview.markupCent / 100).toFixed(2))
|
||
setAddAmountYuan(addYuan)
|
||
setPercentMarkup(basePriceYuan > 0 ? Number(((addYuan / basePriceYuan) * 100).toFixed(1)) : 0)
|
||
}
|
||
|
||
async function handleSaveMarkup() {
|
||
if (!siteID) return
|
||
setMarkupSaving(true)
|
||
try {
|
||
if (targetAccount) {
|
||
await updateAccountMarkup(targetAccount.id, markupRatio)
|
||
message.success(`商品《${targetAccount.title || targetAccount.listing_no}》加价成功!`)
|
||
} else {
|
||
const selectedItems = items.filter((it) => selectedRowKeys.includes(it.id))
|
||
if (selectedItems.length === 0) {
|
||
message.warning('请先勾选要加价的商品')
|
||
return
|
||
}
|
||
|
||
if (batchMode === 'ratio_delta') {
|
||
const listingIDs = selectedItems.map((it) => it.listing_id)
|
||
await batchUpdateAccountMarkup(siteID, listingIDs, batchValue)
|
||
} else if (batchMode === 'percent') {
|
||
await Promise.all(
|
||
selectedItems.map((it) => {
|
||
const basePriceYuan = (it.price_cent || 0) / 100
|
||
const targetYuan = basePriceYuan * (1 + batchValue / 100)
|
||
const delta = calcDeltaFromTargetPrice(it.haf_coin_amount || 0, it.price_cent || 0, targetYuan)
|
||
return updateAccountMarkup(it.id, delta)
|
||
})
|
||
)
|
||
} else if (batchMode === 'add_amount') {
|
||
await Promise.all(
|
||
selectedItems.map((it) => {
|
||
const basePriceYuan = (it.price_cent || 0) / 100
|
||
const targetYuan = basePriceYuan + batchValue
|
||
const delta = calcDeltaFromTargetPrice(it.haf_coin_amount || 0, it.price_cent || 0, targetYuan)
|
||
return updateAccountMarkup(it.id, delta)
|
||
})
|
||
)
|
||
}
|
||
message.success(`已成功为选中的 ${selectedItems.length} 个商品批量设置提价!`)
|
||
setSelectedRowKeys([])
|
||
}
|
||
setMarkupModalVisible(false)
|
||
void load(siteID)
|
||
} catch (error: unknown) {
|
||
const respMessage = (error as { response?: { data?: { message?: string } } })?.response?.data?.message
|
||
message.error(respMessage || '提价设置保存失败')
|
||
} finally {
|
||
setMarkupSaving(false)
|
||
}
|
||
}
|
||
|
||
function handleResetFilters() {
|
||
setSourceFilter('all')
|
||
setMarkupFilter('all')
|
||
setDisplayFilter('all')
|
||
setTxFilter('all')
|
||
setKeyword('')
|
||
}
|
||
|
||
// Multidimensional client-side filter
|
||
const filteredItems = useMemo(() => {
|
||
return items.filter((item) => {
|
||
// 1. Source filter
|
||
if (sourceFilter === 'main') {
|
||
if (item.listing_site_id !== null && item.listing_site_id !== undefined) return false
|
||
} else if (sourceFilter === 'self') {
|
||
if (item.listing_site_id !== siteID) return false
|
||
} else if (sourceFilter === 'collab') {
|
||
if (item.listing_site_id === null || item.listing_site_id === undefined || item.listing_site_id === siteID) {
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 2. Markup filter
|
||
if (markupFilter === 'marked') {
|
||
if (!item.shelf_ratio_delta || item.shelf_ratio_delta <= 0) return false
|
||
} else if (markupFilter === 'unmarked') {
|
||
if (item.shelf_ratio_delta && item.shelf_ratio_delta > 0) return false
|
||
}
|
||
|
||
// 3. Display status filter
|
||
if (displayFilter !== 'all') {
|
||
if (item.display_status !== displayFilter) return false
|
||
}
|
||
|
||
// 4. Transaction filter
|
||
if (txFilter === 'idle') {
|
||
if (item.in_transaction) return false
|
||
} else if (txFilter === 'rented') {
|
||
if (!item.in_transaction) return false
|
||
}
|
||
|
||
// 5. Keyword search
|
||
if (keyword.trim()) {
|
||
const kw = keyword.trim().toLowerCase()
|
||
const matchTitle = item.title && item.title.toLowerCase().includes(kw)
|
||
const matchNo = item.listing_no && item.listing_no.toLowerCase().includes(kw)
|
||
const matchGame = (item.game_name && item.game_name.toLowerCase().includes(kw)) || formatGameName(item.game_name).includes(kw)
|
||
const matchRegion = item.server_region && item.server_region.toLowerCase().includes(kw)
|
||
const matchPlatform = item.login_platform && item.login_platform.toLowerCase().includes(kw)
|
||
const matchId = String(item.account_id).includes(kw) || String(item.listing_id).includes(kw)
|
||
if (!matchTitle && !matchNo && !matchGame && !matchRegion && !matchPlatform && !matchId) {
|
||
return false
|
||
}
|
||
}
|
||
|
||
return true
|
||
})
|
||
}, [items, sourceFilter, markupFilter, displayFilter, txFilter, keyword, siteID])
|
||
|
||
// Statistics counters
|
||
const counts = useMemo(() => {
|
||
let mainCount = 0
|
||
let selfCount = 0
|
||
let collabCount = 0
|
||
let markedCount = 0
|
||
let enabledCount = 0
|
||
|
||
for (const item of items) {
|
||
if (item.listing_site_id === null || item.listing_site_id === undefined) {
|
||
mainCount++
|
||
} else if (item.listing_site_id === siteID) {
|
||
selfCount++
|
||
} else {
|
||
collabCount++
|
||
}
|
||
|
||
if (item.shelf_ratio_delta && item.shelf_ratio_delta > 0) {
|
||
markedCount++
|
||
}
|
||
if (item.display_status === 'enabled') {
|
||
enabledCount++
|
||
}
|
||
}
|
||
|
||
const hasActiveFilters =
|
||
sourceFilter !== 'all' ||
|
||
markupFilter !== 'all' ||
|
||
displayFilter !== 'all' ||
|
||
txFilter !== 'all' ||
|
||
keyword.trim() !== ''
|
||
|
||
return {
|
||
total: items.length,
|
||
main: mainCount,
|
||
self: selfCount,
|
||
collab: collabCount,
|
||
marked: markedCount,
|
||
enabled: enabledCount,
|
||
filtered: filteredItems.length,
|
||
hasActiveFilters,
|
||
}
|
||
}, [items, siteID, sourceFilter, markupFilter, displayFilter, txFilter, keyword, filteredItems.length])
|
||
|
||
const currentSiteName =
|
||
member?.site_name || sites.find((s) => s.id === siteID)?.site_name || '当前分销商'
|
||
|
||
return (
|
||
<Space direction="vertical" className="page" style={{ width: '100%' }}>
|
||
{/* Superadmin Cross-tenant Inspection Banner */}
|
||
{member?.is_superadmin && sites.length > 0 && (
|
||
<Alert
|
||
type="info"
|
||
showIcon
|
||
style={{ marginBottom: 4 }}
|
||
message={
|
||
<Space align="center" wrap>
|
||
<Tag color="purple">平台超管巡检模式</Tag>
|
||
<span>切换巡查分销站点:</span>
|
||
<Select
|
||
style={{ width: 260 }}
|
||
value={siteID}
|
||
options={sites.map((s) => ({
|
||
label: `${s.site_name} (${s.site_code})`,
|
||
value: s.id,
|
||
}))}
|
||
onChange={(val) => {
|
||
setSiteID(val)
|
||
setSelectedRowKeys([])
|
||
void load(val)
|
||
}}
|
||
/>
|
||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||
(普通分销商登录时已完全自动锁定所属本站,无需选择分销网站)
|
||
</Typography.Text>
|
||
</Space>
|
||
}
|
||
/>
|
||
)}
|
||
|
||
{/* Page Header */}
|
||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 12 }}>
|
||
<div>
|
||
<Space align="baseline">
|
||
<Typography.Title level={3} style={{ marginBottom: 4 }}>
|
||
商品与账号展示
|
||
</Typography.Title>
|
||
<Tag color="blue" style={{ fontSize: 13, padding: '2px 8px' }}>
|
||
<ShopOutlined /> {currentSiteName}
|
||
</Tag>
|
||
</Space>
|
||
<div>
|
||
<Typography.Text type="secondary">
|
||
管理本站对外展示与销售的货架商品。所有账号源均来自主站及平台共享池,支持一键铺货并自主提价(调小兑换比例)销售,<strong>加价溢价全额归本站所有</strong>。
|
||
</Typography.Text>
|
||
</div>
|
||
</div>
|
||
<Button
|
||
type="primary"
|
||
icon={<SyncOutlined spin={syncing} />}
|
||
loading={syncing}
|
||
onClick={handleSync}
|
||
style={{ background: '#1677ff' }}
|
||
>
|
||
一键同步主站商品
|
||
</Button>
|
||
</div>
|
||
|
||
{/* Shelf Filter Bar */}
|
||
<Card size="small">
|
||
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
||
{/* Controls row */}
|
||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 8 }}>
|
||
<Space wrap size="small">
|
||
{/* 货源归属 */}
|
||
<Select
|
||
style={{ width: 170 }}
|
||
value={sourceFilter}
|
||
options={SOURCE_OPTIONS}
|
||
onChange={setSourceFilter}
|
||
/>
|
||
|
||
{/* 提价状态 */}
|
||
<Select
|
||
style={{ width: 170 }}
|
||
value={markupFilter}
|
||
options={MARKUP_OPTIONS}
|
||
onChange={setMarkupFilter}
|
||
/>
|
||
|
||
{/* 展示状态 */}
|
||
<Select
|
||
style={{ width: 160 }}
|
||
value={displayFilter}
|
||
options={DISPLAY_OPTIONS}
|
||
onChange={setDisplayFilter}
|
||
/>
|
||
|
||
{/* 交易租借状态 */}
|
||
<Select
|
||
style={{ width: 150 }}
|
||
value={txFilter}
|
||
options={TX_OPTIONS}
|
||
onChange={setTxFilter}
|
||
/>
|
||
|
||
{/* 搜索框 */}
|
||
<Input
|
||
placeholder="搜索商品编号/账号标题/游戏/区服"
|
||
prefix={<SearchOutlined />}
|
||
style={{ width: 250 }}
|
||
allowClear
|
||
value={keyword}
|
||
onChange={(e) => setKeyword(e.target.value)}
|
||
/>
|
||
|
||
{counts.hasActiveFilters && (
|
||
<Button icon={<ReloadOutlined />} onClick={handleResetFilters}>
|
||
重置筛选
|
||
</Button>
|
||
)}
|
||
</Space>
|
||
|
||
{/* Batch actions */}
|
||
{selectedRowKeys.length > 0 && (
|
||
<Space>
|
||
<Typography.Text strong style={{ color: '#1677ff' }}>
|
||
已选 {selectedRowKeys.length} 项
|
||
</Typography.Text>
|
||
<Button
|
||
size="small"
|
||
type="primary"
|
||
ghost
|
||
icon={<DollarCircleOutlined />}
|
||
onClick={() => handleOpenMarkup()}
|
||
>
|
||
批量加价
|
||
</Button>
|
||
<Button
|
||
size="small"
|
||
icon={<CheckCircleOutlined />}
|
||
onClick={() => handleBatchUpdate('enabled')}
|
||
>
|
||
批量展示
|
||
</Button>
|
||
<Button
|
||
size="small"
|
||
danger
|
||
icon={<StopOutlined />}
|
||
onClick={() => handleBatchUpdate('disabled')}
|
||
>
|
||
批量下架
|
||
</Button>
|
||
<Button size="small" type="link" onClick={() => setSelectedRowKeys([])}>
|
||
取消勾选
|
||
</Button>
|
||
</Space>
|
||
)}
|
||
</div>
|
||
|
||
<Divider style={{ margin: '4px 0' }} />
|
||
|
||
{/* Quick Statistics and Data Scope Badges */}
|
||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 8 }}>
|
||
<Space wrap size={4}>
|
||
<Typography.Text type="secondary" style={{ fontSize: 12, marginRight: 4 }}>
|
||
<FilterOutlined /> 货架概览:
|
||
</Typography.Text>
|
||
<Tag color="default">货架总计 {counts.total} 件</Tag>
|
||
<Tag color="green">主站自营 {counts.main} 件</Tag>
|
||
<Tag color="purple">本站收号 {counts.self} 件</Tag>
|
||
{counts.collab > 0 && <Tag color="blue">合作分站 {counts.collab} 件</Tag>}
|
||
<Tag color="gold">已自主加价 {counts.marked} 件</Tag>
|
||
<Tag color="cyan">正常展示中 {counts.enabled} 件</Tag>
|
||
</Space>
|
||
|
||
{counts.hasActiveFilters && (
|
||
<Tag color="processing" style={{ fontWeight: 'bold' }}>
|
||
已按条件筛选:展示 {counts.filtered} / {counts.total} 件
|
||
</Tag>
|
||
)}
|
||
</div>
|
||
</Space>
|
||
</Card>
|
||
|
||
{/* Products Table */}
|
||
<Card>
|
||
<Table
|
||
rowKey="id"
|
||
loading={loading}
|
||
dataSource={filteredItems}
|
||
scroll={{ x: 1200 }}
|
||
locale={{
|
||
emptyText: (
|
||
<Empty
|
||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||
description={
|
||
counts.hasActiveFilters
|
||
? '没有符合当前筛选条件的货架商品,请尝试重置筛选条件'
|
||
: '当前分站货架暂无商品。点击下方按钮一键同步主站共享池,即可在此设置提价并对外展示。'
|
||
}
|
||
>
|
||
{counts.hasActiveFilters ? (
|
||
<Button icon={<ReloadOutlined />} onClick={handleResetFilters}>
|
||
重置筛选条件
|
||
</Button>
|
||
) : (
|
||
<Button
|
||
type="primary"
|
||
icon={<SyncOutlined spin={syncing} />}
|
||
loading={syncing}
|
||
onClick={handleSync}
|
||
>
|
||
一键同步主站商品
|
||
</Button>
|
||
)}
|
||
</Empty>
|
||
),
|
||
}}
|
||
rowSelection={{
|
||
selectedRowKeys,
|
||
onChange: setSelectedRowKeys,
|
||
}}
|
||
pagination={{
|
||
pageSize: 20,
|
||
showTotal: (total) => `共 ${total} 个账号商品`,
|
||
showQuickJumper: true,
|
||
}}
|
||
columns={[
|
||
{
|
||
title: '商品编号',
|
||
dataIndex: 'listing_no',
|
||
width: 170,
|
||
render: (v: string | undefined, row: AccountDisplay) => (
|
||
<div style={{ whiteSpace: 'nowrap' }}>
|
||
<div style={{ display: 'inline-flex', alignItems: 'center', whiteSpace: 'nowrap', marginBottom: 4 }}>
|
||
<Typography.Text
|
||
copyable={{ text: v || String(row.listing_id), tooltips: ['复制商品编号', '已复制'] }}
|
||
strong
|
||
className="mono-code"
|
||
style={{
|
||
fontSize: 13,
|
||
color: '#1677ff',
|
||
whiteSpace: 'nowrap',
|
||
wordBreak: 'keep-all',
|
||
display: 'inline-flex',
|
||
alignItems: 'center',
|
||
}}
|
||
>
|
||
{v || `#${row.listing_id}`}
|
||
</Typography.Text>
|
||
</div>
|
||
<div>
|
||
{row.in_transaction ? (
|
||
<Tag color="warning" style={{ margin: 0, fontSize: 11 }}>
|
||
出租中
|
||
</Tag>
|
||
) : (
|
||
<Tag color="green" style={{ margin: 0, fontSize: 11 }}>
|
||
在售空闲
|
||
</Tag>
|
||
)}
|
||
</div>
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
title: '货源归属',
|
||
width: 110,
|
||
render: (_: unknown, row: AccountDisplay) => {
|
||
if (row.listing_site_id === null || row.listing_site_id === undefined) {
|
||
return (
|
||
<Tooltip title="来自主站官方统一共享池,分销商可提价转售">
|
||
<Tag color="green">主站自营</Tag>
|
||
</Tooltip>
|
||
)
|
||
}
|
||
if (row.listing_site_id === siteID) {
|
||
return (
|
||
<Tooltip title="本分销站专属收录上架的号源">
|
||
<Tag color="purple">本站收号</Tag>
|
||
</Tooltip>
|
||
)
|
||
}
|
||
return (
|
||
<Tooltip title="其他合作分销站铺过来的共享号源">
|
||
<Tag color="blue">合作分站</Tag>
|
||
</Tooltip>
|
||
)
|
||
},
|
||
},
|
||
{
|
||
title: '账号信息',
|
||
dataIndex: 'title',
|
||
width: 260,
|
||
render: (_: unknown, row: AccountDisplay) => (
|
||
<div>
|
||
<Typography.Text
|
||
strong
|
||
style={{ fontSize: 14, lineHeight: '20px', display: 'block' }}
|
||
ellipsis={{ tooltip: row.title }}
|
||
>
|
||
{row.title || `账号 #${row.account_id}`}
|
||
</Typography.Text>
|
||
<Space size={4} style={{ marginTop: 4 }} wrap>
|
||
<Tag color="blue">{formatGameName(row.game_name)}</Tag>
|
||
{row.server_region && <Tag>{row.server_region}</Tag>}
|
||
{row.login_platform && <Tag color="cyan">{formatLoginPlatform(row.login_platform)}</Tag>}
|
||
</Space>
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
title: '段位 / 资产',
|
||
width: 130,
|
||
render: (_: unknown, row: AccountDisplay) => (
|
||
<div>
|
||
<div>{row.rank_level ? <Tag color="purple">{row.rank_level}</Tag> : '-'}</div>
|
||
{row.haf_coin_amount !== undefined && row.haf_coin_amount > 0 && (
|
||
<Typography.Text type="secondary" style={{ fontSize: 12, whiteSpace: 'nowrap', display: 'block', marginTop: 2 }}>
|
||
哈夫币: {row.haf_coin_amount.toLocaleString()}
|
||
</Typography.Text>
|
||
)}
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
title: '租金 / 押金',
|
||
width: 150,
|
||
render: (_: unknown, row: AccountDisplay) => {
|
||
const finalPrice = row.site_price_cent || row.price_cent
|
||
const hasMarkup = row.shelf_ratio_delta && row.shelf_ratio_delta > 0
|
||
return (
|
||
<div>
|
||
<Typography.Text strong style={{ color: '#cf1322', fontSize: 15, whiteSpace: 'nowrap', display: 'block' }}>
|
||
{formatMoney(finalPrice)}
|
||
</Typography.Text>
|
||
<div style={{ fontSize: 12, color: '#888', whiteSpace: 'nowrap', marginTop: 2 }}>
|
||
押金: {formatMoney(row.deposit_amount_cent)}
|
||
</div>
|
||
{hasMarkup && (
|
||
<div style={{ fontSize: 11, color: '#389e0d', marginTop: 2, whiteSpace: 'nowrap' }}>
|
||
含加价: +{formatMoney(row.markup_amount_cent)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
},
|
||
},
|
||
{
|
||
title: '加价比例',
|
||
dataIndex: 'shelf_ratio_delta',
|
||
width: 110,
|
||
render: (v: number | undefined) => {
|
||
if (v && v > 0) {
|
||
return (
|
||
<Tag color="gold" style={{ fontWeight: 'bold' }}>
|
||
+{v} 比例点
|
||
</Tag>
|
||
)
|
||
}
|
||
return <span style={{ color: '#aaa', fontSize: 12 }}>无加价 (基准)</span>
|
||
},
|
||
},
|
||
{
|
||
title: '主站状态',
|
||
dataIndex: 'listing_status',
|
||
width: 110,
|
||
render: (v: string | undefined) => {
|
||
const conf = MAIN_LISTING_STATUS_MAP[v || ''] || { label: v || '未知', color: 'default' }
|
||
return <Tag color={conf.color}>{conf.label}</Tag>
|
||
},
|
||
},
|
||
{
|
||
title: '分销展示',
|
||
dataIndex: 'display_status',
|
||
width: 100,
|
||
render: (v: string) => (
|
||
<Tag color={v === 'enabled' ? 'success' : 'default'}>
|
||
{v === 'enabled' ? '展示中' : '已下架'}
|
||
</Tag>
|
||
),
|
||
},
|
||
{
|
||
title: '操作',
|
||
width: 110,
|
||
fixed: 'right' as const,
|
||
render: (_: unknown, row: AccountDisplay) => (
|
||
<Space size={2}>
|
||
<Button
|
||
type="link"
|
||
size="small"
|
||
icon={<EditOutlined />}
|
||
style={{ padding: 0 }}
|
||
onClick={() => handleOpenMarkup(row)}
|
||
>
|
||
加价
|
||
</Button>
|
||
<Button
|
||
type="link"
|
||
size="small"
|
||
danger={row.display_status === 'enabled'}
|
||
style={{ padding: 0 }}
|
||
onClick={() => toggle(row)}
|
||
>
|
||
{row.display_status === 'enabled' ? '下架' : '展示'}
|
||
</Button>
|
||
</Space>
|
||
),
|
||
},
|
||
]}
|
||
/>
|
||
</Card>
|
||
|
||
{/* Rich Multi-mode Markup Modal */}
|
||
<Modal
|
||
title={
|
||
<Space>
|
||
<DollarCircleOutlined style={{ color: '#52c41a' }} />
|
||
<span>{targetAccount ? `商品加价设置 - ${targetAccount.title || targetAccount.listing_no}` : `批量调价 (${selectedRowKeys.length} 个账号商品)`}</span>
|
||
</Space>
|
||
}
|
||
open={markupModalVisible}
|
||
onCancel={() => setMarkupModalVisible(false)}
|
||
onOk={handleSaveMarkup}
|
||
confirmLoading={markupSaving}
|
||
okText="保存并同步货架"
|
||
cancelText="取消"
|
||
width={580}
|
||
destroyOnClose
|
||
>
|
||
<Space direction="vertical" style={{ width: '100%', padding: '8px 0' }} size="middle">
|
||
<Alert
|
||
type="success"
|
||
showIcon
|
||
message="分销商 100% 独享溢价说明"
|
||
description="在主站基准租金之上增加的全部溢价金额(加价差价),100% 独立结算至本分销商账户,不与任何第三方分成!"
|
||
/>
|
||
|
||
{targetAccount ? (
|
||
<>
|
||
<div
|
||
style={{
|
||
background: '#f8fafc',
|
||
border: '1px solid #e2e8f0',
|
||
borderRadius: 6,
|
||
padding: '12px 16px',
|
||
}}
|
||
>
|
||
<Row gutter={[16, 8]}>
|
||
<Col span={24}>
|
||
<Typography.Text strong style={{ fontSize: 14 }}>
|
||
{targetAccount.title || targetAccount.listing_no}
|
||
</Typography.Text>
|
||
</Col>
|
||
<Col span={12}>
|
||
<Typography.Text type="secondary">游戏区服:</Typography.Text>
|
||
<span>{formatGameName(targetAccount.game_name) || '-'} / {targetAccount.server_region || '-'}</span>
|
||
</Col>
|
||
<Col span={12}>
|
||
<Typography.Text type="secondary">哈夫币资产:</Typography.Text>
|
||
<span>
|
||
{(targetAccount.haf_coin_amount || 0).toLocaleString()} 币
|
||
<span style={{ color: '#8c8c8c', marginLeft: 4 }}>
|
||
({((targetAccount.haf_coin_amount || 0) / 10000).toFixed(1)} 万)
|
||
</span>
|
||
</span>
|
||
</Col>
|
||
<Col span={12}>
|
||
<Typography.Text type="secondary">主站基准价:</Typography.Text>
|
||
<strong style={{ color: '#1677ff' }}>¥{((targetAccount.price_cent || 0) / 100).toFixed(2)}</strong>
|
||
</Col>
|
||
<Col span={12}>
|
||
<Typography.Text type="secondary">主站基准比例:</Typography.Text>
|
||
<span>{targetAccount.base_ratio || '-'} 点</span>
|
||
</Col>
|
||
</Row>
|
||
</div>
|
||
|
||
<div>
|
||
<Typography.Text strong style={{ display: 'block', marginBottom: 8 }}>
|
||
加价模式选择:
|
||
</Typography.Text>
|
||
<Radio.Group
|
||
value={pricingMode}
|
||
onChange={(e) => setPricingMode(e.target.value)}
|
||
buttonStyle="solid"
|
||
style={{ width: '100%' }}
|
||
>
|
||
<Radio.Button value="target_price" style={{ width: '25%', textAlign: 'center' }}>期望售价</Radio.Button>
|
||
<Radio.Button value="add_amount" style={{ width: '25%', textAlign: 'center' }}>固定加价</Radio.Button>
|
||
<Radio.Button value="percent" style={{ width: '25%', textAlign: 'center' }}>百分比上浮</Radio.Button>
|
||
<Radio.Button value="ratio_delta" style={{ width: '25%', textAlign: 'center' }}>比例调减</Radio.Button>
|
||
</Radio.Group>
|
||
</div>
|
||
|
||
<div style={{ background: '#fafafa', padding: 12, borderRadius: 6 }}>
|
||
{pricingMode === 'target_price' && (
|
||
<div>
|
||
<div style={{ marginBottom: 6 }}>
|
||
<Typography.Text strong>最终售价(元):</Typography.Text>
|
||
</div>
|
||
<InputNumber
|
||
prefix="¥"
|
||
min={Number(((targetAccount.price_cent || 0) / 100).toFixed(2))}
|
||
precision={2}
|
||
step={1}
|
||
style={{ width: '100%' }}
|
||
value={targetPriceYuan}
|
||
onChange={onTargetPriceChange}
|
||
placeholder="输入租客最终看到的标价"
|
||
/>
|
||
<Typography.Text type="secondary" style={{ fontSize: 12, display: 'block', marginTop: 4 }}>
|
||
输入最终期望标价,系统自动换算对应比例点
|
||
</Typography.Text>
|
||
</div>
|
||
)}
|
||
|
||
{pricingMode === 'add_amount' && (
|
||
<div>
|
||
<div style={{ marginBottom: 6 }}>
|
||
<Typography.Text strong>加价金额(元):</Typography.Text>
|
||
</div>
|
||
<InputNumber
|
||
prefix="+¥"
|
||
min={0}
|
||
precision={2}
|
||
step={1}
|
||
style={{ width: '100%' }}
|
||
value={addAmountYuan}
|
||
onChange={onAddAmountChange}
|
||
placeholder="输入在主站价格基础上增加的金额"
|
||
/>
|
||
<Typography.Text type="secondary" style={{ fontSize: 12, display: 'block', marginTop: 4 }}>
|
||
在主站基准价上直接增加的纯利润金额
|
||
</Typography.Text>
|
||
</div>
|
||
)}
|
||
|
||
{pricingMode === 'percent' && (
|
||
<div>
|
||
<div style={{ marginBottom: 6 }}>
|
||
<Typography.Text strong>上浮百分比(%):</Typography.Text>
|
||
</div>
|
||
<InputNumber
|
||
prefix="+"
|
||
addonAfter="%"
|
||
min={0}
|
||
max={500}
|
||
precision={1}
|
||
step={5}
|
||
style={{ width: '100%' }}
|
||
value={percentMarkup}
|
||
onChange={onPercentMarkupChange}
|
||
placeholder="例如 15"
|
||
/>
|
||
<Typography.Text type="secondary" style={{ fontSize: 12, display: 'block', marginTop: 4 }}>
|
||
按基准价百分比上浮,例如 10% 或 20%
|
||
</Typography.Text>
|
||
</div>
|
||
)}
|
||
|
||
{pricingMode === 'ratio_delta' && (
|
||
<div>
|
||
<div style={{ marginBottom: 6 }}>
|
||
<Typography.Text strong>调减比例点(点):</Typography.Text>
|
||
</div>
|
||
<InputNumber
|
||
min={0}
|
||
max={100}
|
||
precision={1}
|
||
step={0.1}
|
||
addonAfter="点"
|
||
style={{ width: '100%' }}
|
||
value={markupRatio}
|
||
onChange={onMarkupRatioChange}
|
||
placeholder="0"
|
||
/>
|
||
<Typography.Text type="secondary" style={{ fontSize: 12, display: 'block', marginTop: 4 }}>
|
||
高级模式:直接调减 1 元兑换哈夫币比例,比例越小售价越高
|
||
</Typography.Text>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Real-time Earnings Comparison Card */}
|
||
<div
|
||
style={{
|
||
padding: '14px 16px',
|
||
background: '#f6ffed',
|
||
borderRadius: 6,
|
||
border: '1px solid #b7eb8f',
|
||
}}
|
||
>
|
||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
|
||
<Typography.Text type="secondary">预计本站标价:</Typography.Text>
|
||
<Typography.Text strong style={{ fontSize: 16, color: '#cf1322' }}>
|
||
¥{targetPriceYuan.toFixed(2)}
|
||
</Typography.Text>
|
||
</div>
|
||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
|
||
<Typography.Text type="secondary">本站 100% 独享溢价:</Typography.Text>
|
||
<Typography.Text strong style={{ fontSize: 18, color: '#389e0d' }}>
|
||
+¥{addAmountYuan.toFixed(2)}
|
||
</Typography.Text>
|
||
</div>
|
||
<Divider style={{ margin: '8px 0' }} />
|
||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, color: '#595959' }}>
|
||
<span>兑换比例换算:</span>
|
||
<span>
|
||
基准 {targetAccount.base_ratio || '-'} → 标价{' '}
|
||
<strong>{Math.max((targetAccount.base_ratio || 0) - markupRatio, 0.01).toFixed(1)}</strong>{' '}
|
||
(调减 {markupRatio.toFixed(1)} 点)
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</>
|
||
) : (
|
||
<>
|
||
<div>
|
||
<Typography.Text strong style={{ display: 'block', marginBottom: 8 }}>
|
||
批量加价模式(已勾选 {selectedRowKeys.length} 件商品):
|
||
</Typography.Text>
|
||
<Radio.Group
|
||
value={batchMode}
|
||
onChange={(e) => setBatchMode(e.target.value)}
|
||
buttonStyle="solid"
|
||
style={{ width: '100%' }}
|
||
>
|
||
<Radio.Button value="percent" style={{ width: '33.3%', textAlign: 'center' }}>
|
||
按百分比批量上浮 (推荐)
|
||
</Radio.Button>
|
||
<Radio.Button value="add_amount" style={{ width: '33.3%', textAlign: 'center' }}>
|
||
固定加价金额
|
||
</Radio.Button>
|
||
<Radio.Button value="ratio_delta" style={{ width: '33.3%', textAlign: 'center' }}>
|
||
统一调减比例点
|
||
</Radio.Button>
|
||
</Radio.Group>
|
||
</div>
|
||
|
||
<div style={{ background: '#fafafa', padding: 16, borderRadius: 6 }}>
|
||
{batchMode === 'percent' && (
|
||
<div>
|
||
<Typography.Text strong style={{ display: 'block', marginBottom: 6 }}>
|
||
统一上浮百分比(%):
|
||
</Typography.Text>
|
||
<InputNumber
|
||
prefix="+"
|
||
addonAfter="%"
|
||
min={0}
|
||
max={500}
|
||
precision={1}
|
||
step={5}
|
||
style={{ width: '100%' }}
|
||
value={batchValue}
|
||
onChange={(v) => setBatchValue(v ?? 0)}
|
||
placeholder="例如 10 或 15"
|
||
/>
|
||
<Typography.Text type="secondary" style={{ fontSize: 12, display: 'block', marginTop: 6 }}>
|
||
所有勾选商品将按各自底价自动上浮 {batchValue}%,并自动为每件商品精确匹配比例点
|
||
</Typography.Text>
|
||
</div>
|
||
)}
|
||
|
||
{batchMode === 'add_amount' && (
|
||
<div>
|
||
<Typography.Text strong style={{ display: 'block', marginBottom: 6 }}>
|
||
统一加价金额(元):
|
||
</Typography.Text>
|
||
<InputNumber
|
||
prefix="+¥"
|
||
addonAfter="元"
|
||
min={0}
|
||
precision={2}
|
||
step={1}
|
||
style={{ width: '100%' }}
|
||
value={batchValue}
|
||
onChange={(v) => setBatchValue(v ?? 0)}
|
||
placeholder="例如 5.00"
|
||
/>
|
||
<Typography.Text type="secondary" style={{ fontSize: 12, display: 'block', marginTop: 6 }}>
|
||
所有勾选商品统一定价加价 ¥{batchValue.toFixed(2)},溢价收益全额入账本站
|
||
</Typography.Text>
|
||
</div>
|
||
)}
|
||
|
||
{batchMode === 'ratio_delta' && (
|
||
<div>
|
||
<Typography.Text strong style={{ display: 'block', marginBottom: 6 }}>
|
||
统一调减比例点数:
|
||
</Typography.Text>
|
||
<InputNumber
|
||
min={0}
|
||
max={100}
|
||
precision={1}
|
||
step={0.1}
|
||
addonAfter="点"
|
||
style={{ width: '100%' }}
|
||
value={batchValue}
|
||
onChange={(v) => setBatchValue(v ?? 0)}
|
||
placeholder="例如 0.5"
|
||
/>
|
||
<Typography.Text type="secondary" style={{ fontSize: 12, display: 'block', marginTop: 6 }}>
|
||
统一在各商品原比例基础上调减 {batchValue} 点
|
||
</Typography.Text>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div
|
||
style={{
|
||
padding: '12px 16px',
|
||
background: '#f6ffed',
|
||
borderRadius: 6,
|
||
border: '1px solid #b7eb8f',
|
||
fontSize: 13,
|
||
color: '#389e0d',
|
||
}}
|
||
>
|
||
💡 已勾选 <strong>{selectedRowKeys.length}</strong> 件商品,批量调价后差价收益将 100% 归本分销商账户独享!
|
||
</div>
|
||
</>
|
||
)}
|
||
</Space>
|
||
</Modal>
|
||
</Space>
|
||
)
|
||
}
|