统一前后端代码格式化配置
This commit is contained in:
@@ -126,7 +126,9 @@ function FulfillmentRoutingPanel() {
|
||||
const [executors, setExecutors] = useState<AdminFulfillmentRoutingConfig['executors']>({})
|
||||
const [rules, setRules] = useState<EditableRoutingRule[]>([])
|
||||
const [previewInput, setPreviewInput] = useState('')
|
||||
const [previewResult, setPreviewResult] = useState<AdminFulfillmentRoutingPreviewResult | null>(null)
|
||||
const [previewResult, setPreviewResult] = useState<AdminFulfillmentRoutingPreviewResult | null>(
|
||||
null,
|
||||
)
|
||||
|
||||
const enabledRuleCount = rules.filter((rule) => rule.enabled !== false).length
|
||||
const selectedExecutorKey = previewResult?.fulfillmentRoute?.selectedExecutorKey || ''
|
||||
@@ -276,7 +278,11 @@ function FulfillmentRoutingPanel() {
|
||||
|
||||
<div className="metric-grid four">
|
||||
<MetricCard label="路由状态" value={enabled ? '启用' : '停用'} />
|
||||
<MetricCard label="商品规则" value={String(rules.length)} detail={`启用 ${enabledRuleCount} 条`} />
|
||||
<MetricCard
|
||||
label="商品规则"
|
||||
value={String(rules.length)}
|
||||
detail={`启用 ${enabledRuleCount} 条`}
|
||||
/>
|
||||
<MetricCard
|
||||
label="全局优先级"
|
||||
value={`${priority.length} 个通道`}
|
||||
@@ -298,7 +304,12 @@ function FulfillmentRoutingPanel() {
|
||||
<div className="platform-routing-grid">
|
||||
<div className="field-switch">
|
||||
<span>履约路由</span>
|
||||
<Switch checked={enabled} checkedChildren="启用" unCheckedChildren="停用" onChange={setEnabled} />
|
||||
<Switch
|
||||
checked={enabled}
|
||||
checkedChildren="启用"
|
||||
unCheckedChildren="停用"
|
||||
onChange={setEnabled}
|
||||
/>
|
||||
</div>
|
||||
{ROUTING_EXECUTORS.map((executor) => (
|
||||
<div key={executor.key} className="field-switch">
|
||||
@@ -610,14 +621,23 @@ function KuaishouFeifeiFulfillmentPanel() {
|
||||
{errorMessage ? <Alert type="error" showIcon message={errorMessage} /> : null}
|
||||
|
||||
<div className="metric-grid three">
|
||||
<MetricCard label="名字映射" value={String(mappingCount)} detail={`启用 ${enabledRuleCount} 条`} />
|
||||
<MetricCard
|
||||
label="名字映射"
|
||||
value={String(mappingCount)}
|
||||
detail={`启用 ${enabledRuleCount} 条`}
|
||||
/>
|
||||
<MetricCard label="匹配方式" value="同名" detail="使用 91 商品名规范化匹配" />
|
||||
<Card>
|
||||
<Space wrap>
|
||||
<Button icon={<ReloadOutlined />} loading={loading} onClick={loadConfig}>
|
||||
刷新
|
||||
</Button>
|
||||
<Button type="primary" icon={<ReloadOutlined />} loading={syncing} onClick={syncProducts}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<ReloadOutlined />}
|
||||
loading={syncing}
|
||||
onClick={syncProducts}
|
||||
>
|
||||
同步商品映射
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -626,7 +646,11 @@ function KuaishouFeifeiFulfillmentPanel() {
|
||||
|
||||
<Card
|
||||
title="商品名字映射"
|
||||
extra={<Typography.Text type="secondary">{lastSyncText || 'feifei 商品名会按规范化名字匹配 91 商品名。'}</Typography.Text>}
|
||||
extra={
|
||||
<Typography.Text type="secondary">
|
||||
{lastSyncText || 'feifei 商品名会按规范化名字匹配 91 商品名。'}
|
||||
</Typography.Text>
|
||||
}
|
||||
>
|
||||
<Space.Compact className="full-width">
|
||||
<Input
|
||||
@@ -714,7 +738,10 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
sourceCount: sources.length,
|
||||
readySourceCount: readySources.length,
|
||||
skuCount: skuItems.length,
|
||||
inventoryTotal: skuItems.reduce((sum, item) => sum + Math.max(0, Number(item.inventory || 0)), 0),
|
||||
inventoryTotal: skuItems.reduce(
|
||||
(sum, item) => sum + Math.max(0, Number(item.inventory || 0)),
|
||||
0,
|
||||
),
|
||||
overrideRuleCount: overrideRules.length,
|
||||
}
|
||||
|
||||
@@ -739,7 +766,9 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
fetchAdminCloudtentaclesSourceConfig(),
|
||||
fetchAdminCloudtentaclesOverrideRules(),
|
||||
])
|
||||
const nextSources = Array.isArray(sourceResponse.data.sources) ? sourceResponse.data.sources : []
|
||||
const nextSources = Array.isArray(sourceResponse.data.sources)
|
||||
? sourceResponse.data.sources
|
||||
: []
|
||||
const nextSessions = sourceResponse.data.sessions || {}
|
||||
const nextRows = nextSources.map((source) => {
|
||||
const session = nextSessions[source.key] || null
|
||||
@@ -839,7 +868,11 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
)
|
||||
}
|
||||
|
||||
function updateDeliveryItem(ruleIndex: number, itemIndex: number, patch: Partial<AdminCloudtentaclesOverrideDeliveryItem>) {
|
||||
function updateDeliveryItem(
|
||||
ruleIndex: number,
|
||||
itemIndex: number,
|
||||
patch: Partial<AdminCloudtentaclesOverrideDeliveryItem>,
|
||||
) {
|
||||
setOverrideRules((current) =>
|
||||
current.map((rule, currentRuleIndex) => {
|
||||
if (currentRuleIndex !== ruleIndex) {
|
||||
@@ -877,7 +910,9 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
return rule
|
||||
}
|
||||
|
||||
const nextItems = rule.deliveryItems.filter((_, currentItemIndex) => currentItemIndex !== itemIndex)
|
||||
const nextItems = rule.deliveryItems.filter(
|
||||
(_, currentItemIndex) => currentItemIndex !== itemIndex,
|
||||
)
|
||||
return {
|
||||
...rule,
|
||||
deliveryItems: nextItems.length ? nextItems : [createDeliveryItem(skuItems)],
|
||||
@@ -903,7 +938,10 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
{errorMessage ? <Alert type="error" showIcon message={errorMessage} /> : null}
|
||||
|
||||
<div className="metric-grid five">
|
||||
<MetricCard label="履约账号" value={`${metrics.readySourceCount} / ${metrics.sourceCount}`} />
|
||||
<MetricCard
|
||||
label="履约账号"
|
||||
value={`${metrics.readySourceCount} / ${metrics.sourceCount}`}
|
||||
/>
|
||||
<MetricCard label="可发商品" value={String(metrics.skuCount)} />
|
||||
<MetricCard label="总库存" value={String(metrics.inventoryTotal)} />
|
||||
<MetricCard label="覆盖规则" value={String(metrics.overrideRuleCount)} />
|
||||
@@ -935,7 +973,9 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
<strong>{formatSourceLabel(source)}</strong>
|
||||
<small>{source.username || source.key}</small>
|
||||
</span>
|
||||
<Tag color={source.ready ? 'green' : source.enabled === false ? 'default' : 'orange'}>
|
||||
<Tag
|
||||
color={source.ready ? 'green' : source.enabled === false ? 'default' : 'orange'}
|
||||
>
|
||||
{source.ready ? '可用' : source.enabled === false ? '已停用' : '未登录'}
|
||||
</Tag>
|
||||
</button>
|
||||
@@ -964,7 +1004,12 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
}
|
||||
>
|
||||
{skuErrorMessage ? (
|
||||
<Alert type="error" showIcon message={skuErrorMessage} className="platform-section-gap" />
|
||||
<Alert
|
||||
type="error"
|
||||
showIcon
|
||||
message={skuErrorMessage}
|
||||
className="platform-section-gap"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Space className="full-width" direction="vertical">
|
||||
@@ -980,7 +1025,9 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
{matchedSku ? (
|
||||
<Tag color="green">命中 #{matchedSku.id} {matchedSku.name}</Tag>
|
||||
<Tag color="green">
|
||||
命中 #{matchedSku.id} {matchedSku.name}
|
||||
</Tag>
|
||||
) : matchInput.trim() ? (
|
||||
<Tag color="orange">未命中</Tag>
|
||||
) : null}
|
||||
@@ -1012,7 +1059,12 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
}
|
||||
>
|
||||
{overrideErrorMessage ? (
|
||||
<Alert type="error" showIcon message={overrideErrorMessage} className="platform-section-gap" />
|
||||
<Alert
|
||||
type="error"
|
||||
showIcon
|
||||
message={overrideErrorMessage}
|
||||
className="platform-section-gap"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{overrideRules.length === 0 ? (
|
||||
@@ -1048,7 +1100,9 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
<Input
|
||||
value={rule.productName}
|
||||
placeholder="91 商品名 / productNo"
|
||||
onChange={(event) => updateRule(ruleIndex, { productName: event.target.value })}
|
||||
onChange={(event) =>
|
||||
updateRule(ruleIndex, { productName: event.target.value })
|
||||
}
|
||||
/>
|
||||
<Select
|
||||
allowClear
|
||||
@@ -1070,7 +1124,11 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Space direction="vertical" className="full-width platform-section-gap" size={8}>
|
||||
<Space
|
||||
direction="vertical"
|
||||
className="full-width platform-section-gap"
|
||||
size={8}
|
||||
>
|
||||
{rule.deliveryItems.map((item, itemIndex) => (
|
||||
<div key={`${rule.id}-${itemIndex}`} className="platform-delivery-row">
|
||||
<Select
|
||||
@@ -1082,7 +1140,9 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
label: formatSkuOption(sku),
|
||||
value: sku.id,
|
||||
}))}
|
||||
onChange={(skuId) => handleDeliverySkuChange(ruleIndex, itemIndex, Number(skuId || 0))}
|
||||
onChange={(skuId) =>
|
||||
handleDeliverySkuChange(ruleIndex, itemIndex, Number(skuId || 0))
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
value={item.cloudSkuName}
|
||||
@@ -1103,7 +1163,9 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button onClick={() => removeDeliveryItem(ruleIndex, itemIndex)}>删除</Button>
|
||||
<Button onClick={() => removeDeliveryItem(ruleIndex, itemIndex)}>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button onClick={() => addDeliveryItem(ruleIndex)}>增加发货商品</Button>
|
||||
@@ -1133,7 +1195,9 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
minWidth: 260,
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<Typography.Text strong ellipsis>{row.name || '-'}</Typography.Text>
|
||||
<Typography.Text strong ellipsis>
|
||||
{row.name || '-'}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary" ellipsis>
|
||||
#{row.id} · {row.description || row.name || '-'}
|
||||
</Typography.Text>
|
||||
@@ -1147,7 +1211,12 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
render: (value) => <code>{String(value || '-')}</code>,
|
||||
},
|
||||
{ title: '价格', dataIndex: 'price', width: 120, render: formatCloudPrice },
|
||||
{ title: '库存', dataIndex: 'inventory', width: 120, sorter: (a, b) => a.inventory - b.inventory },
|
||||
{
|
||||
title: '库存',
|
||||
dataIndex: 'inventory',
|
||||
width: 120,
|
||||
sorter: (a, b) => a.inventory - b.inventory,
|
||||
},
|
||||
{
|
||||
title: '发货限制',
|
||||
width: 150,
|
||||
@@ -1186,9 +1255,9 @@ function MetricCard({ label, value, detail }: { label: string; value: string; de
|
||||
|
||||
function normalizeRoutingPriority(value: unknown) {
|
||||
const rawItems = Array.isArray(value) ? value : []
|
||||
const allowed = ROUTING_EXECUTORS
|
||||
.filter((executor) => executor.key !== 'manual_dispatch')
|
||||
.map((executor) => executor.key)
|
||||
const allowed = ROUTING_EXECUTORS.filter((executor) => executor.key !== 'manual_dispatch').map(
|
||||
(executor) => executor.key,
|
||||
)
|
||||
const seen = new Set<string>()
|
||||
const items = rawItems
|
||||
.map((item) => String(item || '').trim())
|
||||
@@ -1198,16 +1267,20 @@ function normalizeRoutingPriority(value: unknown) {
|
||||
}
|
||||
|
||||
function normalizeRoutingExecutors(value: unknown): AdminFulfillmentRoutingConfig['executors'] {
|
||||
const source = value && typeof value === 'object' && !Array.isArray(value)
|
||||
? value as AdminFulfillmentRoutingConfig['executors']
|
||||
: {}
|
||||
const source =
|
||||
value && typeof value === 'object' && !Array.isArray(value)
|
||||
? (value as AdminFulfillmentRoutingConfig['executors'])
|
||||
: {}
|
||||
|
||||
return ROUTING_EXECUTORS.reduce<AdminFulfillmentRoutingConfig['executors']>((result, executor) => {
|
||||
result[executor.key] = {
|
||||
enabled: source[executor.key]?.enabled !== false,
|
||||
}
|
||||
return result
|
||||
}, {})
|
||||
return ROUTING_EXECUTORS.reduce<AdminFulfillmentRoutingConfig['executors']>(
|
||||
(result, executor) => {
|
||||
result[executor.key] = {
|
||||
enabled: source[executor.key]?.enabled !== false,
|
||||
}
|
||||
return result
|
||||
},
|
||||
{},
|
||||
)
|
||||
}
|
||||
|
||||
function normalizeRoutingRule(rule: AdminFulfillmentRoutingRule): EditableRoutingRule {
|
||||
@@ -1272,7 +1345,9 @@ function normalizeEditableRule(rule: AdminCloudtentaclesOverrideRule): EditableO
|
||||
}
|
||||
}
|
||||
|
||||
function createDeliveryItem(skuItems: AdminCloudtentaclesSkuItem[]): AdminCloudtentaclesOverrideDeliveryItem {
|
||||
function createDeliveryItem(
|
||||
skuItems: AdminCloudtentaclesSkuItem[],
|
||||
): AdminCloudtentaclesOverrideDeliveryItem {
|
||||
const firstSku = skuItems[0]
|
||||
return {
|
||||
cloudSkuId: Number(firstSku?.id || 0) || 0,
|
||||
@@ -1300,7 +1375,9 @@ function normalizeMatchName(value: unknown) {
|
||||
.trim()
|
||||
}
|
||||
|
||||
function formatSourceLabel(source: Pick<AdminCloudtentaclesSourceItem, 'key' | 'label' | 'username'>) {
|
||||
function formatSourceLabel(
|
||||
source: Pick<AdminCloudtentaclesSourceItem, 'key' | 'label' | 'username'>,
|
||||
) {
|
||||
return source.label || source.username || source.key
|
||||
}
|
||||
|
||||
@@ -1316,4 +1393,3 @@ function formatSkuOption(sku: AdminCloudtentaclesSkuItem) {
|
||||
function createRuleId() {
|
||||
return `rule_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
||||
}
|
||||
|
||||
|
||||
@@ -124,9 +124,13 @@ export default function AdminPlatformShopsPage() {
|
||||
const activeTab = normalizePlatformTab(searchParams.get('tab'))
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [errorMessage, setErrorMessage] = useState('')
|
||||
const [notificationConfig, setNotificationConfig] = useState<NotificationConfigResponse | null>(null)
|
||||
const [notificationConfig, setNotificationConfig] = useState<NotificationConfigResponse | null>(
|
||||
null,
|
||||
)
|
||||
const [scheduledJobs, setScheduledJobs] = useState<ScheduledJobsState | null>(null)
|
||||
const [industryConfig, setIndustryConfig] = useState<AdminKuaishouIndustryConfigResponse | null>(null)
|
||||
const [industryConfig, setIndustryConfig] = useState<AdminKuaishouIndustryConfigResponse | null>(
|
||||
null,
|
||||
)
|
||||
const [feifeiConfig, setFeifeiConfig] = useState<AdminKuaishouFeifeiConfigResponse | null>(null)
|
||||
const [cloudtentaclesConfig, setCloudtentaclesConfig] =
|
||||
useState<AdminCloudtentaclesSourceConfigResponse | null>(null)
|
||||
@@ -213,25 +217,23 @@ export default function AdminPlatformShopsPage() {
|
||||
{
|
||||
key: 'notifications',
|
||||
label: '内部通知',
|
||||
children: notificationConfig && scheduledJobs ? (
|
||||
<NotificationPanel
|
||||
notificationConfig={notificationConfig}
|
||||
scheduledJobs={scheduledJobs}
|
||||
onNotificationChange={setNotificationConfig}
|
||||
onScheduledJobsChange={setScheduledJobs}
|
||||
/>
|
||||
) : (
|
||||
<Empty description="通知配置未加载" />
|
||||
),
|
||||
children:
|
||||
notificationConfig && scheduledJobs ? (
|
||||
<NotificationPanel
|
||||
notificationConfig={notificationConfig}
|
||||
scheduledJobs={scheduledJobs}
|
||||
onNotificationChange={setNotificationConfig}
|
||||
onScheduledJobsChange={setScheduledJobs}
|
||||
/>
|
||||
) : (
|
||||
<Empty description="通知配置未加载" />
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'kuaishouIndustry',
|
||||
label: '行业电子凭证',
|
||||
children: industryConfig ? (
|
||||
<KuaishouIndustryPanel
|
||||
config={industryConfig}
|
||||
onChange={setIndustryConfig}
|
||||
/>
|
||||
<KuaishouIndustryPanel config={industryConfig} onChange={setIndustryConfig} />
|
||||
) : (
|
||||
<Empty description="快手行业电子凭证配置未加载" />
|
||||
),
|
||||
@@ -399,9 +401,7 @@ function NotificationPanel({
|
||||
function updateJob(index: number, nextJob: AdminScheduledJobItem) {
|
||||
updateJobs({
|
||||
...scheduledJobs.source,
|
||||
jobs: jobsWithMergedAccounts.map((item, jobIndex) =>
|
||||
jobIndex === index ? nextJob : item,
|
||||
),
|
||||
jobs: jobsWithMergedAccounts.map((item, jobIndex) => (jobIndex === index ? nextJob : item)),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -411,10 +411,19 @@ function NotificationPanel({
|
||||
title="通知渠道"
|
||||
extra={
|
||||
<Space wrap>
|
||||
<Button icon={<SendOutlined />} loading={testingNotification} onClick={testNotification}>
|
||||
<Button
|
||||
icon={<SendOutlined />}
|
||||
loading={testingNotification}
|
||||
onClick={testNotification}
|
||||
>
|
||||
发送测试
|
||||
</Button>
|
||||
<Button type="primary" icon={<SaveOutlined />} loading={savingNotification} onClick={saveNotification}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined />}
|
||||
loading={savingNotification}
|
||||
onClick={saveNotification}
|
||||
>
|
||||
保存通知
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -554,8 +563,14 @@ function RecipientList<T extends { id: string; name: string; enabled: boolean }>
|
||||
}: {
|
||||
title: string
|
||||
secretField: 'deviceKey' | 'apiKey'
|
||||
items: Array<T & Partial<Record<'deviceKey' | 'apiKey' | 'deviceKeyMasked' | 'apiKeyMasked', string>>>
|
||||
onChange: (items: Array<T & Partial<Record<'deviceKey' | 'apiKey' | 'deviceKeyMasked' | 'apiKeyMasked', string>>>) => void
|
||||
items: Array<
|
||||
T & Partial<Record<'deviceKey' | 'apiKey' | 'deviceKeyMasked' | 'apiKeyMasked', string>>
|
||||
>
|
||||
onChange: (
|
||||
items: Array<
|
||||
T & Partial<Record<'deviceKey' | 'apiKey' | 'deviceKeyMasked' | 'apiKeyMasked', string>>
|
||||
>,
|
||||
) => void
|
||||
}) {
|
||||
function addRecipient() {
|
||||
onChange([
|
||||
@@ -569,12 +584,24 @@ function RecipientList<T extends { id: string; name: string; enabled: boolean }>
|
||||
])
|
||||
}
|
||||
|
||||
function updateRecipient(index: number, patch: Partial<T & Record<'deviceKey' | 'apiKey', string>>) {
|
||||
function updateRecipient(
|
||||
index: number,
|
||||
patch: Partial<T & Record<'deviceKey' | 'apiKey', string>>,
|
||||
) {
|
||||
onChange(items.map((item, itemIndex) => (itemIndex === index ? { ...item, ...patch } : item)))
|
||||
}
|
||||
|
||||
return (
|
||||
<Card size="small" title={title} className="platform-section-gap" extra={<Button icon={<PlusOutlined />} onClick={addRecipient}>新增</Button>}>
|
||||
<Card
|
||||
size="small"
|
||||
title={title}
|
||||
className="platform-section-gap"
|
||||
extra={
|
||||
<Button icon={<PlusOutlined />} onClick={addRecipient}>
|
||||
新增
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{items.length === 0 ? (
|
||||
<Empty description="暂无接收人" />
|
||||
) : (
|
||||
@@ -583,23 +610,37 @@ function RecipientList<T extends { id: string; name: string; enabled: boolean }>
|
||||
<div key={item.id || index} className="platform-inline-row">
|
||||
<Switch
|
||||
checked={item.enabled !== false}
|
||||
onChange={(enabled) => updateRecipient(index, { enabled } as Partial<T & Record<'deviceKey' | 'apiKey', string>>)}
|
||||
onChange={(enabled) =>
|
||||
updateRecipient(index, { enabled } as Partial<
|
||||
T & Record<'deviceKey' | 'apiKey', string>
|
||||
>)
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
value={item.name}
|
||||
placeholder="名称"
|
||||
onChange={(event) => updateRecipient(index, { name: event.target.value } as Partial<T & Record<'deviceKey' | 'apiKey', string>>)}
|
||||
onChange={(event) =>
|
||||
updateRecipient(index, { name: event.target.value } as Partial<
|
||||
T & Record<'deviceKey' | 'apiKey', string>
|
||||
>)
|
||||
}
|
||||
/>
|
||||
<Input.Password
|
||||
value={String(item[secretField] || '')}
|
||||
placeholder={String(item[`${secretField}Masked` as keyof typeof item] || secretField)}
|
||||
placeholder={String(
|
||||
item[`${secretField}Masked` as keyof typeof item] || secretField,
|
||||
)}
|
||||
onChange={(event) =>
|
||||
updateRecipient(index, {
|
||||
[secretField]: event.target.value,
|
||||
} as Partial<T & Record<'deviceKey' | 'apiKey', string>>)
|
||||
}
|
||||
/>
|
||||
<Button danger icon={<DeleteOutlined />} onClick={() => onChange(items.filter((_, itemIndex) => itemIndex !== index))} />
|
||||
<Button
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => onChange(items.filter((_, itemIndex) => itemIndex !== index))}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</Space>
|
||||
@@ -634,9 +675,7 @@ function ScheduledJobCard({
|
||||
onChange={(enabled) => onChange({ ...job, enabled })}
|
||||
/>
|
||||
<span>{formatScheduledJobTitle(job)}</span>
|
||||
<Tag color={job.enabled ? 'green' : 'default'}>
|
||||
{job.enabled ? '已启用' : '已停用'}
|
||||
</Tag>
|
||||
<Tag color={job.enabled ? 'green' : 'default'}>{job.enabled ? '已启用' : '已停用'}</Tag>
|
||||
</Space>
|
||||
}
|
||||
extra={
|
||||
@@ -660,9 +699,7 @@ function ScheduledJobCard({
|
||||
label="每次扫描数量"
|
||||
value={Number(job.config?.scanLimit ?? 50)}
|
||||
min={1}
|
||||
onChange={(scanLimit) =>
|
||||
onChange({ ...job, config: { ...job.config, scanLimit } })
|
||||
}
|
||||
onChange={(scanLimit) => onChange({ ...job, config: { ...job.config, scanLimit } })}
|
||||
/>
|
||||
</div>
|
||||
{runtime ? (
|
||||
@@ -701,9 +738,7 @@ function ScheduledJobCard({
|
||||
onChange={(enabled) => onChange({ ...job, enabled })}
|
||||
/>
|
||||
<span>{formatScheduledJobTitle(job)}</span>
|
||||
<Tag color={job.enabled ? 'green' : 'default'}>
|
||||
{job.enabled ? '已启用' : '已停用'}
|
||||
</Tag>
|
||||
<Tag color={job.enabled ? 'green' : 'default'}>{job.enabled ? '已启用' : '已停用'}</Tag>
|
||||
</Space>
|
||||
}
|
||||
extra={
|
||||
@@ -713,8 +748,7 @@ function ScheduledJobCard({
|
||||
}
|
||||
>
|
||||
<Typography.Paragraph type="secondary" style={{ marginBottom: 12 }}>
|
||||
将有押金工单验收通过后进入待解冻的押金,在到期后自动转入可用余额(验收后 3
|
||||
天到账)。
|
||||
将有押金工单验收通过后进入待解冻的押金,在到期后自动转入可用余额(验收后 3 天到账)。
|
||||
</Typography.Paragraph>
|
||||
<div className="platform-form-grid">
|
||||
<NumberField
|
||||
@@ -727,9 +761,7 @@ function ScheduledJobCard({
|
||||
label="每次解冻数量"
|
||||
value={Number(job.config?.scanLimit ?? 100)}
|
||||
min={1}
|
||||
onChange={(scanLimit) =>
|
||||
onChange({ ...job, config: { ...job.config, scanLimit } })
|
||||
}
|
||||
onChange={(scanLimit) => onChange({ ...job, config: { ...job.config, scanLimit } })}
|
||||
/>
|
||||
</div>
|
||||
{runtime ? (
|
||||
@@ -758,7 +790,10 @@ function ScheduledJobCard({
|
||||
const accounts = job.config?.accounts || []
|
||||
const defaultThreshold = Number(job.config?.assetThreshold ?? 500)
|
||||
|
||||
function updateAccount(sourceKey: string, patch: Partial<AdminScheduledJobCloudtentaclesAccount>) {
|
||||
function updateAccount(
|
||||
sourceKey: string,
|
||||
patch: Partial<AdminScheduledJobCloudtentaclesAccount>,
|
||||
) {
|
||||
onChange({
|
||||
...job,
|
||||
config: {
|
||||
@@ -849,7 +884,8 @@ function ScheduledJobCard({
|
||||
{formatHealthStatus(accountRuntime.status)}
|
||||
</Tag>
|
||||
<Typography.Text type="secondary">
|
||||
余额 {accountRuntime.asset ?? '-'} / 阈值 {accountRuntime.threshold ?? account.assetThreshold}
|
||||
余额 {accountRuntime.asset ?? '-'} / 阈值{' '}
|
||||
{accountRuntime.threshold ?? account.assetThreshold}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
<Typography.Text type="secondary">
|
||||
@@ -869,12 +905,19 @@ function ScheduledJobCard({
|
||||
size="small"
|
||||
title={
|
||||
<Space wrap>
|
||||
<Switch checked={job.enabled !== false} onChange={(enabled) => onChange({ ...job, enabled })} />
|
||||
<Switch
|
||||
checked={job.enabled !== false}
|
||||
onChange={(enabled) => onChange({ ...job, enabled })}
|
||||
/>
|
||||
<span>{formatScheduledJobTitle(job)}</span>
|
||||
<Tag color={job.enabled ? 'green' : 'default'}>{job.enabled ? '已启用' : '已停用'}</Tag>
|
||||
</Space>
|
||||
}
|
||||
extra={<Button icon={<ReloadOutlined />} loading={running} onClick={onRun}>立即检查</Button>}
|
||||
extra={
|
||||
<Button icon={<ReloadOutlined />} loading={running} onClick={onRun}>
|
||||
立即检查
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="platform-form-grid">
|
||||
<NumberField
|
||||
@@ -909,15 +952,13 @@ function ScheduledJobCard({
|
||||
}
|
||||
showIcon
|
||||
message={runtime.lastMessage || '尚未运行'}
|
||||
description={
|
||||
[
|
||||
`已检查 ${runtime.lastCheckedCount ?? 0} / ${accounts.length}`,
|
||||
`预警 ${runtime.lastLowAssetCount ?? 0}`,
|
||||
`异常 ${runtime.lastFailedCount ?? 0}`,
|
||||
`上次:${formatAdminDateTime(runtime.lastFinishedAt || runtime.lastRunAt)}`,
|
||||
`下次:${formatAdminDateTime(runtime.nextRunAt)}`,
|
||||
].join(' · ')
|
||||
}
|
||||
description={[
|
||||
`已检查 ${runtime.lastCheckedCount ?? 0} / ${accounts.length}`,
|
||||
`预警 ${runtime.lastLowAssetCount ?? 0}`,
|
||||
`异常 ${runtime.lastFailedCount ?? 0}`,
|
||||
`上次:${formatAdminDateTime(runtime.lastFinishedAt || runtime.lastRunAt)}`,
|
||||
`下次:${formatAdminDateTime(runtime.nextRunAt)}`,
|
||||
].join(' · ')}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -953,9 +994,7 @@ function mergeScheduledJobAccounts(
|
||||
sourceKey,
|
||||
label: String(configured?.label || item.label || sourceKey).trim(),
|
||||
enabled: configured ? configured.enabled !== false : item.enabled !== false,
|
||||
assetThreshold: Number(
|
||||
configured?.assetThreshold ?? defaultAssetThreshold,
|
||||
),
|
||||
assetThreshold: Number(configured?.assetThreshold ?? defaultAssetThreshold),
|
||||
}
|
||||
})
|
||||
const mergedKeys = new Set(merged.map((item) => item.sourceKey))
|
||||
@@ -997,13 +1036,15 @@ function formatMonitorAccountMeta(
|
||||
return '账号配置不存在'
|
||||
}
|
||||
|
||||
return [
|
||||
source.username ? `登录名 ${source.username}` : '',
|
||||
source.phoneMasked ? `手机 ${source.phoneMasked}` : '',
|
||||
source.hasToken ? 'Token 已保存' : '未登录',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' · ') || account.sourceKey
|
||||
return (
|
||||
[
|
||||
source.username ? `登录名 ${source.username}` : '',
|
||||
source.phoneMasked ? `手机 ${source.phoneMasked}` : '',
|
||||
source.hasToken ? 'Token 已保存' : '未登录',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' · ') || account.sourceKey
|
||||
)
|
||||
}
|
||||
|
||||
function getAccountRuntime(
|
||||
@@ -1190,9 +1231,7 @@ function KuaishouIndustryPanel({
|
||||
|
||||
function updateShop(index: number, patch: Partial<AdminKuaishouIndustryShopConfig>) {
|
||||
updateSource({
|
||||
shops: shops.map((shop, shopIndex) =>
|
||||
shopIndex === index ? { ...shop, ...patch } : shop,
|
||||
),
|
||||
shops: shops.map((shop, shopIndex) => (shopIndex === index ? { ...shop, ...patch } : shop)),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1281,7 +1320,9 @@ function KuaishouIndustryPanel({
|
||||
return (
|
||||
<div className="status-stack">
|
||||
<Tag color={status.color}>{status.label}</Tag>
|
||||
{row.accessTokenMasked ? <Typography.Text type="secondary">{row.accessTokenMasked}</Typography.Text> : null}
|
||||
{row.accessTokenMasked ? (
|
||||
<Typography.Text type="secondary">{row.accessTokenMasked}</Typography.Text>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
@@ -1291,8 +1332,12 @@ function KuaishouIndustryPanel({
|
||||
minWidth: 220,
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<span>{row.accessTokenExpiresAt ? formatAdminDateTime(row.accessTokenExpiresAt) : '-'}</span>
|
||||
<span className="muted">{formatIndustryTokenCountdown(row.accessTokenExpiresInSeconds)}</span>
|
||||
<span>
|
||||
{row.accessTokenExpiresAt ? formatAdminDateTime(row.accessTokenExpiresAt) : '-'}
|
||||
</span>
|
||||
<span className="muted">
|
||||
{formatIndustryTokenCountdown(row.accessTokenExpiresInSeconds)}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@@ -1325,10 +1370,18 @@ function KuaishouIndustryPanel({
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
<Space wrap>
|
||||
<Button size="small" icon={<CopyOutlined />} onClick={() => copyAuthorizationUrl(row)}>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<CopyOutlined />}
|
||||
onClick={() => copyAuthorizationUrl(row)}
|
||||
>
|
||||
复制授权
|
||||
</Button>
|
||||
<Button size="small" icon={<LinkOutlined />} onClick={() => openAuthorizationUrl(row)}>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<LinkOutlined />}
|
||||
onClick={() => openAuthorizationUrl(row)}
|
||||
>
|
||||
打开授权
|
||||
</Button>
|
||||
<Button
|
||||
@@ -1531,25 +1584,33 @@ function KuaishouIndustryPanel({
|
||||
<Input.Password
|
||||
value={row.refreshToken}
|
||||
placeholder={row.refreshTokenMasked || '留空保持原值'}
|
||||
onChange={(event) => updateShop(index, { refreshToken: event.target.value })}
|
||||
onChange={(event) =>
|
||||
updateShop(index, { refreshToken: event.target.value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<LabeledInput
|
||||
label="accessToken 过期时间"
|
||||
value={row.accessTokenExpiresAt}
|
||||
onChange={(accessTokenExpiresAt) => updateShop(index, { accessTokenExpiresAt })}
|
||||
onChange={(accessTokenExpiresAt) =>
|
||||
updateShop(index, { accessTokenExpiresAt })
|
||||
}
|
||||
/>
|
||||
<LabeledInput
|
||||
label="refreshToken 过期时间"
|
||||
value={row.refreshTokenExpiresAt}
|
||||
onChange={(refreshTokenExpiresAt) => updateShop(index, { refreshTokenExpiresAt })}
|
||||
onChange={(refreshTokenExpiresAt) =>
|
||||
updateShop(index, { refreshTokenExpiresAt })
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
<Typography.Text type="secondary">最近错误</Typography.Text>
|
||||
<Input.TextArea
|
||||
autoSize={{ minRows: 1, maxRows: 4 }}
|
||||
value={row.lastRefreshError}
|
||||
onChange={(event) => updateShop(index, { lastRefreshError: event.target.value })}
|
||||
onChange={(event) =>
|
||||
updateShop(index, { lastRefreshError: event.target.value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1596,7 +1657,9 @@ function KuaishouFeifeiPlatformPanel({
|
||||
try {
|
||||
const response = await syncAdminKuaishouFeifeiProducts({ status: 'on_sale' })
|
||||
onChange(response.data)
|
||||
showSuccess(`商品映射已同步:商品 ${response.data.sync.productCount} 个,规则 ${response.data.sync.ruleCount} 条`)
|
||||
showSuccess(
|
||||
`商品映射已同步:商品 ${response.data.sync.productCount} 个,规则 ${response.data.sync.ruleCount} 条`,
|
||||
)
|
||||
} catch (error) {
|
||||
showError(error instanceof Error ? error.message : '同步 kuaishou-feifei 商品映射失败')
|
||||
} finally {
|
||||
@@ -1636,9 +1699,20 @@ function KuaishouFeifeiPlatformPanel({
|
||||
return (
|
||||
<section className="platform-panel-stack">
|
||||
<div className="metric-grid three">
|
||||
<MetricCard label="运行状态" value={config.effective.enabled ? '已启用' : '停用'} detail={source.enabled ? '源配置启用' : '源配置停用'} />
|
||||
<MetricCard label="API 凭据" value={config.effective.hasAppKey && config.effective.hasAppSecret ? '已配置' : '待补全'} />
|
||||
<MetricCard label="启用规则" value={String(enabledRules.length)} detail={`共 ${source.productRules.length} 条`} />
|
||||
<MetricCard
|
||||
label="运行状态"
|
||||
value={config.effective.enabled ? '已启用' : '停用'}
|
||||
detail={source.enabled ? '源配置启用' : '源配置停用'}
|
||||
/>
|
||||
<MetricCard
|
||||
label="API 凭据"
|
||||
value={config.effective.hasAppKey && config.effective.hasAppSecret ? '已配置' : '待补全'}
|
||||
/>
|
||||
<MetricCard
|
||||
label="启用规则"
|
||||
value={String(enabledRules.length)}
|
||||
detail={`共 ${source.productRules.length} 条`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Card
|
||||
@@ -1655,31 +1729,59 @@ function KuaishouFeifeiPlatformPanel({
|
||||
}
|
||||
>
|
||||
<div className="platform-form-grid">
|
||||
<FieldSwitch label="启用" checked={source.enabled} onChange={(enabled) => updateSource({ enabled })} />
|
||||
<FieldSwitch
|
||||
label="启用"
|
||||
checked={source.enabled}
|
||||
onChange={(enabled) => updateSource({ enabled })}
|
||||
/>
|
||||
<div>
|
||||
<Typography.Text type="secondary">接口地址</Typography.Text>
|
||||
<Input value={source.baseUrl} onChange={(event) => updateSource({ baseUrl: event.target.value })} />
|
||||
<Input
|
||||
value={source.baseUrl}
|
||||
onChange={(event) => updateSource({ baseUrl: event.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Text type="secondary">App Key</Typography.Text>
|
||||
<Input value={source.appKey} onChange={(event) => updateSource({ appKey: event.target.value })} />
|
||||
<Input
|
||||
value={source.appKey}
|
||||
onChange={(event) => updateSource({ appKey: event.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Text type="secondary">App Secret</Typography.Text>
|
||||
<Input.Password value={source.appSecret} onChange={(event) => updateSource({ appSecret: event.target.value })} />
|
||||
<Input.Password
|
||||
value={source.appSecret}
|
||||
onChange={(event) => updateSource({ appSecret: event.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<NumberField label="超时毫秒" value={source.timeoutMs} min={1000} onChange={(timeoutMs) => updateSource({ timeoutMs })} />
|
||||
<NumberField
|
||||
label="超时毫秒"
|
||||
value={source.timeoutMs}
|
||||
min={1000}
|
||||
onChange={(timeoutMs) => updateSource({ timeoutMs })}
|
||||
/>
|
||||
<div>
|
||||
<Typography.Text type="secondary">通知地址</Typography.Text>
|
||||
<Input value={source.notifyUrl} onChange={(event) => updateSource({ notifyUrl: event.target.value })} />
|
||||
<Input
|
||||
value={source.notifyUrl}
|
||||
onChange={(event) => updateSource({ notifyUrl: event.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card title="商品映射">
|
||||
<Space.Compact className="full-width">
|
||||
<Input value={matchInput} placeholder="91 商品名" onChange={(event) => setMatchInput(event.target.value)} onPressEnter={previewMatch} />
|
||||
<Button icon={<SearchOutlined />} loading={matching} onClick={previewMatch}>预览命中</Button>
|
||||
<Input
|
||||
value={matchInput}
|
||||
placeholder="91 商品名"
|
||||
onChange={(event) => setMatchInput(event.target.value)}
|
||||
onPressEnter={previewMatch}
|
||||
/>
|
||||
<Button icon={<SearchOutlined />} loading={matching} onClick={previewMatch}>
|
||||
预览命中
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
{matchResult ? (
|
||||
<Alert
|
||||
@@ -1687,7 +1789,11 @@ function KuaishouFeifeiPlatformPanel({
|
||||
type={matchResult.matched ? 'success' : 'warning'}
|
||||
showIcon
|
||||
message={matchResult.matched ? '已命中' : '未命中'}
|
||||
description={matchResult.match ? `${matchResult.match.productName} -> ${matchResult.match.productCode}` : matchResult.normalizedProductName}
|
||||
description={
|
||||
matchResult.match
|
||||
? `${matchResult.match.productName} -> ${matchResult.match.productCode}`
|
||||
: matchResult.normalizedProductName
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -1702,35 +1808,50 @@ function KuaishouFeifeiPlatformPanel({
|
||||
title: '启用',
|
||||
width: 80,
|
||||
render: (_, row, index) => (
|
||||
<Switch checked={row.enabled !== false} onChange={(enabled) => updateRule(index, { enabled })} />
|
||||
<Switch
|
||||
checked={row.enabled !== false}
|
||||
onChange={(enabled) => updateRule(index, { enabled })}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '商品名',
|
||||
minWidth: 260,
|
||||
render: (_, row, index) => (
|
||||
<Input value={row.productName} onChange={(event) => updateRule(index, { productName: event.target.value })} />
|
||||
<Input
|
||||
value={row.productName}
|
||||
onChange={(event) => updateRule(index, { productName: event.target.value })}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'product_code',
|
||||
minWidth: 160,
|
||||
render: (_, row, index) => (
|
||||
<Input value={row.productCode} onChange={(event) => updateRule(index, { productCode: event.target.value })} />
|
||||
<Input
|
||||
value={row.productCode}
|
||||
onChange={(event) => updateRule(index, { productCode: event.target.value })}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '展示名',
|
||||
minWidth: 220,
|
||||
render: (_, row, index) => (
|
||||
<Input value={row.skuName} onChange={(event) => updateRule(index, { skuName: event.target.value })} />
|
||||
<Input
|
||||
value={row.skuName}
|
||||
onChange={(event) => updateRule(index, { skuName: event.target.value })}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
minWidth: 180,
|
||||
render: (_, row, index) => (
|
||||
<Input value={row.notes} onChange={(event) => updateRule(index, { notes: event.target.value })} />
|
||||
<Input
|
||||
value={row.notes}
|
||||
onChange={(event) => updateRule(index, { notes: event.target.value })}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
@@ -1773,9 +1894,10 @@ function CloudtentaclesPlatformPanel({
|
||||
sources: config.sources,
|
||||
})
|
||||
onChange(response.data)
|
||||
const nextKey = selectedSourceKey && response.data.sources.some((item) => item.key === selectedSourceKey)
|
||||
? selectedSourceKey
|
||||
: response.data.sources[0]?.key || ''
|
||||
const nextKey =
|
||||
selectedSourceKey && response.data.sources.some((item) => item.key === selectedSourceKey)
|
||||
? selectedSourceKey
|
||||
: response.data.sources[0]?.key || ''
|
||||
setSelectedSourceKey(nextKey)
|
||||
showSuccess('kuaishou-lewan 账号配置已保存')
|
||||
} catch (error) {
|
||||
@@ -1791,9 +1913,8 @@ function CloudtentaclesPlatformPanel({
|
||||
await deleteAdminCloudtentaclesSource(sourceKey)
|
||||
const response = await fetchAdminCloudtentaclesSourceConfig()
|
||||
onChange(response.data)
|
||||
const nextKey = selectedSourceKey === sourceKey
|
||||
? response.data.sources[0]?.key || ''
|
||||
: selectedSourceKey
|
||||
const nextKey =
|
||||
selectedSourceKey === sourceKey ? response.data.sources[0]?.key || '' : selectedSourceKey
|
||||
setSelectedSourceKey(
|
||||
response.data.sources.some((item) => item.key === nextKey)
|
||||
? nextKey
|
||||
@@ -1906,9 +2027,18 @@ function CloudtentaclesPlatformPanel({
|
||||
title="kuaishou-lewan 履约账号"
|
||||
extra={
|
||||
<Space wrap>
|
||||
<Switch checked={config.enabled} checkedChildren="启用" unCheckedChildren="停用" onChange={(enabled) => updateConfig({ enabled })} />
|
||||
<Button icon={<PlusOutlined />} onClick={addSource}>新增账号</Button>
|
||||
<Button type="primary" icon={<SaveOutlined />} loading={saving} onClick={saveConfig}>保存账号</Button>
|
||||
<Switch
|
||||
checked={config.enabled}
|
||||
checkedChildren="启用"
|
||||
unCheckedChildren="停用"
|
||||
onChange={(enabled) => updateConfig({ enabled })}
|
||||
/>
|
||||
<Button icon={<PlusOutlined />} onClick={addSource}>
|
||||
新增账号
|
||||
</Button>
|
||||
<Button type="primary" icon={<SaveOutlined />} loading={saving} onClick={saveConfig}>
|
||||
保存账号
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
@@ -1932,8 +2062,20 @@ function CloudtentaclesPlatformPanel({
|
||||
<strong>{formatCloudtentaclesSourceLabel(source)}</strong>
|
||||
<small>{source.username || source.key}</small>
|
||||
</span>
|
||||
<Tag color={session?.hasToken ? 'green' : source.enabled === false ? 'default' : 'orange'}>
|
||||
{session?.hasToken ? '已登录' : source.enabled === false ? '已停用' : '未登录'}
|
||||
<Tag
|
||||
color={
|
||||
session?.hasToken
|
||||
? 'green'
|
||||
: source.enabled === false
|
||||
? 'default'
|
||||
: 'orange'
|
||||
}
|
||||
>
|
||||
{session?.hasToken
|
||||
? '已登录'
|
||||
: source.enabled === false
|
||||
? '已停用'
|
||||
: '未登录'}
|
||||
</Tag>
|
||||
</button>
|
||||
)
|
||||
@@ -1943,7 +2085,11 @@ function CloudtentaclesPlatformPanel({
|
||||
|
||||
<Card
|
||||
size="small"
|
||||
title={selectedSource ? `编辑账号:${formatCloudtentaclesSourceLabel(selectedSource)}` : '编辑账号'}
|
||||
title={
|
||||
selectedSource
|
||||
? `编辑账号:${formatCloudtentaclesSourceLabel(selectedSource)}`
|
||||
: '编辑账号'
|
||||
}
|
||||
extra={
|
||||
selectedSource ? (
|
||||
<Button
|
||||
@@ -2087,9 +2233,7 @@ function CloudtentaclesPlatformPanel({
|
||||
...nextConfig.data.sources,
|
||||
...config.sources.filter(
|
||||
(source) =>
|
||||
!nextConfig.data.sources.some(
|
||||
(item) => item.key === source.key,
|
||||
),
|
||||
!nextConfig.data.sources.some((item) => item.key === source.key),
|
||||
),
|
||||
],
|
||||
})
|
||||
@@ -2158,7 +2302,9 @@ function CloudtentaclesPlatformPanel({
|
||||
查看号码列表
|
||||
</Button>
|
||||
</Space>
|
||||
{debugResult ? <pre className="json-preview">{JSON.stringify(debugResult, null, 2)}</pre> : null}
|
||||
{debugResult ? (
|
||||
<pre className="json-preview">{JSON.stringify(debugResult, null, 2)}</pre>
|
||||
) : null}
|
||||
{vnItems !== null ? (
|
||||
<Table<AdminCloudtentaclesVnListItem>
|
||||
className="platform-section-gap"
|
||||
@@ -2195,7 +2341,13 @@ function CloudtentaclesPlatformPanel({
|
||||
dataIndex: 'countTime',
|
||||
render: (countTime: number) => (
|
||||
<Typography.Text
|
||||
type={Number(countTime) < 300 ? 'danger' : Number(countTime) < 900 ? 'warning' : undefined}
|
||||
type={
|
||||
Number(countTime) < 300
|
||||
? 'danger'
|
||||
: Number(countTime) < 900
|
||||
? 'warning'
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{formatCountdownSeconds(Number(countTime))}
|
||||
</Typography.Text>
|
||||
@@ -2217,11 +2369,7 @@ function CloudtentaclesPlatformPanel({
|
||||
cancelText="取消"
|
||||
onConfirm={() => void backVn(item)}
|
||||
>
|
||||
<Button
|
||||
danger
|
||||
size="small"
|
||||
loading={vnBackingId === item.id}
|
||||
>
|
||||
<Button danger size="small" loading={vnBackingId === item.id}>
|
||||
退号
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
@@ -2237,7 +2385,9 @@ function CloudtentaclesPlatformPanel({
|
||||
)
|
||||
}
|
||||
|
||||
function formatCloudtentaclesSourceLabel(source: Pick<AdminCloudtentaclesSourceItem, 'label' | 'username' | 'key'>) {
|
||||
function formatCloudtentaclesSourceLabel(
|
||||
source: Pick<AdminCloudtentaclesSourceItem, 'label' | 'username' | 'key'>,
|
||||
) {
|
||||
return String(source.label || source.username || source.key || '').trim() || source.key
|
||||
}
|
||||
|
||||
@@ -2606,8 +2756,20 @@ function AffiliateDashPlatformPanel() {
|
||||
{ title: 'sku', dataIndex: 'sku', key: 'sku', width: 180 },
|
||||
{ title: '名称', dataIndex: 'displayName', key: 'displayName' },
|
||||
{ title: '单价', dataIndex: 'priceAmount', key: 'priceAmount', width: 90 },
|
||||
{ title: '库存', dataIndex: 'stock', key: 'stock', width: 70, render: (v: number) => (v === -1 ? '不限' : v) },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status', width: 80, render: (v: string) => <Tag color={v === 'active' ? 'green' : 'red'}>{v}</Tag> },
|
||||
{
|
||||
title: '库存',
|
||||
dataIndex: 'stock',
|
||||
key: 'stock',
|
||||
width: 70,
|
||||
render: (v: number) => (v === -1 ? '不限' : v),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 80,
|
||||
render: (v: string) => <Tag color={v === 'active' ? 'green' : 'red'}>{v}</Tag>,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
@@ -2615,15 +2777,21 @@ function AffiliateDashPlatformPanel() {
|
||||
title="affiliate-dash 发货平台"
|
||||
extra={
|
||||
<Space>
|
||||
<Button icon={<ReloadOutlined />} onClick={loadWallet}>刷新钱包</Button>
|
||||
<Button icon={<ReloadOutlined />} onClick={() => loadProducts(productPage)}>刷新商品</Button>
|
||||
<Button icon={<ReloadOutlined />} onClick={loadWallet}>
|
||||
刷新钱包
|
||||
</Button>
|
||||
<Button icon={<ReloadOutlined />} onClick={() => loadProducts(productPage)}>
|
||||
刷新商品
|
||||
</Button>
|
||||
<Button type="primary" icon={<SaveOutlined />} loading={saving} onClick={saveConfig}>
|
||||
保存配置
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
{errorMessage ? <Alert type="error" message={errorMessage} showIcon style={{ marginBottom: 16 }} /> : null}
|
||||
{errorMessage ? (
|
||||
<Alert type="error" message={errorMessage} showIcon style={{ marginBottom: 16 }} />
|
||||
) : null}
|
||||
|
||||
<Spin spinning={loading}>
|
||||
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
||||
@@ -2637,25 +2805,53 @@ function AffiliateDashPlatformPanel() {
|
||||
</Space>
|
||||
<Space wrap>
|
||||
<span>BASE_URL</span>
|
||||
<Input style={{ width: 320 }} value={baseUrl} onChange={(e) => setBaseUrl(e.target.value)} placeholder="https://skin.khhao.com" />
|
||||
<Input
|
||||
style={{ width: 320 }}
|
||||
value={baseUrl}
|
||||
onChange={(e) => setBaseUrl(e.target.value)}
|
||||
placeholder="https://skin.khhao.com"
|
||||
/>
|
||||
</Space>
|
||||
<Space wrap>
|
||||
<span>App Key</span>
|
||||
<Input style={{ width: 320 }} value={appKey} onChange={(e) => setAppKey(e.target.value)} placeholder="ak_..." />
|
||||
<Input
|
||||
style={{ width: 320 }}
|
||||
value={appKey}
|
||||
onChange={(e) => setAppKey(e.target.value)}
|
||||
placeholder="ak_..."
|
||||
/>
|
||||
</Space>
|
||||
<Space wrap>
|
||||
<span>App Secret</span>
|
||||
<Input.Password style={{ width: 320 }} value={appSecret} onChange={(e) => setAppSecret(e.target.value)} placeholder="sk_..." />
|
||||
<Input.Password
|
||||
style={{ width: 320 }}
|
||||
value={appSecret}
|
||||
onChange={(e) => setAppSecret(e.target.value)}
|
||||
placeholder="sk_..."
|
||||
/>
|
||||
</Space>
|
||||
<Space wrap>
|
||||
<span>回调 Secret</span>
|
||||
<Input.Password style={{ width: 320 }} value={callbackSecret} onChange={(e) => setCallbackSecret(e.target.value)} placeholder="cb_..." />
|
||||
<Input.Password
|
||||
style={{ width: 320 }}
|
||||
value={callbackSecret}
|
||||
onChange={(e) => setCallbackSecret(e.target.value)}
|
||||
placeholder="cb_..."
|
||||
/>
|
||||
</Space>
|
||||
<Space wrap>
|
||||
<span>超时(ms)</span>
|
||||
<InputNumber min={1000} value={timeoutMs} onChange={(v) => setTimeoutMs(Number(v || 10000))} />
|
||||
<InputNumber
|
||||
min={1000}
|
||||
value={timeoutMs}
|
||||
onChange={(v) => setTimeoutMs(Number(v || 10000))}
|
||||
/>
|
||||
<span>时间容差(s)</span>
|
||||
<InputNumber min={1} value={timestampToleranceSeconds} onChange={(v) => setTimestampToleranceSeconds(Number(v || 300))} />
|
||||
<InputNumber
|
||||
min={1}
|
||||
value={timestampToleranceSeconds}
|
||||
onChange={(v) => setTimestampToleranceSeconds(Number(v || 300))}
|
||||
/>
|
||||
</Space>
|
||||
<Space wrap>
|
||||
<span>回调 URL(展示)</span>
|
||||
@@ -2664,7 +2860,9 @@ function AffiliateDashPlatformPanel() {
|
||||
{wallet ? (
|
||||
<Space wrap>
|
||||
<span>钱包余额</span>
|
||||
<Typography.Text strong>{wallet.availableBalance} {wallet.currency}</Typography.Text>
|
||||
<Typography.Text strong>
|
||||
{wallet.availableBalance} {wallet.currency}
|
||||
</Typography.Text>
|
||||
<span>(冻结 {wallet.frozenBalance})</span>
|
||||
</Space>
|
||||
) : null}
|
||||
@@ -2693,7 +2891,10 @@ function AffiliateDashPlatformPanel() {
|
||||
/>
|
||||
</Space>
|
||||
))}
|
||||
<Button icon={<PlusOutlined />} onClick={() => setSkuRows((rows) => [...rows, { productNo: '', sku: '' }])}>
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setSkuRows((rows) => [...rows, { productNo: '', sku: '' }])}
|
||||
>
|
||||
新增映射
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -2708,7 +2909,9 @@ function AffiliateDashPlatformPanel() {
|
||||
value={matchInput}
|
||||
onChange={(e) => setMatchInput(e.target.value)}
|
||||
/>
|
||||
<Button icon={<SearchOutlined />} onClick={previewMatch}>测试映射</Button>
|
||||
<Button icon={<SearchOutlined />} onClick={previewMatch}>
|
||||
测试映射
|
||||
</Button>
|
||||
{matchResult ? (
|
||||
<Typography.Text type={matchResult.matched ? 'success' : 'warning'}>
|
||||
{matchResult.matched
|
||||
|
||||
Reference in New Issue
Block a user