feat(rock): 支持手动采购指定数量通用 Token 弹窗与实时积分试算
1. 新增手动采购 Modal 弹窗,支持自由输入指定数量(InputNumber)与快捷胶囊选择(10/20/50/100/全部可购); 2. 动态联动 0666.pro 实时积分余额,提供单价折算(21点/个)、预计消耗与结余防超支校验; 3. PageHeader 采购按钮与指标看板无缝唤起采购弹窗,采购完成后自动关闭并刷新数据。
This commit is contained in:
@@ -46,6 +46,8 @@ export default function PlatformRock() {
|
||||
const [testingConn, setTestingConn] = useState(false)
|
||||
const [savingConfig, setSavingConfig] = useState(false)
|
||||
const [purchasing, setPurchasing] = useState(false)
|
||||
const [purchaseModalOpen, setPurchaseModalOpen] = useState(false)
|
||||
const [purchaseCount, setPurchaseCount] = useState(50)
|
||||
|
||||
// Token 列表与筛选
|
||||
const [tokens, setTokens] = useState<RockTokenItem[]>([])
|
||||
@@ -169,12 +171,22 @@ export default function PlatformRock() {
|
||||
}
|
||||
}
|
||||
|
||||
// 批量采购 50 个
|
||||
const handlePurchase = async (count = 50) => {
|
||||
const openPurchaseModal = (count = 50) => {
|
||||
setPurchaseCount(count)
|
||||
setPurchaseModalOpen(true)
|
||||
}
|
||||
|
||||
// 采购指定数量 Token
|
||||
const handlePurchase = async (count: number) => {
|
||||
if (!count || count <= 0) {
|
||||
message.warning('请输入有效的采购数量')
|
||||
return
|
||||
}
|
||||
setPurchasing(true)
|
||||
try {
|
||||
const res = await rockApi.purchaseTokens(count)
|
||||
message.success(res.message || `成功采购 ${count} 个通用黑色 Token!`)
|
||||
setPurchaseModalOpen(false)
|
||||
loadData()
|
||||
loadTokens()
|
||||
} catch (e) {
|
||||
@@ -371,6 +383,10 @@ export default function PlatformRock() {
|
||||
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 maxPurchasable = Math.max(0, Math.floor(upstreamPoints / 21))
|
||||
const estimatedCost = (purchaseCount || 0) * 21
|
||||
const isPointsInsufficient = estimatedCost > upstreamPoints
|
||||
|
||||
return (
|
||||
<div style={{ padding: '0 4px' }}>
|
||||
@@ -382,14 +398,14 @@ export default function PlatformRock() {
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<ShoppingCartOutlined />}
|
||||
onClick={() => handlePurchase(50)}
|
||||
onClick={() => openPurchaseModal(50)}
|
||||
loading={purchasing}
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%)',
|
||||
boxShadow: '0 2px 4px rgba(37,99,235,0.2)',
|
||||
}}
|
||||
>
|
||||
一键采购 50 个通用 Token
|
||||
采购通用 Token
|
||||
</Button>
|
||||
<Button icon={<ReloadOutlined />} onClick={loadData} loading={loadingStats}>
|
||||
刷新状态
|
||||
@@ -474,12 +490,18 @@ export default function PlatformRock() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="metric-card-value" style={{ color: '#7c3aed' }}>
|
||||
{(stats?.upstream_points ?? 0).toFixed(1)}
|
||||
{upstreamPoints.toFixed(1)}
|
||||
<span style={{ fontSize: 13, fontWeight: 500, marginLeft: 4, color: '#64748b' }}>点</span>
|
||||
</div>
|
||||
<div className="metric-card-footer">
|
||||
<span>账号: {stats?.username || '未同步'}</span>
|
||||
<span>约可采购 <strong style={{ color: '#0f172a' }}>{Math.floor((stats?.upstream_points ?? 0) / 21)}</strong> 个</span>
|
||||
<a
|
||||
onClick={() => openPurchaseModal(maxPurchasable > 0 ? Math.min(50, maxPurchasable) : 10)}
|
||||
style={{ fontWeight: 600, color: '#2563eb', cursor: 'pointer' }}
|
||||
title="点击快速打开手动采购弹窗"
|
||||
>
|
||||
约可采购 {maxPurchasable} 个 ➔
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
@@ -761,7 +783,159 @@ export default function PlatformRock() {
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 手动采购指定数量 Token 弹窗 */}
|
||||
<Modal
|
||||
title={
|
||||
<Space>
|
||||
<ShoppingCartOutlined style={{ color: '#2563eb' }} />
|
||||
<span>手动采购通用黑色 Token (0666.pro)</span>
|
||||
</Space>
|
||||
}
|
||||
open={purchaseModalOpen}
|
||||
onCancel={() => setPurchaseModalOpen(false)}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={() => setPurchaseModalOpen(false)}>
|
||||
取消
|
||||
</Button>,
|
||||
<Button
|
||||
key="submit"
|
||||
type="primary"
|
||||
icon={<ShoppingCartOutlined />}
|
||||
loading={purchasing}
|
||||
disabled={isPointsInsufficient || purchaseCount <= 0}
|
||||
onClick={() => handlePurchase(purchaseCount)}
|
||||
style={{
|
||||
background: isPointsInsufficient
|
||||
? undefined
|
||||
: 'linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%)',
|
||||
}}
|
||||
>
|
||||
确认采购 {purchaseCount} 个 ({estimatedCost} 点)
|
||||
</Button>,
|
||||
]}
|
||||
destroyOnClose
|
||||
width={500}
|
||||
>
|
||||
<div style={{ marginTop: 16 }}>
|
||||
{/* 账户与资产概览 */}
|
||||
<div
|
||||
style={{
|
||||
background: '#f8fafc',
|
||||
border: '1px solid #e2e8f0',
|
||||
borderRadius: 8,
|
||||
padding: '12px 16px',
|
||||
marginBottom: 18,
|
||||
}}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<div style={{ fontSize: 12, color: '#64748b' }}>0666.pro 采购商户</div>
|
||||
<div style={{ fontSize: 15, fontWeight: 600, color: '#0f172a', marginTop: 2 }}>
|
||||
{stats?.username || '正常商户'}
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div style={{ fontSize: 12, color: '#64748b' }}>当前实时积分余额</div>
|
||||
<div style={{ fontSize: 15, fontWeight: 700, color: '#7c3aed', marginTop: 2 }}>
|
||||
{upstreamPoints.toFixed(1)} 点
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider style={{ margin: '10px 0' }} />
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, color: '#64748b' }}>
|
||||
<span>采购单价:<strong style={{ color: '#0f172a' }}>21 点 / 个</strong></span>
|
||||
<span>最大可采购:<strong style={{ color: '#2563eb' }}>{maxPurchasable} 个</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 采购数量输入 */}
|
||||
<div style={{ marginBottom: 14 }}>
|
||||
<div style={{ fontWeight: 600, marginBottom: 8, color: '#0f172a' }}>
|
||||
采购数量:
|
||||
</div>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={maxPurchasable > 0 ? maxPurchasable : 10000}
|
||||
value={purchaseCount}
|
||||
onChange={(val) => setPurchaseCount(val || 1)}
|
||||
style={{ width: '100%' }}
|
||||
addonAfter="个 Token"
|
||||
size="large"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 快捷数量选择胶囊 */}
|
||||
<div style={{ marginBottom: 18 }}>
|
||||
<div style={{ fontSize: 12, color: '#64748b', marginBottom: 8 }}>快捷选择:</div>
|
||||
<Space wrap size={[8, 8]}>
|
||||
{[10, 20, 50, 100].map((num) => (
|
||||
<Button
|
||||
key={num}
|
||||
size="small"
|
||||
type={purchaseCount === num ? 'primary' : 'default'}
|
||||
onClick={() => setPurchaseCount(num)}
|
||||
disabled={maxPurchasable > 0 && num > maxPurchasable}
|
||||
>
|
||||
{num} 个
|
||||
</Button>
|
||||
))}
|
||||
{maxPurchasable > 0 && (
|
||||
<Button
|
||||
size="small"
|
||||
type={purchaseCount === maxPurchasable ? 'primary' : 'dashed'}
|
||||
onClick={() => setPurchaseCount(maxPurchasable)}
|
||||
style={{ color: '#2563eb', borderColor: '#bfdbfe' }}
|
||||
>
|
||||
全部可购 ({maxPurchasable} 个)
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{/* 实时消耗与结余试算 */}
|
||||
<div
|
||||
style={{
|
||||
background: isPointsInsufficient ? '#fef2f2' : '#f0fdf4',
|
||||
border: `1px solid ${isPointsInsufficient ? '#fecaca' : '#bbf7d0'}`,
|
||||
borderRadius: 8,
|
||||
padding: '12px 16px',
|
||||
fontSize: 13,
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span style={{ color: isPointsInsufficient ? '#991b1b' : '#166534' }}>
|
||||
本次采购消耗积分:
|
||||
</span>
|
||||
<strong style={{ color: isPointsInsufficient ? '#dc2626' : '#16a34a', fontSize: 16 }}>
|
||||
{estimatedCost} 点
|
||||
</strong>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginTop: 6,
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
<span style={{ color: isPointsInsufficient ? '#b91c1c' : '#15803d' }}>
|
||||
{isPointsInsufficient
|
||||
? '⚠️ 账户积分不足,请先充值或减少采购数量'
|
||||
: '采购后预计剩余积分:'}
|
||||
</span>
|
||||
{!isPointsInsufficient && (
|
||||
<span style={{ color: '#64748b' }}>
|
||||
{(upstreamPoints - estimatedCost).toFixed(1)} 点
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user