平台配置 kuaishou-lewan 调试区新增虚拟号列表查看与手动退号
This commit is contained in:
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
Reference in New Issue
Block a user