平台配置 kuaishou-lewan 调试区新增虚拟号列表查看与手动退号

This commit is contained in:
yml2213
2026-08-08 14:30:43 +08:00
parent 6d0534296c
commit 23b3711e64
5 changed files with 177 additions and 1 deletions
@@ -16,6 +16,7 @@ import {
Empty, Empty,
Input, Input,
InputNumber, InputNumber,
Popconfirm,
Select, Select,
Space, Space,
Spin, Spin,
@@ -32,11 +33,13 @@ import { useSearchParams } from 'react-router'
import PageHeader from '@/components/admin/PageHeader' import PageHeader from '@/components/admin/PageHeader'
import { showError, showSuccess } from '@/lib/feedback' import { showError, showSuccess } from '@/lib/feedback'
import { import {
backAdminCloudtentaclesVn,
deleteAdminCloudtentaclesSource, deleteAdminCloudtentaclesSource,
exchangeAdminKuaishouIndustryAuthorizationCode, exchangeAdminKuaishouIndustryAuthorizationCode,
fetchAdminCloudtentaclesAsset, fetchAdminCloudtentaclesAsset,
fetchAdminCloudtentaclesSkuList, fetchAdminCloudtentaclesSkuList,
fetchAdminCloudtentaclesSourceConfig, fetchAdminCloudtentaclesSourceConfig,
fetchAdminCloudtentaclesVnList,
fetchAdminAffiliateDashConfig, fetchAdminAffiliateDashConfig,
fetchAdminAffiliateDashProducts, fetchAdminAffiliateDashProducts,
fetchAdminAffiliateDashWallet, fetchAdminAffiliateDashWallet,
@@ -64,6 +67,7 @@ import type {
AdminCloudtentaclesMonitorAccount, AdminCloudtentaclesMonitorAccount,
AdminCloudtentaclesSourceConfigResponse, AdminCloudtentaclesSourceConfigResponse,
AdminCloudtentaclesSourceItem, AdminCloudtentaclesSourceItem,
AdminCloudtentaclesVnListItem,
AdminAffiliateDashConfig, AdminAffiliateDashConfig,
AdminAffiliateDashConfigResponse, AdminAffiliateDashConfigResponse,
AdminAffiliateDashMatchResult, AdminAffiliateDashMatchResult,
@@ -1748,6 +1752,9 @@ function CloudtentaclesPlatformPanel({
const [selectedSourceKey, setSelectedSourceKey] = useState(config.sources[0]?.key || '') const [selectedSourceKey, setSelectedSourceKey] = useState(config.sources[0]?.key || '')
const [smsCode, setSmsCode] = useState('') const [smsCode, setSmsCode] = useState('')
const [debugResult, setDebugResult] = useState<unknown>(null) const [debugResult, setDebugResult] = useState<unknown>(null)
const [vnItems, setVnItems] = useState<AdminCloudtentaclesVnListItem[] | null>(null)
const [vnLoading, setVnLoading] = useState(false)
const [vnBackingId, setVnBackingId] = useState<number | null>(null)
useEffect(() => { useEffect(() => {
if (!config.sources.some((source) => source.key === selectedSourceKey)) { if (!config.sources.some((source) => source.key === selectedSourceKey)) {
@@ -1822,6 +1829,45 @@ function CloudtentaclesPlatformPanel({
onChange({ ...config, ...next }) onChange({ ...config, ...next })
} }
async function loadVnList() {
if (!selectedSource) {
showError('请先选择一个账号')
return
}
setVnLoading(true)
try {
const response = await fetchAdminCloudtentaclesVnList({
sourceKey: selectedSource.key,
key: '1',
})
setVnItems(response.data.items || [])
} catch (error) {
showError(error instanceof Error ? error.message : '查询虚拟号列表失败')
} finally {
setVnLoading(false)
}
}
async function backVn(item: AdminCloudtentaclesVnListItem) {
if (!selectedSource) {
return
}
setVnBackingId(item.id)
try {
await backAdminCloudtentaclesVn({
sourceKey: selectedSource.key,
key: '1',
id: item.id,
})
showSuccess(`号码 ${item.phone} 已退回`)
await loadVnList()
} catch (error) {
showError(error instanceof Error ? error.message : '退回号码失败')
} finally {
setVnBackingId(null)
}
}
function updateSource(sourceKey: string, patch: Partial<AdminCloudtentaclesSourceItem>) { function updateSource(sourceKey: string, patch: Partial<AdminCloudtentaclesSourceItem>) {
updateConfig({ updateConfig({
sources: config.sources.map((source) => sources: config.sources.map((source) =>
@@ -2104,8 +2150,86 @@ function CloudtentaclesPlatformPanel({
> >
</Button> </Button>
<Button
icon={<SearchOutlined />}
loading={debugLoading === 'vnList' || vnLoading}
onClick={() => void loadVnList()}
>
</Button>
</Space> </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"
size="small"
rowKey="id"
loading={vnLoading}
dataSource={vnItems}
pagination={{ pageSize: 10, showSizeChanger: false }}
columns={[
{
title: '手机号',
dataIndex: 'phone',
render: (phone: string) => phone || '-',
},
{
title: '绑定角色',
dataIndex: 'bindInfo',
render: (bindInfo: AdminCloudtentaclesVnListItem['bindInfo']) => {
const role = resolveVnBindRole(bindInfo)
return role ? (
<span>
<Tag color="blue">{role.name || '-'}</Tag>
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
{role.rid || ''}
</Typography.Text>
</span>
) : (
<Typography.Text type="secondary"></Typography.Text>
)
},
},
{
title: '回收倒计时',
dataIndex: 'countTime',
render: (countTime: number) => (
<Typography.Text
type={Number(countTime) < 300 ? 'danger' : Number(countTime) < 900 ? 'warning' : undefined}
>
{formatCountdownSeconds(Number(countTime))}
</Typography.Text>
),
},
{
title: '状态',
dataIndex: 'status',
render: (status: number) =>
Number(status) === 0 ? <Tag></Tag> : <Tag color="orange"></Tag>,
},
{
title: '操作',
width: 120,
render: (_, item) => (
<Popconfirm
title={`确认退回号码 ${item.phone}`}
okText="确认退回"
cancelText="取消"
onConfirm={() => void backVn(item)}
>
<Button
danger
size="small"
loading={vnBackingId === item.id}
>
退
</Button>
</Popconfirm>
),
},
]}
/>
) : null}
</> </>
)} )}
</Card> </Card>
@@ -2117,6 +2241,36 @@ function formatCloudtentaclesSourceLabel(source: Pick<AdminCloudtentaclesSourceI
return String(source.label || source.username || source.key || '').trim() || source.key return String(source.label || source.username || source.key || '').trim() || source.key
} }
function resolveVnBindRole(bindInfo: AdminCloudtentaclesVnListItem['bindInfo']) {
if (typeof bindInfo === 'object' && bindInfo !== null) {
return {
name: String((bindInfo as Record<string, unknown>).name || '').trim(),
rid: String((bindInfo as Record<string, unknown>).rid || '').trim(),
}
}
if (typeof bindInfo === 'string' && bindInfo.trim()) {
try {
const parsed = JSON.parse(bindInfo) as Record<string, unknown>
if (parsed && typeof parsed === 'object') {
return {
name: String(parsed.name || '').trim(),
rid: String(parsed.rid || '').trim(),
}
}
} catch {
return null
}
}
return null
}
function formatCountdownSeconds(totalSeconds: number) {
const seconds = Math.max(0, Number(totalSeconds) || 0)
const minutes = Math.floor(seconds / 60)
const restSeconds = seconds % 60
return `${String(minutes).padStart(2, '0')}:${String(restSeconds).padStart(2, '0')}`
}
function resolveIndustryAccessTokenStatus( function resolveIndustryAccessTokenStatus(
source: Pick<AdminKuaishouIndustrySourceConfig, 'accessTokenStatus'>, source: Pick<AdminKuaishouIndustrySourceConfig, 'accessTokenStatus'>,
) { ) {
@@ -9,6 +9,7 @@ import type {
AdminCloudtentaclesSourceItem, AdminCloudtentaclesSourceItem,
AdminCloudtentaclesSourcesConfig, AdminCloudtentaclesSourcesConfig,
AdminCloudtentaclesValidateSessionResult, AdminCloudtentaclesValidateSessionResult,
AdminCloudtentaclesVnListResult,
} from '@/types/admin' } from '@/types/admin'
export function fetchAdminCloudtentaclesSourceConfig() { export function fetchAdminCloudtentaclesSourceConfig() {
@@ -223,7 +224,7 @@ export function fetchAdminCloudtentaclesVnList(payload: {
key?: string key?: string
sourceKey?: string sourceKey?: string
}) { }) {
return apiPost<Record<string, unknown>>( return apiPost<AdminCloudtentaclesVnListResult>(
'/api/v1/admin/platform-config/cloudtentacles/vn/list', '/api/v1/admin/platform-config/cloudtentacles/vn/list',
payload, payload,
) )
+2
View File
@@ -93,6 +93,8 @@ export type {
AdminCloudtentaclesValidateSessionResult, AdminCloudtentaclesValidateSessionResult,
AdminCloudtentaclesSkuItem, AdminCloudtentaclesSkuItem,
AdminCloudtentaclesSkuListResult, AdminCloudtentaclesSkuListResult,
AdminCloudtentaclesVnListItem,
AdminCloudtentaclesVnListResult,
AdminCloudtentaclesDeliveryRecordItem, AdminCloudtentaclesDeliveryRecordItem,
AdminCloudtentaclesDeliveryRecordListResult, AdminCloudtentaclesDeliveryRecordListResult,
AdminFulfillmentRoutingConfig, AdminFulfillmentRoutingConfig,
@@ -179,3 +179,20 @@ export interface AdminCloudtentaclesSourceConfig {
deviceId: string deviceId: string
deviceType: number deviceType: number
} }
export interface AdminCloudtentaclesVnListItem {
id: number
phone: string
status: number
countTime: number
bindInfo: string | Record<string, unknown> | null
raw: Record<string, unknown>
}
export interface AdminCloudtentaclesVnListResult {
baseUrl: string
key: string
itemCount: number
items: AdminCloudtentaclesVnListItem[]
rawItems: Array<Record<string, unknown>>
}
@@ -66,6 +66,8 @@ export type {
AdminCloudtentaclesValidateSessionResult, AdminCloudtentaclesValidateSessionResult,
AdminCloudtentaclesSkuItem, AdminCloudtentaclesSkuItem,
AdminCloudtentaclesSkuListResult, AdminCloudtentaclesSkuListResult,
AdminCloudtentaclesVnListItem,
AdminCloudtentaclesVnListResult,
AdminCloudtentaclesDeliveryRecordItem, AdminCloudtentaclesDeliveryRecordItem,
AdminCloudtentaclesDeliveryRecordListResult, AdminCloudtentaclesDeliveryRecordListResult,
} from './cloudtentacles' } from './cloudtentacles'