```
feat(qr-login): 增加租户ID支持并优化登录成功处理逻辑- 在 QR 登码返回数据中新增 `tenantId` 字段,用于标识用户所属租户 - 登录成功后将 `tenantId` 存入 localStorage,便于后续权限判断 - 调整 `loginSuccess`事件传递参数,由 token 改为完整响应数据对象- 二维码组件增加点击刷新功能,提升用户体验 - 移除测试用的临时日志输出和无用的测试路由配置 -修复登录页图标显示逻辑,根据登录方式切换二维码/手机图标```
This commit is contained in:
@@ -0,0 +1 @@
|
||||
<svg width="70" height="70" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><linearGradient x1="17.621%" y1="50%" x2="100%" y2="50%" id="a"><stop stop-color="#FFE2B8" offset="0%"/><stop stop-color="#FFCA7C" offset="100%"/></linearGradient><filter x="-10.7%" y="-10.7%" width="121.4%" height="121.4%" filterUnits="objectBoundingBox" id="c"><feGaussianBlur stdDeviation="6" in="SourceAlpha" result="shadowBlurInner1"/><feOffset dx="3" in="shadowBlurInner1" result="shadowOffsetInner1"/><feComposite in="shadowOffsetInner1" in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="shadowInnerInner1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowInnerInner1"/></filter><path id="b" d="m1291 377 70 70v-70z"/></defs><g transform="translate(-1291 -377)" fill="none" fill-rule="evenodd"><use fill="url(#a)" xlink:href="#b"/><use fill="#000" filter="url(#c)" xlink:href="#b"/></g></svg>
|
||||
|
After Width: | Height: | Size: 953 B |
@@ -19,6 +19,7 @@ export interface QrCodeStatusResponse {
|
||||
accessToken?: string; // 登录成功时返回的JWT token
|
||||
userInfo?: any; // 用户信息
|
||||
expiresIn?: number; // 剩余过期时间(秒)
|
||||
tenantId?: string; // 租户ID
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +70,7 @@ export async function confirmQrLogin(requestData: QrLoginConfirmRequest): Promis
|
||||
SERVER_API_URL + '/qr-login/confirm',
|
||||
requestData
|
||||
);
|
||||
|
||||
console.log(res,'>>>89898989')
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
<p class="loading-text">正在生成二维码...</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="qrCodeStatus === 'active'" class="qr-active">
|
||||
<ele-qr-code-svg :value="qrCodeData" :size="200" />
|
||||
<div v-else-if="qrCodeStatus === 'active'" class="qr-active cursor-pointer">
|
||||
<ele-qr-code-svg :value="qrCodeData" :size="200" @click="refreshQrCode" />
|
||||
<p class="qr-tip">请使用手机APP或小程序扫码登录</p>
|
||||
<p class="qr-expire-tip">二维码有效期:{{ formatTime(expireTime) }}</p>
|
||||
</div>
|
||||
@@ -55,11 +55,11 @@ import {
|
||||
ExclamationCircleOutlined,
|
||||
ReloadOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { generateQrCode, checkQrCodeStatus, type QrCodeResponse } from '@/api/passport/qrLogin';
|
||||
import {generateQrCode, checkQrCodeStatus, type QrCodeResponse, QrCodeStatusResponse} from '@/api/passport/qrLogin';
|
||||
|
||||
// 定义组件事件
|
||||
const emit = defineEmits<{
|
||||
(e: 'loginSuccess', token: string): void;
|
||||
(e: 'loginSuccess', data: QrCodeStatusResponse): void;
|
||||
(e: 'loginError', error: string): void;
|
||||
}>();
|
||||
|
||||
@@ -131,9 +131,12 @@ const startStatusCheck = () => {
|
||||
break;
|
||||
case 'confirmed':
|
||||
// 登录成功
|
||||
if(status.tenantId){
|
||||
localStorage.setItem('TenantId', `${status.tenantId}`)
|
||||
}
|
||||
qrCodeStatus.value = 'active';
|
||||
stopAllTimers();
|
||||
emit('loginSuccess', status.accessToken || '');
|
||||
emit('loginSuccess', status);
|
||||
break;
|
||||
case 'expired':
|
||||
qrCodeStatus.value = 'expired';
|
||||
|
||||
@@ -45,11 +45,6 @@ export const routes = [
|
||||
component: () => import('@/components/QrLogin/demo.vue'),
|
||||
meta: { title: '二维码登录演示' }
|
||||
},
|
||||
{
|
||||
path: '/qr-test',
|
||||
component: () => import('@/views/test/qrLoginTest.vue'),
|
||||
meta: { title: '二维码登录接口测试' }
|
||||
},
|
||||
// {
|
||||
// path: '/forget',
|
||||
// component: () => import('@/views/passport/forget/index.vue'),
|
||||
|
||||
@@ -53,11 +53,12 @@
|
||||
</div>
|
||||
<div class="login-bar absolute top-0 z-50 right-0 cursor-pointer" @click="onScan">
|
||||
<div class="go-to-register cursor-pointer">
|
||||
<img src="https://img.alicdn.com/imgextra/i3/O1CN01yz6fEl1MwaRtkJyvf_!!6000000001499-55-tps-70-70.svg"
|
||||
<img src="@/assets/O1CN01yz6fEl1MwaRtkJyvf_!!6000000001499-55-tps-70-70.svg"
|
||||
alt=""/>
|
||||
</div>
|
||||
<div class="absolute top-3 right-3 text-lg text-white font-bold cursor-pointer">
|
||||
<QrcodeOutlined/>
|
||||
<div class="absolute top-2 right-2 text-lg text-white font-bold cursor-pointer">
|
||||
<QrcodeOutlined v-if="loginType === 'sms'"/>
|
||||
<MobileOutlined v-else/>
|
||||
</div>
|
||||
<!-- <span class="absolute top-3 right-1.5 text-sm text-white font-bold cursor-pointer">{{ '登录' }}</span>-->
|
||||
</div>
|
||||
@@ -266,6 +267,7 @@ import {
|
||||
LockOutlined,
|
||||
UserOutlined,
|
||||
QrcodeOutlined,
|
||||
MobileOutlined,
|
||||
SafetyCertificateOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import {goHomeRoute, cleanPageTabs} from '@/utils/page-tab-util';
|
||||
@@ -283,6 +285,7 @@ import {phoneReg} from 'ele-admin-pro';
|
||||
import router from "@/router";
|
||||
import {listAdminsByPhoneAll} from "@/api/system/user";
|
||||
import {getUserInfo} from "@/api/layout";
|
||||
import {QrCodeStatusResponse} from "@/api/passport/qrLogin";
|
||||
|
||||
const useForm = Form.useForm;
|
||||
const {currentRoute} = useRouter();
|
||||
@@ -523,15 +526,13 @@ const onScan = () => {
|
||||
}
|
||||
|
||||
/* 二维码登录成功处理 */
|
||||
const onQrLoginSuccess = async (token: string) => {
|
||||
const onQrLoginSuccess = async (item: QrCodeStatusResponse) => {
|
||||
// 设置token到localStorage或其他存储
|
||||
localStorage.setItem('access_token', token);
|
||||
localStorage.setItem('access_token', item.accessToken || '');
|
||||
message.success('扫码登录成功');
|
||||
const data = await getUserInfo();
|
||||
if (data) {
|
||||
localStorage.setItem('access_token', token);
|
||||
localStorage.setItem('TenantId', String(data.tenantId));
|
||||
localStorage.setItem('UserId', String(data.userId));
|
||||
if (item) {
|
||||
localStorage.setItem('access_token', `${item.accessToken}`);
|
||||
localStorage.setItem('TenantId', `${item.tenantId}`);
|
||||
cleanPageTabs();
|
||||
goHome();
|
||||
}
|
||||
|
||||
@@ -1,323 +0,0 @@
|
||||
<template>
|
||||
<div class="qr-login-test">
|
||||
<a-card title="二维码登录接口测试" :bordered="false">
|
||||
<a-space direction="vertical" style="width: 100%;" size="large">
|
||||
|
||||
<!-- 生成二维码测试 -->
|
||||
<a-card size="small" title="1. 生成二维码">
|
||||
<a-button type="primary" @click="testGenerateQrCode" :loading="generateLoading">
|
||||
生成二维码
|
||||
</a-button>
|
||||
<div v-if="qrCodeData" style="margin-top: 16px;">
|
||||
<a-descriptions :column="1" size="small">
|
||||
<a-descriptions-item label="Token">{{ qrCodeData.token }}</a-descriptions-item>
|
||||
<a-descriptions-item label="二维码内容">{{ qrCodeData.qrCode }}</a-descriptions-item>
|
||||
<a-descriptions-item label="过期时间">{{ qrCodeData.expiresIn }}秒</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
<div style="margin-top: 16px;">
|
||||
<ele-qr-code-svg :value="qrCodeUrl" :size="200" />
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<!-- 检查状态测试 -->
|
||||
<a-card size="small" title="2. 检查二维码状态">
|
||||
<a-space>
|
||||
<a-button @click="testCheckStatus" :loading="checkLoading" :disabled="!qrCodeData">
|
||||
检查状态
|
||||
</a-button>
|
||||
<a-switch
|
||||
v-model:checked="autoCheck"
|
||||
checked-children="自动检查"
|
||||
un-checked-children="手动检查"
|
||||
@change="onAutoCheckChange"
|
||||
/>
|
||||
</a-space>
|
||||
<div v-if="statusData" style="margin-top: 16px;">
|
||||
<a-descriptions :column="1" size="small">
|
||||
<a-descriptions-item label="状态">
|
||||
<a-tag :color="getStatusColor(statusData.status)">
|
||||
{{ getStatusText(statusData.status) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="剩余时间" v-if="statusData.expiresIn">
|
||||
{{ statusData.expiresIn }}秒
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="访问令牌" v-if="statusData.accessToken">
|
||||
{{ statusData.accessToken.substring(0, 50) }}...
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<!-- 扫码标记测试 -->
|
||||
<a-card size="small" title="3. 扫码标记">
|
||||
<a-button @click="testScanQrCode" :loading="scanLoading" :disabled="!qrCodeData">
|
||||
模拟扫码
|
||||
</a-button>
|
||||
</a-card>
|
||||
|
||||
<!-- 确认登录测试 -->
|
||||
<a-card size="small" title="4. 确认登录">
|
||||
<a-space>
|
||||
<a-input-number
|
||||
v-model:value="testUserId"
|
||||
placeholder="用户ID"
|
||||
:min="1"
|
||||
style="width: 120px;"
|
||||
/>
|
||||
<a-button @click="testConfirmLogin" :loading="confirmLoading" :disabled="!qrCodeData">
|
||||
确认登录
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-card>
|
||||
|
||||
<!-- 操作日志 -->
|
||||
<a-card size="small" title="操作日志">
|
||||
<div class="log-container">
|
||||
<div
|
||||
v-for="(log, index) in logs"
|
||||
:key="index"
|
||||
class="log-item"
|
||||
:class="log.type"
|
||||
>
|
||||
<span class="log-time">{{ log.time }}</span>
|
||||
<span class="log-message">{{ log.message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
</a-space>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onUnmounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import {
|
||||
generateQrCode,
|
||||
checkQrCodeStatus,
|
||||
scanQrCode,
|
||||
confirmQrLogin,
|
||||
type QrCodeResponse,
|
||||
type QrCodeStatusResponse,
|
||||
type QrLoginConfirmRequest
|
||||
} from '@/api/passport/qrLogin';
|
||||
|
||||
// 响应式数据
|
||||
const generateLoading = ref(false);
|
||||
const checkLoading = ref(false);
|
||||
const scanLoading = ref(false);
|
||||
const confirmLoading = ref(false);
|
||||
const autoCheck = ref(false);
|
||||
const testUserId = ref<number>(1);
|
||||
|
||||
const qrCodeData = ref<QrCodeResponse | null>(null);
|
||||
const statusData = ref<QrCodeStatusResponse | null>(null);
|
||||
const logs = ref<Array<{time: string, message: string, type: string}>>([]);
|
||||
|
||||
// 自动检查定时器
|
||||
let autoCheckTimer: number | null = null;
|
||||
|
||||
// 计算二维码URL
|
||||
const qrCodeUrl = computed(() => {
|
||||
if (!qrCodeData.value) return '';
|
||||
const baseUrl = window.location.origin;
|
||||
return `${baseUrl}/qr-confirm?qrCodeKey=${qrCodeData.value.token}`;
|
||||
});
|
||||
|
||||
// 添加日志
|
||||
const addLog = (message: string, type: 'info' | 'success' | 'error' | 'warning' = 'info') => {
|
||||
const now = new Date();
|
||||
const time = now.toLocaleTimeString();
|
||||
logs.value.unshift({ time, message, type });
|
||||
|
||||
if (logs.value.length > 100) {
|
||||
logs.value = logs.value.slice(0, 100);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取状态颜色
|
||||
const getStatusColor = (status: string) => {
|
||||
const colors = {
|
||||
pending: 'blue',
|
||||
scanned: 'orange',
|
||||
confirmed: 'green',
|
||||
expired: 'red'
|
||||
};
|
||||
return colors[status] || 'default';
|
||||
};
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status: string) => {
|
||||
const texts = {
|
||||
pending: '等待扫码',
|
||||
scanned: '已扫码',
|
||||
confirmed: '已确认',
|
||||
expired: '已过期'
|
||||
};
|
||||
return texts[status] || status;
|
||||
};
|
||||
|
||||
// 测试生成二维码
|
||||
const testGenerateQrCode = async () => {
|
||||
generateLoading.value = true;
|
||||
try {
|
||||
const response = await generateQrCode();
|
||||
qrCodeData.value = response;
|
||||
statusData.value = null;
|
||||
addLog(`生成二维码成功: ${response.token}`, 'success');
|
||||
message.success('生成二维码成功');
|
||||
} catch (error: any) {
|
||||
addLog(`生成二维码失败: ${error.message}`, 'error');
|
||||
message.error(error.message);
|
||||
} finally {
|
||||
generateLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 测试检查状态
|
||||
const testCheckStatus = async () => {
|
||||
if (!qrCodeData.value) return;
|
||||
|
||||
checkLoading.value = true;
|
||||
try {
|
||||
const response = await checkQrCodeStatus(qrCodeData.value.token);
|
||||
statusData.value = response;
|
||||
addLog(`检查状态成功: ${response.status}`, 'info');
|
||||
} catch (error: any) {
|
||||
addLog(`检查状态失败: ${error.message}`, 'error');
|
||||
message.error(error.message);
|
||||
} finally {
|
||||
checkLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 测试扫码标记
|
||||
const testScanQrCode = async () => {
|
||||
if (!qrCodeData.value) return;
|
||||
|
||||
scanLoading.value = true;
|
||||
try {
|
||||
await scanQrCode(qrCodeData.value.token);
|
||||
addLog('扫码标记成功', 'success');
|
||||
message.success('扫码标记成功');
|
||||
|
||||
// 自动检查状态
|
||||
setTimeout(() => {
|
||||
testCheckStatus();
|
||||
}, 1000);
|
||||
} catch (error: any) {
|
||||
addLog(`扫码标记失败: ${error.message}`, 'error');
|
||||
message.error(error.message);
|
||||
} finally {
|
||||
scanLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 测试确认登录
|
||||
const testConfirmLogin = async () => {
|
||||
if (!qrCodeData.value || !testUserId.value) return;
|
||||
|
||||
confirmLoading.value = true;
|
||||
try {
|
||||
const requestData: QrLoginConfirmRequest = {
|
||||
token: qrCodeData.value.token,
|
||||
userId: testUserId.value,
|
||||
platform: 'web'
|
||||
};
|
||||
|
||||
const response = await confirmQrLogin(requestData);
|
||||
statusData.value = response;
|
||||
addLog(`确认登录成功: ${response.status}`, 'success');
|
||||
message.success('确认登录成功');
|
||||
} catch (error: any) {
|
||||
addLog(`确认登录失败: ${error.message}`, 'error');
|
||||
message.error(error.message);
|
||||
} finally {
|
||||
confirmLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 自动检查状态变化
|
||||
const onAutoCheckChange = (checked: boolean) => {
|
||||
if (checked) {
|
||||
autoCheckTimer = window.setInterval(() => {
|
||||
if (qrCodeData.value) {
|
||||
testCheckStatus();
|
||||
}
|
||||
}, 3000);
|
||||
addLog('开启自动检查状态', 'info');
|
||||
} else {
|
||||
if (autoCheckTimer) {
|
||||
clearInterval(autoCheckTimer);
|
||||
autoCheckTimer = null;
|
||||
}
|
||||
addLog('关闭自动检查状态', 'info');
|
||||
}
|
||||
};
|
||||
|
||||
// 组件卸载时清理定时器
|
||||
onUnmounted(() => {
|
||||
if (autoCheckTimer) {
|
||||
clearInterval(autoCheckTimer);
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化日志
|
||||
addLog('二维码登录接口测试页面已加载', 'info');
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.qr-login-test {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.log-container {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
background: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.log-item {
|
||||
display: flex;
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.log-time {
|
||||
color: #999;
|
||||
margin-right: 8px;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.log-message {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&.info .log-message {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&.success .log-message {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
&.error .log-message {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
&.warning .log-message {
|
||||
color: #faad14;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user