```
feat(passport): 实现统一扫码功能并迁移二维码登录页面 将原有的扫码登录和扫码核销功能合并为统一扫码功能,支持智能识别二维码类型, 自动执行相应操作。同时将二维码登录相关页面迁移到 /passport 路径下,优化用户体验与代码结构。 主要变更: - 新增统一扫码 Hook (useUnifiedQRScan) 和按钮组件 (UnifiedQRButton)- 新增统一扫码页面 /passport/unified-qr/index - 迁移二维码登录页面路径:/pages/qr-login → /passport/qr-login - 更新管理员面板及用户卡片中的扫码入口为统一扫码- 移除旧的 QRLoginDemo 和 qr-test 测试页面- 补充统一扫码使用指南文档```
This commit is contained in:
4
src/passport/unified-qr/index.config.ts
Normal file
4
src/passport/unified-qr/index.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default {
|
||||
navigationBarTitleText: '统一扫码',
|
||||
navigationBarTextStyle: 'black'
|
||||
}
|
||||
320
src/passport/unified-qr/index.tsx
Normal file
320
src/passport/unified-qr/index.tsx
Normal file
@@ -0,0 +1,320 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import { Card, Button, Tag } from '@nutui/nutui-react-taro';
|
||||
import { Scan, Success, Failure, Tips, ArrowLeft } from '@nutui/icons-react-taro';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { useUnifiedQRScan, ScanType, type UnifiedScanResult } from '@/hooks/useUnifiedQRScan';
|
||||
|
||||
/**
|
||||
* 统一扫码页面
|
||||
* 支持登录和核销两种类型的二维码扫描
|
||||
*/
|
||||
const UnifiedQRPage: React.FC = () => {
|
||||
const [scanHistory, setScanHistory] = useState<any[]>([]);
|
||||
const {
|
||||
startScan,
|
||||
isLoading,
|
||||
canScan,
|
||||
state,
|
||||
result,
|
||||
error,
|
||||
scanType,
|
||||
reset
|
||||
} = useUnifiedQRScan();
|
||||
|
||||
// 处理扫码成功
|
||||
const handleScanSuccess = (result: UnifiedScanResult) => {
|
||||
console.log('扫码成功:', result);
|
||||
|
||||
// 添加到扫码历史
|
||||
const newRecord = {
|
||||
id: Date.now(),
|
||||
time: new Date().toLocaleString(),
|
||||
type: result.type,
|
||||
data: result.data,
|
||||
message: result.message,
|
||||
success: true
|
||||
};
|
||||
setScanHistory(prev => [newRecord, ...prev.slice(0, 4)]); // 只保留最近5条记录
|
||||
|
||||
// 根据类型给出不同提示
|
||||
if (result.type === ScanType.VERIFICATION) {
|
||||
// 核销成功后询问是否继续扫码
|
||||
setTimeout(() => {
|
||||
Taro.showModal({
|
||||
title: '核销成功',
|
||||
content: '是否继续扫码核销其他礼品卡?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
handleStartScan();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 2000);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理扫码失败
|
||||
const handleScanError = (error: string) => {
|
||||
console.error('扫码失败:', error);
|
||||
|
||||
// 添加到扫码历史
|
||||
const newRecord = {
|
||||
id: Date.now(),
|
||||
time: new Date().toLocaleString(),
|
||||
error,
|
||||
success: false
|
||||
};
|
||||
setScanHistory(prev => [newRecord, ...prev.slice(0, 4)]); // 只保留最近5条记录
|
||||
};
|
||||
|
||||
// 开始扫码
|
||||
const handleStartScan = async () => {
|
||||
try {
|
||||
const scanResult = await startScan();
|
||||
if (scanResult) {
|
||||
handleScanSuccess(scanResult);
|
||||
}
|
||||
} catch (error: any) {
|
||||
handleScanError(error.message || '扫码失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 返回上一页
|
||||
const handleGoBack = () => {
|
||||
Taro.navigateBack();
|
||||
};
|
||||
|
||||
// 获取状态图标
|
||||
const getStatusIcon = (success: boolean, type?: ScanType) => {
|
||||
console.log(type,'获取状态图标')
|
||||
if (success) {
|
||||
return <Success className="text-green-500" size="16" />;
|
||||
} else {
|
||||
return <Failure className="text-red-500" size="16" />;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取类型标签
|
||||
const getTypeTag = (type: ScanType) => {
|
||||
switch (type) {
|
||||
case ScanType.LOGIN:
|
||||
return <Tag type="success">登录</Tag>;
|
||||
case ScanType.VERIFICATION:
|
||||
return <Tag type="warning">核销</Tag>;
|
||||
default:
|
||||
return <Tag type="default">未知</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">
|
||||
{/* 状态显示 */}
|
||||
{state === 'idle' && (
|
||||
<>
|
||||
<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"
|
||||
onClick={handleStartScan}
|
||||
disabled={!canScan() || isLoading}
|
||||
className="w-full"
|
||||
>
|
||||
{canScan() ? '开始扫码' : '请先登录'}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{state === 'scanning' && (
|
||||
<>
|
||||
<Scan className="text-blue-500 mx-auto mb-4" size="48" />
|
||||
<Text className="text-lg font-medium text-blue-600 mb-2 block">
|
||||
扫码中...
|
||||
</Text>
|
||||
<Text className="text-gray-600 mb-6 block">
|
||||
请对准二维码
|
||||
</Text>
|
||||
<Button
|
||||
type="default"
|
||||
size="large"
|
||||
onClick={reset}
|
||||
className="w-full"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{state === 'processing' && (
|
||||
<>
|
||||
<View className="text-orange-500 mx-auto mb-4">
|
||||
<Tips size="48" />
|
||||
</View>
|
||||
<Text className="text-lg font-medium text-orange-600 mb-2 block">
|
||||
处理中...
|
||||
</Text>
|
||||
<Text className="text-gray-600 mb-6 block">
|
||||
{scanType === ScanType.LOGIN ? '正在确认登录' :
|
||||
scanType === ScanType.VERIFICATION ? '正在核销礼品卡' : '正在处理'}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
|
||||
{state === 'success' && result && (
|
||||
<>
|
||||
<Success className="text-green-500 mx-auto mb-4" size="48" />
|
||||
<Text className="text-lg font-medium text-green-600 mb-2 block">
|
||||
{result.message}
|
||||
</Text>
|
||||
{result.type === ScanType.VERIFICATION && result.data && (
|
||||
<View className="bg-green-50 rounded-lg p-3 mb-4">
|
||||
<Text className="text-sm text-green-800 block">
|
||||
礼品卡:{result.data.goodsName || '未知商品'}
|
||||
</Text>
|
||||
<Text className="text-sm text-green-800 block">
|
||||
面值:¥{result.data.faceValue}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<View className="mt-2">
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={handleStartScan}
|
||||
className="w-full mb-2"
|
||||
>
|
||||
继续扫码
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
size="normal"
|
||||
onClick={reset}
|
||||
className="w-full"
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
|
||||
{state === 'error' && (
|
||||
<>
|
||||
<Failure className="text-red-500 mx-auto mb-4" size="48" />
|
||||
<Text className="text-lg font-medium text-red-600 mb-2 block">
|
||||
操作失败
|
||||
</Text>
|
||||
<Text className="text-gray-600 mb-6 block">
|
||||
{error || '未知错误'}
|
||||
</Text>
|
||||
<View className="mt-2">
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={handleStartScan}
|
||||
className="w-full mb-2"
|
||||
>
|
||||
重试
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
size="normal"
|
||||
onClick={reset}
|
||||
className="w-full"
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</Card>
|
||||
|
||||
{/* 扫码历史 */}
|
||||
{scanHistory.length > 0 && (
|
||||
<Card className="m-4">
|
||||
<View className="pb-4">
|
||||
<Text className="text-lg font-medium text-gray-800 mb-3 block">
|
||||
最近扫码记录
|
||||
</Text>
|
||||
|
||||
{scanHistory.map((record, index) => (
|
||||
<View
|
||||
key={record.id}
|
||||
className={`flex items-center justify-between p-3 bg-gray-50 rounded-lg ${index < scanHistory.length - 1 ? 'mb-2' : ''}`}
|
||||
>
|
||||
<View className="flex items-center flex-1">
|
||||
{getStatusIcon(record.success, record.type)}
|
||||
<View className="ml-3 flex-1">
|
||||
<View className="flex items-center mb-1">
|
||||
{record.type && getTypeTag(record.type)}
|
||||
<Text className="text-sm text-gray-600 ml-2">
|
||||
{record.time}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className="text-sm text-gray-800">
|
||||
{record.success ? record.message : record.error}
|
||||
</Text>
|
||||
{record.success && record.type === ScanType.VERIFICATION && record.data && (
|
||||
<Text className="text-xs text-gray-500">
|
||||
{record.data.goodsName} - ¥{record.data.faceValue}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 功能说明 */}
|
||||
<Card className="m-4 bg-blue-50 border border-blue-200">
|
||||
<View className="p-4">
|
||||
<View className="flex items-start">
|
||||
<Tips className="text-blue-600 mr-2 mt-1" size="16" />
|
||||
<View>
|
||||
<Text className="text-sm font-medium text-blue-800 mb-1 block">
|
||||
功能说明
|
||||
</Text>
|
||||
<Text className="text-xs text-blue-700 block mb-1">
|
||||
• 登录二维码:自动确认网页端登录
|
||||
</Text>
|
||||
<Text className="text-xs text-blue-700 block mb-1">
|
||||
• 核销二维码:门店核销用户礼品卡
|
||||
</Text>
|
||||
<Text className="text-xs text-blue-700 block">
|
||||
• 系统会自动识别二维码类型并执行相应操作
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Card>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default UnifiedQRPage;
|
||||
Reference in New Issue
Block a user