forked from gxwebsoft/mp-10550
```
feat(passport): 实现统一扫码功能并迁移二维码登录页面 将原有的扫码登录和扫码核销功能合并为统一扫码功能,支持智能识别二维码类型, 自动执行相应操作。同时将二维码登录相关页面迁移到 /passport 路径下,优化用户体验与代码结构。 主要变更: - 新增统一扫码 Hook (useUnifiedQRScan) 和按钮组件 (UnifiedQRButton)- 新增统一扫码页面 /passport/unified-qr/index - 迁移二维码登录页面路径:/pages/qr-login → /passport/qr-login - 更新管理员面板及用户卡片中的扫码入口为统一扫码- 移除旧的 QRLoginDemo 和 qr-test 测试页面- 补充统一扫码使用指南文档```
This commit is contained in:
@@ -26,24 +26,13 @@ const AdminPanel: React.FC<AdminPanelProps> = ({
|
||||
// 管理员功能列表
|
||||
const adminFeatures = [
|
||||
{
|
||||
id: 'store-verification',
|
||||
title: '门店核销',
|
||||
description: '扫码核销用户优惠券',
|
||||
id: 'unified-qr',
|
||||
title: '统一扫码',
|
||||
description: '扫码登录和核销一体化功能',
|
||||
icon: <Scan className="text-blue-500" size="24" />,
|
||||
color: 'bg-blue-50 border-blue-200',
|
||||
onClick: () => {
|
||||
navTo('/user/store/verification', true);
|
||||
onClose?.();
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'qr-login',
|
||||
title: '扫码登录',
|
||||
description: '扫码快速登录网页端',
|
||||
icon: <Scan className="text-green-500" size="24" />,
|
||||
color: 'bg-green-50 border-green-200',
|
||||
onClick: () => {
|
||||
navTo('/pages/qr-login/index', true);
|
||||
navTo('/passport/unified-qr/index', true);
|
||||
onClose?.();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -45,7 +45,7 @@ const QRLoginButton: React.FC<QRLoginButtonProps> = ({
|
||||
// 跳转到专门的扫码登录页面
|
||||
if (canScan()) {
|
||||
Taro.navigateTo({
|
||||
url: '/pages/qr-login/index'
|
||||
url: '/passport/qr-login/index'
|
||||
});
|
||||
} else {
|
||||
Taro.showToast({
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import { Button, Card } from '@nutui/nutui-react-taro';
|
||||
import { Scan, User } from '@nutui/icons-react-taro';
|
||||
import Taro from '@tarojs/taro';
|
||||
import QRLoginButton from './QRLoginButton';
|
||||
import QRScanModal from './QRScanModal';
|
||||
import { useUser } from '@/hooks/useUser';
|
||||
|
||||
/**
|
||||
* 扫码登录功能演示组件
|
||||
* 展示如何在页面中集成扫码登录功能
|
||||
*/
|
||||
const QRLoginDemo: React.FC = () => {
|
||||
const { user, getDisplayName } = useUser();
|
||||
const [showScanModal, setShowScanModal] = useState(false);
|
||||
const [loginHistory, setLoginHistory] = useState<any[]>([]);
|
||||
|
||||
// 处理扫码成功
|
||||
const handleScanSuccess = (result: any) => {
|
||||
console.log('扫码登录成功:', result);
|
||||
|
||||
// 添加到历史记录
|
||||
const newRecord = {
|
||||
id: Date.now(),
|
||||
time: new Date().toLocaleString(),
|
||||
success: true,
|
||||
userInfo: result.userInfo
|
||||
};
|
||||
setLoginHistory(prev => [newRecord, ...prev.slice(0, 4)]);
|
||||
};
|
||||
|
||||
// 处理扫码失败
|
||||
const handleScanError = (error: string) => {
|
||||
console.error('扫码登录失败:', error);
|
||||
|
||||
// 添加到历史记录
|
||||
const newRecord = {
|
||||
id: Date.now(),
|
||||
time: new Date().toLocaleString(),
|
||||
success: false,
|
||||
error
|
||||
};
|
||||
setLoginHistory(prev => [newRecord, ...prev.slice(0, 4)]);
|
||||
};
|
||||
|
||||
// 跳转到专门的扫码页面
|
||||
const goToQRLoginPage = () => {
|
||||
Taro.navigateTo({
|
||||
url: '/pages/qr-login/index'
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="qr-login-demo p-4">
|
||||
{/* 用户信息 */}
|
||||
<Card className="mb-4">
|
||||
<View className="p-4">
|
||||
<View className="flex items-center mb-4">
|
||||
<View className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center mr-3">
|
||||
<User className="text-blue-500" size="24" />
|
||||
</View>
|
||||
<View>
|
||||
<Text className="text-lg font-bold text-gray-800 block">
|
||||
{getDisplayName()}
|
||||
</Text>
|
||||
<Text className="text-sm text-gray-500 block">
|
||||
当前登录用户
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Card>
|
||||
|
||||
{/* 扫码登录方式 */}
|
||||
<Card className="mb-4">
|
||||
<View className="p-4">
|
||||
<Text className="text-lg font-bold mb-4 block">扫码登录方式</Text>
|
||||
|
||||
<View className="space-y-3">
|
||||
{/* 方式1: 直接扫码按钮 */}
|
||||
<View>
|
||||
<Text className="text-sm text-gray-600 mb-2 block">
|
||||
方式1: 直接扫码登录
|
||||
</Text>
|
||||
<QRLoginButton
|
||||
text="立即扫码登录"
|
||||
onSuccess={handleScanSuccess}
|
||||
onError={handleScanError}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* 方式2: 弹窗扫码 */}
|
||||
<View>
|
||||
<Text className="text-sm text-gray-600 mb-2 block">
|
||||
方式2: 弹窗扫码
|
||||
</Text>
|
||||
<Button
|
||||
type="success"
|
||||
size="normal"
|
||||
onClick={() => setShowScanModal(true)}
|
||||
>
|
||||
<Scan className="mr-2" />
|
||||
弹窗扫码
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
{/* 方式3: 跳转到专门页面 */}
|
||||
<View>
|
||||
<Text className="text-sm text-gray-600 mb-2 block">
|
||||
方式3: 专门页面
|
||||
</Text>
|
||||
<QRLoginButton
|
||||
text="进入扫码页面"
|
||||
type="warning"
|
||||
usePageMode={true}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* 方式4: 自定义按钮 */}
|
||||
<View>
|
||||
<Text className="text-sm text-gray-600 mb-2 block">
|
||||
方式4: 自定义跳转
|
||||
</Text>
|
||||
<Button
|
||||
type="default"
|
||||
size="normal"
|
||||
onClick={goToQRLoginPage}
|
||||
>
|
||||
自定义跳转
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Card>
|
||||
|
||||
{/* 登录历史 */}
|
||||
{loginHistory.length > 0 && (
|
||||
<Card>
|
||||
<View className="p-4">
|
||||
<Text className="text-lg font-bold mb-4 block">最近登录记录</Text>
|
||||
|
||||
<View className="space-y-2">
|
||||
{loginHistory.map((record) => (
|
||||
<View
|
||||
key={record.id}
|
||||
className="p-3 bg-gray-50 rounded-lg"
|
||||
>
|
||||
<View className="flex items-center justify-between">
|
||||
<View>
|
||||
<Text className={`text-sm font-medium ${
|
||||
record.success ? 'text-green-600' : 'text-red-600'
|
||||
} block`}>
|
||||
{record.success ? '登录成功' : '登录失败'}
|
||||
</Text>
|
||||
{record.error && (
|
||||
<Text className="text-xs text-red-500 block">
|
||||
{record.error}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<Text className="text-xs text-gray-500">
|
||||
{record.time}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 扫码弹窗 */}
|
||||
<QRScanModal
|
||||
visible={showScanModal}
|
||||
onClose={() => setShowScanModal(false)}
|
||||
onSuccess={handleScanSuccess}
|
||||
onError={handleScanError}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default QRLoginDemo;
|
||||
126
src/components/UnifiedQRButton.tsx
Normal file
126
src/components/UnifiedQRButton.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import React from 'react';
|
||||
import { Button } from '@nutui/nutui-react-taro';
|
||||
import { View } from '@tarojs/components';
|
||||
import { Scan } from '@nutui/icons-react-taro';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { useUnifiedQRScan, ScanType, type UnifiedScanResult } from '@/hooks/useUnifiedQRScan';
|
||||
|
||||
export interface UnifiedQRButtonProps {
|
||||
/** 按钮类型 */
|
||||
type?: 'primary' | 'success' | 'warning' | 'danger' | 'default';
|
||||
/** 按钮大小 */
|
||||
size?: 'large' | 'normal' | 'small';
|
||||
/** 按钮文本 */
|
||||
text?: string;
|
||||
/** 是否显示图标 */
|
||||
showIcon?: boolean;
|
||||
/** 自定义样式类名 */
|
||||
className?: string;
|
||||
/** 扫码成功回调 */
|
||||
onSuccess?: (result: UnifiedScanResult) => void;
|
||||
/** 扫码失败回调 */
|
||||
onError?: (error: string) => void;
|
||||
/** 是否使用页面模式(跳转到专门页面) */
|
||||
usePageMode?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一扫码按钮组件
|
||||
* 支持登录和核销两种类型的二维码扫描
|
||||
*/
|
||||
const UnifiedQRButton: React.FC<UnifiedQRButtonProps> = ({
|
||||
type = 'default',
|
||||
size = 'small',
|
||||
text = '扫码',
|
||||
showIcon = true,
|
||||
onSuccess,
|
||||
onError,
|
||||
usePageMode = false
|
||||
}) => {
|
||||
const { startScan, isLoading, canScan, state, result } = useUnifiedQRScan();
|
||||
console.log(result,'useUnifiedQRScan>>result')
|
||||
// 处理点击事件
|
||||
const handleClick = async () => {
|
||||
if (usePageMode) {
|
||||
// 跳转到专门的统一扫码页面
|
||||
if (canScan()) {
|
||||
Taro.navigateTo({
|
||||
url: '/passport/unified-qr/index'
|
||||
});
|
||||
} else {
|
||||
Taro.showToast({
|
||||
title: '请先登录小程序',
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 直接执行扫码
|
||||
try {
|
||||
const scanResult = await startScan();
|
||||
if (scanResult) {
|
||||
onSuccess?.(scanResult);
|
||||
|
||||
// 根据扫码类型给出不同的后续提示
|
||||
if (scanResult.type === ScanType.VERIFICATION) {
|
||||
// 核销成功后可以继续扫码
|
||||
setTimeout(() => {
|
||||
Taro.showModal({
|
||||
title: '核销成功',
|
||||
content: '是否继续扫码核销其他礼品卡?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
handleClick(); // 递归调用继续扫码
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
onError?.(error.message || '扫码失败');
|
||||
}
|
||||
};
|
||||
|
||||
const disabled = !canScan() || isLoading;
|
||||
|
||||
// 根据当前状态动态显示文本
|
||||
const getButtonText = () => {
|
||||
if (isLoading) {
|
||||
switch (state) {
|
||||
case 'scanning':
|
||||
return '扫码中...';
|
||||
case 'processing':
|
||||
return '处理中...';
|
||||
default:
|
||||
return '扫码中...';
|
||||
}
|
||||
}
|
||||
|
||||
if (disabled && !canScan()) {
|
||||
return '请先登录';
|
||||
}
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
type={type}
|
||||
size={size}
|
||||
loading={isLoading}
|
||||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<View className="flex items-center justify-center">
|
||||
{showIcon && !isLoading && (
|
||||
<Scan className="mr-1" />
|
||||
)}
|
||||
{getButtonText()}
|
||||
</View>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default UnifiedQRButton;
|
||||
Reference in New Issue
Block a user