From e47e34825a69e042217c50843e4213c98de877bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Mon, 22 Sep 2025 15:44:44 +0800 Subject: [PATCH] =?UTF-8?q?```=20feat(passport):=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E6=89=AB=E7=A0=81=E5=8A=9F=E8=83=BD=E5=B9=B6?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E4=BA=8C=E7=BB=B4=E7=A0=81=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将原有的扫码登录和扫码核销功能合并为统一扫码功能,支持智能识别二维码类型, 自动执行相应操作。同时将二维码登录相关页面迁移到 /passport 路径下,优化用户体验与代码结构。 主要变更: - 新增统一扫码 Hook (useUnifiedQRScan) 和按钮组件 (UnifiedQRButton)- 新增统一扫码页面 /passport/unified-qr/index - 迁移二维码登录页面路径:/pages/qr-login → /passport/qr-login - 更新管理员面板及用户卡片中的扫码入口为统一扫码- 移除旧的 QRLoginDemo 和 qr-test 测试页面- 补充统一扫码使用指南文档``` --- README_QR_LOGIN.md | 8 +- docs/QR_LOGIN_INTEGRATION.md | 4 +- docs/QR_LOGIN_USAGE.md | 6 +- docs/UNIFIED_QR_SCAN_GUIDE.md | 212 +++++++++++ src/api/qr-login/index.ts | 30 +- src/app.config.ts | 8 +- src/components/AdminPanel.tsx | 19 +- src/components/QRLoginButton.tsx | 2 +- src/components/QRLoginDemo.tsx | 184 ---------- src/components/UnifiedQRButton.tsx | 126 +++++++ src/hooks/useUnifiedQRScan.ts | 332 ++++++++++++++++++ src/pages/qr-test/index.config.ts | 5 - src/pages/qr-test/index.tsx | 33 -- src/pages/user/components/UserCard.tsx | 41 +-- .../qr-confirm/index.config.ts | 0 src/{pages => passport}/qr-confirm/index.tsx | 4 +- .../qr-login/index.config.ts | 0 src/{pages => passport}/qr-login/index.tsx | 0 src/passport/unified-qr/index.config.ts | 4 + src/passport/unified-qr/index.tsx | 320 +++++++++++++++++ 20 files changed, 1036 insertions(+), 302 deletions(-) create mode 100644 docs/UNIFIED_QR_SCAN_GUIDE.md delete mode 100644 src/components/QRLoginDemo.tsx create mode 100644 src/components/UnifiedQRButton.tsx create mode 100644 src/hooks/useUnifiedQRScan.ts delete mode 100644 src/pages/qr-test/index.config.ts delete mode 100644 src/pages/qr-test/index.tsx rename src/{pages => passport}/qr-confirm/index.config.ts (100%) rename src/{pages => passport}/qr-confirm/index.tsx (98%) rename src/{pages => passport}/qr-login/index.config.ts (100%) rename src/{pages => passport}/qr-login/index.tsx (100%) create mode 100644 src/passport/unified-qr/index.config.ts create mode 100644 src/passport/unified-qr/index.tsx diff --git a/README_QR_LOGIN.md b/README_QR_LOGIN.md index f1f4ba1..e362af2 100644 --- a/README_QR_LOGIN.md +++ b/README_QR_LOGIN.md @@ -79,7 +79,7 @@ import QRScanModal from '@/components/QRScanModal'; import Taro from '@tarojs/taro'; Taro.navigateTo({ - url: '/pages/qr-login/index' + url: '/passport/qr-login/index' }); ``` @@ -93,18 +93,18 @@ Taro.navigateTo({ ## 📱 页面说明 -### 1. 扫码登录页面 (`/pages/qr-login/index`) +### 1. 扫码登录页面 (`/passport/qr-login/index`) - 完整的扫码登录功能 - 用户信息显示 - 登录历史记录 - 使用说明和安全提示 -### 2. 登录确认页面 (`/pages/qr-confirm/index`) +### 2. 登录确认页面 (`/passport/qr-confirm/index`) - 处理二维码跳转确认 - 支持URL参数:`qrCodeKey` 或 `token` - 用户确认界面 -### 3. 功能测试页面 (`/pages/qr-test/index`) +### 3. 功能测试页面 (`/passport/qr-test/index`) - 演示各种集成方式 - 功能测试和调试 diff --git a/docs/QR_LOGIN_INTEGRATION.md b/docs/QR_LOGIN_INTEGRATION.md index ecfacf1..e0f186d 100644 --- a/docs/QR_LOGIN_INTEGRATION.md +++ b/docs/QR_LOGIN_INTEGRATION.md @@ -98,7 +98,7 @@ const { ``` ### 4. 页面层 -文件:`src/pages/qr-login/index.tsx` +文件:`src/passport/qr-login/index.tsx` 专门的扫码登录页面,提供完整的用户体验: - 用户信息展示 @@ -155,7 +155,7 @@ import Taro from '@tarojs/taro'; const handleQRLogin = () => { Taro.navigateTo({ - url: '/pages/qr-login/index' + url: '/passport/qr-login/index' }); }; ``` diff --git a/docs/QR_LOGIN_USAGE.md b/docs/QR_LOGIN_USAGE.md index 81699bf..ab942ba 100644 --- a/docs/QR_LOGIN_USAGE.md +++ b/docs/QR_LOGIN_USAGE.md @@ -105,7 +105,7 @@ import QRLoginButton from '@/components/QRLoginButton'; // 或者自定义跳转 + +// 现在 + +``` + +#### 管理员面板更新 +管理员面板自动合并了原有的两个扫码功能,无需额外操作。 + +## 🚀 优势总结 + +### 用户体验 +- ✅ **简化操作**: 一个按钮处理所有扫码需求 +- ✅ **智能识别**: 无需用户手动选择扫码类型 +- ✅ **统一界面**: 一致的交互体验 + +### 开发维护 +- ✅ **代码复用**: 统一的扫码逻辑和错误处理 +- ✅ **易于扩展**: 新增扫码类型只需修改识别逻辑 +- ✅ **降低复杂度**: 减少重复代码和功能入口 + +### 功能完整性 +- ✅ **保留所有原功能**: 登录和核销功能完全保留 +- ✅ **增强用户体验**: 添加历史记录和智能提示 +- ✅ **向后兼容**: 原有的单独页面仍然可用 + +## 🔧 配置说明 + +### 页面路由配置 +需要在 `app.config.ts` 中添加新页面路由: + +```typescript +export default { + pages: [ + // ... 其他页面 + 'passport/unified-qr/index' + ] +} +``` + +### 权限要求 +- **扫码权限**: 所有用户都可以扫码 +- **登录功能**: 需要用户已登录小程序 +- **核销功能**: 需要管理员权限 + +## 🎯 未来规划 + +### 扩展可能性 +- 支持更多类型的二维码(商品码、活动码等) +- 增加扫码统计和分析功能 +- 支持批量扫码操作 +- 增加扫码记录的云端同步 + +### 性能优化 +- 扫码响应速度优化 +- 二维码识别准确率提升 +- 网络请求优化和缓存机制 diff --git a/src/api/qr-login/index.ts b/src/api/qr-login/index.ts index 291ce16..13c15e3 100644 --- a/src/api/qr-login/index.ts +++ b/src/api/qr-login/index.ts @@ -145,7 +145,6 @@ export async function confirmWechatQRLogin(token: string, userId: number) { try { // 获取微信用户信息 const userInfo = await getUserInfo(); - console.log('获取微信用户信息3:', userInfo) const data: ConfirmLoginParam = { token, @@ -169,37 +168,10 @@ export async function confirmWechatQRLogin(token: string, userId: number) { } return Promise.reject(new Error(res.message)); } catch (error: any) { - return Promise.reject(new Error(error.message || '确认登录失败')); + return Promise.reject(new Error(error.message || '22确认登录失败')); } } -/** - * 获取微信用户信息 - */ -async function getUserProfile() { - return new Promise((resolve, reject) => { - Taro.getUserProfile({ - desc: '用于扫码登录身份确认', - success: (res) => { - resolve(res.userInfo); - }, - fail: (err) => { - // 如果获取失败,尝试使用已存储的用户信息 - const storedUser = Taro.getStorageSync('User'); - if (storedUser) { - resolve({ - nickName: storedUser.nickname, - avatarUrl: storedUser.avatar, - gender: storedUser.gender - }); - } else { - reject(err); - } - } - }); - }); -} - /** * 获取设备信息 */ diff --git a/src/app.config.ts b/src/app.config.ts index 1aa5fec..88fdfb6 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -4,9 +4,6 @@ export default { 'pages/cart/cart', 'pages/find/find', 'pages/user/user', - 'pages/qr-login/index', - 'pages/qr-confirm/index', - 'pages/qr-test/index', 'pages/cms/category/index' ], "subpackages": [ @@ -18,7 +15,10 @@ export default { "forget", "setting", "agreement", - "sms-login" + "sms-login", + 'qr-login/index', + 'qr-confirm/index', + 'unified-qr/index' ] }, { diff --git a/src/components/AdminPanel.tsx b/src/components/AdminPanel.tsx index 30aa14c..8aa3d9b 100644 --- a/src/components/AdminPanel.tsx +++ b/src/components/AdminPanel.tsx @@ -26,24 +26,13 @@ const AdminPanel: React.FC = ({ // 管理员功能列表 const adminFeatures = [ { - id: 'store-verification', - title: '门店核销', - description: '扫码核销用户优惠券', + id: 'unified-qr', + title: '统一扫码', + description: '扫码登录和核销一体化功能', icon: , color: 'bg-blue-50 border-blue-200', onClick: () => { - navTo('/user/store/verification', true); - onClose?.(); - } - }, - { - id: 'qr-login', - title: '扫码登录', - description: '扫码快速登录网页端', - icon: , - color: 'bg-green-50 border-green-200', - onClick: () => { - navTo('/pages/qr-login/index', true); + navTo('/passport/unified-qr/index', true); onClose?.(); } }, diff --git a/src/components/QRLoginButton.tsx b/src/components/QRLoginButton.tsx index 5775db3..fff3a87 100644 --- a/src/components/QRLoginButton.tsx +++ b/src/components/QRLoginButton.tsx @@ -45,7 +45,7 @@ const QRLoginButton: React.FC = ({ // 跳转到专门的扫码登录页面 if (canScan()) { Taro.navigateTo({ - url: '/pages/qr-login/index' + url: '/passport/qr-login/index' }); } else { Taro.showToast({ diff --git a/src/components/QRLoginDemo.tsx b/src/components/QRLoginDemo.tsx deleted file mode 100644 index 1be26e7..0000000 --- a/src/components/QRLoginDemo.tsx +++ /dev/null @@ -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([]); - - // 处理扫码成功 - 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 ( - - {/* 用户信息 */} - - - - - - - - - {getDisplayName()} - - - 当前登录用户 - - - - - - - {/* 扫码登录方式 */} - - - 扫码登录方式 - - - {/* 方式1: 直接扫码按钮 */} - - - 方式1: 直接扫码登录 - - - - - {/* 方式2: 弹窗扫码 */} - - - 方式2: 弹窗扫码 - - - - - {/* 方式3: 跳转到专门页面 */} - - - 方式3: 专门页面 - - - - - {/* 方式4: 自定义按钮 */} - - - 方式4: 自定义跳转 - - - - - - - - {/* 登录历史 */} - {loginHistory.length > 0 && ( - - - 最近登录记录 - - - {loginHistory.map((record) => ( - - - - - {record.success ? '登录成功' : '登录失败'} - - {record.error && ( - - {record.error} - - )} - - - {record.time} - - - - ))} - - - - )} - - {/* 扫码弹窗 */} - setShowScanModal(false)} - onSuccess={handleScanSuccess} - onError={handleScanError} - /> - - ); -}; - -export default QRLoginDemo; diff --git a/src/components/UnifiedQRButton.tsx b/src/components/UnifiedQRButton.tsx new file mode 100644 index 0000000..18fee99 --- /dev/null +++ b/src/components/UnifiedQRButton.tsx @@ -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 = ({ + 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 ( + + ); +}; + +export default UnifiedQRButton; diff --git a/src/hooks/useUnifiedQRScan.ts b/src/hooks/useUnifiedQRScan.ts new file mode 100644 index 0000000..b34d055 --- /dev/null +++ b/src/hooks/useUnifiedQRScan.ts @@ -0,0 +1,332 @@ +import { useState, useCallback, useRef, useEffect } from 'react'; +import Taro from '@tarojs/taro'; +import { + confirmWechatQRLogin, + parseQRContent +} from '@/api/qr-login'; +import { getShopGiftByCode, updateShopGift, decryptQrData } from "@/api/shop/shopGift"; +import { useUser } from "@/hooks/useUser"; +import { isValidJSON } from "@/utils/jsonUtils"; +import dayjs from 'dayjs'; + +/** + * 统一扫码状态 + */ +export enum UnifiedScanState { + IDLE = 'idle', // 空闲状态 + SCANNING = 'scanning', // 正在扫码 + PROCESSING = 'processing', // 正在处理 + SUCCESS = 'success', // 处理成功 + ERROR = 'error' // 处理失败 +} + +/** + * 扫码类型 + */ +export enum ScanType { + LOGIN = 'login', // 登录二维码 + VERIFICATION = 'verification', // 核销二维码 + UNKNOWN = 'unknown' // 未知类型 +} + +/** + * 统一扫码结果 + */ +export interface UnifiedScanResult { + type: ScanType; + data: any; + message: string; +} + +/** + * 统一扫码Hook + * 可以处理登录和核销两种类型的二维码 + */ +export function useUnifiedQRScan() { + const { isAdmin } = useUser(); + const [state, setState] = useState(UnifiedScanState.IDLE); + const [error, setError] = useState(''); + const [result, setResult] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [scanType, setScanType] = useState(ScanType.UNKNOWN); + + // 用于取消操作的引用 + const cancelRef = useRef(false); + + /** + * 重置状态 + */ + const reset = useCallback(() => { + setState(UnifiedScanState.IDLE); + setError(''); + setResult(null); + setIsLoading(false); + setScanType(ScanType.UNKNOWN); + cancelRef.current = false; + }, []); + + /** + * 检测二维码类型 + */ + const detectScanType = useCallback((scanResult: string): ScanType => { + try { + // 1. 检查是否为JSON格式(核销二维码) + if (isValidJSON(scanResult)) { + const json = JSON.parse(scanResult); + if (json.businessType === 'gift' && json.token && json.data) { + return ScanType.VERIFICATION; + } + } + + // 2. 检查是否为登录二维码 + const loginToken = parseQRContent(scanResult); + if (loginToken) { + return ScanType.LOGIN; + } + + // 3. 检查是否为纯文本核销码(6位数字) + if (/^\d{6}$/.test(scanResult.trim())) { + return ScanType.VERIFICATION; + } + + return ScanType.UNKNOWN; + } catch (error) { + console.error('检测二维码类型失败:', error); + return ScanType.UNKNOWN; + } + }, []); + + /** + * 处理登录二维码 + */ + const handleLoginQR = useCallback(async (scanResult: string): Promise => { + const userId = Taro.getStorageSync('UserId'); + if (!userId) { + throw new Error('请先登录小程序'); + } + + const token = parseQRContent(scanResult); + if (!token) { + throw new Error('无效的登录二维码'); + } + + const confirmResult = await confirmWechatQRLogin(token, parseInt(userId)); + + if (confirmResult.success) { + return { + type: ScanType.LOGIN, + data: confirmResult, + message: '登录确认成功' + }; + } else { + throw new Error(confirmResult.message || '登录确认失败'); + } + }, []); + + /** + * 处理核销二维码 + */ + const handleVerificationQR = useCallback(async (scanResult: string): Promise => { + if (!isAdmin()) { + throw new Error('您没有核销权限'); + } + + let code = ''; + + // 判断是否为加密的JSON格式 + if (isValidJSON(scanResult)) { + const json = JSON.parse(scanResult); + if (json.businessType === 'gift' && json.token && json.data) { + // 解密获取核销码 + const decryptedData = await decryptQrData({ + token: json.token, + encryptedData: json.data + }); + + if (decryptedData) { + code = decryptedData.toString(); + } else { + throw new Error('解密失败'); + } + } + } else { + // 直接使用扫码结果作为核销码 + code = scanResult.trim(); + } + + if (!code) { + throw new Error('无法获取有效的核销码'); + } + + // 验证核销码 + const gift = await getShopGiftByCode(code); + + if (!gift) { + throw new Error('核销码无效'); + } + + if (gift.status === 1) { + throw new Error('此礼品码已使用'); + } + + if (gift.status === 2) { + throw new Error('此礼品码已失效'); + } + + if (gift.userId === 0) { + throw new Error('此礼品码未认领'); + } + + // 执行核销 + await updateShopGift({ + ...gift, + status: 1, + operatorUserId: Number(Taro.getStorageSync('UserId')) || 0, + takeTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), + verificationTime: dayjs().format('YYYY-MM-DD HH:mm:ss') + }); + + return { + type: ScanType.VERIFICATION, + data: gift, + message: '核销成功' + }; + }, [isAdmin]); + + /** + * 开始扫码 + */ + const startScan = useCallback(async (): Promise => { + try { + reset(); + setState(UnifiedScanState.SCANNING); + + // 调用扫码API + const scanResult = await new Promise((resolve, reject) => { + Taro.scanCode({ + onlyFromCamera: true, + scanType: ['qrCode'], + success: (res) => { + if (res.result) { + resolve(res.result); + } else { + reject(new Error('扫码结果为空')); + } + }, + fail: (err) => { + reject(new Error(err.errMsg || '扫码失败')); + } + }); + }); + + // 检查是否被取消 + if (cancelRef.current) { + return null; + } + + // 检测二维码类型 + const type = detectScanType(scanResult); + setScanType(type); + + if (type === ScanType.UNKNOWN) { + throw new Error('不支持的二维码类型'); + } + + // 开始处理 + setState(UnifiedScanState.PROCESSING); + setIsLoading(true); + + let result: UnifiedScanResult; + + switch (type) { + case ScanType.LOGIN: + result = await handleLoginQR(scanResult); + break; + case ScanType.VERIFICATION: + result = await handleVerificationQR(scanResult); + break; + default: + throw new Error('未知的扫码类型'); + } + + if (cancelRef.current) { + return null; + } + + setState(UnifiedScanState.SUCCESS); + setResult(result); + + // 显示成功提示 + Taro.showToast({ + title: result.message, + icon: 'success', + duration: 2000 + }); + + return result; + + } catch (err: any) { + if (!cancelRef.current) { + setState(UnifiedScanState.ERROR); + const errorMessage = err.message || '处理失败'; + setError(errorMessage); + + // 显示错误提示 + Taro.showToast({ + title: errorMessage, + icon: 'error', + duration: 3000 + }); + } + return null; + } finally { + setIsLoading(false); + } + }, [reset, detectScanType, handleLoginQR, handleVerificationQR]); + + /** + * 取消扫码 + */ + const cancel = useCallback(() => { + cancelRef.current = true; + reset(); + }, [reset]); + + /** + * 检查是否可以进行扫码 + */ + const canScan = useCallback(() => { + const userId = Taro.getStorageSync('UserId'); + const accessToken = Taro.getStorageSync('access_token'); + return !!(userId && accessToken); + }, []); + + // 组件卸载时取消操作 + useEffect(() => { + return () => { + cancelRef.current = true; + }; + }, []); + + return { + // 状态 + state, + error, + result, + isLoading, + scanType, + + // 方法 + startScan, + cancel, + reset, + canScan, + + // 便捷状态判断 + isIdle: state === UnifiedScanState.IDLE, + isScanning: state === UnifiedScanState.SCANNING, + isProcessing: state === UnifiedScanState.PROCESSING, + isSuccess: state === UnifiedScanState.SUCCESS, + isError: state === UnifiedScanState.ERROR + }; +} diff --git a/src/pages/qr-test/index.config.ts b/src/pages/qr-test/index.config.ts deleted file mode 100644 index 3e9b6bf..0000000 --- a/src/pages/qr-test/index.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default { - navigationBarTitleText: '扫码登录测试', - navigationBarTextStyle: 'black', - navigationBarBackgroundColor: '#ffffff' -} diff --git a/src/pages/qr-test/index.tsx b/src/pages/qr-test/index.tsx deleted file mode 100644 index 3781f60..0000000 --- a/src/pages/qr-test/index.tsx +++ /dev/null @@ -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 ( - - - - {/* 页面标题 */} - - - - 扫码登录功能测试 - - - 测试各种扫码登录集成方式 - - - - - {/* 演示组件 */} - - - - ); -}; - -export default QRTestPage; diff --git a/src/pages/user/components/UserCard.tsx b/src/pages/user/components/UserCard.tsx index b60dfb4..164fa72 100644 --- a/src/pages/user/components/UserCard.tsx +++ b/src/pages/user/components/UserCard.tsx @@ -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((_, ref) => { - const { - isAdmin - } = useUser(); const {data, refresh} = useUserData() const {getDisplayName, getRoleName} = useUser(); const [IsLogin, setIsLogin] = useState(false) @@ -213,19 +208,25 @@ const UserCard = forwardRef((_, ref) => { marginTop: '30px', marginRight: '10px' }}> - {/*扫码登录*/} - - {!isAdmin() && - - } + {/*统一扫码入口 - 支持登录和核销*/} + { + console.log('统一扫码成功:', result); + // 根据扫码类型给出不同的提示 + if (result.type === 'verification') { + // 核销成功,可以显示更多信息或跳转到详情页 + Taro.showModal({ + title: '核销成功', + content: `已成功核销的品类:${result.data.goodsName || '礼品卡'},面值¥${result.data.faceValue}` + }); + } + }} + onError={(error) => { + console.error('统一扫码失败:', error); + }} + /> diff --git a/src/pages/qr-confirm/index.config.ts b/src/passport/qr-confirm/index.config.ts similarity index 100% rename from src/pages/qr-confirm/index.config.ts rename to src/passport/qr-confirm/index.config.ts diff --git a/src/pages/qr-confirm/index.tsx b/src/passport/qr-confirm/index.tsx similarity index 98% rename from src/pages/qr-confirm/index.tsx rename to src/passport/qr-confirm/index.tsx index afe765b..9c8b8ba 100644 --- a/src/pages/qr-confirm/index.tsx +++ b/src/passport/qr-confirm/index.tsx @@ -191,12 +191,12 @@ const QRConfirmPage: React.FC = () => { ) : ( - + + + )} + + {state === 'scanning' && ( + <> + + + 扫码中... + + + 请对准二维码 + + + + )} + + {state === 'processing' && ( + <> + + + + + 处理中... + + + {scanType === ScanType.LOGIN ? '正在确认登录' : + scanType === ScanType.VERIFICATION ? '正在核销礼品卡' : '正在处理'} + + + )} + + {state === 'success' && result && ( + <> + + + {result.message} + + {result.type === ScanType.VERIFICATION && result.data && ( + + + 礼品卡:{result.data.goodsName || '未知商品'} + + + 面值:¥{result.data.faceValue} + + + )} + + + + + + )} + + {state === 'error' && ( + <> + + + 操作失败 + + + {error || '未知错误'} + + + + + + + )} + + + + {/* 扫码历史 */} + {scanHistory.length > 0 && ( + + + + 最近扫码记录 + + + {scanHistory.map((record, index) => ( + + + {getStatusIcon(record.success, record.type)} + + + {record.type && getTypeTag(record.type)} + + {record.time} + + + + {record.success ? record.message : record.error} + + {record.success && record.type === ScanType.VERIFICATION && record.data && ( + + {record.data.goodsName} - ¥{record.data.faceValue} + + )} + + + + ))} + + + )} + + {/* 功能说明 */} + + + + + + + 功能说明 + + + • 登录二维码:自动确认网页端登录 + + + • 核销二维码:门店核销用户礼品卡 + + + • 系统会自动识别二维码类型并执行相应操作 + + + + + + + ); +}; + +export default UnifiedQRPage;