226 lines
7.3 KiB
TypeScript
226 lines
7.3 KiB
TypeScript
import { Button, Col, Divider, InputNumber, Modal, Row, Space } from 'antd'
|
|
import { ShoppingCartOutlined } from '@ant-design/icons'
|
|
import type { RockStats } from '../../api'
|
|
|
|
export interface PurchaseTokenModalProps {
|
|
open: boolean
|
|
stats: RockStats | null
|
|
purchaseCount: number
|
|
purchasing: boolean
|
|
maxPurchasable: number
|
|
upstreamPoints: number
|
|
estimatedCost: number
|
|
isPointsInsufficient: boolean
|
|
tokenCost: number
|
|
upstreamStock: number | null
|
|
onPurchaseCountChange: (value: number) => void
|
|
onCancel: () => void
|
|
onConfirm: (count: number) => void
|
|
}
|
|
|
|
export function PurchaseTokenModal({
|
|
open,
|
|
stats,
|
|
purchaseCount,
|
|
purchasing,
|
|
maxPurchasable,
|
|
upstreamPoints,
|
|
estimatedCost,
|
|
isPointsInsufficient,
|
|
tokenCost,
|
|
upstreamStock,
|
|
onPurchaseCountChange,
|
|
onCancel,
|
|
onConfirm,
|
|
}: PurchaseTokenModalProps) {
|
|
const isStockInsufficient = upstreamStock !== null && purchaseCount > upstreamStock
|
|
return (
|
|
<Modal
|
|
title={
|
|
<Space>
|
|
<ShoppingCartOutlined style={{ color: '#2563eb' }} />
|
|
<span>手动采购通用黑色 Token (0666.pro)</span>
|
|
</Space>
|
|
}
|
|
open={open}
|
|
onCancel={onCancel}
|
|
footer={[
|
|
<Button key="cancel" onClick={onCancel}>
|
|
取消
|
|
</Button>,
|
|
<Button
|
|
key="submit"
|
|
type="primary"
|
|
icon={<ShoppingCartOutlined />}
|
|
loading={purchasing}
|
|
disabled={isPointsInsufficient || isStockInsufficient || purchaseCount <= 0}
|
|
onClick={() => onConfirm(purchaseCount)}
|
|
style={{
|
|
background:
|
|
isPointsInsufficient || isStockInsufficient
|
|
? undefined
|
|
: 'linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%)',
|
|
}}
|
|
>
|
|
确认采购 {purchaseCount} 个 ({estimatedCost.toFixed(2)} 点)
|
|
</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',
|
|
flexWrap: 'wrap',
|
|
gap: 8,
|
|
fontSize: 12,
|
|
color: '#64748b',
|
|
}}
|
|
>
|
|
<span>
|
|
采购单价:<strong style={{ color: '#0f172a' }}>{tokenCost.toFixed(2)} 点 / 个</strong>
|
|
</span>
|
|
<span>
|
|
上游可用库存:
|
|
<strong style={{ color: isStockInsufficient ? '#dc2626' : '#0f172a' }}>
|
|
{upstreamStock === null ? '未获取' : `${upstreamStock} 个`}
|
|
</strong>
|
|
</span>
|
|
<span>
|
|
最大可采购:<strong style={{ color: '#2563eb' }}>{maxPurchasable} 个</strong>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 上游库存不足提示 */}
|
|
{isStockInsufficient && (
|
|
<div
|
|
style={{
|
|
background: '#fef2f2',
|
|
border: '1px solid #fecaca',
|
|
borderRadius: 8,
|
|
padding: '10px 16px',
|
|
marginBottom: 14,
|
|
fontSize: 13,
|
|
color: '#b91c1c',
|
|
}}
|
|
>
|
|
⚠️ 上游可用库存不足:当前 {upstreamStock} 个,本次需要 {purchaseCount} 个,请减少采购数量。
|
|
</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) => onPurchaseCountChange(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={() => onPurchaseCountChange(num)}
|
|
disabled={maxPurchasable > 0 && num > maxPurchasable}
|
|
>
|
|
{num} 个
|
|
</Button>
|
|
))}
|
|
{maxPurchasable > 0 && (
|
|
<Button
|
|
size="small"
|
|
type={purchaseCount === maxPurchasable ? 'primary' : 'dashed'}
|
|
onClick={() => onPurchaseCountChange(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.toFixed(2)} 点
|
|
</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>
|
|
)
|
|
}
|