Merge remote-tracking branch 'origin/ai-hub' into ai-hub

This commit is contained in:
ccnetcore
2025-08-29 22:21:11 +08:00
3 changed files with 262 additions and 73 deletions

View File

@@ -1,6 +1,21 @@
import { get } from '@/utils/request';
import { get, post } from '@/utils/request';
// 获取用户信息
export function getUserInfo() {
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();
}

View File

@@ -1,23 +1,24 @@
<script lang="ts" setup>
import { Check, Picture as IconPicture, Refresh } from '@element-plus/icons-vue';
import { useCountdown } from '@vueuse/core';
import { useQRCode } from '@vueuse/integrations/useQRCode';
import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue';
import { getQrCode, getQrCodeResult, getWechatAuth } from '@/api';
// 响应式状态
const urlText = shallowRef('');
const qrCodeUrl = useQRCode(urlText);
// const urlText = shallowRef('');
const qrCodeUrl = ref('');
const isExpired = ref(false);
const isScanned = ref(false); // 新增:是否已扫码
const isConfirming = ref(false); // 新增:是否进入确认登录阶段
const confirmCountdownSeconds = shallowRef(180); // 确认登录倒计时3分钟
const isScanned = ref(false);
const isConfirming = ref(false);
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,
onComplete: () => {
isExpired.value = true;
stopPolling(); // 二维码过期时停止轮询
stopPolling();
},
});
@@ -27,47 +28,204 @@ const { start: confirmStart, stop: confirmStop } = useCountdown(confirmCountdown
onComplete: () => {
isExpired.value = true;
isConfirming.value = false;
stopPolling(); // 确认倒计时结束时停止轮询
stopPolling();
},
});
// 轮询相关
let scanPolling: number | null = null;
let confirmPolling: number | null = null;
let statusPolling: number | null = null;
// 模拟后端接口 这里返回新的二维码地址
async function fetchNewQRCode() {
await new Promise(resolve => setTimeout(resolve, 500));
return `https://login-api.com/qr/${Date.now()}`;
}
// 模拟后端接口 这里返回是否已扫码
async function checkScanStatus() {
// 模拟扫码状态接口(实际应调用后端接口)
await new Promise(resolve => setTimeout(resolve, 300));
return Math.random() > 0.3; // 30%概率未扫码70%概率已扫码
// 获取登录二维码图片和二维码标识
async function fetchQRCodeInfo() {
try {
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 checkConfirmStatus() {
// 模拟确认登录接口(实际应调用后端接口)
await new Promise(resolve => setTimeout(resolve, 200));
return Math.random() > 0.5; // 50%概率已确认
// 轮询二维码状态
async function checkQRCodeStatus() {
if (!sceneStr.value)
return;
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状态
updateUIStatus(response.data.sceneResult);
}
catch (error) {
console.error('检查二维码状态失败:', error);
}
}
// 模拟登录逻辑 如果在客户端已确认,则会调用这个方法进行登录
async function mockLogin() {
// 模拟登录成功逻辑
await new Promise(resolve => setTimeout(resolve, 500));
// 处理登录成功
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() {
if (scanPolling)
clearInterval(scanPolling);
if (confirmPolling)
clearInterval(confirmPolling);
scanPolling = null;
confirmPolling = null;
if (statusPolling) {
clearInterval(statusPolling);
statusPolling = null;
}
}
/** 刷新二维码 */
@@ -76,48 +234,40 @@ async function handleRefresh() {
isScanned.value = false;
isConfirming.value = false;
stopPolling();
qrStart(shallowRef(60));
const newUrl = await fetchNewQRCode();
urlText.value = newUrl;
const success = await fetchQRCodeInfo();
if (success) {
qrStart(shallowRef(600));
startStatusPolling();
}
else {
ElMessage.error('刷新二维码失败');
}
}
/** 启动扫码状态轮询 */
function startScanPolling() {
scanPolling = setInterval(async () => {
if (!isExpired.value && !isScanned.value) {
const scanned = await checkScanStatus();
if (scanned) {
isScanned.value = true;
isConfirming.value = true;
confirmStart(confirmCountdownSeconds); // 启动确认倒计时
startConfirmPolling(); // 开始确认登录轮询
stopPolling(); // 停止扫码轮询
}
}
}, 2000); // 每2秒轮询一次
}
/** 启动状态轮询 */
function startStatusPolling() {
console.log('1111----');
stopPolling(); // 先停止之前的轮询
/** 启动确认登录轮询 */
function startConfirmPolling() {
confirmPolling = setInterval(async () => {
if (isConfirming.value && !isExpired.value) {
const confirmed = await checkConfirmStatus();
if (confirmed) {
stopPolling();
confirmStop();
await mockLogin();
handleRefresh(); // 登录成功后刷新二维码
}
statusPolling = setInterval(async () => {
if (!isExpired.value) {
await checkQRCodeStatus();
}
}, 2000); // 每2秒轮询一次
}
/** 组件初始化 */
onMounted(async () => {
const initialUrl = await fetchNewQRCode();
urlText.value = initialUrl;
qrStart();
startScanPolling(); // 初始启动扫码轮询
const success = await fetchQRCodeInfo();
console.log('qrCodeUrl---', success);
if (success) {
qrStart();
startStatusPolling();
}
else {
ElMessage.error('初始化二维码失败');
}
});
/** 组件卸载清理 */
@@ -126,12 +276,24 @@ onBeforeUnmount(() => {
confirmStop();
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>
<template>
<div class="qr-wrapper">
<div class="tip">
请使用手机扫码登录
请使用手机微信扫码登录
</div>
<div class="qr-img-wrapper">
@@ -164,12 +326,20 @@ onBeforeUnmount(() => {
已扫码
</p>
<p class="scanned-text">
<p v-if="isConfirming" class="scanned-text">
请在手机端确认登录
</p>
<p v-else class="scanned-text">
处理中...
</p>
</div>
</div>
</div>
<div class="help-text">
扫码后请在微信中确认登录
</div>
</div>
</template>
@@ -256,5 +426,9 @@ onBeforeUnmount(() => {
}
}
}
.help-text {
font-size: 12px;
color: #909399;
}
}
</style>

View File

@@ -284,7 +284,7 @@ function openContact() {
</div>
<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-else name="zhanghaodenglu" />
</div>