629 lines
23 KiB
TypeScript
629 lines
23 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react'
|
||
import { Alert, Button, Form, Modal, Space, message } from 'antd'
|
||
import { HistoryOutlined, ImportOutlined, ReloadOutlined, ShoppingCartOutlined } from '@ant-design/icons'
|
||
import { rockApi, type RockConfig, type RockStats, type RockTokenDeleteResult, type RockTokenItem } from '../api'
|
||
import { PageHeader } from '../components/PageHeader'
|
||
import { RockMetricCards } from './platformRock/RockMetricCards'
|
||
import { RockStrategyCard } from './platformRock/RockStrategyCard'
|
||
import { RockTokenFilterBar } from './platformRock/RockTokenFilterBar'
|
||
import { RockTokenTable } from './platformRock/RockTokenTable'
|
||
import { PurchaseTokenModal } from './platformRock/PurchaseTokenModal'
|
||
import { PurchaseLogDrawer } from './platformRock/PurchaseLogDrawer'
|
||
import { ImportAllPackagesModal } from './platformRock/ImportAllPackagesModal'
|
||
import { TokenDeleteModal, type TokenDeleteMode } from './platformRock/TokenDeleteModal'
|
||
import { TokenRecycleBinDrawer } from './platformRock/TokenRecycleBinDrawer'
|
||
import { UidEditModal } from './platformRock/UidEditModal'
|
||
import { VoucherPreviewModal } from './platformRock/VoucherPreviewModal'
|
||
import type { UidEditState } from './platformRock/types'
|
||
|
||
function prettyRawData(raw?: string) {
|
||
if (!raw) return '暂无原始数据'
|
||
try {
|
||
return JSON.stringify(JSON.parse(raw), null, 2)
|
||
} catch {
|
||
return raw
|
||
}
|
||
}
|
||
|
||
export default function PlatformRock() {
|
||
const [config, setConfig] = useState<RockConfig | null>(null)
|
||
const [stats, setStats] = useState<RockStats | null>(null)
|
||
const [upstreamStock, setUpstreamStock] = useState<number | null>(null)
|
||
const [loadingStats, setLoadingStats] = useState(false)
|
||
const [testingConn, setTestingConn] = useState(false)
|
||
const [testingLogin, setTestingLogin] = useState(false)
|
||
const [refreshingToken, setRefreshingToken] = useState(false)
|
||
const [savingConfig, setSavingConfig] = useState(false)
|
||
const [autoPurchaseEnabled, setAutoPurchaseEnabled] = useState(false)
|
||
const [togglingAutoPurchase, setTogglingAutoPurchase] = useState(false)
|
||
const [autoRefreshMismatchEnabled, setAutoRefreshMismatchEnabled] = useState(false)
|
||
const [togglingAutoRefresh, setTogglingAutoRefresh] = useState(false)
|
||
const [refreshingStale, setRefreshingStale] = useState(false)
|
||
const [purchasing, setPurchasing] = useState(false)
|
||
const [purchaseModalOpen, setPurchaseModalOpen] = useState(false)
|
||
const [purchaseCount, setPurchaseCount] = useState(50)
|
||
const [purchaseLogOpen, setPurchaseLogOpen] = useState(false)
|
||
const [importPackageOpen, setImportPackageOpen] = useState(false)
|
||
const [rawDataRecord, setRawDataRecord] = useState<RockTokenItem | null>(null)
|
||
const [selectedTokenIds, setSelectedTokenIds] = useState<number[]>([])
|
||
const [selectedTokens, setSelectedTokens] = useState<RockTokenItem[]>([])
|
||
const [tokenDeleteMode, setTokenDeleteMode] = useState<TokenDeleteMode | null>(null)
|
||
const [recycleOpen, setRecycleOpen] = useState(false)
|
||
|
||
// 凭证图片操作状态
|
||
const [copyingVoucherId, setCopyingVoucherId] = useState<number | null>(null)
|
||
const [previewVoucherUrl, setPreviewVoucherUrl] = useState('')
|
||
const [previewVoucherOpen, setPreviewVoucherOpen] = useState(false)
|
||
|
||
// Token 列表与筛选
|
||
const [tokens, setTokens] = useState<RockTokenItem[]>([])
|
||
const [totalTokens, setTotalTokens] = useState(0)
|
||
const [page, setPage] = useState(1)
|
||
const [pageSize, setPageSize] = useState(20)
|
||
const [filterStatus, setFilterStatus] = useState<string>('all')
|
||
const [searchKeyword, setSearchKeyword] = useState('')
|
||
const [loadingTokens, setLoadingTokens] = useState(false)
|
||
|
||
const [form] = Form.useForm()
|
||
const [uidEdit, setUidEdit] = useState<UidEditState | null>(null)
|
||
const [savingUid, setSavingUid] = useState(false)
|
||
|
||
const loadData = useCallback(async () => {
|
||
setLoadingStats(true)
|
||
try {
|
||
try {
|
||
const dashboard = await rockApi.refreshDashboard()
|
||
setUpstreamStock(dashboard.available_black)
|
||
} catch (e) {
|
||
setUpstreamStock(null)
|
||
message.warning('获取上游库存/采购单价失败: ' + (e instanceof Error ? e.message : '未知错误'))
|
||
}
|
||
const [cfg, st] = await Promise.all([rockApi.getConfig(), rockApi.getStats()])
|
||
setConfig(cfg)
|
||
setStats(st)
|
||
setAutoPurchaseEnabled(cfg.auto_purchase_enabled)
|
||
setAutoRefreshMismatchEnabled(cfg.auto_refresh_mismatch_enabled ?? false)
|
||
form.setFieldsValue({
|
||
auto_delivery_enabled: cfg.auto_delivery_enabled,
|
||
base_url: cfg.base_url,
|
||
bearer_token: cfg.bearer_token,
|
||
login_username: cfg.login_username,
|
||
min_points_warning: cfg.min_points_warning,
|
||
token_warning_stock: cfg.token_warning_stock,
|
||
using_warn_days: cfg.using_warn_days ?? 3,
|
||
using_danger_days: cfg.using_danger_days ?? 5,
|
||
bound_warn_days: cfg.bound_warn_days ?? 2,
|
||
auto_purchase_count: cfg.auto_purchase_count,
|
||
purchase_cooldown_minutes: cfg.purchase_cooldown_minutes,
|
||
})
|
||
} catch (e) {
|
||
message.error('加载 Rock 平台配置失败: ' + (e instanceof Error ? e.message : '未知错误'))
|
||
} finally {
|
||
setLoadingStats(false)
|
||
}
|
||
}, [form])
|
||
|
||
// 只刷新统计,不触碰配置表单,供定时刷新和列表刷新共用。
|
||
const loadStats = useCallback(async () => {
|
||
const st = await rockApi.getStats()
|
||
setStats(st)
|
||
}, [])
|
||
|
||
const loadTokens = useCallback(async () => {
|
||
setLoadingTokens(true)
|
||
try {
|
||
const res = await rockApi.listTokens({
|
||
page,
|
||
page_size: pageSize,
|
||
status: filterStatus === 'all' ? undefined : filterStatus,
|
||
search: searchKeyword || undefined,
|
||
})
|
||
setTokens(res.list || [])
|
||
setTotalTokens(res.total || 0)
|
||
} catch (e) {
|
||
message.error('加载库存列表失败: ' + (e instanceof Error ? e.message : '未知错误'))
|
||
} finally {
|
||
setLoadingTokens(false)
|
||
}
|
||
}, [page, pageSize, filterStatus, searchKeyword])
|
||
|
||
useEffect(() => {
|
||
loadData()
|
||
}, [loadData])
|
||
|
||
useEffect(() => {
|
||
loadTokens()
|
||
}, [loadTokens])
|
||
|
||
// 定时刷新列表与统计,保证跨过 2 天/3 天阈值后标签和徽标自动更新。
|
||
const refreshTokensAndStats = useCallback(() => {
|
||
void loadStats().catch(() => {})
|
||
void loadTokens()
|
||
}, [loadStats, loadTokens])
|
||
|
||
useEffect(() => {
|
||
const timer = window.setInterval(() => {
|
||
if (document.visibilityState !== 'visible') return
|
||
refreshTokensAndStats()
|
||
}, 60000)
|
||
return () => window.clearInterval(timer)
|
||
}, [refreshTokensAndStats])
|
||
|
||
useEffect(() => {
|
||
setSelectedTokenIds([])
|
||
setSelectedTokens([])
|
||
}, [filterStatus, searchKeyword])
|
||
|
||
// 一键切换自动发货总开关 (Kill Switch)
|
||
const handleToggleAutoDelivery = async (checked: boolean) => {
|
||
Modal.confirm({
|
||
title: checked ? '确认开启 Rock 自动发货通道?' : '确认一键关闭 Rock 自动发货通道(软熔断)?',
|
||
content: checked
|
||
? '开启后,黑色 ROCK 与蓝色运动服两款商品将恢复 0666.pro 全自动反代发货。'
|
||
: '关闭后,两款商品将脱离自动发货通道,进单将转交人工打手大厅处理,防止错单穿透。',
|
||
okText: checked ? '确认开启' : '立即熔断关闭',
|
||
cancelText: '取消',
|
||
okButtonProps: {
|
||
danger: !checked,
|
||
},
|
||
onOk: async () => {
|
||
try {
|
||
const updated = await rockApi.updateConfig({ auto_delivery_enabled: checked })
|
||
setConfig(updated)
|
||
message.success(checked ? '自动发货通道已开启' : '自动发货通道已成功熔断关闭')
|
||
loadData()
|
||
} catch (e) {
|
||
message.error('更新总开关失败: ' + (e instanceof Error ? e.message : '未知错误'))
|
||
}
|
||
},
|
||
})
|
||
}
|
||
|
||
// 自动补货开关:切换即保存,避免只改表单值后被刷新回填覆盖
|
||
const handleToggleAutoPurchase = async (checked: boolean) => {
|
||
const previous = autoPurchaseEnabled
|
||
setAutoPurchaseEnabled(checked)
|
||
setTogglingAutoPurchase(true)
|
||
try {
|
||
const updated = await rockApi.updateConfig({ auto_purchase_enabled: checked })
|
||
setConfig(updated)
|
||
setAutoPurchaseEnabled(updated.auto_purchase_enabled)
|
||
message.success(checked ? '自动补货已开启' : '自动补货已关闭')
|
||
} catch (e) {
|
||
setAutoPurchaseEnabled(previous)
|
||
message.error('更新自动补货开关失败: ' + (e instanceof Error ? e.message : '未知错误'))
|
||
} finally {
|
||
setTogglingAutoPurchase(false)
|
||
}
|
||
}
|
||
|
||
// 一键刷新异常 Token:绑定错误 + 待绑定超时 + 绑定后未发货,同步执行,单次最多 20 条
|
||
const handleRefreshStale = async () => {
|
||
setRefreshingStale(true)
|
||
try {
|
||
const res = await rockApi.refreshStaleTokens()
|
||
if (res.scanned === 0) {
|
||
message.info('暂无可刷新的异常 Token(10 分钟内已刷新过的会自动跳过)')
|
||
} else if (res.failed > 0 || res.remaining > 0) {
|
||
message.warning(res.message)
|
||
} else {
|
||
message.success(res.message)
|
||
}
|
||
loadData()
|
||
loadTokens()
|
||
} catch (e) {
|
||
message.error('一键刷新失败: ' + (e instanceof Error ? e.message : '未知错误'))
|
||
} finally {
|
||
setRefreshingStale(false)
|
||
}
|
||
}
|
||
|
||
// 自动刷新开关:与「一键刷新」同规则(绑定错误 + 待绑定超时 + 绑定后未发货),切换即保存
|
||
const handleToggleAutoRefreshMismatch = async (checked: boolean) => {
|
||
const previous = autoRefreshMismatchEnabled
|
||
setAutoRefreshMismatchEnabled(checked)
|
||
setTogglingAutoRefresh(true)
|
||
try {
|
||
const updated = await rockApi.updateConfig({ auto_refresh_mismatch_enabled: checked })
|
||
setConfig(updated)
|
||
setAutoRefreshMismatchEnabled(updated.auto_refresh_mismatch_enabled)
|
||
message.success(
|
||
checked ? '自动刷新已开启:每 5 分钟按同样规则刷新最多 20 条异常 Token' : '自动刷新已关闭',
|
||
)
|
||
} catch (e) {
|
||
setAutoRefreshMismatchEnabled(previous)
|
||
message.error('更新自动刷新开关失败: ' + (e instanceof Error ? e.message : '未知错误'))
|
||
} finally {
|
||
setTogglingAutoRefresh(false)
|
||
}
|
||
}
|
||
|
||
// 测试连通性
|
||
const handleTestConnection = async () => {
|
||
const bearerToken = form.getFieldValue('bearer_token')
|
||
if (!bearerToken) {
|
||
message.warning('请先输入 Bearer Token')
|
||
return
|
||
}
|
||
setTestingConn(true)
|
||
try {
|
||
const res = await rockApi.testConnection(bearerToken)
|
||
message.success(`连通成功!商户:${res.user?.username || '正常'},可用积分:${res.user?.points || 0}`)
|
||
loadData()
|
||
} catch (e) {
|
||
message.error('连通性测试失败:' + (e instanceof Error ? e.message : '未知错误'))
|
||
} finally {
|
||
setTestingConn(false)
|
||
}
|
||
}
|
||
|
||
// 测试上游账号密码登录(只验证,不保存 token)
|
||
const handleTestLogin = async () => {
|
||
const username = form.getFieldValue('login_username') as string | undefined
|
||
const password = form.getFieldValue('login_password') as string | undefined
|
||
setTestingLogin(true)
|
||
try {
|
||
const res = await rockApi.testLogin({ username, password })
|
||
message.success(`登录成功:${res.username}(${res.role}),积分 ${res.points}`)
|
||
} catch (e) {
|
||
message.error('测试登录失败: ' + (e instanceof Error ? e.message : '未知错误'))
|
||
} finally {
|
||
setTestingLogin(false)
|
||
}
|
||
}
|
||
|
||
// 强制登录上游并保存新的 Token
|
||
const handleRefreshToken = async () => {
|
||
setRefreshingToken(true)
|
||
try {
|
||
const res = await rockApi.refreshRockToken()
|
||
message.success(`Token 已刷新:${res.username}(${res.role}),积分 ${res.points}`)
|
||
await loadData()
|
||
} catch (e) {
|
||
message.error('刷新 Token 失败: ' + (e instanceof Error ? e.message : '未知错误'))
|
||
} finally {
|
||
setRefreshingToken(false)
|
||
}
|
||
}
|
||
|
||
// 保存策略配置
|
||
const handleSaveConfig = async () => {
|
||
try {
|
||
const values = await form.validateFields()
|
||
// 密码留空表示不修改,避免每次保存都清空已保存的密文
|
||
if (!values.login_password) {
|
||
delete values.login_password
|
||
}
|
||
setSavingConfig(true)
|
||
const updated = await rockApi.updateConfig(values)
|
||
setConfig(updated)
|
||
message.success('配置已成功保存生效')
|
||
loadData()
|
||
} catch (e) {
|
||
if (e instanceof Error) {
|
||
message.error('保存失败: ' + e.message)
|
||
}
|
||
} finally {
|
||
setSavingConfig(false)
|
||
}
|
||
}
|
||
|
||
const openPurchaseModal = (count = 50) => {
|
||
setPurchaseCount(count)
|
||
setPurchaseModalOpen(true)
|
||
}
|
||
|
||
// 采购指定数量 Token
|
||
const handlePurchase = async (count: number) => {
|
||
if (!count || count <= 0) {
|
||
message.warning('请输入有效的采购数量')
|
||
return
|
||
}
|
||
if (upstreamStock !== null && count > upstreamStock) {
|
||
message.warning(`上游可用库存不足:当前 ${upstreamStock} 个,本次需要 ${count} 个`)
|
||
return
|
||
}
|
||
setPurchasing(true)
|
||
try {
|
||
const res = await rockApi.purchaseTokens(count)
|
||
if (res.limit_reason) {
|
||
message.warning(res.message || `上游货源不足,已按 ${res.planned_count} 个采购`)
|
||
} else {
|
||
message.success(res.message || `成功采购 ${count} 个通用黑色 Token!`)
|
||
}
|
||
setPurchaseModalOpen(false)
|
||
loadData()
|
||
loadTokens()
|
||
} catch (e) {
|
||
message.error('采购失败: ' + (e instanceof Error ? e.message : '未知错误'))
|
||
} finally {
|
||
setPurchasing(false)
|
||
}
|
||
}
|
||
|
||
const handleCorrectUid = async () => {
|
||
if (!uidEdit) {
|
||
return
|
||
}
|
||
const nextUid = uidEdit.value.trim()
|
||
if (!/^\d{1,15}$/.test(nextUid)) {
|
||
message.warning('请填写 15 位以内的纯数字 UID')
|
||
return
|
||
}
|
||
setSavingUid(true)
|
||
try {
|
||
const res = await rockApi.correctExpectedUid(uidEdit.id, nextUid)
|
||
message.success(res.message || '已更新买家 UID')
|
||
setUidEdit(null)
|
||
loadData()
|
||
loadTokens()
|
||
} catch (e) {
|
||
message.error('修改 UID 失败: ' + (e instanceof Error ? e.message : '未知错误'))
|
||
} finally {
|
||
setSavingUid(false)
|
||
}
|
||
}
|
||
|
||
const handleFilterStatusChange = (value: string) => {
|
||
setFilterStatus(value)
|
||
setPage(1)
|
||
}
|
||
|
||
const handlePageChange = (nextPage: number, nextPageSize: number) => {
|
||
setSelectedTokenIds([])
|
||
setSelectedTokens([])
|
||
setPage(nextPage)
|
||
setPageSize(nextPageSize)
|
||
}
|
||
|
||
const handleTokenDeleted = (result: RockTokenDeleteResult) => {
|
||
setTokenDeleteMode(null)
|
||
setSelectedTokenIds([])
|
||
setSelectedTokens([])
|
||
setPage(1)
|
||
message.success(
|
||
`已软删除 ${result.deleted} 条 Token${result.forced ? '(含强制确认的进行中 Token)' : ''}`,
|
||
)
|
||
loadData()
|
||
loadTokens()
|
||
}
|
||
|
||
const handleUidValueChange = (value: string) => {
|
||
setUidEdit((current) => (current ? { ...current, value } : current))
|
||
}
|
||
|
||
const autoDelivery = config?.auto_delivery_enabled ?? true
|
||
const availableStock = stats?.available_stock ?? 0
|
||
const warningStock = config?.token_warning_stock ?? 10
|
||
const isStockLow = availableStock <= warningStock
|
||
const hasMismatch = (stats?.mismatch_stock ?? 0) > 0
|
||
const upstreamPoints = stats?.upstream_points ?? 0
|
||
const tokenCost = config?.black_token_cost && config.black_token_cost > 0 ? config.black_token_cost : 20
|
||
const usingWarnDays = config?.using_warn_days && config.using_warn_days > 0 ? config.using_warn_days : 3
|
||
const usingDangerDays =
|
||
config?.using_danger_days && config.using_danger_days > usingWarnDays ? config.using_danger_days : 5
|
||
const boundWarnDays = config?.bound_warn_days && config.bound_warn_days > 0 ? config.bound_warn_days : 2
|
||
const maxPurchasable = Math.max(0, Math.floor(upstreamPoints / tokenCost))
|
||
const estimatedCost = (purchaseCount || 0) * tokenCost
|
||
const isPointsInsufficient = estimatedCost > upstreamPoints
|
||
|
||
return (
|
||
<div style={{ padding: '0 4px' }}>
|
||
<PageHeader
|
||
title="Rock 发货平台 (0666.pro) 自动发货与库存风控"
|
||
subtitle="和平精英「黑色ROCK长袖T恤」与「蓝色运动服上衣」通用库存池、上游采购、UID强一致防错门禁与软熔断机制"
|
||
extra={
|
||
<Space size="middle">
|
||
<Button
|
||
type="primary"
|
||
icon={<ShoppingCartOutlined />}
|
||
onClick={() => openPurchaseModal(50)}
|
||
loading={purchasing}
|
||
style={{
|
||
background: 'linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%)',
|
||
boxShadow: '0 2px 4px rgba(37,99,235,0.2)',
|
||
}}
|
||
>
|
||
采购通用 Token
|
||
</Button>
|
||
<Button icon={<ReloadOutlined />} onClick={loadData} loading={loadingStats}>
|
||
刷新状态
|
||
</Button>
|
||
<Button icon={<HistoryOutlined />} onClick={() => setPurchaseLogOpen(true)}>
|
||
采购日志
|
||
</Button>
|
||
<Button icon={<ImportOutlined />} onClick={() => setImportPackageOpen(true)}>
|
||
全量导入
|
||
</Button>
|
||
</Space>
|
||
}
|
||
/>
|
||
|
||
{stats?.last_purchase_error ? (
|
||
<Alert
|
||
type="warning"
|
||
showIcon
|
||
style={{ marginBottom: 16 }}
|
||
message="最近一次 Rock 采购/打包未完全成功"
|
||
description={`${stats.last_purchase_error}${
|
||
stats.last_purchase_error_at ? `(${stats.last_purchase_error_at})` : ''
|
||
}`}
|
||
/>
|
||
) : null}
|
||
|
||
{/* 顶部高质感指标卡片 */}
|
||
<RockMetricCards
|
||
stats={stats}
|
||
upstreamStock={upstreamStock}
|
||
blackTokenCost={tokenCost}
|
||
availableStock={availableStock}
|
||
warningStock={warningStock}
|
||
isStockLow={isStockLow}
|
||
hasMismatch={hasMismatch}
|
||
upstreamPoints={upstreamPoints}
|
||
maxPurchasable={maxPurchasable}
|
||
onOpenPurchase={openPurchaseModal}
|
||
/>
|
||
|
||
{/* 核心控制与上游策略看板 */}
|
||
<RockStrategyCard
|
||
form={form}
|
||
autoDelivery={autoDelivery}
|
||
autoPurchaseEnabled={autoPurchaseEnabled}
|
||
autoPurchaseCoolingSeconds={stats?.auto_purchase_cooldown_seconds ?? 0}
|
||
nextAutoPurchaseAt={stats?.next_auto_purchase_at}
|
||
loginPasswordSet={config?.login_password_set}
|
||
lastTokenRefreshAt={config?.last_token_refresh_at}
|
||
lastTokenError={config?.last_token_error}
|
||
testingLogin={testingLogin}
|
||
onTestLogin={handleTestLogin}
|
||
refreshingToken={refreshingToken}
|
||
onRefreshToken={handleRefreshToken}
|
||
togglingAutoPurchase={togglingAutoPurchase}
|
||
testingConn={testingConn}
|
||
savingConfig={savingConfig}
|
||
onToggleAutoDelivery={handleToggleAutoDelivery}
|
||
onToggleAutoPurchase={handleToggleAutoPurchase}
|
||
onTestConnection={handleTestConnection}
|
||
onSaveConfig={handleSaveConfig}
|
||
/>
|
||
|
||
{/* 筛选与搜索工具条(对齐标杆页面) */}
|
||
<RockTokenFilterBar
|
||
stats={stats}
|
||
searchKeyword={searchKeyword}
|
||
filterStatus={filterStatus}
|
||
totalTokens={totalTokens}
|
||
loadingTokens={loadingTokens}
|
||
autoRefreshMismatchEnabled={autoRefreshMismatchEnabled}
|
||
refreshingStale={refreshingStale}
|
||
togglingAutoRefresh={togglingAutoRefresh}
|
||
onSearchKeywordChange={setSearchKeyword}
|
||
onFilterStatusChange={handleFilterStatusChange}
|
||
onRefresh={refreshTokensAndStats}
|
||
onRefreshStale={handleRefreshStale}
|
||
onToggleAutoRefreshMismatch={handleToggleAutoRefreshMismatch}
|
||
/>
|
||
|
||
{/* 主库存与错单审计表格 */}
|
||
<RockTokenTable
|
||
tokens={tokens}
|
||
loadingTokens={loadingTokens}
|
||
usingWarnDays={usingWarnDays}
|
||
usingDangerDays={usingDangerDays}
|
||
boundWarnDays={boundWarnDays}
|
||
page={page}
|
||
pageSize={pageSize}
|
||
totalTokens={totalTokens}
|
||
selectedTokenIds={selectedTokenIds}
|
||
onSelectedTokenIdsChange={(ids, rows) => {
|
||
setSelectedTokenIds(ids)
|
||
setSelectedTokens(rows)
|
||
}}
|
||
onBatchDelete={() => setTokenDeleteMode('batch')}
|
||
onDeleteAll={() => setTokenDeleteMode('all')}
|
||
deletedCount={stats?.deleted_stock ?? 0}
|
||
onOpenRecycleBin={() => setRecycleOpen(true)}
|
||
copyingVoucherId={copyingVoucherId}
|
||
setCopyingVoucherId={setCopyingVoucherId}
|
||
setPreviewVoucherUrl={setPreviewVoucherUrl}
|
||
setPreviewVoucherOpen={setPreviewVoucherOpen}
|
||
onEditUid={setUidEdit}
|
||
onViewRawData={setRawDataRecord}
|
||
onPageChange={handlePageChange}
|
||
onReloadData={loadData}
|
||
onReloadTokens={loadTokens}
|
||
/>
|
||
|
||
{/* 手动采购指定数量 Token 弹窗 */}
|
||
<PurchaseTokenModal
|
||
open={purchaseModalOpen}
|
||
stats={stats}
|
||
purchaseCount={purchaseCount}
|
||
purchasing={purchasing}
|
||
maxPurchasable={maxPurchasable}
|
||
upstreamPoints={upstreamPoints}
|
||
estimatedCost={estimatedCost}
|
||
isPointsInsufficient={isPointsInsufficient}
|
||
tokenCost={tokenCost}
|
||
upstreamStock={upstreamStock}
|
||
onPurchaseCountChange={setPurchaseCount}
|
||
onCancel={() => setPurchaseModalOpen(false)}
|
||
onConfirm={handlePurchase}
|
||
/>
|
||
|
||
<Modal
|
||
open={Boolean(rawDataRecord)}
|
||
title={rawDataRecord ? `原始数据 · 0666 #${rawDataRecord.upstream_token_id}` : '原始数据'}
|
||
footer={null}
|
||
width={760}
|
||
onCancel={() => setRawDataRecord(null)}
|
||
>
|
||
<pre
|
||
style={{
|
||
maxHeight: 540,
|
||
overflow: 'auto',
|
||
margin: 0,
|
||
padding: 16,
|
||
borderRadius: 8,
|
||
background: '#0f172a',
|
||
color: '#e2e8f0',
|
||
fontSize: 12,
|
||
lineHeight: 1.6,
|
||
whiteSpace: 'pre-wrap',
|
||
wordBreak: 'break-all',
|
||
}}
|
||
>
|
||
{prettyRawData(rawDataRecord?.raw_data)}
|
||
</pre>
|
||
</Modal>
|
||
|
||
<UidEditModal
|
||
uidEdit={uidEdit}
|
||
savingUid={savingUid}
|
||
onValueChange={handleUidValueChange}
|
||
onConfirm={handleCorrectUid}
|
||
onCancel={() => setUidEdit(null)}
|
||
/>
|
||
|
||
{/* 发货结果图片高清预览弹窗 */}
|
||
<VoucherPreviewModal
|
||
open={previewVoucherOpen}
|
||
previewVoucherUrl={previewVoucherUrl}
|
||
onClose={() => setPreviewVoucherOpen(false)}
|
||
/>
|
||
|
||
<PurchaseLogDrawer open={purchaseLogOpen} onClose={() => setPurchaseLogOpen(false)} />
|
||
|
||
<ImportAllPackagesModal
|
||
open={importPackageOpen}
|
||
onClose={() => setImportPackageOpen(false)}
|
||
onImported={() => {
|
||
loadData()
|
||
loadTokens()
|
||
}}
|
||
/>
|
||
|
||
<TokenDeleteModal
|
||
open={tokenDeleteMode !== null}
|
||
mode={tokenDeleteMode ?? 'batch'}
|
||
selectedTokens={selectedTokens}
|
||
selectedTokenIds={selectedTokenIds}
|
||
filterStatus={filterStatus}
|
||
searchKeyword={searchKeyword}
|
||
onCancel={() => setTokenDeleteMode(null)}
|
||
onDeleted={handleTokenDeleted}
|
||
/>
|
||
|
||
<TokenRecycleBinDrawer
|
||
open={recycleOpen}
|
||
onClose={() => setRecycleOpen(false)}
|
||
onRestored={() => {
|
||
loadData()
|
||
loadTokens()
|
||
}}
|
||
/>
|
||
</div>
|
||
)
|
||
}
|