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 ( - - - - - - } - > - {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 })} - /> - - - - - - - - setMatchInput(e.target.value)} - /> - - {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 ( + + + + + + } + > + {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 })} + /> + + + + + + + + setMatchInput(e.target.value)} + /> + + {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` 通过;后端未改动。