feat(admin): 实现管理员模式切换和扫码登录功能
- 新增管理员模式切换方案,统一管理所有管理员功能 - 实现扫码登录功能,支持用户通过小程序扫描网页端二维码快速登录 - 添加管理员面板组件,集中展示所有管理员功能 - 开发扫码登录按钮和扫描器组件,方便集成到不同页面 - 优化用户界面设计,提高管理员用户的使用体验
This commit is contained in:
227
src/hooks/useQRLogin.ts
Normal file
227
src/hooks/useQRLogin.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
import { useState, useCallback, useRef, useEffect } from 'react';
|
||||
import Taro from '@tarojs/taro';
|
||||
import {
|
||||
confirmWechatQRLogin,
|
||||
parseQRContent,
|
||||
type ConfirmLoginResult
|
||||
} from '@/api/qr-login';
|
||||
|
||||
/**
|
||||
* 扫码登录状态
|
||||
*/
|
||||
export enum ScanLoginState {
|
||||
IDLE = 'idle', // 空闲状态
|
||||
SCANNING = 'scanning', // 正在扫码
|
||||
CONFIRMING = 'confirming', // 正在确认登录
|
||||
SUCCESS = 'success', // 登录成功
|
||||
ERROR = 'error' // 登录失败
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫码登录Hook
|
||||
*/
|
||||
export function useQRLogin() {
|
||||
const [state, setState] = useState<ScanLoginState>(ScanLoginState.IDLE);
|
||||
const [error, setError] = useState<string>('');
|
||||
const [result, setResult] = useState<ConfirmLoginResult | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// 用于取消操作的引用
|
||||
const cancelRef = useRef<boolean>(false);
|
||||
|
||||
/**
|
||||
* 重置状态
|
||||
*/
|
||||
const reset = useCallback(() => {
|
||||
setState(ScanLoginState.IDLE);
|
||||
setError('');
|
||||
setResult(null);
|
||||
setIsLoading(false);
|
||||
cancelRef.current = false;
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* 开始扫码登录
|
||||
*/
|
||||
const startScan = useCallback(async () => {
|
||||
try {
|
||||
reset();
|
||||
setState(ScanLoginState.SCANNING);
|
||||
|
||||
// 检查用户是否已登录
|
||||
const userId = Taro.getStorageSync('UserId');
|
||||
if (!userId) {
|
||||
throw new Error('请先登录小程序');
|
||||
}
|
||||
|
||||
// 调用扫码API
|
||||
const scanResult = await new Promise<string>((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;
|
||||
}
|
||||
|
||||
// 解析二维码内容
|
||||
const token = parseQRContent(scanResult);
|
||||
if (!token) {
|
||||
throw new Error('无效的登录二维码');
|
||||
}
|
||||
|
||||
// 确认登录
|
||||
setState(ScanLoginState.CONFIRMING);
|
||||
setIsLoading(true);
|
||||
|
||||
const confirmResult = await confirmWechatQRLogin(token, parseInt(userId));
|
||||
|
||||
if (cancelRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirmResult.success) {
|
||||
setState(ScanLoginState.SUCCESS);
|
||||
setResult(confirmResult);
|
||||
|
||||
// 显示成功提示
|
||||
Taro.showToast({
|
||||
title: '登录确认成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
} else {
|
||||
throw new Error(confirmResult.message || '登录确认失败');
|
||||
}
|
||||
|
||||
} catch (err: any) {
|
||||
if (!cancelRef.current) {
|
||||
setState(ScanLoginState.ERROR);
|
||||
const errorMessage = err.message || '扫码登录失败';
|
||||
setError(errorMessage);
|
||||
|
||||
// 显示错误提示
|
||||
Taro.showToast({
|
||||
title: errorMessage,
|
||||
icon: 'error',
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [reset]);
|
||||
|
||||
/**
|
||||
* 取消扫码登录
|
||||
*/
|
||||
const cancel = useCallback(() => {
|
||||
cancelRef.current = true;
|
||||
reset();
|
||||
}, [reset]);
|
||||
|
||||
/**
|
||||
* 处理扫码结果(用于已有扫码结果的情况)
|
||||
*/
|
||||
const handleScanResult = useCallback(async (qrContent: string) => {
|
||||
try {
|
||||
reset();
|
||||
setState(ScanLoginState.CONFIRMING);
|
||||
setIsLoading(true);
|
||||
|
||||
// 检查用户是否已登录
|
||||
const userId = Taro.getStorageSync('UserId');
|
||||
if (!userId) {
|
||||
throw new Error('请先登录小程序');
|
||||
}
|
||||
|
||||
// 解析二维码内容
|
||||
const token = parseQRContent(qrContent);
|
||||
if (!token) {
|
||||
throw new Error('无效的登录二维码');
|
||||
}
|
||||
|
||||
// 确认登录
|
||||
const confirmResult = await confirmWechatQRLogin(token, parseInt(userId));
|
||||
|
||||
if (confirmResult.success) {
|
||||
setState(ScanLoginState.SUCCESS);
|
||||
setResult(confirmResult);
|
||||
|
||||
// 显示成功提示
|
||||
Taro.showToast({
|
||||
title: '登录确认成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
} else {
|
||||
throw new Error(confirmResult.message || '登录确认失败');
|
||||
}
|
||||
|
||||
} catch (err: any) {
|
||||
setState(ScanLoginState.ERROR);
|
||||
const errorMessage = err.message || '登录确认失败';
|
||||
setError(errorMessage);
|
||||
|
||||
// 显示错误提示
|
||||
Taro.showToast({
|
||||
title: errorMessage,
|
||||
icon: 'error',
|
||||
duration: 3000
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [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,
|
||||
|
||||
// 方法
|
||||
startScan,
|
||||
cancel,
|
||||
reset,
|
||||
handleScanResult,
|
||||
canScan,
|
||||
|
||||
// 便捷状态判断
|
||||
isIdle: state === ScanLoginState.IDLE,
|
||||
isScanning: state === ScanLoginState.SCANNING,
|
||||
isConfirming: state === ScanLoginState.CONFIRMING,
|
||||
isSuccess: state === ScanLoginState.SUCCESS,
|
||||
isError: state === ScanLoginState.ERROR
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user