Files
affiliate_dash/frontend/src/pages/platformRock/RockStrategyCard.tsx
T

401 lines
15 KiB
TypeScript

import {
Button,
Card,
Col,
Divider,
Form,
Input,
InputNumber,
Row,
Space,
Switch,
Tag,
Tooltip,
Typography,
} from 'antd'
import type { FormInstance } from 'antd'
import {
ControlOutlined,
ExclamationCircleOutlined,
KeyOutlined,
SafetyCertificateOutlined,
ThunderboltOutlined,
} from '@ant-design/icons'
import { formatDateTime } from '../../utils/time'
const { Text } = Typography
export interface RockStrategyCardProps {
form: FormInstance
autoDelivery: boolean
autoPurchaseEnabled: boolean
autoPurchaseCoolingSeconds?: number
nextAutoPurchaseAt?: string
loginPasswordSet?: boolean
lastTokenRefreshAt?: string
lastTokenError?: string
testingLogin: boolean
onTestLogin: () => void
refreshingToken: boolean
onRefreshToken: () => void
togglingAutoPurchase: boolean
testingConn: boolean
savingConfig: boolean
onToggleAutoDelivery: (checked: boolean) => void
onToggleAutoPurchase: (checked: boolean) => void
onTestConnection: () => void
onSaveConfig: () => void
}
export function RockStrategyCard({
form,
autoDelivery,
autoPurchaseEnabled,
autoPurchaseCoolingSeconds,
nextAutoPurchaseAt,
loginPasswordSet,
lastTokenRefreshAt,
lastTokenError,
testingLogin,
onTestLogin,
refreshingToken,
onRefreshToken,
togglingAutoPurchase,
testingConn,
savingConfig,
onToggleAutoDelivery,
onToggleAutoPurchase,
onTestConnection,
onSaveConfig,
}: RockStrategyCardProps) {
return (
<Card
style={{
marginBottom: 20,
borderRadius: 12,
border: '1px solid #e2e8f0',
boxShadow: 'var(--shadow-sm)',
}}
styles={{ body: { padding: '20px 24px' } }}
>
{/* 通道状态与软熔断横幅 */}
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '14px 18px',
borderRadius: 8,
marginBottom: 20,
background: autoDelivery ? '#f0fdf4' : '#fef2f2',
border: autoDelivery ? '1px solid #bbf7d0' : '1px solid #fecaca',
transition: 'all 0.3s ease',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<div
style={{
width: 38,
height: 38,
borderRadius: 8,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 18,
background: autoDelivery ? '#dcfce7' : '#fee2e2',
color: autoDelivery ? '#16a34a' : '#dc2626',
}}
>
{autoDelivery ? <SafetyCertificateOutlined /> : <ControlOutlined />}
</div>
<div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<strong style={{ fontSize: 14, color: autoDelivery ? '#166534' : '#991b1b' }}>
{autoDelivery
? '自动发货通道正常运转 (Active)'
: '自动发货已全局软熔断 (Kill Switch Engaged)'}
</strong>
<Tag color={autoDelivery ? 'success' : 'error'} style={{ borderRadius: 10 }}>
{autoDelivery ? '反代履约中' : '已转交人工打手'}
</Tag>
</div>
<div style={{ fontSize: 12, color: autoDelivery ? '#15803d' : '#b91c1c', marginTop: 2 }}>
{autoDelivery
? '和平精英「黑色 ROCK 长袖」与「蓝色运动服」开启 0666.pro 全自动反代发货,UID 强一致安全门禁全面守护。'
: '两款商品已彻底切断上游 0666.pro 自动发货通道,所有新订单均保留在待发货状态,由人工接单平台处理。'}
</div>
</div>
</div>
<Space size="middle">
<Switch
checked={autoDelivery}
onChange={onToggleAutoDelivery}
checkedChildren="开启"
unCheckedChildren="熔断"
style={{
backgroundColor: autoDelivery ? '#16a34a' : '#dc2626',
}}
/>
<Button
size="small"
danger={autoDelivery}
type={autoDelivery ? 'default' : 'primary'}
onClick={() => onToggleAutoDelivery(!autoDelivery)}
>
{autoDelivery ? '一键紧急熔断' : '恢复自动发货'}
</Button>
</Space>
</div>
{/* 策略表单 */}
<Form form={form} layout="vertical" autoComplete="off">
{/* 阻断浏览器对凭据配置项的强制账密自动填充 (Honeypot) */}
<div style={{ position: 'absolute', top: -9999, left: -9999, width: 0, height: 0, overflow: 'hidden' }} aria-hidden="true">
<input type="text" name="chrome_prevent_autofill_username" tabIndex={-1} autoComplete="username" />
<input type="password" name="chrome_prevent_autofill_password" tabIndex={-1} autoComplete="current-password" />
</div>
<Row gutter={24}>
<Col xs={24} lg={12}>
<div
style={{
background: '#f8fafc',
padding: '16px 18px',
borderRadius: 10,
border: '1px solid #f1f5f9',
height: '100%',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 16 }}>
<KeyOutlined style={{ color: '#2563eb', fontSize: 16 }} />
<strong style={{ fontSize: 14, color: '#0f172a' }}>0666.pro 采购认证凭据</strong>
</div>
<Form.Item name="base_url" label="上游平台接口地址 (Base URL)" style={{ marginBottom: 14 }}>
<Input
placeholder="https://0666.pro"
autoComplete="off"
name="upstream_base_url"
id="upstream_base_url"
/>
</Form.Item>
<Form.Item
name="bearer_token"
label={
<Space>
<span>上游商户 Bearer Token (yd_token)</span>
<Tooltip title="Token 失效时自动使用下方账号密码登录刷新;手动粘贴的 Token 会被自动登录覆盖">
<ExclamationCircleOutlined style={{ color: '#94a3b8' }} />
</Tooltip>
</Space>
}
style={{ marginBottom: 12 }}
>
<Input.Password
placeholder="粘贴 64 位十六进制 Bearer Token"
autoComplete="new-password"
name="upstream_bearer_token"
id="upstream_bearer_token"
addonAfter={
<Button
type="link"
size="small"
onClick={onTestConnection}
loading={testingConn}
style={{ padding: '0 4px', height: 'auto' }}
>
测试连通性
</Button>
}
/>
</Form.Item>
<Row gutter={12}>
<Col span={12}>
<Form.Item
name="login_username"
label="上游登录账号"
style={{ marginBottom: 12 }}
>
<Input
placeholder="0666.pro 登录账号"
autoComplete="off"
name="upstream_login_username"
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="login_password"
label="上游登录密码"
style={{ marginBottom: 12 }}
extra={
loginPasswordSet
? '已保存密码,留空表示不修改'
: '用于 Token 失效后自动登录'
}
>
<Input.Password
placeholder={loginPasswordSet ? '已保存,留空不修改' : '请输入密码'}
autoComplete="new-password"
name="upstream_login_password"
/>
</Form.Item>
</Col>
</Row>
<Space wrap style={{ marginBottom: 12 }}>
<Button size="small" onClick={onTestLogin} loading={testingLogin}>
测试登录
</Button>
<Button size="small" onClick={onRefreshToken} loading={refreshingToken}>
刷新 Token
</Button>
{lastTokenError ? (
<Text type="danger" style={{ fontSize: 12 }}>
Token 刷新失败:{lastTokenError}
</Text>
) : lastTokenRefreshAt ? (
<Text type="secondary" style={{ fontSize: 12 }}>
Token 上次刷新:{formatDateTime(lastTokenRefreshAt)}
</Text>
) : (
<Text type="secondary" style={{ fontSize: 12 }}>
尚未自动刷新 Token
</Text>
)}
</Space>
<div
style={{
background: '#eff6ff',
border: '1px dashed #bfdbfe',
borderRadius: 6,
padding: '8px 12px',
fontSize: 12,
color: '#1e40af',
}}
>
💡 <strong>Token 兜底:</strong>自动登录失败时,可登录 0666.pro 网页按 F12 打开控制台,执行{' '}
<Text code copyable={{ text: "localStorage.getItem('yd_token')" }}>
localStorage.getItem('yd_token')
</Text>{' '}
手动粘贴到上方。
</div>
</div>
</Col>
<Col xs={24} lg={12}>
<div
style={{
background: '#f8fafc',
padding: '16px 18px',
borderRadius: 10,
border: '1px solid #f1f5f9',
height: '100%',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 16 }}>
<ThunderboltOutlined style={{ color: '#7c3aed', fontSize: 16 }} />
<strong style={{ fontSize: 14, color: '#0f172a' }}>库存风控与自动补货策略</strong>
</div>
<Row gutter={16}>
<Col span={12}>
<Form.Item name="token_warning_stock" label="库存告警安全水位">
<InputNumber min={1} max={500} style={{ width: '100%' }} addonAfter="个" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="自动补货触发开关">
<Space direction="vertical" size={2}>
<Switch
checked={autoPurchaseEnabled}
onChange={onToggleAutoPurchase}
loading={togglingAutoPurchase}
checkedChildren="开启自动补货"
unCheckedChildren="关闭"
/>
<Text type="secondary" style={{ fontSize: 11 }}>
切换后立即保存
</Text>
{autoPurchaseEnabled && (
<Text
type={(autoPurchaseCoolingSeconds ?? 0) > 0 ? 'warning' : 'secondary'}
style={{ fontSize: 11 }}
>
{(autoPurchaseCoolingSeconds ?? 0) > 0
? `冷却中,${
nextAutoPurchaseAt
? `${nextAutoPurchaseAt.slice(11, 19)} 后`
: '稍后'
}可再次补货`
: '自动补货就绪'}
</Text>
)}
</Space>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="auto_purchase_count" label="单次自动补货数量">
<InputNumber min={1} max={200} style={{ width: '100%' }} addonAfter="个" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="purchase_cooldown_minutes" label="补货防抖冷却时间">
<InputNumber min={1} max={60} style={{ width: '100%' }} addonAfter="分钟" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="using_warn_days"
label="待绑定橙色预警"
tooltip="链接已发出但尚未扫码绑定的 Token 超过该天数后,状态标签变为橙色"
>
<InputNumber min={1} max={365} style={{ width: '100%' }} addonAfter="天" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="using_danger_days"
label="待绑定红色预警"
tooltip="必须大于橙色预警天数;超过后状态标签变为红色"
>
<InputNumber min={1} max={365} style={{ width: '100%' }} addonAfter="天" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="bound_warn_days"
label="绑定后未发货预警"
tooltip="本轮真实绑定后超过该天数仍未完成发货,标记为「绑定后未发货」"
>
<InputNumber min={1} max={365} style={{ width: '100%' }} addonAfter="天" />
</Form.Item>
</Col>
</Row>
</div>
</Col>
</Row>
<Divider style={{ margin: '18px 0 16px 0' }} />
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button
type="primary"
onClick={onSaveConfig}
loading={savingConfig}
style={{
background: 'linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%)',
minWidth: 130,
}}
>
保存策略配置
</Button>
</div>
</Form>
</Card>
)
}