收敛半自动平台为登录获取 CK 流程
移除角色绑定对客户页、状态展示和完成门禁的阻塞;虎牙与微博统一保存并直接导出 CK;同步精简客服工作台、配置项和权威文档。
This commit is contained in:
@@ -48,7 +48,6 @@ import {
|
||||
} from '@/services/admin/semi-auto'
|
||||
import type { SemiAutoTask } from '@/types/admin/semi-auto'
|
||||
import type { UploadedFile } from '@/types/worker-platform'
|
||||
import { hasAdminPermission } from '@/utils/admin-auth'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
|
||||
const { Text } = Typography
|
||||
@@ -290,24 +289,19 @@ export default function AdminSemiAutoTasksPage() {
|
||||
}
|
||||
|
||||
function credentialMenu(task: SemiAutoTask) {
|
||||
const canExport = hasAdminPermission('semi_auto.credential.export')
|
||||
return {
|
||||
items: [
|
||||
...(canExport
|
||||
? [
|
||||
{
|
||||
key: 'copy',
|
||||
icon: <KeyOutlined />,
|
||||
label: '复制最终 CK',
|
||||
disabled: !task.credentialReady || credentialExportMutation.isPending,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: 'copy',
|
||||
icon: <KeyOutlined />,
|
||||
label: '复制最终 CK',
|
||||
disabled: !task.credentialReady || credentialExportMutation.isPending,
|
||||
},
|
||||
{
|
||||
key: 'inject',
|
||||
icon: <ChromeOutlined />,
|
||||
label: '注入浏览器',
|
||||
disabled: task.bindingStatus !== 'confirmed' || injectionMutation.isPending,
|
||||
disabled: !taskCanUseCredential(task) || injectionMutation.isPending,
|
||||
},
|
||||
],
|
||||
onClick: ({ key }: { key: string }) => {
|
||||
@@ -438,20 +432,19 @@ export default function AdminSemiAutoTasksPage() {
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '授权 / 绑定角色',
|
||||
key: 'binding',
|
||||
title: '账号凭证',
|
||||
key: 'credential',
|
||||
width: 190,
|
||||
render: (_, task) =>
|
||||
task.bindGameRole ? (
|
||||
<Space direction="vertical" size={2}>
|
||||
<Text strong>{task.bindGameRole}</Text>
|
||||
<Text type="secondary">
|
||||
{[task.bindGameAccount, task.bindGameChannel].filter(Boolean).join(' · ') || '-'}
|
||||
</Text>
|
||||
</Space>
|
||||
) : (
|
||||
<Text type="secondary">{task.platform === 'weibo' ? '未授权' : '未绑定'}</Text>
|
||||
),
|
||||
render: (_, task) => (
|
||||
<Space direction="vertical" size={2}>
|
||||
<Text strong>{task.platformNickname || task.huyaUid || '未获取账号'}</Text>
|
||||
<Text type="secondary">
|
||||
{task.credentialSource
|
||||
? `${task.credentialSource} · ${formatAdminDateTime(task.credentialUpdatedAt)}`
|
||||
: '尚无 CK'}
|
||||
</Text>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '客服',
|
||||
@@ -497,7 +490,7 @@ export default function AdminSemiAutoTasksPage() {
|
||||
size="small"
|
||||
type="primary"
|
||||
icon={<CheckCircleOutlined />}
|
||||
disabled={task.bindingStatus !== 'confirmed'}
|
||||
disabled={!taskCanUseCredential(task)}
|
||||
onClick={() => openComplete(task)}
|
||||
>
|
||||
完成
|
||||
@@ -649,23 +642,12 @@ export default function AdminSemiAutoTasksPage() {
|
||||
</Text>
|
||||
</Space>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={detail.task.platform === 'weibo' ? '微博昵称' : '游戏账号'}>
|
||||
{detail.task.bindGameAccount || '-'}
|
||||
<Descriptions.Item label="平台昵称">
|
||||
{detail.task.platformNickname || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="登录状态">
|
||||
{detail.task.loginStatus === 'authenticated' ? 'CK 已获取' : '等待客户登录'}
|
||||
</Descriptions.Item>
|
||||
{detail.task.platform === 'weibo' ? (
|
||||
<Descriptions.Item label="授权状态">
|
||||
{detail.task.bindingStatus === 'confirmed' ? '登录即授权' : '未授权'}
|
||||
</Descriptions.Item>
|
||||
) : (
|
||||
<Descriptions.Item label="游戏角色">
|
||||
{detail.task.bindGameRole || '-'}
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
{detail.task.platform !== 'weibo' ? (
|
||||
<Descriptions.Item label="区服" span={2}>
|
||||
{detail.task.bindGameChannel || '-'}
|
||||
</Descriptions.Item>
|
||||
) : null}
|
||||
<Descriptions.Item label="客服">
|
||||
{detail.task.assignedAdminName || '未领取'}
|
||||
</Descriptions.Item>
|
||||
@@ -800,3 +782,12 @@ function taskNeedsCustomerReauth(task: SemiAutoTask) {
|
||||
task.loginStatus === 'expired'
|
||||
)
|
||||
}
|
||||
|
||||
function taskCanUseCredential(task: SemiAutoTask) {
|
||||
return (
|
||||
task.loginStatus === 'authenticated' &&
|
||||
task.credentialReady &&
|
||||
task.credentialStatus !== 'invalid' &&
|
||||
task.credentialStatus !== 'error'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -820,7 +820,7 @@ function resolveTaskListBlocker(item: AdminTaskListItem): TaskListBlocker {
|
||||
level: 'normal',
|
||||
detail: item.roleConfirmedAt
|
||||
? '客户已提交角色信息,等待平台履约收口。'
|
||||
: '等待客户打开链接、绑定或提交信息。',
|
||||
: '等待客户授权、绑定或提交信息。',
|
||||
showDetail: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,6 @@ type ConfigFormValues = {
|
||||
pageTitle?: string
|
||||
pageDescription?: string
|
||||
loginInstructions?: string
|
||||
bindingInstructions?: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -694,9 +693,6 @@ export function ManualSkuConfigPanel() {
|
||||
<Form.Item name={['semiAutoConfig', 'loginInstructions']} label="登录指引">
|
||||
<Input.TextArea rows={2} maxLength={1000} showCount />
|
||||
</Form.Item>
|
||||
<Form.Item name={['semiAutoConfig', 'bindingInstructions']} label="绑定指引">
|
||||
<Input.TextArea rows={2} maxLength={1000} showCount />
|
||||
</Form.Item>
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
|
||||
@@ -5,44 +5,27 @@ import {
|
||||
ReloadOutlined,
|
||||
SafetyCertificateOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { App, Button, Image, Input, Select, Space, Spin, Tabs } from 'antd'
|
||||
import { App, Button, Image, Input, Spin, Tabs } from 'antd'
|
||||
import QRCode from 'qrcode'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react'
|
||||
import { useParams } from 'react-router'
|
||||
|
||||
import {
|
||||
confirmCustomerBinding,
|
||||
fetchCustomerFulfillment,
|
||||
fetchCustomerWeiboCountryCodes,
|
||||
loginCustomerWeiboSms,
|
||||
markCustomerFulfillmentOpened,
|
||||
pollCustomerBinding,
|
||||
pollCustomerLogin,
|
||||
pollCustomerWeiboQrLogin,
|
||||
sendCustomerWeiboSmsCode,
|
||||
startCustomerBinding,
|
||||
startCustomerLogin,
|
||||
startCustomerWeiboQrLogin,
|
||||
} from '@/services/customer-fulfillment'
|
||||
import type { CustomerWeiboCountryCode } from '@/services/customer-fulfillment'
|
||||
import type {
|
||||
CustomerBindingInfo,
|
||||
CustomerLoginInfo,
|
||||
CustomerSemiAutoTask,
|
||||
} from '@/types/customer-fulfillment'
|
||||
import type { CustomerLoginInfo, CustomerSemiAutoTask } from '@/types/customer-fulfillment'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
import { runWeiboYidunChallenge } from '@/utils/weibo-yidun'
|
||||
|
||||
const POLL_INTERVAL_MS = 2000
|
||||
|
||||
const DEFAULT_WEIBO_COUNTRY_CODES: CustomerWeiboCountryCode[] = [
|
||||
{ code: '86', name: '中国' },
|
||||
{ code: '852', name: '中国香港' },
|
||||
{ code: '886', name: '中国台湾' },
|
||||
{ code: '853', name: '中国澳门' },
|
||||
{ code: '81', name: '日本' },
|
||||
]
|
||||
|
||||
export default function CustomerFulfillmentPage() {
|
||||
const { message } = App.useApp()
|
||||
const { orderNo: routeOrderNo } = useParams<{ orderNo?: string }>()
|
||||
@@ -52,22 +35,14 @@ export default function CustomerFulfillmentPage() {
|
||||
const [tasks, setTasks] = useState<CustomerSemiAutoTask[]>([])
|
||||
const [selectedTaskId, setSelectedTaskId] = useState(0)
|
||||
const [loginInfo, setLoginInfo] = useState<CustomerLoginInfo | null>(null)
|
||||
const [bindingInfo, setBindingInfo] = useState<CustomerBindingInfo | null>(null)
|
||||
const [loginQrImage, setLoginQrImage] = useState('')
|
||||
const [bindingActionLoading, setBindingActionLoading] = useState(false)
|
||||
const [loginMode, setLoginMode] = useState<'sms' | 'qr'>('sms')
|
||||
const [mobile, setMobile] = useState('')
|
||||
const [countryCode, setCountryCode] = useState('86')
|
||||
const [countryCodes, setCountryCodes] = useState<CustomerWeiboCountryCode[]>(
|
||||
DEFAULT_WEIBO_COUNTRY_CODES,
|
||||
)
|
||||
const [smsCode, setSmsCode] = useState('')
|
||||
const [smsSending, setSmsSending] = useState(false)
|
||||
const [smsLogging, setSmsLogging] = useState(false)
|
||||
const [smsCountdown, setSmsCountdown] = useState(0)
|
||||
const [smsMfaRequired, setSmsMfaRequired] = useState(false)
|
||||
const startingRef = useRef(false)
|
||||
const hydratingBindingRef = useRef(false)
|
||||
|
||||
const selectedTask = useMemo(
|
||||
() => tasks.find((task) => task.id === selectedTaskId) || tasks[0] || null,
|
||||
@@ -131,27 +106,11 @@ export default function CustomerFulfillmentPage() {
|
||||
if (!selectedTask) return
|
||||
if (!['waiting_login', 'failed', 'expired'].includes(selectedTask.loginStatus)) return
|
||||
setLoginInfo(null)
|
||||
setBindingInfo(null)
|
||||
setSmsCode('')
|
||||
setSmsMfaRequired(false)
|
||||
setSmsCountdown(0)
|
||||
}, [selectedTask?.id, selectedTask?.loginStatus])
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedTask || selectedTask.platform !== 'weibo' || !orderNo) return
|
||||
let cancelled = false
|
||||
void fetchCustomerWeiboCountryCodes(orderNo, selectedTask.id)
|
||||
.then((response) => {
|
||||
if (cancelled) return
|
||||
const list = response.data?.countryCodes || []
|
||||
if (list.length > 0) setCountryCodes(list)
|
||||
})
|
||||
.catch(() => undefined)
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [orderNo, selectedTask?.id, selectedTask?.platform])
|
||||
|
||||
useEffect(() => {
|
||||
if (loginInfo?.qrImage) {
|
||||
setLoginQrImage(loginInfo.qrImage)
|
||||
@@ -199,86 +158,6 @@ export default function CustomerFulfillmentPage() {
|
||||
return () => window.clearInterval(timer)
|
||||
}, [orderNo, selectedTask?.id, selectedTask?.loginStatus, selectedTask?.platform])
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedTask || !orderNo || selectedTask.platform === 'weibo') return
|
||||
if (
|
||||
selectedTask.loginStatus !== 'authenticated' ||
|
||||
selectedTask.bindingStatus === 'confirmed'
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (selectedTask.bindingStatus === 'not_started' && !startingRef.current) {
|
||||
startingRef.current = true
|
||||
void startCustomerBinding(orderNo, selectedTask.id)
|
||||
.then((response) => {
|
||||
setTasks((current) =>
|
||||
current.map((task) => (task.id === response.data.task.id ? response.data.task : task)),
|
||||
)
|
||||
setBindingInfo(response.data.binding)
|
||||
})
|
||||
.catch((error) => {
|
||||
message.error(error instanceof Error ? error.message : '创建绑定二维码失败')
|
||||
})
|
||||
.finally(() => {
|
||||
startingRef.current = false
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!['qr_pending', 'scanned'].includes(selectedTask.bindingStatus)) return
|
||||
const tick = async () => {
|
||||
try {
|
||||
const response = await pollCustomerBinding(orderNo, selectedTask.id)
|
||||
if (response.data.task) {
|
||||
setTasks((current) =>
|
||||
current.map((task) => (task.id === response.data.task.id ? response.data.task : task)),
|
||||
)
|
||||
}
|
||||
if (response.data.binding) setBindingInfo(response.data.binding)
|
||||
} catch {
|
||||
// 同上,下一轮继续。
|
||||
}
|
||||
}
|
||||
const timer = window.setInterval(() => {
|
||||
void tick()
|
||||
}, POLL_INTERVAL_MS)
|
||||
return () => window.clearInterval(timer)
|
||||
}, [message, orderNo, selectedTask?.bindingStatus, selectedTask?.id, selectedTask?.loginStatus])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!selectedTask ||
|
||||
!orderNo ||
|
||||
selectedTask.platform === 'weibo' ||
|
||||
bindingInfo ||
|
||||
hydratingBindingRef.current
|
||||
)
|
||||
return
|
||||
if (
|
||||
selectedTask.loginStatus !== 'authenticated' ||
|
||||
selectedTask.bindingStatus !== 'role_ready'
|
||||
) {
|
||||
return
|
||||
}
|
||||
hydratingBindingRef.current = true
|
||||
void pollCustomerBinding(orderNo, selectedTask.id)
|
||||
.then((response) => {
|
||||
setTasks((current) =>
|
||||
current.map((task) => (task.id === response.data.task.id ? response.data.task : task)),
|
||||
)
|
||||
if (response.data.binding) setBindingInfo(response.data.binding)
|
||||
})
|
||||
.catch(() => undefined)
|
||||
.finally(() => {
|
||||
hydratingBindingRef.current = false
|
||||
})
|
||||
}, [
|
||||
bindingInfo,
|
||||
orderNo,
|
||||
selectedTask?.bindingStatus,
|
||||
selectedTask?.id,
|
||||
selectedTask?.loginStatus,
|
||||
])
|
||||
|
||||
async function handleStartLogin() {
|
||||
if (!selectedTask) return
|
||||
setLoading(true)
|
||||
@@ -291,7 +170,6 @@ export default function CustomerFulfillmentPage() {
|
||||
current.map((task) => (task.id === response.data.task.id ? response.data.task : task)),
|
||||
)
|
||||
setLoginInfo(response.data.login)
|
||||
setBindingInfo(null)
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '创建登录二维码失败')
|
||||
} finally {
|
||||
@@ -302,13 +180,13 @@ export default function CustomerFulfillmentPage() {
|
||||
async function handleSendWeiboSms() {
|
||||
if (!selectedTask) return
|
||||
const phone = mobile.trim()
|
||||
if (!isValidWeiboMobile(countryCode, phone)) {
|
||||
if (!isValidWeiboMobile(phone)) {
|
||||
message.warning('请输入正确的手机号')
|
||||
return
|
||||
}
|
||||
setSmsSending(true)
|
||||
try {
|
||||
let response = await sendCustomerWeiboSmsCode(orderNo, selectedTask.id, phone, countryCode)
|
||||
let response = await sendCustomerWeiboSmsCode(orderNo, selectedTask.id, phone, '86')
|
||||
if (response.data.mfaRequired) {
|
||||
setSmsMfaRequired(true)
|
||||
if (!response.data.mfaId || !response.data.captchaId) {
|
||||
@@ -319,7 +197,7 @@ export default function CustomerFulfillmentPage() {
|
||||
orderNo,
|
||||
selectedTask.id,
|
||||
phone,
|
||||
countryCode,
|
||||
'86',
|
||||
response.data.mfaId,
|
||||
)
|
||||
if (response.data.mfaRequired) {
|
||||
@@ -339,7 +217,7 @@ export default function CustomerFulfillmentPage() {
|
||||
async function handleLoginWeiboSms() {
|
||||
if (!selectedTask) return
|
||||
const phone = mobile.trim()
|
||||
if (!isValidWeiboMobile(countryCode, phone)) {
|
||||
if (!isValidWeiboMobile(phone)) {
|
||||
message.warning('请输入正确的手机号')
|
||||
return
|
||||
}
|
||||
@@ -352,13 +230,12 @@ export default function CustomerFulfillmentPage() {
|
||||
const response = await loginCustomerWeiboSms(orderNo, selectedTask.id, {
|
||||
mobile: phone,
|
||||
code: smsCode.trim(),
|
||||
countryCode,
|
||||
countryCode: '86',
|
||||
})
|
||||
setTasks((current) =>
|
||||
current.map((task) => (task.id === response.data.task.id ? response.data.task : task)),
|
||||
)
|
||||
setLoginInfo(response.data.login)
|
||||
setBindingInfo(null)
|
||||
message.success('微博登录成功,等待客服处理')
|
||||
} catch (error) {
|
||||
const errorCode = (error as { errorCode?: string } | null)?.errorCode || ''
|
||||
@@ -373,57 +250,11 @@ export default function CustomerFulfillmentPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleConfirmBinding() {
|
||||
if (!selectedTask) return
|
||||
setBindingActionLoading(true)
|
||||
try {
|
||||
const response = await confirmCustomerBinding(orderNo, selectedTask.id)
|
||||
setTasks((current) =>
|
||||
current.map((task) => (task.id === response.data.task.id ? response.data.task : task)),
|
||||
)
|
||||
setBindingInfo(response.data.binding)
|
||||
message.success('绑定已确认,等待客服处理')
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '确认绑定失败')
|
||||
} finally {
|
||||
setBindingActionLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStartRebind() {
|
||||
if (!selectedTask) return
|
||||
setBindingActionLoading(true)
|
||||
try {
|
||||
const response = await startCustomerBinding(orderNo, selectedTask.id, {
|
||||
forceRebind: true,
|
||||
})
|
||||
setTasks((current) =>
|
||||
current.map((task) => (task.id === response.data.task.id ? response.data.task : task)),
|
||||
)
|
||||
setBindingInfo(response.data.binding)
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '创建换绑二维码失败')
|
||||
} finally {
|
||||
setBindingActionLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const config = selectedTask?.semiAutoConfig || {}
|
||||
const title = config.pageTitle || (isWeibo ? '微博账号授权' : '账号授权与角色绑定')
|
||||
const displayTitle = isWeibo ? '微博账号登录授权' : title
|
||||
const loginQrVisible =
|
||||
selectedTask && ['qr_pending', 'scanned'].includes(selectedTask.loginStatus)
|
||||
const bindingQrVisible =
|
||||
selectedTask?.platform !== 'weibo' &&
|
||||
selectedTask?.loginStatus === 'authenticated' &&
|
||||
['qr_pending', 'scanned', 'role_ready'].includes(selectedTask.bindingStatus)
|
||||
const bindingQrPending = ['qr_pending', 'scanned'].includes(selectedTask?.bindingStatus || '')
|
||||
const bindingRole = bindingInfo?.bindGameRole || selectedTask?.bindGameRole || ''
|
||||
const bindingAccount = bindingInfo?.bindGameAccount || selectedTask?.bindGameAccount || ''
|
||||
const bindingChannel = bindingInfo?.bindGameChannel || selectedTask?.bindGameChannel || ''
|
||||
const roleChoiceRequired =
|
||||
selectedTask?.bindingStatus === 'role_ready' &&
|
||||
bindingInfo?.hasExistingBinding === true &&
|
||||
bindingInfo?.rebindRequested !== true
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -452,7 +283,7 @@ export default function CustomerFulfillmentPage() {
|
||||
<CustomerAuthFrame
|
||||
platform={selectedTask.platform}
|
||||
eyebrow={isWeibo ? '微博账号授权' : '虎牙账号授权'}
|
||||
title={title}
|
||||
title={displayTitle}
|
||||
>
|
||||
<CustomerTaskFacts task={selectedTask} />
|
||||
<div className="customer-auth-state is-success">
|
||||
@@ -481,7 +312,7 @@ export default function CustomerFulfillmentPage() {
|
||||
<CustomerAuthFrame
|
||||
platform={selectedTask?.platform}
|
||||
eyebrow={isWeibo ? '微博账号授权' : '虎牙账号授权'}
|
||||
title={title}
|
||||
title={displayTitle}
|
||||
>
|
||||
<div className="customer-auth-content-stack">
|
||||
{tasks.length > 1 ? (
|
||||
@@ -495,7 +326,6 @@ export default function CustomerFulfillmentPage() {
|
||||
onClick={() => {
|
||||
setSelectedTaskId(task.id)
|
||||
setLoginInfo(null)
|
||||
setBindingInfo(null)
|
||||
}}
|
||||
>
|
||||
{task.productName || task.skuName} × {task.quantity}
|
||||
@@ -511,20 +341,6 @@ export default function CustomerFulfillmentPage() {
|
||||
selectedTask?.loginStatus === 'failed' ||
|
||||
selectedTask?.loginStatus === 'expired' ? (
|
||||
<section className="customer-auth-section">
|
||||
<div className="customer-auth-section-heading">
|
||||
<span className="customer-auth-section-icon">
|
||||
<QrcodeOutlined />
|
||||
</span>
|
||||
<div>
|
||||
<h2>{isWeibo ? '登录微博账号' : '登录平台账号'}</h2>
|
||||
<p>
|
||||
{config.loginInstructions ||
|
||||
(isWeibo
|
||||
? '使用微博绑定手机号接收验证码,或使用微博 App 扫码登录。'
|
||||
: '使用虎牙 App 扫码登录,请确认扫码账号属于本人。')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{isWeibo ? (
|
||||
<Tabs
|
||||
className="customer-auth-tabs"
|
||||
@@ -536,46 +352,23 @@ export default function CustomerFulfillmentPage() {
|
||||
label: '手机号登录',
|
||||
children: (
|
||||
<div className="customer-auth-form">
|
||||
<div className="customer-auth-inline-input">
|
||||
<Select
|
||||
value={countryCode}
|
||||
onChange={(value) => setCountryCode(String(value))}
|
||||
className="customer-auth-country-code"
|
||||
options={countryCodes.map((item) => ({
|
||||
value: item.code,
|
||||
label: `+${item.code}`,
|
||||
name: item.name,
|
||||
}))}
|
||||
optionRender={(option) => (
|
||||
<Space size={6}>
|
||||
<span>{(option.data as { name?: string }).name}</span>
|
||||
<span className="customer-auth-option-hint">
|
||||
+{option.data.value}
|
||||
</span>
|
||||
</Space>
|
||||
)}
|
||||
filterOption={(input, option) =>
|
||||
String(
|
||||
(option as { name?: string } | undefined)?.name || '',
|
||||
).includes(input) || String(option?.value || '').includes(input)
|
||||
}
|
||||
showSearch
|
||||
popupMatchSelectWidth={240}
|
||||
/>
|
||||
<div className="customer-auth-field customer-auth-phone-field">
|
||||
<span className="customer-auth-country-prefix">+86</span>
|
||||
<Input
|
||||
value={mobile}
|
||||
onChange={(event) => setMobile(event.target.value)}
|
||||
placeholder="请输入微博绑定手机号"
|
||||
maxLength={countryCode === '86' ? 11 : 15}
|
||||
maxLength={11}
|
||||
inputMode="numeric"
|
||||
/>
|
||||
<Button
|
||||
className="customer-auth-send-code"
|
||||
type="primary"
|
||||
loading={smsSending}
|
||||
disabled={smsCountdown > 0}
|
||||
onClick={() => void handleSendWeiboSms()}
|
||||
>
|
||||
{smsCountdown > 0 ? `${smsCountdown}s 后重发` : '发送验证码'}
|
||||
{smsCountdown > 0 ? `${smsCountdown}s` : '发送验证码'}
|
||||
</Button>
|
||||
</div>
|
||||
{smsMfaRequired ? (
|
||||
@@ -583,16 +376,18 @@ export default function CustomerFulfillmentPage() {
|
||||
请在安全验证弹窗中完成验证,通过后会继续发送短信验证码。
|
||||
</div>
|
||||
) : null}
|
||||
<div className="customer-auth-inline-input is-code">
|
||||
<div className="customer-auth-field customer-auth-code-field">
|
||||
<Input
|
||||
value={smsCode}
|
||||
onChange={(event) => setSmsCode(event.target.value)}
|
||||
placeholder="请输入短信验证码"
|
||||
maxLength={8}
|
||||
prefix={<SafetyCertificateOutlined />}
|
||||
onPressEnter={() => void handleLoginWeiboSms()}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
className="customer-auth-code-submit"
|
||||
loading={smsLogging}
|
||||
onClick={() => void handleLoginWeiboSms()}
|
||||
>
|
||||
@@ -628,6 +423,9 @@ export default function CustomerFulfillmentPage() {
|
||||
/>
|
||||
) : (
|
||||
<div className="customer-auth-form">
|
||||
<p className="customer-auth-form-copy">
|
||||
{config.loginInstructions || '使用虎牙 App 扫码登录,请确认扫码账号属于本人。'}
|
||||
</p>
|
||||
<Button type="primary" size="large" block onClick={() => void handleStartLogin()}>
|
||||
获取登录二维码
|
||||
</Button>
|
||||
@@ -679,90 +477,14 @@ export default function CustomerFulfillmentPage() {
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{bindingQrVisible ? (
|
||||
<section className="customer-auth-section">
|
||||
<div className="customer-auth-section-heading">
|
||||
<span className="customer-auth-section-icon">
|
||||
<SafetyCertificateOutlined />
|
||||
</span>
|
||||
<div>
|
||||
<h2>
|
||||
{selectedTask.bindingStatus === 'role_ready' ? '确认绑定角色' : '扫码绑定角色'}
|
||||
</h2>
|
||||
<p>
|
||||
{selectedTask.bindingStatus === 'role_ready'
|
||||
? '当前账号已有绑定角色,请确认是否继续使用;如需更换,请重新扫码换绑。'
|
||||
: '请使用虎牙 App 扫码完成游戏角色绑定。'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="customer-auth-qr">
|
||||
{bindingQrPending && bindingInfo?.qrImage ? (
|
||||
<div className="customer-auth-qr-image">
|
||||
<Image src={bindingInfo.qrImage} alt="游戏角色绑定二维码" width={232} />
|
||||
</div>
|
||||
) : bindingQrPending ? (
|
||||
<div className="customer-auth-qr-placeholder">
|
||||
<Spin />
|
||||
<span>正在生成绑定二维码...</span>
|
||||
</div>
|
||||
) : null}
|
||||
<p>
|
||||
{bindingInfo?.message ||
|
||||
config.bindingInstructions ||
|
||||
'扫码完成后,请回到本页面确认角色。'}
|
||||
</p>
|
||||
{bindingRole ? (
|
||||
<div className="customer-auth-role-card">
|
||||
<span>{roleChoiceRequired ? '当前已绑定角色' : '已识别角色'}</span>
|
||||
<strong>{bindingRole}</strong>
|
||||
<small>{[bindingAccount, bindingChannel].filter(Boolean).join(' · ')}</small>
|
||||
</div>
|
||||
) : null}
|
||||
{selectedTask.bindingStatus === 'role_ready' ? (
|
||||
<div className="customer-auth-button-stack">
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
block
|
||||
loading={bindingActionLoading}
|
||||
onClick={() => void handleConfirmBinding()}
|
||||
>
|
||||
{roleChoiceRequired
|
||||
? '使用当前角色'
|
||||
: bindingInfo?.rebindRequested
|
||||
? '确认换绑角色'
|
||||
: '确认绑定角色'}
|
||||
</Button>
|
||||
{roleChoiceRequired ? (
|
||||
<Button
|
||||
className="customer-auth-secondary-button"
|
||||
size="large"
|
||||
block
|
||||
loading={bindingActionLoading}
|
||||
onClick={() => void handleStartRebind()}
|
||||
>
|
||||
换绑其他角色
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{selectedTask?.bindingStatus === 'confirmed' &&
|
||||
{selectedTask?.loginStatus === 'authenticated' &&
|
||||
selectedTask.executionStatus !== 'completed' ? (
|
||||
<div className="customer-auth-state is-waiting">
|
||||
<div className="customer-auth-state-icon">
|
||||
<LoadingOutlined spin />
|
||||
</div>
|
||||
<h2>{isWeibo ? '微博账号已授权' : '账号角色已绑定'}</h2>
|
||||
<p>
|
||||
{isWeibo
|
||||
? '客服正在使用本次授权完成微博发帖,页面会自动更新。'
|
||||
: '客服正在处理本订单,页面会自动更新。'}
|
||||
</p>
|
||||
<h2>客服正在安排</h2>
|
||||
<p>请耐心等待</p>
|
||||
<Button
|
||||
className="customer-auth-secondary-button"
|
||||
icon={<ReloadOutlined />}
|
||||
@@ -792,16 +514,19 @@ function CustomerAuthFrame(input: {
|
||||
<div className="customer-auth-shell">
|
||||
<header className="customer-auth-hero">
|
||||
<div className="customer-auth-brand">
|
||||
<span className="customer-auth-logo">
|
||||
<QrcodeOutlined />
|
||||
</span>
|
||||
<div>
|
||||
<span className="customer-auth-eyebrow">{input.eyebrow}</span>
|
||||
{input.platform !== 'weibo' ? (
|
||||
<span className="customer-auth-eyebrow">{input.eyebrow}</span>
|
||||
) : null}
|
||||
<h1>{input.title}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div className="customer-auth-warning">温馨提示:请使用本人账号完成授权</div>
|
||||
<div className="customer-auth-warning">
|
||||
{input.platform === 'weibo'
|
||||
? '温馨提示:未成年人禁止下单'
|
||||
: '温馨提示:请使用本人账号完成授权'}
|
||||
</div>
|
||||
<main className="customer-auth-main">{input.children}</main>
|
||||
</div>
|
||||
</div>
|
||||
@@ -814,9 +539,10 @@ function CustomerTaskFacts(input: { task: CustomerSemiAutoTask | null }) {
|
||||
const statusTone =
|
||||
task.displayStatus.key === 'credential_expired' || task.displayStatus.key === 'failed'
|
||||
? 'is-danger'
|
||||
: task.bindingStatus === 'confirmed'
|
||||
: task.loginStatus === 'authenticated'
|
||||
? 'is-success'
|
||||
: 'is-pending'
|
||||
const showAccountDetails = ['authenticated', 'scanned'].includes(task.loginStatus)
|
||||
return (
|
||||
<div className="customer-auth-facts">
|
||||
<div>
|
||||
@@ -833,30 +559,18 @@ function CustomerTaskFacts(input: { task: CustomerSemiAutoTask | null }) {
|
||||
<span>平台</span>
|
||||
<strong>{task.platformLabel || task.platform}</strong>
|
||||
</div>
|
||||
{task.huyaUid ? (
|
||||
{showAccountDetails && task.huyaUid ? (
|
||||
<div>
|
||||
<span>平台账号 ID</span>
|
||||
<strong>{task.huyaUid}</strong>
|
||||
</div>
|
||||
) : null}
|
||||
{task.platform === 'weibo' && task.platformNickname ? (
|
||||
{showAccountDetails && task.platform === 'weibo' && task.platformNickname ? (
|
||||
<div>
|
||||
<span>账号昵称</span>
|
||||
<strong>{task.platformNickname}</strong>
|
||||
</div>
|
||||
) : null}
|
||||
{task.platform === 'huya' && task.bindGameRole ? (
|
||||
<div>
|
||||
<span>绑定角色</span>
|
||||
<strong>{task.bindGameRole}</strong>
|
||||
</div>
|
||||
) : null}
|
||||
{task.platform === 'huya' && task.bindGameChannel ? (
|
||||
<div>
|
||||
<span>区服 / 渠道</span>
|
||||
<strong>{task.bindGameChannel}</strong>
|
||||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
<span>状态</span>
|
||||
<strong className={`customer-auth-status-pill ${statusTone}`}>
|
||||
@@ -875,9 +589,8 @@ function CustomerPrivacyNote() {
|
||||
)
|
||||
}
|
||||
|
||||
function isValidWeiboMobile(countryCode: string, value: string) {
|
||||
function isValidWeiboMobile(value: string) {
|
||||
const digits = value.replace(/\D/g, '')
|
||||
if (!digits) return false
|
||||
if (countryCode === '86') return /^1\d{10}$/.test(digits)
|
||||
return digits.length >= 5 && digits.length <= 15
|
||||
return /^1\d{10}$/.test(digits)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { apiGet, apiPost } from '@/lib/http'
|
||||
import type {
|
||||
CustomerBindingInfo,
|
||||
CustomerLoginInfo,
|
||||
CustomerSemiAutoTask,
|
||||
} from '@/types/customer-fulfillment'
|
||||
import type { CustomerLoginInfo, CustomerSemiAutoTask } from '@/types/customer-fulfillment'
|
||||
|
||||
export function fetchCustomerFulfillment(orderNo: string) {
|
||||
return apiGet<CustomerSemiAutoTask[]>(
|
||||
@@ -31,30 +27,6 @@ export function pollCustomerLogin(orderNo: string, taskId: number) {
|
||||
)
|
||||
}
|
||||
|
||||
export function startCustomerBinding(
|
||||
orderNo: string,
|
||||
taskId: number,
|
||||
options: { forceRebind?: boolean } = {},
|
||||
) {
|
||||
return apiPost<{ task: CustomerSemiAutoTask; binding: CustomerBindingInfo }>(
|
||||
`/api/v1/customer-fulfillment/${encodeURIComponent(orderNo)}/tasks/${taskId}/binding/start`,
|
||||
options.forceRebind ? { forceRebind: true } : {},
|
||||
)
|
||||
}
|
||||
|
||||
export function pollCustomerBinding(orderNo: string, taskId: number) {
|
||||
return apiGet<{ task: CustomerSemiAutoTask; binding: CustomerBindingInfo | null }>(
|
||||
`/api/v1/customer-fulfillment/${encodeURIComponent(orderNo)}/tasks/${taskId}/binding/status`,
|
||||
)
|
||||
}
|
||||
|
||||
export function confirmCustomerBinding(orderNo: string, taskId: number) {
|
||||
return apiPost<{ task: CustomerSemiAutoTask; binding: CustomerBindingInfo }>(
|
||||
`/api/v1/customer-fulfillment/${encodeURIComponent(orderNo)}/tasks/${taskId}/binding/confirm`,
|
||||
{},
|
||||
)
|
||||
}
|
||||
|
||||
export type CustomerWeiboCountryCode = {
|
||||
code: string
|
||||
name: string
|
||||
|
||||
@@ -1453,11 +1453,6 @@
|
||||
background: #202124;
|
||||
}
|
||||
|
||||
.customer-auth-page.is-weibo {
|
||||
--auth-accent: #ff5c9a;
|
||||
--auth-accent-hover: #ff78aa;
|
||||
}
|
||||
|
||||
.customer-auth-shell {
|
||||
width: min(760px, 100%);
|
||||
min-width: 0;
|
||||
@@ -1658,6 +1653,21 @@
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.customer-auth-section-copy h2 {
|
||||
margin: 0 0 8px;
|
||||
color: #fff;
|
||||
font-size: 23px;
|
||||
line-height: 1.35;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.customer-auth-section-copy p {
|
||||
margin: 0;
|
||||
color: var(--auth-muted);
|
||||
font-size: 15px;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.customer-auth-tabs {
|
||||
margin-top: 22px;
|
||||
}
|
||||
@@ -1667,24 +1677,32 @@
|
||||
}
|
||||
|
||||
.customer-auth-tabs .ant-tabs-nav::before {
|
||||
border-bottom-color: var(--auth-line);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.customer-auth-tabs .ant-tabs-nav-list {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 5px;
|
||||
padding: 5px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.18);
|
||||
border-radius: 11px;
|
||||
background: rgba(4, 15, 37, 0.72);
|
||||
}
|
||||
|
||||
.customer-auth-tabs .ant-tabs-tab {
|
||||
padding: 12px 30px;
|
||||
justify-content: center;
|
||||
margin: 0 !important;
|
||||
padding: 11px 18px;
|
||||
color: var(--auth-muted);
|
||||
background: rgba(255, 255, 255, 0.025);
|
||||
border: 1px solid var(--auth-line) !important;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.customer-auth-tabs .ant-tabs-tab + .ant-tabs-tab {
|
||||
margin-left: 4px;
|
||||
background: transparent;
|
||||
border: 0 !important;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.customer-auth-tabs .ant-tabs-tab.ant-tabs-tab-active {
|
||||
border-color: rgba(255, 255, 255, 0.18) !important;
|
||||
background: rgba(255, 255, 255, 0.09);
|
||||
background: var(--auth-accent);
|
||||
}
|
||||
|
||||
.customer-auth-tabs .ant-tabs-tab-btn {
|
||||
@@ -1692,8 +1710,13 @@
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.customer-auth-tabs .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
|
||||
color: #111827 !important;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.customer-auth-tabs .ant-tabs-ink-bar {
|
||||
background: var(--auth-accent);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.customer-auth-form {
|
||||
@@ -1703,73 +1726,129 @@
|
||||
padding-top: 18px;
|
||||
}
|
||||
|
||||
.customer-auth-inline-input {
|
||||
.customer-auth-field {
|
||||
display: grid;
|
||||
grid-template-columns: 104px minmax(0, 1fr) 126px;
|
||||
align-items: center;
|
||||
min-height: 50px;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(148, 163, 184, 0.28);
|
||||
border-radius: 5px;
|
||||
background: rgba(6, 18, 43, 0.7);
|
||||
border: 1px solid rgba(148, 163, 184, 0.24);
|
||||
border-radius: 10px;
|
||||
background: rgba(13, 29, 61, 0.72);
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.customer-auth-inline-input.is-code {
|
||||
grid-template-columns: minmax(0, 1fr) 126px;
|
||||
.customer-auth-field:focus-within {
|
||||
border-color: var(--auth-accent);
|
||||
box-shadow: 0 0 0 3px rgba(255, 138, 0, 0.12);
|
||||
}
|
||||
|
||||
.customer-auth-inline-input > * {
|
||||
.customer-auth-field > * {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.customer-auth-inline-input .ant-select-selector,
|
||||
.customer-auth-inline-input .ant-input,
|
||||
.customer-auth-inline-input .ant-input-affix-wrapper {
|
||||
.customer-auth-phone-field {
|
||||
grid-template-columns: 58px minmax(0, 1fr) 112px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.customer-auth-code-field {
|
||||
grid-template-columns: minmax(0, 1fr) 122px;
|
||||
gap: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.customer-auth-field .ant-select-selector,
|
||||
.customer-auth-field .ant-input,
|
||||
.customer-auth-field .ant-input-affix-wrapper {
|
||||
color: #f8fafc !important;
|
||||
border: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
background: rgba(6, 18, 43, 0.7) !important;
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.customer-auth-inline-input .ant-select-selector {
|
||||
.customer-auth-field .ant-select-selector {
|
||||
height: 48px !important;
|
||||
padding-inline: 14px !important;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.customer-auth-inline-input .ant-select-selection-item {
|
||||
color: #f8fafc !important;
|
||||
.customer-auth-field .ant-select {
|
||||
height: 48px;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.customer-auth-inline-input .ant-input {
|
||||
.customer-auth-field .ant-select-selection-item {
|
||||
color: #f8fafc !important;
|
||||
line-height: 46px !important;
|
||||
}
|
||||
|
||||
.customer-auth-field .ant-select-arrow {
|
||||
color: #c4cede !important;
|
||||
}
|
||||
|
||||
.customer-auth-country-prefix {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
align-self: stretch;
|
||||
color: #f8fafc;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
border-right: 1px solid var(--auth-line);
|
||||
}
|
||||
|
||||
.customer-auth-field .ant-input-affix-wrapper {
|
||||
height: 48px;
|
||||
padding-inline: 16px;
|
||||
padding-inline: 15px;
|
||||
}
|
||||
|
||||
.customer-auth-field .ant-input {
|
||||
height: 48px;
|
||||
padding-inline: 10px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.customer-auth-inline-input .ant-input::placeholder {
|
||||
.customer-auth-field .ant-input-affix-wrapper .ant-input {
|
||||
padding-inline: 0;
|
||||
}
|
||||
|
||||
.customer-auth-field .ant-input::placeholder {
|
||||
color: #7f8ca5;
|
||||
}
|
||||
|
||||
.customer-auth-inline-input .ant-select,
|
||||
.customer-auth-inline-input .ant-input {
|
||||
border-right: 1px solid var(--auth-line) !important;
|
||||
.customer-auth-field .ant-input-prefix {
|
||||
margin-inline-end: 9px;
|
||||
color: var(--auth-accent);
|
||||
}
|
||||
|
||||
.customer-auth-inline-input .ant-btn {
|
||||
height: 48px;
|
||||
color: #fff;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: rgba(48, 75, 124, 0.55);
|
||||
}
|
||||
|
||||
.customer-auth-inline-input .ant-btn-primary {
|
||||
.customer-auth-send-code {
|
||||
height: 40px !important;
|
||||
padding-inline: 10px !important;
|
||||
color: #111827;
|
||||
background: var(--auth-accent);
|
||||
border: 0 !important;
|
||||
border-radius: 7px !important;
|
||||
background: var(--auth-accent) !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.customer-auth-option-hint {
|
||||
color: var(--auth-muted);
|
||||
.customer-auth-send-code:hover {
|
||||
background: var(--auth-accent-hover) !important;
|
||||
}
|
||||
|
||||
.customer-auth-code-submit {
|
||||
height: 40px !important;
|
||||
color: #111827;
|
||||
border: 0 !important;
|
||||
border-radius: 7px !important;
|
||||
background: var(--auth-accent) !important;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.customer-auth-code-submit:hover {
|
||||
background: var(--auth-accent-hover) !important;
|
||||
}
|
||||
|
||||
.customer-auth-note {
|
||||
@@ -1983,34 +2062,31 @@
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.customer-auth-page {
|
||||
height: 100dvh;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
background: var(--auth-panel);
|
||||
}
|
||||
|
||||
.customer-auth-shell {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.customer-auth-hero {
|
||||
min-height: 150px;
|
||||
padding: 26px 22px;
|
||||
min-height: 116px;
|
||||
flex: none;
|
||||
padding: 20px 22px;
|
||||
background-position: 58% center;
|
||||
}
|
||||
|
||||
.customer-auth-logo {
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
font-size: 29px;
|
||||
}
|
||||
|
||||
.customer-auth-brand {
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.customer-auth-eyebrow {
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -2020,8 +2096,20 @@
|
||||
}
|
||||
|
||||
.customer-auth-warning {
|
||||
padding: 16px 14px;
|
||||
font-size: 17px;
|
||||
flex: none;
|
||||
padding: 12px 14px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.customer-auth-main {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.customer-auth-content-stack {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.customer-auth-facts {
|
||||
@@ -2031,55 +2119,104 @@
|
||||
.customer-auth-facts > div {
|
||||
grid-template-columns: 92px minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
min-height: 68px;
|
||||
padding-block: 14px;
|
||||
min-height: 52px;
|
||||
padding-block: 8px;
|
||||
}
|
||||
|
||||
.customer-auth-facts > div > span {
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.customer-auth-facts > div > strong {
|
||||
font-size: 16px;
|
||||
font-size: 15px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.customer-auth-section {
|
||||
padding: 24px 18px 27px;
|
||||
padding: 14px 16px 10px;
|
||||
}
|
||||
|
||||
.customer-auth-section-heading h2,
|
||||
.customer-auth-section-copy h2,
|
||||
.customer-auth-state h2 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.customer-auth-section-heading p,
|
||||
.customer-auth-section-copy p,
|
||||
.customer-auth-state p,
|
||||
.customer-auth-form-copy {
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.customer-auth-section-icon {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
font-size: 20px;
|
||||
.customer-auth-tabs {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.customer-auth-inline-input,
|
||||
.customer-auth-inline-input.is-code {
|
||||
grid-template-columns: 94px minmax(0, 1fr);
|
||||
.customer-auth-tabs .ant-tabs-nav {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.customer-auth-inline-input > .ant-btn {
|
||||
grid-column: 1 / -1;
|
||||
border-top: 1px solid var(--auth-line);
|
||||
.customer-auth-tabs .ant-tabs-nav-list {
|
||||
gap: 4px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.customer-auth-inline-input.is-code {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
.customer-auth-tabs .ant-tabs-tab {
|
||||
padding: 9px 14px;
|
||||
}
|
||||
|
||||
.customer-auth-inline-input.is-code > .ant-btn {
|
||||
grid-column: auto;
|
||||
.customer-auth-tabs .ant-tabs-tab-btn {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.customer-auth-form {
|
||||
gap: 10px;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.customer-auth-field {
|
||||
min-height: 44px;
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.customer-auth-phone-field {
|
||||
grid-template-columns: 50px minmax(0, 1fr) 104px;
|
||||
}
|
||||
|
||||
.customer-auth-code-field {
|
||||
grid-template-columns: minmax(0, 1fr) 104px;
|
||||
}
|
||||
|
||||
.customer-auth-field .ant-select,
|
||||
.customer-auth-field .ant-select-selector,
|
||||
.customer-auth-field .ant-input,
|
||||
.customer-auth-field .ant-input-affix-wrapper {
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.customer-auth-field .ant-select-selection-item {
|
||||
line-height: 42px !important;
|
||||
}
|
||||
|
||||
.customer-auth-field .ant-input-affix-wrapper {
|
||||
padding-inline: 12px;
|
||||
}
|
||||
|
||||
.customer-auth-code-field {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.customer-auth-code-submit {
|
||||
height: 36px !important;
|
||||
}
|
||||
|
||||
.customer-auth-send-code {
|
||||
height: 36px !important;
|
||||
}
|
||||
|
||||
.customer-auth-form-hint {
|
||||
font-size: 10px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.customer-auth-qr-image,
|
||||
@@ -2089,7 +2226,9 @@
|
||||
}
|
||||
|
||||
.customer-auth-privacy {
|
||||
margin: 20px 18px 24px;
|
||||
font-size: 13px;
|
||||
margin: 8px 16px 12px;
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,7 +436,6 @@ export interface ManualFulfillmentConfig {
|
||||
pageTitle?: string
|
||||
pageDescription?: string
|
||||
loginInstructions?: string
|
||||
bindingInstructions?: string
|
||||
browserTargets?: Array<{ key: string; label: string; url: string }>
|
||||
}
|
||||
note?: string
|
||||
|
||||
@@ -13,7 +13,6 @@ export type SemiAutoTask = {
|
||||
platform: SemiAutoPlatform
|
||||
platformLabel: string
|
||||
loginStatus: string
|
||||
bindingStatus: string
|
||||
executionStatus: string
|
||||
connectorMode: 'embedded' | 'external' | 'mock'
|
||||
displayStatus: SemiAutoDisplayStatus
|
||||
@@ -24,9 +23,6 @@ export type SemiAutoTask = {
|
||||
credentialUpdatedAt: string | null
|
||||
huyaUid: string
|
||||
platformNickname: string
|
||||
bindGameAccount: string
|
||||
bindGameRole: string
|
||||
bindGameChannel: string
|
||||
targetQuantity: number
|
||||
completedQuantity: number
|
||||
assignedAdminUserId: number | null
|
||||
@@ -54,7 +50,6 @@ export type SemiAutoTask = {
|
||||
pageTitle?: string
|
||||
pageDescription?: string
|
||||
loginInstructions?: string
|
||||
bindingInstructions?: string
|
||||
browserTargets?: Array<{ key: string; label: string; url: string }>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,21 +8,16 @@ export type CustomerSemiAutoTask = {
|
||||
skuCode: string
|
||||
quantity: number
|
||||
loginStatus: string
|
||||
bindingStatus: string
|
||||
executionStatus: string
|
||||
displayStatus: { key: string; label: string }
|
||||
huyaUid: string
|
||||
platformNickname: string
|
||||
bindGameAccount: string
|
||||
bindGameRole: string
|
||||
bindGameChannel: string
|
||||
completedAt: string | null
|
||||
completionNote: string
|
||||
semiAutoConfig: {
|
||||
pageTitle?: string
|
||||
pageDescription?: string
|
||||
loginInstructions?: string
|
||||
bindingInstructions?: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,15 +28,3 @@ export type CustomerLoginInfo = {
|
||||
expiresAt: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export type CustomerBindingInfo = {
|
||||
status: string
|
||||
qrImage: string
|
||||
qrExpiresAt: string
|
||||
bindGameAccount: string
|
||||
bindGameRole: string
|
||||
bindGameChannel: string
|
||||
hasExistingBinding?: boolean
|
||||
rebindRequested?: boolean
|
||||
message: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user