401 lines
15 KiB
TypeScript
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>
|
|
)
|
|
}
|