feat:自动补货/手动采购逻辑修改:批量获取-》打包-》获取Token列表

This commit is contained in:
2026-10-29 16:05:39 +08:00
parent 8198bd6feb
commit 8494fabc82
23 changed files with 3679 additions and 85 deletions
@@ -11,6 +11,8 @@ export interface PurchaseTokenModalProps {
upstreamPoints: number
estimatedCost: number
isPointsInsufficient: boolean
tokenCost: number
upstreamStock: number | null
onPurchaseCountChange: (value: number) => void
onCancel: () => void
onConfirm: (count: number) => void
@@ -25,10 +27,13 @@ export function PurchaseTokenModal({
upstreamPoints,
estimatedCost,
isPointsInsufficient,
tokenCost,
upstreamStock,
onPurchaseCountChange,
onCancel,
onConfirm,
}: PurchaseTokenModalProps) {
const isStockInsufficient = upstreamStock !== null && purchaseCount > upstreamStock
return (
<Modal
title={
@@ -48,15 +53,16 @@ export function PurchaseTokenModal({
type="primary"
icon={<ShoppingCartOutlined />}
loading={purchasing}
disabled={isPointsInsufficient || purchaseCount <= 0}
disabled={isPointsInsufficient || isStockInsufficient || purchaseCount <= 0}
onClick={() => onConfirm(purchaseCount)}
style={{
background: isPointsInsufficient
? undefined
: 'linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%)',
background:
isPointsInsufficient || isStockInsufficient
? undefined
: 'linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%)',
}}
>
确认采购 {purchaseCount} 个 ({estimatedCost} 点)
确认采购 {purchaseCount} 个 ({estimatedCost.toFixed(2)} 点)
</Button>,
]}
destroyOnClose
@@ -88,12 +94,48 @@ export function PurchaseTokenModal({
</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
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' }}>
@@ -153,7 +195,7 @@ export function PurchaseTokenModal({
本次采购消耗积分:
</span>
<strong style={{ color: isPointsInsufficient ? '#dc2626' : '#16a34a', fontSize: 16 }}>
{estimatedCost} 点
{estimatedCost.toFixed(2)} 点
</strong>
</div>
<div
@@ -1,6 +1,7 @@
import { Col, Row, Tag } from 'antd'
import {
CheckCircleOutlined,
CloudServerOutlined,
ExclamationCircleOutlined,
FireOutlined,
ShoppingCartOutlined,
@@ -9,6 +10,8 @@ import type { RockStats } from '../../api'
export interface RockMetricCardsProps {
stats: RockStats | null
upstreamStock: number | null
blackTokenCost: number
availableStock: number
warningStock: number
isStockLow: boolean
@@ -20,6 +23,8 @@ export interface RockMetricCardsProps {
export function RockMetricCards({
stats,
upstreamStock,
blackTokenCost,
availableStock,
warningStock,
isStockLow,
@@ -30,7 +35,38 @@ export function RockMetricCards({
}: RockMetricCardsProps) {
return (
<Row gutter={[16, 16]} style={{ marginBottom: 20 }}>
<Col xs={24} sm={12} lg={6}>
<Col flex="1 1 200px">
<div className="metric-card-box">
<div className="metric-card-header">
<span className="metric-card-title">上游可用库存 (0666.pro)</span>
<div className="metric-card-icon" style={{ background: '#e0f2fe', color: '#0284c7' }}>
<CloudServerOutlined />
</div>
</div>
<div className="metric-card-value" style={{ color: upstreamStock === null ? '#94a3b8' : '#0369a1' }}>
{upstreamStock === null ? '-' : upstreamStock}
<span style={{ fontSize: 13, fontWeight: 500, marginLeft: 4, color: '#64748b' }}>个</span>
</div>
<div className="metric-card-footer">
<span>采购单价: {blackTokenCost.toFixed(2)} 点/个</span>
{upstreamStock === null ? (
<Tag color="warning" style={{ margin: 0, borderRadius: 4 }}>
未获取
</Tag>
) : upstreamStock === 0 ? (
<Tag color="error" style={{ margin: 0, borderRadius: 4 }}>
无库存
</Tag>
) : (
<Tag color="processing" style={{ margin: 0, borderRadius: 4 }}>
实时
</Tag>
)}
</div>
</div>
</Col>
<Col flex="1 1 200px">
<div className="metric-card-box">
<div className="metric-card-header">
<span className="metric-card-title">可用通用库存 (Available)</span>
@@ -45,7 +81,9 @@ export function RockMetricCards({
<div className="metric-card-footer">
<span>警戒水位: {warningStock} 个</span>
{isStockLow ? (
<Tag color="error" style={{ margin: 0, borderRadius: 4 }}>库存紧缺</Tag>
<Tag color="error" style={{ margin: 0, borderRadius: 4 }}>
库存紧缺
</Tag>
) : (
<span style={{ color: '#10b981', fontWeight: 600 }}>储备充足</span>
)}
@@ -53,7 +91,7 @@ export function RockMetricCards({
</div>
</Col>
<Col xs={24} sm={12} lg={6}>
<Col flex="1 1 200px">
<div className="metric-card-box">
<div className="metric-card-header">
<span className="metric-card-title">绑定错误阻断 (Mismatch)</span>
@@ -68,7 +106,9 @@ export function RockMetricCards({
<div className="metric-card-footer">
<span>{hasMismatch ? 'UID强一致校验拦截保护' : '暂无异常阻断'}</span>
{hasMismatch ? (
<Tag color="error" style={{ margin: 0, borderRadius: 4 }}>需核对处理</Tag>
<Tag color="error" style={{ margin: 0, borderRadius: 4 }}>
需核对处理
</Tag>
) : (
<span style={{ color: '#10b981', fontWeight: 600 }}>状态正常</span>
)}
@@ -76,7 +116,7 @@ export function RockMetricCards({
</div>
</Col>
<Col xs={24} sm={12} lg={6}>
<Col flex="1 1 200px">
<div className="metric-card-box">
<div className="metric-card-header">
<span className="metric-card-title">已履约交付 (Redeemed)</span>
@@ -95,7 +135,7 @@ export function RockMetricCards({
</div>
</Col>
<Col xs={24} sm={12} lg={6}>
<Col flex="1 1 200px">
<div className="metric-card-box">
<div className="metric-card-header">
<span className="metric-card-title">0666.pro 账号积分</span>
@@ -27,9 +27,12 @@ const { Text } = Typography
export interface RockStrategyCardProps {
form: FormInstance
autoDelivery: boolean
autoPurchaseEnabled: boolean
togglingAutoPurchase: boolean
testingConn: boolean
savingConfig: boolean
onToggleAutoDelivery: (checked: boolean) => void
onToggleAutoPurchase: (checked: boolean) => void
onTestConnection: () => void
onSaveConfig: () => void
}
@@ -37,9 +40,12 @@ export interface RockStrategyCardProps {
export function RockStrategyCard({
form,
autoDelivery,
autoPurchaseEnabled,
togglingAutoPurchase,
testingConn,
savingConfig,
onToggleAutoDelivery,
onToggleAutoPurchase,
onTestConnection,
onSaveConfig,
}: RockStrategyCardProps) {
@@ -227,13 +233,24 @@ export function RockStrategyCard({
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="auto_purchase_enabled" label="自动补货触发开关" valuePropName="checked">
<Switch checkedChildren="开启自动补货" unCheckedChildren="关闭" />
<Form.Item label="自动补货触发开关">
<Space direction="vertical" size={2}>
<Switch
checked={autoPurchaseEnabled}
onChange={onToggleAutoPurchase}
loading={togglingAutoPurchase}
checkedChildren="开启自动补货"
unCheckedChildren="关闭"
/>
<Text type="secondary" style={{ fontSize: 11 }}>
切换后立即保存
</Text>
</Space>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="auto_purchase_count" label="单次自动补货数量">
<InputNumber min={5} max={200} style={{ width: '100%' }} addonAfter="个" />
<InputNumber min={1} max={200} style={{ width: '100%' }} addonAfter="个" />
</Form.Item>
</Col>
<Col span={12}>
@@ -34,12 +34,12 @@ export function RockTokenFilterBar({
<Col xs={24} md={16}>
<Space size="middle" wrap>
<Input
placeholder="搜索 0666 编号 / 提取码 / 订单号 / UID"
placeholder="搜索 包ID / 0666 编号 / 提取码 / 兑换码 / 订单号 / UID"
prefix={<SearchOutlined style={{ color: '#94a3b8' }} />}
value={searchKeyword}
onChange={(e) => onSearchKeywordChange(e.target.value)}
onPressEnter={onRefresh}
style={{ width: 340 }}
style={{ width: 400 }}
allowClear
/>
<Radio.Group
@@ -72,7 +72,7 @@ export function RockTokenFilterBar({
</Col>
<Col xs={24} md={8} style={{ textAlign: 'right' }}>
<Text type="secondary" style={{ fontSize: 13 }}>
共检索到 <strong style={{ color: '#0f172a' }}>{totalTokens}</strong> 条库存记录
共检索到 <strong style={{ color: '#0f172a' }}>{totalTokens}</strong> 条 Token 记录
</Text>
</Col>
</Row>
@@ -18,6 +18,7 @@ export interface RockTokenTableProps {
setPreviewVoucherUrl: Dispatch<SetStateAction<string>>
setPreviewVoucherOpen: Dispatch<SetStateAction<boolean>>
onEditUid: (state: UidEditState) => void
onViewRawData: (record: RockTokenItem) => void
onPageChange: (page: number, pageSize: number) => void
onReloadData: () => void
onReloadTokens: () => void
@@ -34,6 +35,7 @@ export function RockTokenTable({
setPreviewVoucherUrl,
setPreviewVoucherOpen,
onEditUid,
onViewRawData,
onPageChange,
onReloadData,
onReloadTokens,
@@ -153,6 +155,7 @@ export function RockTokenTable({
onCopyVoucher: handleCopyVoucher,
onPreviewVoucher: handlePreviewVoucher,
onEditUid,
onViewRawData,
onResetToken: handleResetToken,
onReleaseToken: handleReleaseToken,
onUpdateRemark: handleUpdateRemark,
@@ -165,14 +168,14 @@ export function RockTokenTable({
columns={columns}
dataSource={tokens}
loading={loadingTokens}
scroll={{ x: 1300 }}
scroll={{ x: 1700 }}
pagination={{
current: page,
pageSize,
total: totalTokens,
showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'],
showTotal: (total) => `共 ${total} 条库存记录`,
showTotal: (total) => `共 ${total} 条 Token 记录`,
onChange: (p, ps) => {
onPageChange(p, ps)
},
@@ -13,6 +13,7 @@ export interface TokenColumnHandlers {
onCopyVoucher: (record: RockTokenItem) => void
onPreviewVoucher: (record: RockTokenItem) => void
onEditUid: (state: UidEditState) => void
onViewRawData: (record: RockTokenItem) => void
onResetToken: (id: number) => void
onReleaseToken: (id: number) => void
onUpdateRemark: (id: number, remark: string, status?: string) => void
@@ -24,6 +25,7 @@ export function buildTokenColumns({
onCopyVoucher,
onPreviewVoucher,
onEditUid,
onViewRawData,
onResetToken,
onReleaseToken,
onUpdateRemark,
@@ -31,7 +33,7 @@ export function buildTokenColumns({
}: TokenColumnHandlers): ColumnsType<RockTokenItem> {
return [
{
title: '0666 编号 / 提取码',
title: '0666 编号 / 兑换码',
dataIndex: 'upstream_token_id',
width: 250,
render: (_, record) => (
@@ -53,6 +55,32 @@ export function buildTokenColumns({
</Space>
),
},
{
title: '包ID / 提取码',
width: 230,
render: (_, record) => (
<Space direction="vertical" size={2}>
<div style={{ fontSize: 12, color: '#334155' }}>
<span style={{ color: '#94a3b8' }}>包ID: </span>
{record.package_id ? `#${record.package_id}` : '-'}
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ color: '#94a3b8', fontSize: 12 }}>提取码:</span>
<Text
copyable={record.token ? { text: record.token } : false}
style={{ fontSize: 12, fontFamily: 'monospace', fontWeight: 600 }}
>
{record.token || '-'}
</Text>
</div>
{record.source === 'package' ? (
<Tag color="purple" style={{ margin: 0, width: 'fit-content', fontSize: 10 }}>
打包导入
</Tag>
) : null}
</Space>
),
},
{
title: '规格与适用范围',
dataIndex: 'color',
@@ -338,6 +366,18 @@ export function buildTokenColumns({
)
},
},
{
title: '原始数据',
width: 100,
render: (_, record) =>
record.raw_data ? (
<Button size="small" onClick={() => onViewRawData(record)}>
查看
</Button>
) : (
<Text type="secondary">-</Text>
),
},
{
title: '操作',
key: 'action',