Merge remote-tracking branch 'origin/ai-hub' into ai-hub
This commit is contained in:
@@ -1,6 +1,21 @@
|
|||||||
import { get } from '@/utils/request';
|
import { get, post } from '@/utils/request';
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
export function getUserInfo() {
|
export function getUserInfo() {
|
||||||
return get<any>('/ai-chat/account').json();
|
return get<any>('/ai-chat/account').json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取二维码 LoginOrRegister 登录注册, Bind 绑定
|
||||||
|
export function getQrCode(data: any) {
|
||||||
|
return post<any>('/fuwuhao/qrcode', data).json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扫码轮询
|
||||||
|
// 0=Wait, 1=Login, 2=Register, 3=Bind, 10=Expired
|
||||||
|
export function getQrCodeResult(data: any) {
|
||||||
|
return get<any>('/fuwuhao/qrcode/result', data).json();
|
||||||
|
}
|
||||||
|
// 注册微信授权
|
||||||
|
export function getWechatAuth(data: any) {
|
||||||
|
return post<any>('/fuwuhao/register', data).json();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Check, Picture as IconPicture, Refresh } from '@element-plus/icons-vue';
|
import { Check, Picture as IconPicture, Refresh } from '@element-plus/icons-vue';
|
||||||
import { useCountdown } from '@vueuse/core';
|
import { useCountdown } from '@vueuse/core';
|
||||||
import { useQRCode } from '@vueuse/integrations/useQRCode';
|
|
||||||
import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue';
|
import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue';
|
||||||
|
import { getQrCode, getQrCodeResult, getWechatAuth } from '@/api';
|
||||||
|
|
||||||
// 响应式状态
|
// 响应式状态
|
||||||
const urlText = shallowRef('');
|
// const urlText = shallowRef('');
|
||||||
const qrCodeUrl = useQRCode(urlText);
|
const qrCodeUrl = ref('');
|
||||||
const isExpired = ref(false);
|
const isExpired = ref(false);
|
||||||
const isScanned = ref(false); // 新增:是否已扫码
|
const isScanned = ref(false);
|
||||||
const isConfirming = ref(false); // 新增:是否进入确认登录阶段
|
const isConfirming = ref(false);
|
||||||
const confirmCountdownSeconds = shallowRef(180); // 确认登录倒计时(3分钟)
|
const confirmCountdownSeconds = shallowRef(180);
|
||||||
|
const sceneStr = ref(''); // 场景值,用于标识二维码
|
||||||
|
|
||||||
// 二维码倒计时实例
|
// 二维码倒计时实例
|
||||||
const { start: qrStart, stop: qrStop } = useCountdown(shallowRef(60), {
|
const { start: qrStart, stop: qrStop } = useCountdown(shallowRef(600), {
|
||||||
interval: 1000,
|
interval: 1000,
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
isExpired.value = true;
|
isExpired.value = true;
|
||||||
stopPolling(); // 二维码过期时停止轮询
|
stopPolling();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -27,47 +28,204 @@ const { start: confirmStart, stop: confirmStop } = useCountdown(confirmCountdown
|
|||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
isExpired.value = true;
|
isExpired.value = true;
|
||||||
isConfirming.value = false;
|
isConfirming.value = false;
|
||||||
stopPolling(); // 确认倒计时结束时停止轮询
|
stopPolling();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 轮询相关
|
// 轮询相关
|
||||||
let scanPolling: number | null = null;
|
let statusPolling: number | null = null;
|
||||||
let confirmPolling: number | null = null;
|
|
||||||
|
|
||||||
// 模拟后端接口 这里返回新的二维码地址
|
// 获取登录二维码图片和二维码标识
|
||||||
async function fetchNewQRCode() {
|
async function fetchQRCodeInfo() {
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
try {
|
||||||
return `https://login-api.com/qr/${Date.now()}`;
|
const param = {
|
||||||
|
sceneType: 'LoginOrRegister',
|
||||||
|
};
|
||||||
|
const response = await getQrCode(param);
|
||||||
|
console.log('response---', response);
|
||||||
|
// {
|
||||||
|
// "code": 200,
|
||||||
|
// "data": {
|
||||||
|
// "qrCodeUrl": "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=gQG17zwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAySnR6NzBJcGxhTC0xQmR4TXhFY1UAAgT1XrBoAwRYAgAA",
|
||||||
|
// "scene": "8baf623f79dc452880c6832415deb26a"
|
||||||
|
// },
|
||||||
|
// "msg": "success"
|
||||||
|
// }
|
||||||
|
if (response && response.data.qrCodeUrl && response.data.scene) {
|
||||||
|
qrCodeUrl.value = response.data.qrCodeUrl;
|
||||||
|
sceneStr.value = response.data.scene;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('获取二维码失败:', error);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
// 模拟后端接口 这里返回是否已扫码
|
|
||||||
async function checkScanStatus() {
|
|
||||||
// 模拟扫码状态接口(实际应调用后端接口)
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
|
||||||
return Math.random() > 0.3; // 30%概率未扫码,70%概率已扫码
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模拟后端接口 这里返回扫码后是否已确认
|
// 轮询二维码状态
|
||||||
async function checkConfirmStatus() {
|
async function checkQRCodeStatus() {
|
||||||
// 模拟确认登录接口(实际应调用后端接口)
|
if (!sceneStr.value)
|
||||||
await new Promise(resolve => setTimeout(resolve, 200));
|
return;
|
||||||
return Math.random() > 0.5; // 50%概率已确认
|
|
||||||
|
try {
|
||||||
|
console.log('startStatusPolling---');
|
||||||
|
const param = {
|
||||||
|
scene: sceneStr.value,
|
||||||
|
};
|
||||||
|
const response = await getQrCodeResult(param);
|
||||||
|
console.log('response.data.sceneResult---', response.data);
|
||||||
|
console.log('response.data.sceneResult---', response.data.sceneResult);
|
||||||
|
switch (response.data.sceneResult) {
|
||||||
|
case 'Wait': // Wait
|
||||||
|
// 继续等待
|
||||||
|
break;
|
||||||
|
case 'Login': // Login
|
||||||
|
// 登录成功
|
||||||
|
handleLoginSuccess(response.data.token, response.refreshToken);
|
||||||
|
break;
|
||||||
|
case 'Register': // Register
|
||||||
|
// 需要注册
|
||||||
|
handleRegister();
|
||||||
|
break;
|
||||||
|
case 'Bind': // Bind
|
||||||
|
// 需要绑定
|
||||||
|
handleBind(response.data.token);
|
||||||
|
break;
|
||||||
|
case 'Expired': // Expired
|
||||||
|
// 二维码过期
|
||||||
|
isExpired.value = true;
|
||||||
|
stopPolling();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn('未知状态:', response.data.sceneResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模拟登录逻辑 如果在客户端已确认,则会调用这个方法进行登录
|
// 更新UI状态
|
||||||
async function mockLogin() {
|
updateUIStatus(response.data.sceneResult);
|
||||||
// 模拟登录成功逻辑
|
}
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
catch (error) {
|
||||||
|
console.error('检查二维码状态失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理登录成功
|
||||||
|
function handleLoginSuccess(token: string, refreshToken: string) {
|
||||||
|
// 停止轮询
|
||||||
|
stopPolling();
|
||||||
|
|
||||||
|
// 存储token
|
||||||
|
localStorage.setItem('access_token', token);
|
||||||
|
localStorage.setItem('refresh_token', refreshToken || '');
|
||||||
|
|
||||||
|
// 提示用户
|
||||||
|
ElMessage.success('登录成功');
|
||||||
|
|
||||||
|
// 刷新页面或跳转到首页
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理注册授权
|
||||||
|
function handleRegister() {
|
||||||
|
console.log('需要注册授权');
|
||||||
|
ElMessage.info('请在微信授权');
|
||||||
|
// const appId = 'wx373eb3ecd65bdac4';
|
||||||
|
// const redirectUri = encodeURIComponent('http://localhost:17001/wechat/callback');
|
||||||
|
// const state = 'register'; // 用于防止CSRF,可根据业务生成随机字符串
|
||||||
|
|
||||||
|
// scope 可以选 snsapi_base(静默,只拿 openid) 或 snsapi_userinfo(需要用户确认,可拿昵称头像)
|
||||||
|
// const scope = 'snsapi_userinfo';
|
||||||
|
|
||||||
|
// 拼接授权链接
|
||||||
|
// const wechatAuthUrl
|
||||||
|
// = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}`
|
||||||
|
// + `&redirect_uri=${redirectUri}`
|
||||||
|
// + `&response_type=code&scope=${scope}&state=${state}#wechat_redirect`;
|
||||||
|
// console.log('wechatAuthUrl---', wechatAuthUrl);
|
||||||
|
// 打开授权页(推荐用 location.href 替换,直接跳转)
|
||||||
|
// window.location.href = wechatAuthUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理绑定
|
||||||
|
function handleBind(token: string) {
|
||||||
|
// 处理账号绑定逻辑
|
||||||
|
console.log('需要绑定,临时token:', token);
|
||||||
|
ElMessage.info('请绑定您的账号');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成注册
|
||||||
|
async function completeRegistration() {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// 调用后端API完成注册
|
||||||
|
// const response = await completeRegister({ token });
|
||||||
|
// ElMessage.success('注册成功,正在登录...');
|
||||||
|
// 模拟登录成功
|
||||||
|
// handleLoginSuccess();
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('注册失败:', error);
|
||||||
|
ElMessage.error('注册失败,请重试');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新UI状态
|
||||||
|
function updateUIStatus(status: string) {
|
||||||
|
switch (status) {
|
||||||
|
case 'Wait': // Wait
|
||||||
|
isScanned.value = false;
|
||||||
|
isConfirming.value = false;
|
||||||
|
break;
|
||||||
|
case 'Login': // Login - 已扫码并确认
|
||||||
|
case 'Register': // Register - 已扫码并确认
|
||||||
|
case 'Bind': // Bind - 已扫码并确认
|
||||||
|
isScanned.value = true;
|
||||||
|
isConfirming.value = false;
|
||||||
|
break;
|
||||||
|
case 'Expired': // Expired
|
||||||
|
isExpired.value = true;
|
||||||
|
isScanned.value = false;
|
||||||
|
isConfirming.value = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// 其他状态认为是已扫码但未确认
|
||||||
|
isScanned.value = true;
|
||||||
|
isConfirming.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在微信授权页面获取到微信code后发给服务端
|
||||||
|
async function handleWechatAuth(code: string) {
|
||||||
|
try {
|
||||||
|
const param = {
|
||||||
|
code,
|
||||||
|
};
|
||||||
|
const response = await getWechatAuth(param);
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
// 授权成功,可以获取用户信息或完成登录
|
||||||
|
console.log('微信授权成功', response);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error('微信授权失败:', response.message);
|
||||||
|
ElMessage.error(`微信授权失败: ${response.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('处理微信授权失败:', error);
|
||||||
|
ElMessage.error('处理微信授权时出错');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 停止所有轮询 */
|
/** 停止所有轮询 */
|
||||||
function stopPolling() {
|
function stopPolling() {
|
||||||
if (scanPolling)
|
if (statusPolling) {
|
||||||
clearInterval(scanPolling);
|
clearInterval(statusPolling);
|
||||||
if (confirmPolling)
|
statusPolling = null;
|
||||||
clearInterval(confirmPolling);
|
}
|
||||||
scanPolling = null;
|
|
||||||
confirmPolling = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 刷新二维码 */
|
/** 刷新二维码 */
|
||||||
@@ -76,48 +234,40 @@ async function handleRefresh() {
|
|||||||
isScanned.value = false;
|
isScanned.value = false;
|
||||||
isConfirming.value = false;
|
isConfirming.value = false;
|
||||||
stopPolling();
|
stopPolling();
|
||||||
qrStart(shallowRef(60));
|
|
||||||
const newUrl = await fetchNewQRCode();
|
const success = await fetchQRCodeInfo();
|
||||||
urlText.value = newUrl;
|
if (success) {
|
||||||
|
qrStart(shallowRef(600));
|
||||||
|
startStatusPolling();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ElMessage.error('刷新二维码失败');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 启动扫码状态轮询 */
|
/** 启动状态轮询 */
|
||||||
function startScanPolling() {
|
function startStatusPolling() {
|
||||||
scanPolling = setInterval(async () => {
|
console.log('1111----');
|
||||||
if (!isExpired.value && !isScanned.value) {
|
stopPolling(); // 先停止之前的轮询
|
||||||
const scanned = await checkScanStatus();
|
|
||||||
if (scanned) {
|
|
||||||
isScanned.value = true;
|
|
||||||
isConfirming.value = true;
|
|
||||||
confirmStart(confirmCountdownSeconds); // 启动确认倒计时
|
|
||||||
startConfirmPolling(); // 开始确认登录轮询
|
|
||||||
stopPolling(); // 停止扫码轮询
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 2000); // 每2秒轮询一次
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 启动确认登录轮询 */
|
statusPolling = setInterval(async () => {
|
||||||
function startConfirmPolling() {
|
if (!isExpired.value) {
|
||||||
confirmPolling = setInterval(async () => {
|
await checkQRCodeStatus();
|
||||||
if (isConfirming.value && !isExpired.value) {
|
|
||||||
const confirmed = await checkConfirmStatus();
|
|
||||||
if (confirmed) {
|
|
||||||
stopPolling();
|
|
||||||
confirmStop();
|
|
||||||
await mockLogin();
|
|
||||||
handleRefresh(); // 登录成功后刷新二维码
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, 2000); // 每2秒轮询一次
|
}, 2000); // 每2秒轮询一次
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 组件初始化 */
|
/** 组件初始化 */
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const initialUrl = await fetchNewQRCode();
|
const success = await fetchQRCodeInfo();
|
||||||
urlText.value = initialUrl;
|
console.log('qrCodeUrl---', success);
|
||||||
|
if (success) {
|
||||||
qrStart();
|
qrStart();
|
||||||
startScanPolling(); // 初始启动扫码轮询
|
startStatusPolling();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ElMessage.error('初始化二维码失败');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 组件卸载清理 */
|
/** 组件卸载清理 */
|
||||||
@@ -126,12 +276,24 @@ onBeforeUnmount(() => {
|
|||||||
confirmStop();
|
confirmStop();
|
||||||
stopPolling();
|
stopPolling();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 监听URL参数中的微信code(适用于微信授权回调)
|
||||||
|
onMounted(() => {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const code = urlParams.get('code');
|
||||||
|
const state = urlParams.get('state');
|
||||||
|
|
||||||
|
if (code) {
|
||||||
|
// 处理微信授权回调
|
||||||
|
handleWechatAuth(code);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="qr-wrapper">
|
<div class="qr-wrapper">
|
||||||
<div class="tip">
|
<div class="tip">
|
||||||
请使用手机扫码登录
|
请使用手机微信扫码登录
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="qr-img-wrapper">
|
<div class="qr-img-wrapper">
|
||||||
@@ -164,12 +326,20 @@ onBeforeUnmount(() => {
|
|||||||
已扫码
|
已扫码
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="scanned-text">
|
<p v-if="isConfirming" class="scanned-text">
|
||||||
请在手机端确认登录
|
请在手机端确认登录
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p v-else class="scanned-text">
|
||||||
|
处理中...
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="help-text">
|
||||||
|
扫码后请在微信中确认登录
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -256,5 +426,9 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.help-text {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ function openContact() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="right-section">
|
<div class="right-section">
|
||||||
<!-- 隐藏二维码登录 -->
|
<!-- 隐藏二维码登录 -->
|
||||||
<div v-if="false" class="mode-toggle" @click.stop="toggleLoginMode">
|
<div class="mode-toggle" @click.stop="toggleLoginMode">
|
||||||
<SvgIcon v-if="!isQrMode" name="erweimadenglu" />
|
<SvgIcon v-if="!isQrMode" name="erweimadenglu" />
|
||||||
<SvgIcon v-else name="zhanghaodenglu" />
|
<SvgIcon v-else name="zhanghaodenglu" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user