统一前后端代码格式化配置

This commit is contained in:
yml2213
2026-08-16 17:27:12 +08:00
parent 8705f6a6a1
commit 5c7c3e14e3
278 changed files with 6386 additions and 5500 deletions
@@ -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