```
feat(passport): 实现统一扫码功能并迁移二维码登录页面 将原有的扫码登录和扫码核销功能合并为统一扫码功能,支持智能识别二维码类型, 自动执行相应操作。同时将二维码登录相关页面迁移到 /passport 路径下,优化用户体验与代码结构。 主要变更: - 新增统一扫码 Hook (useUnifiedQRScan) 和按钮组件 (UnifiedQRButton)- 新增统一扫码页面 /passport/unified-qr/index - 迁移二维码登录页面路径:/pages/qr-login → /passport/qr-login - 更新管理员面板及用户卡片中的扫码入口为统一扫码- 移除旧的 QRLoginDemo 和 qr-test 测试页面- 补充统一扫码使用指南文档```
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
export default {
|
||||
navigationBarTitleText: '确认登录',
|
||||
navigationBarTextStyle: 'black',
|
||||
navigationBarBackgroundColor: '#ffffff'
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import { Button, Loading, Card } from '@nutui/nutui-react-taro';
|
||||
import { Success, Failure, Tips, User } from '@nutui/icons-react-taro';
|
||||
import Taro, { useRouter } from '@tarojs/taro';
|
||||
import { confirmQRLogin } from '@/api/qr-login';
|
||||
import { useUser } from '@/hooks/useUser';
|
||||
|
||||
/**
|
||||
* 扫码登录确认页面
|
||||
* 用于处理从二维码跳转过来的登录确认
|
||||
*/
|
||||
const QRConfirmPage: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const { user, getDisplayName } = useUser();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [confirmed, setConfirmed] = useState(false);
|
||||
const [error, setError] = useState<string>('');
|
||||
const [token, setToken] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
// 从URL参数中获取token
|
||||
const { qrCodeKey, token: urlToken } = router.params;
|
||||
const loginToken = qrCodeKey || urlToken;
|
||||
|
||||
if (loginToken) {
|
||||
setToken(loginToken);
|
||||
} else {
|
||||
setError('无效的登录链接');
|
||||
}
|
||||
}, [router.params]);
|
||||
|
||||
// 确认登录
|
||||
const handleConfirmLogin = async () => {
|
||||
if (!token) {
|
||||
setError('缺少登录token');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!user?.userId) {
|
||||
setError('请先登录小程序');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
const result = await confirmQRLogin({
|
||||
token,
|
||||
userId: user.userId,
|
||||
platform: 'wechat',
|
||||
wechatInfo: {
|
||||
nickname: user.nickname,
|
||||
avatar: user.avatar
|
||||
}
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
setConfirmed(true);
|
||||
Taro.showToast({
|
||||
title: '登录确认成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
// 3秒后自动返回
|
||||
setTimeout(() => {
|
||||
Taro.navigateBack();
|
||||
}, 3000);
|
||||
} else {
|
||||
setError(result.message || '登录确认失败');
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err.message || '登录确认失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 取消登录
|
||||
const handleCancel = () => {
|
||||
Taro.navigateBack();
|
||||
};
|
||||
|
||||
// 重试
|
||||
const handleRetry = () => {
|
||||
setError('');
|
||||
setConfirmed(false);
|
||||
handleConfirmLogin();
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="qr-confirm-page min-h-screen bg-gray-50">
|
||||
<View className="p-4">
|
||||
{/* 主要内容卡片 */}
|
||||
<Card className="bg-white rounded-lg shadow-sm">
|
||||
<View className="p-6 text-center">
|
||||
{/* 图标 */}
|
||||
<View className="mb-6">
|
||||
{loading ? (
|
||||
<View className="w-16 h-16 mx-auto flex items-center justify-center">
|
||||
<Loading className="text-blue-500" />
|
||||
</View>
|
||||
) : confirmed ? (
|
||||
<View className="w-16 h-16 mx-auto bg-green-100 rounded-full flex items-center justify-center">
|
||||
<Success className="text-green-500" />
|
||||
</View>
|
||||
) : error ? (
|
||||
<View className="w-16 h-16 mx-auto bg-red-100 rounded-full flex items-center justify-center">
|
||||
<Failure className="text-red-500" />
|
||||
</View>
|
||||
) : (
|
||||
<View className="w-16 h-16 mx-auto bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<User size="32" className="text-blue-500" />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 标题 */}
|
||||
<Text className="text-xl font-bold text-gray-800 mb-2 block">
|
||||
{loading ? '正在确认登录...' :
|
||||
confirmed ? '登录确认成功' :
|
||||
error ? '登录确认失败' : '确认登录'}
|
||||
</Text>
|
||||
|
||||
{/* 描述 */}
|
||||
<Text className="text-gray-600 mb-6 block">
|
||||
{loading ? '请稍候,正在为您确认登录' :
|
||||
confirmed ? '您已成功确认登录,网页端将自动登录' :
|
||||
error ? error :
|
||||
`确认使用 ${getDisplayName()} 登录网页端?`}
|
||||
</Text>
|
||||
|
||||
{/* 用户信息 */}
|
||||
{!loading && !confirmed && !error && user && (
|
||||
<View className="bg-gray-50 rounded-lg p-4 mb-6">
|
||||
<View className="flex items-center justify-center">
|
||||
<View className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center mr-3">
|
||||
<User className="text-blue-500" size="20" />
|
||||
</View>
|
||||
<View className="text-left">
|
||||
<Text className="text-sm font-medium text-gray-800 block">
|
||||
{user.nickname || user.username || '用户'}
|
||||
</Text>
|
||||
<Text className="text-xs text-gray-500 block">
|
||||
ID: {user.userId}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<View className="space-y-3">
|
||||
{loading ? (
|
||||
<Button
|
||||
type="default"
|
||||
size="large"
|
||||
disabled
|
||||
className="w-full"
|
||||
>
|
||||
确认中...
|
||||
</Button>
|
||||
) : confirmed ? (
|
||||
<Button
|
||||
type="success"
|
||||
size="large"
|
||||
onClick={handleCancel}
|
||||
className="w-full"
|
||||
>
|
||||
完成
|
||||
</Button>
|
||||
) : error ? (
|
||||
<View className="space-y-2">
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={handleRetry}
|
||||
className="w-full"
|
||||
>
|
||||
重试
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
size="large"
|
||||
onClick={handleCancel}
|
||||
className="w-full"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</View>
|
||||
) : (
|
||||
<View className="space-y-2">
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={handleConfirmLogin}
|
||||
className="w-full"
|
||||
disabled={!token || !user?.userId}
|
||||
>
|
||||
确认登录
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
size="large"
|
||||
onClick={handleCancel}
|
||||
className="w-full"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</Card>
|
||||
|
||||
{/* 安全提示 */}
|
||||
<Card className="bg-yellow-50 border border-yellow-200 rounded-lg mt-4">
|
||||
<View className="p-4">
|
||||
<View className="flex items-start">
|
||||
<Tips className="text-yellow-600 mr-2 mt-1" size="16" />
|
||||
<View>
|
||||
<Text className="text-sm font-medium text-yellow-800 mb-1 block">
|
||||
安全提示
|
||||
</Text>
|
||||
<Text className="text-xs text-yellow-700 block">
|
||||
请确认这是您本人的登录操作。如果不是,请点击取消并检查账户安全。
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Card>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default QRConfirmPage;
|
||||
@@ -1,5 +0,0 @@
|
||||
export default {
|
||||
navigationBarTitleText: '扫码登录',
|
||||
navigationBarTextStyle: 'black',
|
||||
navigationBarBackgroundColor: '#ffffff'
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import { Card, Divider, Button } from '@nutui/nutui-react-taro';
|
||||
import { Scan, Success, Failure, Tips } from '@nutui/icons-react-taro';
|
||||
import Taro from '@tarojs/taro';
|
||||
import QRLoginScanner from '@/components/QRLoginScanner';
|
||||
import { useUser } from '@/hooks/useUser';
|
||||
|
||||
/**
|
||||
* 扫码登录页面
|
||||
*/
|
||||
const QRLoginPage: React.FC = () => {
|
||||
const [loginHistory, setLoginHistory] = useState<any[]>([]);
|
||||
const { getDisplayName } = useUser();
|
||||
|
||||
// 处理扫码成功
|
||||
const handleScanSuccess = (result: any) => {
|
||||
console.log('扫码登录成功:', result);
|
||||
|
||||
// 添加到登录历史
|
||||
const newRecord = {
|
||||
id: Date.now(),
|
||||
time: new Date().toLocaleString(),
|
||||
userInfo: result.userInfo,
|
||||
success: true
|
||||
};
|
||||
setLoginHistory(prev => [newRecord, ...prev.slice(0, 4)]); // 只保留最近5条记录
|
||||
|
||||
// 显示成功提示
|
||||
Taro.showToast({
|
||||
title: '登录确认成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
};
|
||||
|
||||
// 处理扫码失败
|
||||
const handleScanError = (error: string) => {
|
||||
console.error('扫码登录失败:', error);
|
||||
|
||||
// 添加到登录历史
|
||||
const newRecord = {
|
||||
id: Date.now(),
|
||||
time: new Date().toLocaleString(),
|
||||
error,
|
||||
success: false
|
||||
};
|
||||
setLoginHistory(prev => [newRecord, ...prev.slice(0, 4)]);
|
||||
};
|
||||
|
||||
// 返回上一页
|
||||
// const handleBack = () => {
|
||||
// Taro.navigateBack();
|
||||
// };
|
||||
|
||||
// 清除历史记录
|
||||
const clearHistory = () => {
|
||||
setLoginHistory([]);
|
||||
Taro.showToast({
|
||||
title: '已清除历史记录',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="qr-login-page min-h-screen bg-gray-50">
|
||||
{/* 导航栏 */}
|
||||
{/*<NavBar*/}
|
||||
{/* title="扫码登录"*/}
|
||||
{/* leftShow*/}
|
||||
{/* onBackClick={handleBack}*/}
|
||||
{/* leftText={<ArrowLeft />}*/}
|
||||
{/* className="bg-white"*/}
|
||||
{/*/>*/}
|
||||
|
||||
{/* 主要内容 */}
|
||||
<View className="p-4">
|
||||
{/* 用户信息卡片 */}
|
||||
<Card className="bg-white rounded-lg shadow-sm">
|
||||
<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">
|
||||
<Scan className="text-blue-500" size="24" />
|
||||
</View>
|
||||
<View>
|
||||
<Text className="text-lg font-bold text-gray-800">
|
||||
{getDisplayName()}
|
||||
</Text>
|
||||
<Text className="text-sm text-gray-500">
|
||||
使用小程序扫码快速登录网页端
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 扫码登录组件 */}
|
||||
<QRLoginScanner
|
||||
onSuccess={handleScanSuccess}
|
||||
onError={handleScanError}
|
||||
buttonText="开始扫码登录"
|
||||
showStatus={true}
|
||||
/>
|
||||
</View>
|
||||
</Card>
|
||||
|
||||
{/* 使用说明 */}
|
||||
<Card className="bg-white rounded-lg shadow-sm">
|
||||
<View className="p-4">
|
||||
<View className="flex items-center mb-3">
|
||||
<Tips className="text-orange-500 mr-2" />
|
||||
<Text className="font-medium text-gray-800">使用说明</Text>
|
||||
</View>
|
||||
<View className="text-sm text-gray-600">
|
||||
<Text className="block">1. 在电脑或其他设备上打开网页端登录页面</Text>
|
||||
<Text className="block">2. 点击"扫码登录"按钮,显示登录二维码</Text>
|
||||
<Text className="block">3. 使用此功能扫描二维码即可快速登录</Text>
|
||||
<Text className="block">4. 扫码成功后,网页端将自动完成登录</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Card>
|
||||
|
||||
{/* 登录历史 */}
|
||||
{loginHistory.length > 0 && (
|
||||
<Card className="bg-white rounded-lg shadow-sm">
|
||||
<View className="p-4">
|
||||
<View className="flex items-center justify-between mb-3">
|
||||
<Text className="font-medium text-gray-800">最近登录记录</Text>
|
||||
<Button
|
||||
size="small"
|
||||
type="default"
|
||||
onClick={clearHistory}
|
||||
className="text-xs"
|
||||
>
|
||||
清除
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
{loginHistory.map((record, index) => (
|
||||
<View key={record.id}>
|
||||
<View className="flex items-center justify-between">
|
||||
<View className="flex items-center">
|
||||
{record.success ? (
|
||||
<Success className="text-green-500 mr-2" size="16" />
|
||||
) : (
|
||||
<Failure className="text-red-500 mr-2" size="16" />
|
||||
)}
|
||||
<View>
|
||||
<Text className="text-sm text-gray-800">
|
||||
{record.success ? '登录成功' : '登录失败'}
|
||||
</Text>
|
||||
{record.error && (
|
||||
<Text className="text-xs text-red-500">
|
||||
{record.error}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
<Text className="text-xs text-gray-500">
|
||||
{record.time}
|
||||
</Text>
|
||||
</View>
|
||||
{index < loginHistory.length - 1 && (
|
||||
<Divider className="my-2" />
|
||||
)}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 安全提示 */}
|
||||
<Card className="bg-yellow-50 border border-yellow-200 rounded-lg">
|
||||
<View className="p-4">
|
||||
<View className="flex items-start">
|
||||
<Tips className="text-yellow-600 mr-2 mt-1" size="16" />
|
||||
<View>
|
||||
<Text className="text-sm font-medium text-yellow-800 mb-1">
|
||||
安全提示
|
||||
</Text>
|
||||
<Text className="text-xs text-yellow-700">
|
||||
请确保只扫描来自官方网站的登录二维码,避免扫描来源不明的二维码,保护账户安全。
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Card>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default QRLoginPage;
|
||||
@@ -1,5 +0,0 @@
|
||||
export default {
|
||||
navigationBarTitleText: '扫码登录测试',
|
||||
navigationBarTextStyle: 'black',
|
||||
navigationBarBackgroundColor: '#ffffff'
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import { Card } from '@nutui/nutui-react-taro';
|
||||
import QRLoginDemo from '@/components/QRLoginDemo';
|
||||
import QRLoginButton from "@/components/QRLoginButton";
|
||||
|
||||
/**
|
||||
* 扫码登录测试页面
|
||||
*/
|
||||
const QRTestPage = () => {
|
||||
return (
|
||||
<View className="qr-test-page min-h-screen bg-gray-50">
|
||||
<QRLoginButton />
|
||||
<View className="p-4">
|
||||
{/* 页面标题 */}
|
||||
<Card className="mb-4">
|
||||
<View className="p-4 text-center">
|
||||
<Text className="text-xl font-bold text-gray-800 mb-2 block">
|
||||
扫码登录功能测试
|
||||
</Text>
|
||||
<Text className="text-sm text-gray-600 block">
|
||||
测试各种扫码登录集成方式
|
||||
</Text>
|
||||
</View>
|
||||
</Card>
|
||||
|
||||
{/* 演示组件 */}
|
||||
<QRLoginDemo />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default QRTestPage;
|
||||
@@ -1,8 +1,6 @@
|
||||
import {Button} from '@nutui/nutui-react-taro'
|
||||
import {Avatar, Tag, Space} from '@nutui/nutui-react-taro'
|
||||
import {Avatar, Tag, Space, Button} from '@nutui/nutui-react-taro'
|
||||
import {View, Text, Image} from '@tarojs/components'
|
||||
import {getUserInfo, getWxOpenId} from '@/api/layout';
|
||||
import {Scan} from '@nutui/icons-react-taro';
|
||||
import Taro from '@tarojs/taro';
|
||||
import {useEffect, useState, forwardRef, useImperativeHandle} from "react";
|
||||
import {User} from "@/api/system/user/model";
|
||||
@@ -11,12 +9,9 @@ import {TenantId} from "@/config/app";
|
||||
import {useUser} from "@/hooks/useUser";
|
||||
import {useUserData} from "@/hooks/useUserData";
|
||||
import {getStoredInviteParams} from "@/utils/invite";
|
||||
import QRLoginButton from "@/components/QRLoginButton";
|
||||
import UnifiedQRButton from "@/components/UnifiedQRButton";
|
||||
|
||||
const UserCard = forwardRef<any, any>((_, ref) => {
|
||||
const {
|
||||
isAdmin
|
||||
} = useUser();
|
||||
const {data, refresh} = useUserData()
|
||||
const {getDisplayName, getRoleName} = useUser();
|
||||
const [IsLogin, setIsLogin] = useState<boolean>(false)
|
||||
@@ -213,19 +208,25 @@ const UserCard = forwardRef<any, any>((_, ref) => {
|
||||
marginTop: '30px',
|
||||
marginRight: '10px'
|
||||
}}>
|
||||
{/*扫码登录*/}
|
||||
<QRLoginButton/>
|
||||
{!isAdmin() &&
|
||||
<Button
|
||||
size={'small'}
|
||||
onClick={() => navTo('/user/store/verification', true)}
|
||||
>
|
||||
<View className="flex items-center justify-center">
|
||||
<Scan className="mr-1"/>
|
||||
扫码核销
|
||||
</View>
|
||||
</Button>
|
||||
}
|
||||
{/*统一扫码入口 - 支持登录和核销*/}
|
||||
<UnifiedQRButton
|
||||
text="扫码"
|
||||
size="small"
|
||||
onSuccess={(result) => {
|
||||
console.log('统一扫码成功:', result);
|
||||
// 根据扫码类型给出不同的提示
|
||||
if (result.type === 'verification') {
|
||||
// 核销成功,可以显示更多信息或跳转到详情页
|
||||
Taro.showModal({
|
||||
title: '核销成功',
|
||||
content: `已成功核销的品类:${result.data.goodsName || '礼品卡'},面值¥${result.data.faceValue}`
|
||||
});
|
||||
}
|
||||
}}
|
||||
onError={(error) => {
|
||||
console.error('统一扫码失败:', error);
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
</View>
|
||||
<View className={'flex justify-around mt-1'}>
|
||||
|
||||
Reference in New Issue
Block a user