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

248
src/api/qr-login/index.ts Normal file
View File

@@ -0,0 +1,248 @@
import request from '@/utils/request';
import type { ApiResult } from '@/api';
import Taro from '@tarojs/taro';
/**
* 扫码登录相关接口
*/
// 生成扫码token请求参数
export interface GenerateQRTokenParam {
// 客户端类型web, app, wechat
clientType?: string;
// 设备信息
deviceInfo?: string;
// 过期时间(分钟)
expireMinutes?: number;
}
// 生成扫码token响应
export interface GenerateQRTokenResult {
// 扫码token
token: string;
// 二维码内容通常是包含token的URL或JSON
qrContent: string;
// 过期时间戳
expireTime: number;
// 二维码图片URL可选
qrImageUrl?: string;
}
// 扫码状态枚举
export enum QRLoginStatus {
PENDING = 'pending', // 等待扫码
SCANNED = 'scanned', // 已扫码,等待确认
CONFIRMED = 'confirmed', // 已确认登录
EXPIRED = 'expired', // 已过期
CANCELLED = 'cancelled' // 已取消
}
// 检查扫码状态响应
export interface QRLoginStatusResult {
// 当前状态
status: QRLoginStatus;
// 状态描述
message?: string;
// 如果已确认登录返回JWT token
accessToken?: string;
// 用户信息
userInfo?: {
userId: number;
nickname?: string;
avatar?: string;
phone?: string;
};
// 剩余有效时间(秒)
remainingTime?: number;
}
// 确认登录请求参数
export interface ConfirmLoginParam {
// 扫码token
token: string;
// 用户ID
userId: number;
// 登录平台web, app, wechat
platform?: string;
// 微信用户信息当platform为wechat时
wechatInfo?: {
openid?: string;
unionid?: string;
nickname?: string;
avatar?: string;
gender?: number;
};
// 设备信息
deviceInfo?: string;
}
// 确认登录响应
export interface ConfirmLoginResult {
// 是否成功
success: boolean;
// 消息
message: string;
// 登录用户信息
userInfo?: {
userId: number;
nickname?: string;
avatar?: string;
phone?: string;
};
}
/**
* 生成扫码登录token
*/
export async function generateQRToken(data?: GenerateQRTokenParam) {
const res = await request.post<ApiResult<GenerateQRTokenResult>>(
'http://127.0.0.1:9200/api/qr-login/generate',
{
clientType: 'wechat',
expireMinutes: 5,
...data
}
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* 检查扫码登录状态
*/
export async function checkQRLoginStatus(token: string) {
const res = await request.get<ApiResult<QRLoginStatusResult>>(
`http://127.0.0.1:9200/api/qr-login/status/${token}`
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* 确认扫码登录(通用接口)
*/
export async function confirmQRLogin(data: ConfirmLoginParam) {
const res = await request.post<ApiResult<ConfirmLoginResult>>(
'http://127.0.0.1:9200/api/qr-login/confirm',
data
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* 微信小程序扫码登录确认(便捷接口)
*/
export async function confirmWechatQRLogin(token: string, userId: number) {
try {
// 获取微信用户信息
const userInfo = await getUserProfile();
const data: ConfirmLoginParam = {
token,
userId,
platform: 'wechat',
wechatInfo: {
nickname: userInfo?.nickName,
avatar: userInfo?.avatarUrl,
gender: userInfo?.gender
},
deviceInfo: await getDeviceInfo()
};
const res = await request.post<ApiResult<ConfirmLoginResult>>(
'http://127.0.0.1:9200/api/qr-login/wechat-confirm',
data
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
} catch (error: any) {
return Promise.reject(new Error(error.message || '确认登录失败'));
}
}
/**
* 获取微信用户信息
*/
async function getUserProfile() {
return new Promise<any>((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);
}
}
});
});
}
/**
* 获取设备信息
*/
async function getDeviceInfo() {
return new Promise<string>((resolve) => {
Taro.getSystemInfo({
success: (res) => {
const deviceInfo = {
platform: res.platform,
system: res.system,
version: res.version,
model: res.model,
brand: res.brand,
screenWidth: res.screenWidth,
screenHeight: res.screenHeight
};
resolve(JSON.stringify(deviceInfo));
},
fail: () => {
resolve('unknown');
}
});
});
}
/**
* 解析二维码内容提取token
*/
export function parseQRContent(qrContent: string): string | null {
try {
// 尝试解析JSON格式
if (qrContent.startsWith('{')) {
const parsed = JSON.parse(qrContent);
return parsed.token || null;
}
// 尝试解析URL格式
if (qrContent.includes('token=')) {
const url = new URL(qrContent);
return url.searchParams.get('token');
}
// 直接返回内容作为token
return qrContent;
} catch (error) {
console.error('解析二维码内容失败:', error);
return null;
}
}