feat(binding): verify 4G relations before Banban unbind
This commit is contained in:
@@ -26,6 +26,7 @@ import {
|
||||
validateChildNameInput,
|
||||
} from '@/services/child'
|
||||
import { useSystemBanner } from '@/components/system-banner/use-system-banner'
|
||||
import { trafficPageUrl } from '@/services/traffic-binding'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
@@ -678,6 +679,10 @@ export default function Bind() {
|
||||
<Button className='bind-result-button primary' onClick={() => Taro.reLaunch({ url: bindResultDestination })}>
|
||||
{bindResultDestination.includes('/sleep/') ? '去关联宝贝' : '返回首页'}
|
||||
</Button>
|
||||
{!isAddingCard && <Button className='bind-result-button primary' onClick={() => Taro.navigateTo({ url: trafficPageUrl(boundDeviceId) })}>
|
||||
关联流量服务
|
||||
</Button>}
|
||||
{!isAddingCard && <Text className='bind-result-description'>设备已绑定。流量查询和充值需另外关联,可稍后在管理页完成。</Text>}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -10,6 +10,7 @@ import Taro, { useDidHide, useDidShow, useShareAppMessage } from '@tarojs/taro'
|
||||
import { authorizeParentPhone, clearToken, getParent, getToken, Parent } from '@/services/auth'
|
||||
import { DEVICE_UNAVAILABLE_MESSAGE } from '@/services/api'
|
||||
import { getCurrentUserId } from '@/services/session'
|
||||
import { TrafficOverview, getTrafficOverview, trafficLinked, openTrafficPage, openTrafficPortal, flushTrafficReceipt } from '@/services/traffic-binding'
|
||||
import {
|
||||
BindingListItem,
|
||||
BindingCardItem,
|
||||
@@ -19,7 +20,6 @@ import {
|
||||
loadCurrentChildBindingContext,
|
||||
setSelectedBindingDeviceId,
|
||||
updateBindingCardName,
|
||||
unbindDevice,
|
||||
} from '@/services/binding'
|
||||
import {
|
||||
Child,
|
||||
@@ -77,6 +77,20 @@ export default function Sleep() {
|
||||
const [children, setChildren] = useState<Child[]>([])
|
||||
const [bindings, setBindings] = useState<BindingListItem[]>([])
|
||||
const [binding, setBinding] = useState<BindingListItem | null>(null)
|
||||
const [trafficOverview, setTrafficOverview] = useState<TrafficOverview | null>(null)
|
||||
const [trafficBusy, setTrafficBusy] = useState(false)
|
||||
const trafficLock = useRef(false)
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
setTrafficOverview(null)
|
||||
if (binding?.device_id) {
|
||||
void flushTrafficReceipt().catch(() => null)
|
||||
.then(() => getTrafficOverview(binding.device_id))
|
||||
.then((value) => { if (!cancelled) setTrafficOverview(value) })
|
||||
.catch(() => { /* Core device management remains available. */ })
|
||||
}
|
||||
return () => { cancelled = true }
|
||||
}, [binding])
|
||||
const [currentChild, setCurrentChild] = useState<Child | null>(null)
|
||||
const [firmwareStatus, setFirmwareStatus] = useState<DeviceFirmwareStatus | null>(null)
|
||||
const [showFirmwareProgress, setShowFirmwareProgress] = useState(false)
|
||||
@@ -651,12 +665,7 @@ export default function Sleep() {
|
||||
if (!result.confirm) return
|
||||
|
||||
try {
|
||||
await unbindDevice(targetBinding.device_id)
|
||||
if (binding?.device_id === targetBinding.device_id) {
|
||||
clearSelectedBindingDeviceId()
|
||||
}
|
||||
Taro.showToast({ title: '已删除设备', icon: 'success' })
|
||||
await loadData()
|
||||
await openTrafficPage(targetBinding.device_id, 'unbind')
|
||||
} catch (error: any) {
|
||||
Taro.showToast({
|
||||
title: error?.message || '删除设备失败,请重试',
|
||||
@@ -834,10 +843,7 @@ export default function Sleep() {
|
||||
if (!result.confirm) return
|
||||
|
||||
try {
|
||||
await unbindDevice(binding.device_id)
|
||||
clearSelectedBindingDeviceId()
|
||||
Taro.showToast({ title: '已解绑', icon: 'success' })
|
||||
await loadData()
|
||||
await openTrafficPage(binding.device_id, 'unbind')
|
||||
} catch (error: any) {
|
||||
Taro.showToast({
|
||||
title: error?.message || '解绑失败,请重试',
|
||||
@@ -956,6 +962,18 @@ export default function Sleep() {
|
||||
return
|
||||
}
|
||||
|
||||
if (['关联流量服务', '继续确认流量服务', '继续解绑', '流量查询', '套餐充值'].includes(item.name)) {
|
||||
if (!binding || trafficLock.current) return
|
||||
trafficLock.current = true
|
||||
setTrafficBusy(true)
|
||||
const operation = item.name === '流量查询' || item.name === '套餐充值'
|
||||
? openTrafficPortal(binding.device_id, item.name === '套餐充值' ? 'recharge' : 'device')
|
||||
: openTrafficPage(binding.device_id, item.name === '继续解绑' ? 'unbind' : 'bind')
|
||||
void operation.catch((error: any) => Taro.showToast({ title: error.message || error.errMsg || '操作未完成,请重试', icon: 'none' }))
|
||||
.finally(() => { trafficLock.current = false; setTrafficBusy(false) })
|
||||
return
|
||||
}
|
||||
|
||||
if (item.name === '我的卡片') {
|
||||
setShowCardsModal(true)
|
||||
return
|
||||
@@ -1129,6 +1147,14 @@ export default function Sleep() {
|
||||
value: binding?.device_id ? `当前: ${binding.device_id}` : '未绑定',
|
||||
arrow: true,
|
||||
},
|
||||
...(binding ? (trafficLinked(trafficOverview) && trafficOverview?.device_id === binding.device_id
|
||||
? [
|
||||
{ icon: require('../../assets/ui/menu-binding.png'), iconBgClass: 'green', name: '流量查询', value: '', arrow: true, disabled: !isFamilyOwner || trafficBusy },
|
||||
{ icon: require('../../assets/ui/menu-binding.png'), iconBgClass: 'green', name: '套餐充值', value: '', arrow: true, disabled: !isFamilyOwner || trafficBusy },
|
||||
]
|
||||
: [{ icon: require('../../assets/ui/menu-binding.png'), iconBgClass: 'green',
|
||||
name: trafficOverview?.unbind ? '继续解绑' : trafficOverview?.bind ? '继续确认流量服务' : '关联流量服务',
|
||||
value: isFamilyOwner ? '不影响伴伴使用' : '仅设备归属账号可操作', arrow: true, disabled: !isFamilyOwner || trafficBusy }]) : []),
|
||||
{
|
||||
icon: require('../../assets/ui/menu-cards.png'),
|
||||
iconBgClass: 'green',
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export default definePageConfig({ navigationBarTitleText: '流量服务关联' })
|
||||
@@ -0,0 +1,7 @@
|
||||
.traffic-binding-page {
|
||||
min-height: 100vh; box-sizing: border-box; padding: 72rpx 40rpx;
|
||||
.title { display: block; font-size: 38rpx; font-weight: bold; margin-bottom: 30rpx; }
|
||||
.description { display: block; font-size: 28rpx; line-height: 1.7; margin-bottom: 30rpx; }
|
||||
.error { display: block; color: #a52a2a; margin-bottom: 24rpx; }
|
||||
button { margin-top: 24rpx; }
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { View, Text, Button } from '@tarojs/components'
|
||||
import Taro, { useDidShow, useRouter } from '@tarojs/taro'
|
||||
import { useRef, useState } from 'react'
|
||||
import { getToken } from '@/services/auth'
|
||||
import { clearSelectedBindingDeviceId } from '@/services/binding'
|
||||
import { getPageStyle } from '@/utils/page-style'
|
||||
import { TrafficOperation, getTrafficOperation, resumeTrafficOperation,
|
||||
flushTrafficReceipt, finishTrafficUnbind, jumpTraffic } from '@/services/traffic-binding'
|
||||
import './index.scss'
|
||||
|
||||
export default function TrafficBindingPage() {
|
||||
const { params } = useRouter()
|
||||
const deviceId = String(params.device_id || '')
|
||||
const action = params.action === 'unbind' ? 'unbind' : 'bind'
|
||||
const [operation, setOperation] = useState<TrafficOperation | null>(null)
|
||||
const [busy, setBusy] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const lock = useRef(false)
|
||||
|
||||
const recover = async () => {
|
||||
if (lock.current) return
|
||||
if (!getToken()) { Taro.navigateTo({ url: '/pages/login/index' }); return }
|
||||
lock.current = true
|
||||
setBusy(true)
|
||||
try {
|
||||
const current = await resumeTrafficOperation(deviceId, action)
|
||||
setOperation(current)
|
||||
if (action === 'unbind' && current.status === 'completed') clearSelectedBindingDeviceId()
|
||||
setError('')
|
||||
} catch (e: any) { setError(e.message || '暂时无法确认,请重试') }
|
||||
finally { lock.current = false; setBusy(false) }
|
||||
}
|
||||
useDidShow(() => { void recover() })
|
||||
|
||||
const proceed = async () => {
|
||||
if (!operation) { await recover(); return }
|
||||
if (lock.current) return
|
||||
lock.current = true
|
||||
setBusy(true)
|
||||
setError('')
|
||||
try {
|
||||
await flushTrafficReceipt()
|
||||
let current = await getTrafficOperation(operation.operation_id)
|
||||
if (current.status !== 'completed') {
|
||||
const result = await jumpTraffic(current)
|
||||
if (!result) return
|
||||
current = result
|
||||
if (action === 'unbind') current = await finishTrafficUnbind(current)
|
||||
}
|
||||
setOperation(current)
|
||||
if (action === 'unbind' && current.status === 'completed') clearSelectedBindingDeviceId()
|
||||
} catch (e: any) { setError(e.message || e.errMsg || '操作未完成,请继续重试') }
|
||||
finally { lock.current = false; setBusy(false) }
|
||||
}
|
||||
const completed = operation?.status === 'completed'
|
||||
const finalUnbind = action === 'unbind' && operation?.traffic_status === 'server_confirmed'
|
||||
return <View className='traffic-binding-page' style={getPageStyle(true)}>
|
||||
<Text className='title'>{action === 'bind' ? completed ? '流量服务已关联' : '设备已绑定,流量服务待关联' : completed ? '设备已解除绑定' : '解除设备与流量服务绑定'}</Text>
|
||||
<Text className='description'>{action === 'bind'
|
||||
? completed ? '可以查询流量和购买套餐了。' : '伴伴设备可以正常使用。继续前往流量管理小程序登录并关联,也可以稍后完成。'
|
||||
: completed ? '设备已解绑,流量侧已确认没有本次有效绑定。' : finalUnbind ? '流量后端已确认可以解绑,点击完成伴伴解绑。' : '先确认流量侧原绑定已解除,再解除伴伴绑定;套餐和充值记录保留。继续处理会停止本次流量关联,暂不支持取消恢复。'}</Text>
|
||||
{operation?.last_error === 'HANDOFF_RELATION_UNKNOWN' && <Text className='error'>流量后端无法确认原绑定记录,伴伴绑定仍保留,请联系服务人员核查。</Text>}
|
||||
{operation?.last_error === 'TRAFFIC_UNBIND_REQUIRED' && <Text className='description'>原流量绑定仍有效,请继续前往流量管理小程序解绑。</Text>}
|
||||
{operation?.status === 'partial_failed' && <Text className='description'>上次流量操作未完成,可以继续重试。</Text>}
|
||||
{error && <Text className='error'>{error}</Text>}
|
||||
{!completed && <Button disabled={busy} onClick={proceed}>{busy ? '处理中…' : finalUnbind ? '完成伴伴解绑' : '继续处理'}</Button>}
|
||||
<Button disabled={busy} onClick={() => Taro.switchTab({ url: '/pages/sleep/index' })}>{completed ? '返回管理' : '稍后处理'}</Button>
|
||||
</View>
|
||||
}
|
||||
Reference in New Issue
Block a user