feat(admin): 实现管理员模式切换和扫码登录功能

- 新增管理员模式切换方案,统一管理所有管理员功能
- 实现扫码登录功能,支持用户通过小程序扫描网页端二维码快速登录
- 添加管理员面板组件,集中展示所有管理员功能
- 开发扫码登录按钮和扫描器组件,方便集成到不同页面
- 优化用户界面设计,提高管理员用户的使用体验
This commit is contained in:
2025-09-01 14:26:00 +08:00
parent 7a7d8b4605
commit cbcf591f71
16 changed files with 1785 additions and 33 deletions

View File

@@ -0,0 +1,5 @@
export default {
navigationBarTitleText: '扫码登录',
navigationBarTextStyle: 'black',
navigationBarBackgroundColor: '#ffffff'
}

View File

@@ -0,0 +1,193 @@
import React, { useState } from 'react';
import { View, Text } from '@tarojs/components';
import { NavBar, Card, Divider, Button } from '@nutui/nutui-react-taro';
import { ArrowLeft, 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 space-y-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="space-y-2 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 className="space-y-3">
{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;

View File

@@ -11,6 +11,9 @@ import {TenantId} from "@/config/app";
import {useUser} from "@/hooks/useUser";
import {useUserData} from "@/hooks/useUserData";
import {getStoredInviteParams} from "@/utils/invite";
import {useQRLogin} from "@/hooks/useQRLogin";
import {useAdminMode} from "@/hooks/useAdminMode";
import AdminPanel from "@/components/AdminPanel";
const UserCard = forwardRef<any, any>((_, ref) => {
const {
@@ -21,6 +24,15 @@ const UserCard = forwardRef<any, any>((_, ref) => {
const [IsLogin, setIsLogin] = useState<boolean>(false)
const [userInfo, setUserInfo] = useState<User>()
// 扫码登录Hook
const { startScan, isLoading: isScanLoading } = useQRLogin();
// 管理员模式Hook
const { isAdminMode, toggleAdminMode } = useAdminMode();
// 管理员面板显示状态
const [showAdminPanel, setShowAdminPanel] = useState(false);
// 下拉刷新
const handleRefresh = async () => {
await refresh()
@@ -168,6 +180,25 @@ const UserCard = forwardRef<any, any>((_, ref) => {
})
}
// 处理扫码登录
const handleQRLogin = async () => {
try {
await startScan();
} catch (error) {
console.error('扫码登录失败:', error);
}
};
// 打开管理员面板
const handleOpenAdminPanel = () => {
setShowAdminPanel(true);
};
// 关闭管理员面板
const handleCloseAdminPanel = () => {
setShowAdminPanel(false);
};
return (
<View className={'header-bg pt-20'}>
<View className={'p-4'}>
@@ -205,7 +236,31 @@ const UserCard = forwardRef<any, any>((_, ref) => {
) : ''}
</View>
</View>
{isAdmin() && <Scan onClick={() => navTo('/user/store/verification', true)} />}
{isAdmin() && (
<View className="flex items-center space-x-2">
{/* 管理员模式切换按钮 */}
<View
className={`px-3 py-1 rounded-full text-xs font-medium transition-all ${
isAdminMode
? 'bg-blue-500 text-white'
: 'bg-gray-100 text-gray-600 border border-gray-300'
}`}
onClick={toggleAdminMode}
>
{isAdminMode ? '管理员' : '普通用户'}
</View>
{/* 管理员功能入口 */}
{isAdminMode && (
<View
className="px-3 py-1 bg-blue-50 border border-blue-200 rounded-full text-xs text-blue-600 font-medium"
onClick={handleOpenAdminPanel}
>
</View>
)}
</View>
)}
<View className={'mr-4 text-sm px-3 py-1 text-black border-gray-400 border-solid border-2 rounded-3xl'}
onClick={() => navTo('/user/profile/profile', true)}>
{'个人资料'}
@@ -234,6 +289,14 @@ const UserCard = forwardRef<any, any>((_, ref) => {
</View>
</View>
</View>
{/* 管理员面板 */}
{isAdmin() && (
<AdminPanel
visible={showAdminPanel}
onClose={handleCloseAdminPanel}
/>
)}
</View>
)