- 新增地址类型定义,增强前端地址数据结构 - 新增地址编辑页面,支持地址智能识别和定位选点功能 - 地址编辑支持省市区选择及默认地址设置 - 新增地址列表页面,支持地址展示、删除、编辑和选择功能 - 实现售后申请页面,支持选择售后类型和退款原因 - 售后申请支持商品选择、退款金额计算和凭证上传 - 新增售后详情页面,支持售后状态展示及申请取消 - 优化页面加载和用户交互体验,增加错误提示和权限处理
178 lines
5.7 KiB
TypeScript
178 lines
5.7 KiB
TypeScript
import React, { useState } from 'react'
|
||
import { View, Text } from '@tarojs/components'
|
||
import { Card, Button, Tag } from '@nutui/nutui-react-taro'
|
||
import { Scan, Success, Failure, ArrowLeft } from '@nutui/icons-react-taro'
|
||
import Taro from '@tarojs/taro'
|
||
import { confirmWechatQRLogin, parseQRContent } from '@/api/passport/qr-login'
|
||
|
||
enum ScanType {
|
||
LOGIN = 'login',
|
||
UNKNOWN = 'unknown',
|
||
}
|
||
|
||
interface ScanRecord {
|
||
id: number
|
||
time: string
|
||
success: boolean
|
||
type: ScanType
|
||
message: string
|
||
raw?: string
|
||
}
|
||
|
||
const UnifiedQRPage: React.FC = () => {
|
||
const [loading, setLoading] = useState(false)
|
||
const [lastRecord, setLastRecord] = useState<ScanRecord | null>(null)
|
||
const [scanHistory, setScanHistory] = useState<ScanRecord[]>([])
|
||
|
||
const pushHistory = (record: ScanRecord) => {
|
||
setLastRecord(record)
|
||
setScanHistory((prev) => [record, ...prev.slice(0, 4)])
|
||
}
|
||
|
||
const handleGoBack = () => {
|
||
Taro.navigateBack()
|
||
}
|
||
|
||
const buildRecord = (success: boolean, type: ScanType, message: string, raw?: string): ScanRecord => ({
|
||
id: Date.now(),
|
||
time: new Date().toLocaleString(),
|
||
success,
|
||
type,
|
||
message,
|
||
raw,
|
||
})
|
||
|
||
const handleLoginScan = async (raw: string) => {
|
||
const token = parseQRContent(raw)
|
||
const userId = Number(Taro.getStorageSync('UserId') || 0)
|
||
|
||
if (!token) {
|
||
throw new Error('未识别到可登录的二维码内容')
|
||
}
|
||
if (!userId) {
|
||
throw new Error('请先登录小程序账号后再扫码')
|
||
}
|
||
|
||
const result = await confirmWechatQRLogin(token, userId)
|
||
if (result?.status !== 'confirmed') {
|
||
throw new Error(result?.message || '二维码登录确认失败')
|
||
}
|
||
|
||
return '登录确认成功'
|
||
}
|
||
|
||
const handleStartScan = async () => {
|
||
if (loading) return
|
||
|
||
try {
|
||
setLoading(true)
|
||
const res = await Taro.scanCode({ scanType: ['qrCode'] })
|
||
const raw = (res.result || '').trim()
|
||
|
||
if (!raw) {
|
||
throw new Error('扫码结果为空')
|
||
}
|
||
|
||
const token = parseQRContent(raw)
|
||
if (token) {
|
||
const message = await handleLoginScan(raw)
|
||
pushHistory(buildRecord(true, ScanType.LOGIN, message, raw))
|
||
Taro.showToast({ title: message, icon: 'success' })
|
||
return
|
||
}
|
||
|
||
const unsupportedMessage = '当前版本仅支持登录二维码,核销能力待补充'
|
||
pushHistory(buildRecord(false, ScanType.UNKNOWN, unsupportedMessage, raw))
|
||
Taro.showToast({ title: unsupportedMessage, icon: 'none' })
|
||
} catch (error: any) {
|
||
const message =
|
||
error?.errMsg?.includes('cancel')
|
||
? '已取消扫码'
|
||
: error?.message || '扫码失败'
|
||
|
||
pushHistory(buildRecord(false, ScanType.UNKNOWN, message))
|
||
if (message !== '已取消扫码') {
|
||
Taro.showToast({ title: message, icon: 'none' })
|
||
}
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
const getTypeTag = (type: ScanType) => {
|
||
if (type === ScanType.LOGIN) {
|
||
return <Tag type='success'>登录</Tag>
|
||
}
|
||
return <Tag type='primary'>其他</Tag>
|
||
}
|
||
|
||
return (
|
||
<View className='unified-qr-page min-h-screen bg-gray-50'>
|
||
<View className='bg-white px-4 py-3 border-b border-gray-100 flex items-center'>
|
||
<ArrowLeft className='text-gray-600 mr-3' size='20' onClick={handleGoBack} />
|
||
<View className='flex-1'>
|
||
<Text className='text-lg font-bold'>统一扫码</Text>
|
||
<Text className='text-sm text-gray-600 block'>先恢复登录扫码,后续再补齐核销流程</Text>
|
||
</View>
|
||
</View>
|
||
|
||
<Card className='m-4'>
|
||
<View className='text-center py-6'>
|
||
<Scan className='text-blue-500 mx-auto mb-4' size='48' />
|
||
<Text className='text-lg font-medium text-gray-800 mb-2 block'>扫码入口</Text>
|
||
<Text className='text-gray-600 mb-6 block'>当前可识别网页登录二维码,其他码先给出明确提示</Text>
|
||
<Button
|
||
type='primary'
|
||
size='large'
|
||
loading={loading}
|
||
disabled={loading}
|
||
onClick={handleStartScan}
|
||
className='w-full'
|
||
>
|
||
{loading ? '扫码中...' : '开始扫码'}
|
||
</Button>
|
||
</View>
|
||
</Card>
|
||
|
||
{lastRecord && (
|
||
<Card className='mx-4 mb-4'>
|
||
<View className='flex items-start'>
|
||
<View className='mr-3 mt-1'>
|
||
{lastRecord.success ? (
|
||
<Success className='text-green-500' size='18' />
|
||
) : (
|
||
<Failure className='text-red-500' size='18' />
|
||
)}
|
||
</View>
|
||
<View className='flex-1'>
|
||
<View className='flex items-center justify-between mb-2'>
|
||
<Text className='font-medium'>最近一次结果</Text>
|
||
{getTypeTag(lastRecord.type)}
|
||
</View>
|
||
<Text className='text-sm text-gray-800 block mb-1'>{lastRecord.message}</Text>
|
||
<Text className='text-xs text-gray-500 block'>{lastRecord.time}</Text>
|
||
</View>
|
||
</View>
|
||
</Card>
|
||
)}
|
||
|
||
{scanHistory.length > 0 && (
|
||
<Card className='mx-4 mb-4'>
|
||
<Text className='font-medium block mb-3'>扫码记录</Text>
|
||
{scanHistory.map((item) => (
|
||
<View key={item.id} className='py-2 border-b border-gray-100' style={item === scanHistory[scanHistory.length - 1] ? { borderBottom: 'none' } : undefined}>
|
||
<View className='flex items-center justify-between mb-1'>
|
||
<Text className='text-sm text-gray-800'>{item.message}</Text>
|
||
{getTypeTag(item.type)}
|
||
</View>
|
||
<Text className='text-xs text-gray-500 block'>{item.time}</Text>
|
||
</View>
|
||
))}
|
||
</Card>
|
||
)}
|
||
</View>
|
||
)
|
||
}
|
||
|
||
export default UnifiedQRPage
|