diff --git a/apps/frontend/src/pages/admin/platform/AdminPlatformFulfillmentPage.tsx b/apps/frontend/src/pages/admin/platform/AdminPlatformFulfillmentPage.tsx
index 6b65cae4..18f436cc 100644
--- a/apps/frontend/src/pages/admin/platform/AdminPlatformFulfillmentPage.tsx
+++ b/apps/frontend/src/pages/admin/platform/AdminPlatformFulfillmentPage.tsx
@@ -29,28 +29,18 @@ import { useEffect, useMemo, useState } from 'react'
import PageHeader from '@/components/admin/PageHeader'
import { showError, showSuccess } from '@/lib/feedback'
import {
- fetchAdminAffiliateDashConfig,
- fetchAdminAffiliateDashProducts,
- fetchAdminAffiliateDashWallet,
fetchAdminCloudtentaclesOverrideRules,
fetchAdminCloudtentaclesSkuList,
fetchAdminCloudtentaclesSourceConfig,
fetchAdminFulfillmentRoutingConfig,
fetchAdminKuaishouFeifeiConfig,
- matchAdminAffiliateDashSku,
matchAdminKuaishouFeifeiProduct,
previewAdminFulfillmentRouting,
- saveAdminAffiliateDashConfig,
saveAdminCloudtentaclesOverrideRules,
saveAdminFulfillmentRoutingConfig,
syncAdminKuaishouFeifeiProducts,
} from '@/services/admin'
import type {
- AdminAffiliateDashConfig,
- AdminAffiliateDashConfigResponse,
- AdminAffiliateDashMatchResult,
- AdminAffiliateDashProductItem,
- AdminAffiliateDashWallet,
AdminCloudtentaclesOverrideDeliveryItem,
AdminCloudtentaclesOverrideRule,
AdminCloudtentaclesSessionItem,
@@ -113,11 +103,6 @@ export default function AdminPlatformFulfillmentPage() {
label: 'kuaishou-feifei',
children: ,
},
- {
- key: 'affiliate-dash',
- label: 'affiliate-dash',
- children: ,
- },
{
key: 'kuaishou-cloud',
label: 'kuaishou-lewan',
@@ -1332,277 +1317,3 @@ function createRuleId() {
return `rule_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
}
-type SkuMappingRow = { productNo: string; sku: string }
-
-function AffiliateDashFulfillmentPanel() {
- const [loading, setLoading] = useState(true)
- const [saving, setSaving] = useState(false)
- const [errorMessage, setErrorMessage] = useState('')
- const [enabled, setEnabled] = useState(true)
- const [baseUrl, setBaseUrl] = useState('')
- const [appKey, setAppKey] = useState('')
- const [appSecret, setAppSecret] = useState('')
- const [callbackSecret, setCallbackSecret] = useState('')
- const [timeoutMs, setTimeoutMs] = useState(10000)
- const [notifyUrl, setNotifyUrl] = useState('')
- const [timestampToleranceSeconds, setTimestampToleranceSeconds] = useState(300)
- const [skuRows, setSkuRows] = useState([])
- const [matchInput, setMatchInput] = useState('')
- const [matchResult, setMatchResult] = useState(null)
- const [wallet, setWallet] = useState(null)
- const [products, setProducts] = useState([])
- const [productTotal, setProductTotal] = useState(0)
- const [productPage, setProductPage] = useState(1)
- const [productsLoading, setProductsLoading] = useState(false)
-
- const skuMapping = skuRows.reduce>((acc, row) => {
- if (row.productNo.trim() && row.sku.trim()) {
- acc[row.productNo.trim()] = row.sku.trim()
- }
- return acc
- }, {})
-
- useEffect(() => {
- void loadConfig()
- void loadWallet()
- void loadProducts(1)
- }, [])
-
- async function loadConfig() {
- setLoading(true)
- setErrorMessage('')
-
- try {
- const response = await fetchAdminAffiliateDashConfig()
- hydrateConfig(response.data)
- } catch (error) {
- setErrorMessage(error instanceof Error ? error.message : '读取 affiliate-dash 配置失败')
- } finally {
- setLoading(false)
- }
- }
-
- function hydrateConfig(response: AdminAffiliateDashConfigResponse) {
- const source = response.source
- setEnabled(source.enabled !== false)
- setBaseUrl(source.baseUrl || '')
- setAppKey(source.appKey || '')
- setAppSecret(source.appSecret || '')
- setCallbackSecret(source.callbackSecret || '')
- setTimeoutMs(Number(source.timeoutMs || 10000))
- setNotifyUrl(source.notifyUrl || '')
- setTimestampToleranceSeconds(Number(source.timestampToleranceSeconds || 300))
- setSkuRows(
- Object.entries(source.skuMapping || {}).map(([productNo, sku]) => ({
- productNo,
- sku: String(sku || ''),
- })),
- )
- }
-
- async function saveConfig() {
- setSaving(true)
- setErrorMessage('')
-
- try {
- const payload: AdminAffiliateDashConfig = {
- enabled,
- baseUrl,
- appKey,
- appSecret,
- callbackSecret,
- timeoutMs,
- notifyUrl,
- timestampToleranceSeconds,
- skuMapping,
- }
- const response = await saveAdminAffiliateDashConfig(payload)
- hydrateConfig(response.data)
- showSuccess('affiliate-dash 配置已保存')
- } catch (error) {
- const message = error instanceof Error ? error.message : '保存 affiliate-dash 配置失败'
- setErrorMessage(message)
- showError(message)
- } finally {
- setSaving(false)
- }
- }
-
- async function previewMatch() {
- const productNo = matchInput.trim()
- if (!productNo) {
- showError('请输入 91 商品编码(productNo)')
- return
- }
-
- setMatchResult(null)
-
- try {
- const response = await matchAdminAffiliateDashSku(productNo)
- setMatchResult(response.data)
- } catch (error) {
- showError(error instanceof Error ? error.message : '预览 affiliate-dash sku 映射失败')
- }
- }
-
- async function loadWallet() {
- try {
- const response = await fetchAdminAffiliateDashWallet()
- setWallet(response.data)
- } catch (error) {
- showError(error instanceof Error ? error.message : '查询 affiliate-dash 钱包失败')
- }
- }
-
- async function loadProducts(page: number) {
- setProductsLoading(true)
-
- try {
- const response = await fetchAdminAffiliateDashProducts({ page, size: 20 })
- setProducts(response.data.list)
- setProductTotal(response.data.total)
- setProductPage(response.data.page)
- } catch (error) {
- showError(error instanceof Error ? error.message : '拉取 affiliate-dash 商品失败')
- } finally {
- setProductsLoading(false)
- }
- }
-
- function updateSkuRow(index: number, patch: Partial) {
- setSkuRows((rows) => rows.map((row, i) => (i === index ? { ...row, ...patch } : row)))
- }
-
- const productColumns: TableColumnsType = [
- { 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) => {v} },
- ]
-
- return (
-
- } onClick={loadWallet}>刷新钱包
- } onClick={() => loadProducts(productPage)}>刷新商品
- } loading={saving} onClick={saveConfig}>
- 保存配置
-
-
- }
- >
- {errorMessage ? : null}
-
-
-
-
-
-
- 启用
-
-
-
- BASE_URL
- setBaseUrl(e.target.value)} placeholder="https://skin.khhao.com" />
-
-
- App Key
- setAppKey(e.target.value)} placeholder="ak_..." />
-
-
- App Secret
- setAppSecret(e.target.value)} placeholder="sk_..." />
-
-
- 回调 Secret
- setCallbackSecret(e.target.value)} placeholder="cb_..." />
-
-
- 超时(ms)
- setTimeoutMs(Number(v || 10000))} />
- 时间容差(s)
- setTimestampToleranceSeconds(Number(v || 300))} />
-
-
- 回调 URL(展示)
- {notifyUrl || '未配置'}
-
- {wallet ? (
-
- 钱包余额
- {wallet.availableBalance} {wallet.currency}
- (冻结 {wallet.frozenBalance})
-
- ) : null}
-
-
-
-
-
- {skuRows.map((row, index) => (
-
- updateSkuRow(index, { productNo: e.target.value })}
- />
- updateSkuRow(index, { sku: e.target.value })}
- />
- }
- onClick={() => setSkuRows((rows) => rows.filter((_, i) => i !== index))}
- />
-
- ))}
- } onClick={() => setSkuRows((rows) => [...rows, { productNo: '', sku: '' }])}>
- 新增映射
-
-
-
-
-
-
-
- setMatchInput(e.target.value)}
- />
- } onClick={previewMatch}>测试映射
- {matchResult ? (
-
- {matchResult.matched
- ? `命中 sku: ${matchResult.sku}`
- : `未命中(productNo=${matchResult.productNo || '-'})`}
-
- ) : null}
-
-
- rowKey="sku"
- size="small"
- loading={productsLoading}
- columns={productColumns}
- dataSource={products}
- pagination={buildAdminLocalTablePagination({
- current: productPage,
- pageSize: 20,
- total: productTotal,
- onChange: (page) => loadProducts(page),
- })}
- />
-
-
-
-
-
- )
-}
diff --git a/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx b/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx
index 46fe72da..f11a8594 100644
--- a/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx
+++ b/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx
@@ -37,14 +37,19 @@ import {
fetchAdminCloudtentaclesAsset,
fetchAdminCloudtentaclesSkuList,
fetchAdminCloudtentaclesSourceConfig,
+ fetchAdminAffiliateDashConfig,
+ fetchAdminAffiliateDashProducts,
+ fetchAdminAffiliateDashWallet,
fetchAdminKuaishouFeifeiConfig,
fetchAdminKuaishouIndustrySourceConfig,
fetchAdminNotificationConfig,
fetchAdminScheduledJobsConfig,
+ matchAdminAffiliateDashSku,
matchAdminKuaishouFeifeiProduct,
refreshAdminKuaishouIndustryAccessToken,
runAdminScheduledJob,
saveAdminCloudtentaclesSourceConfig,
+ saveAdminAffiliateDashConfig,
saveAdminKuaishouFeifeiConfig,
saveAdminKuaishouIndustrySourceConfig,
saveAdminNotificationConfig,
@@ -59,6 +64,11 @@ import type {
AdminCloudtentaclesMonitorAccount,
AdminCloudtentaclesSourceConfigResponse,
AdminCloudtentaclesSourceItem,
+ AdminAffiliateDashConfig,
+ AdminAffiliateDashConfigResponse,
+ AdminAffiliateDashMatchResult,
+ AdminAffiliateDashProductItem,
+ AdminAffiliateDashWallet,
AdminKuaishouIndustryConfigResponse,
AdminKuaishouIndustryShopConfig,
AdminKuaishouIndustrySourceConfig,
@@ -83,6 +93,7 @@ type PlatformTab =
| 'kuaishouIndustry'
| 'kuaishouFeifei'
| 'cloudtentacles'
+ | 'affiliateDash'
type NotificationConfigResponse = {
filePath: string
@@ -101,6 +112,7 @@ const platformTabs: PlatformTab[] = [
'kuaishouIndustry',
'kuaishouFeifei',
'cloudtentacles',
+ 'affiliateDash',
]
export default function AdminPlatformShopsPage() {
@@ -114,6 +126,8 @@ export default function AdminPlatformShopsPage() {
const [feifeiConfig, setFeifeiConfig] = useState(null)
const [cloudtentaclesConfig, setCloudtentaclesConfig] =
useState(null)
+ const [affiliateDashConfig, setAffiliateDashConfig] =
+ useState(null)
useEffect(() => {
void loadConfigs()
@@ -135,12 +149,14 @@ export default function AdminPlatformShopsPage() {
industryResponse,
feifeiResponse,
cloudtentaclesResponse,
+ affiliateDashResponse,
] = await Promise.all([
fetchAdminNotificationConfig(),
fetchAdminScheduledJobsConfig(),
fetchAdminKuaishouIndustrySourceConfig(),
fetchAdminKuaishouFeifeiConfig(),
fetchAdminCloudtentaclesSourceConfig(),
+ fetchAdminAffiliateDashConfig(),
])
setNotificationConfig(notificationResponse.data)
@@ -148,6 +164,7 @@ export default function AdminPlatformShopsPage() {
setIndustryConfig(industryResponse.data)
setFeifeiConfig(feifeiResponse.data)
setCloudtentaclesConfig(cloudtentaclesResponse.data)
+ setAffiliateDashConfig(affiliateDashResponse.data)
} catch (error) {
setErrorMessage(error instanceof Error ? error.message : '读取平台配置失败')
} finally {
@@ -227,6 +244,11 @@ export default function AdminPlatformShopsPage() {
),
},
+ {
+ key: 'affiliateDash',
+ label: 'affiliate-dash',
+ children: ,
+ },
{
key: 'cloudtentacles',
label: 'kuaishou-lewan',
@@ -2270,3 +2292,278 @@ function normalizePlatformTab(value: unknown): PlatformTab {
? (value as PlatformTab)
: 'notifications'
}
+
+type SkuMappingRow = { productNo: string; sku: string }
+
+function AffiliateDashPlatformPanel() {
+ const [loading, setLoading] = useState(true)
+ const [saving, setSaving] = useState(false)
+ const [errorMessage, setErrorMessage] = useState('')
+ const [enabled, setEnabled] = useState(true)
+ const [baseUrl, setBaseUrl] = useState('')
+ const [appKey, setAppKey] = useState('')
+ const [appSecret, setAppSecret] = useState('')
+ const [callbackSecret, setCallbackSecret] = useState('')
+ const [timeoutMs, setTimeoutMs] = useState(10000)
+ const [notifyUrl, setNotifyUrl] = useState('')
+ const [timestampToleranceSeconds, setTimestampToleranceSeconds] = useState(300)
+ const [skuRows, setSkuRows] = useState([])
+ const [matchInput, setMatchInput] = useState('')
+ const [matchResult, setMatchResult] = useState(null)
+ const [wallet, setWallet] = useState(null)
+ const [products, setProducts] = useState([])
+ const [productTotal, setProductTotal] = useState(0)
+ const [productPage, setProductPage] = useState(1)
+ const [productsLoading, setProductsLoading] = useState(false)
+
+ const skuMapping = skuRows.reduce>((acc, row) => {
+ if (row.productNo.trim() && row.sku.trim()) {
+ acc[row.productNo.trim()] = row.sku.trim()
+ }
+ return acc
+ }, {})
+
+ useEffect(() => {
+ void loadConfig()
+ void loadWallet()
+ void loadProducts(1)
+ }, [])
+
+ async function loadConfig() {
+ setLoading(true)
+ setErrorMessage('')
+
+ try {
+ const response = await fetchAdminAffiliateDashConfig()
+ hydrateConfig(response.data)
+ } catch (error) {
+ setErrorMessage(error instanceof Error ? error.message : '读取 affiliate-dash 配置失败')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ function hydrateConfig(response: AdminAffiliateDashConfigResponse) {
+ const source = response.source
+ setEnabled(source.enabled !== false)
+ setBaseUrl(source.baseUrl || '')
+ setAppKey(source.appKey || '')
+ setAppSecret(source.appSecret || '')
+ setCallbackSecret(source.callbackSecret || '')
+ setTimeoutMs(Number(source.timeoutMs || 10000))
+ setNotifyUrl(source.notifyUrl || '')
+ setTimestampToleranceSeconds(Number(source.timestampToleranceSeconds || 300))
+ setSkuRows(
+ Object.entries(source.skuMapping || {}).map(([productNo, sku]) => ({
+ productNo,
+ sku: String(sku || ''),
+ })),
+ )
+ }
+
+ async function saveConfig() {
+ setSaving(true)
+ setErrorMessage('')
+
+ try {
+ const payload: AdminAffiliateDashConfig = {
+ enabled,
+ baseUrl,
+ appKey,
+ appSecret,
+ callbackSecret,
+ timeoutMs,
+ notifyUrl,
+ timestampToleranceSeconds,
+ skuMapping,
+ }
+ const response = await saveAdminAffiliateDashConfig(payload)
+ hydrateConfig(response.data)
+ showSuccess('affiliate-dash 配置已保存')
+ } catch (error) {
+ const message = error instanceof Error ? error.message : '保存 affiliate-dash 配置失败'
+ setErrorMessage(message)
+ showError(message)
+ } finally {
+ setSaving(false)
+ }
+ }
+
+ async function previewMatch() {
+ const productNo = matchInput.trim()
+ if (!productNo) {
+ showError('请输入 91 商品编码(productNo)')
+ return
+ }
+
+ setMatchResult(null)
+
+ try {
+ const response = await matchAdminAffiliateDashSku(productNo)
+ setMatchResult(response.data)
+ } catch (error) {
+ showError(error instanceof Error ? error.message : '预览 affiliate-dash sku 映射失败')
+ }
+ }
+
+ async function loadWallet() {
+ try {
+ const response = await fetchAdminAffiliateDashWallet()
+ setWallet(response.data)
+ } catch (error) {
+ showError(error instanceof Error ? error.message : '查询 affiliate-dash 钱包失败')
+ }
+ }
+
+ async function loadProducts(page: number) {
+ setProductsLoading(true)
+
+ try {
+ const response = await fetchAdminAffiliateDashProducts({ page, size: 20 })
+ setProducts(response.data.list)
+ setProductTotal(response.data.total)
+ setProductPage(response.data.page)
+ } catch (error) {
+ showError(error instanceof Error ? error.message : '拉取 affiliate-dash 商品失败')
+ } finally {
+ setProductsLoading(false)
+ }
+ }
+
+ function updateSkuRow(index: number, patch: Partial) {
+ setSkuRows((rows) => rows.map((row, i) => (i === index ? { ...row, ...patch } : row)))
+ }
+
+ const productColumns: TableColumnsType = [
+ { 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) => {v} },
+ ]
+
+ return (
+
+ } onClick={loadWallet}>刷新钱包
+ } onClick={() => loadProducts(productPage)}>刷新商品
+ } loading={saving} onClick={saveConfig}>
+ 保存配置
+
+
+ }
+ >
+ {errorMessage ? : null}
+
+
+
+
+
+
+ 启用
+
+
+
+ BASE_URL
+ setBaseUrl(e.target.value)} placeholder="https://skin.khhao.com" />
+
+
+ App Key
+ setAppKey(e.target.value)} placeholder="ak_..." />
+
+
+ App Secret
+ setAppSecret(e.target.value)} placeholder="sk_..." />
+
+
+ 回调 Secret
+ setCallbackSecret(e.target.value)} placeholder="cb_..." />
+
+
+ 超时(ms)
+ setTimeoutMs(Number(v || 10000))} />
+ 时间容差(s)
+ setTimestampToleranceSeconds(Number(v || 300))} />
+
+
+ 回调 URL(展示)
+ {notifyUrl || '未配置'}
+
+ {wallet ? (
+
+ 钱包余额
+ {wallet.availableBalance} {wallet.currency}
+ (冻结 {wallet.frozenBalance})
+
+ ) : null}
+
+
+
+
+
+ {skuRows.map((row, index) => (
+
+ updateSkuRow(index, { productNo: e.target.value })}
+ />
+ updateSkuRow(index, { sku: e.target.value })}
+ />
+ }
+ onClick={() => setSkuRows((rows) => rows.filter((_, i) => i !== index))}
+ />
+
+ ))}
+ } onClick={() => setSkuRows((rows) => [...rows, { productNo: '', sku: '' }])}>
+ 新增映射
+
+
+
+
+
+
+
+ setMatchInput(e.target.value)}
+ />
+ } onClick={previewMatch}>测试映射
+ {matchResult ? (
+
+ {matchResult.matched
+ ? `命中 sku: ${matchResult.sku}`
+ : `未命中(productNo=${matchResult.productNo || '-'})`}
+
+ ) : null}
+
+
+ rowKey="sku"
+ size="small"
+ loading={productsLoading}
+ columns={productColumns}
+ dataSource={products}
+ pagination={buildAdminLocalTablePagination({
+ current: productPage,
+ pageSize: 20,
+ total: productTotal,
+ onChange: (page) => loadProducts(page),
+ })}
+ />
+
+
+
+
+
+ )
+}
diff --git a/docs/ affiliate_dash_发货平台/affiliate-dash对接.md b/docs/ affiliate_dash_发货平台/affiliate-dash对接.md
index 876ee676..eebe417e 100644
--- a/docs/ affiliate_dash_发货平台/affiliate-dash对接.md
+++ b/docs/ affiliate_dash_发货平台/affiliate-dash对接.md
@@ -712,3 +712,22 @@ order_site 处理要求:
| `not_a_real_sku` | (无)回退 | — | — |
**验证**:typecheck 通过;后端全量 224 测试通过(透传纯函数测试 +3)。
+
+---
+
+## 24. affiliate-dash 配置位置重构(v2.9 · 已完成)
+
+**需求**:affiliate_dash 平台配置原先杂糅在「履约配置」页(`AdminPlatformFulfillmentPage` 的 affiliate-dash tab),与履约路由/通道混在一起;项目已有独立的「平台配置」页(`AdminPlatformShopsPage`,按平台管理来源接入、凭据与店铺能力),应归位。
+
+**改动**(纯前端 UI 位置移动,后端与数据通路零变化——后端路由与前端 API 本就挂在 `/api/v1/admin/platform-config/affiliate-dash/*`):
+
+| 文件 | 改动 |
+| --- | --- |
+| `AdminPlatformShopsPage.tsx` | 新增 `affiliateDash` tab(label「affiliate-dash」);`AffiliateDashPlatformPanel` 组件整体迁入(自管理状态:对接配置/密钥/钱包/商品目录/映射表/映射测试),加载函数并入 `loadConfigs` 的 `Promise.all` |
+| `AdminPlatformFulfillmentPage.tsx` | 移除 affiliate-dash tab、`AffiliateDashFulfillmentPanel` 组件及全部 affiliate 相关 import/类型 |
+
+**页面归属**:
+- **平台配置**(`AdminPlatformShopsPage`):affiliate-dash、kuaishou-feifei、kuaishou-lewan、行业电子凭证、内部通知 —— 平台接入凭据
+- **履约配置**(`AdminPlatformFulfillmentPage`):履约路由、kuaishou-feifei 履约、kuaishou-lewan 履约 —— 通道路由与履约参数
+
+**验证**:前端 `tsc -b --noEmit` 通过;后端未改动。