feat: implement WeChat MP OAuth login

- Add wechat-mp.service.ts for MP web authorization
- Add wechat-mp.routes.ts with /api/mp endpoints
- Update EntryQRCode.vue to show H5 URL QR code
- Update HomeView.vue with WeChat auth detection

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
empty
2026-02-03 21:33:32 +08:00
parent b53e732ffa
commit 9b11f99fed
5 changed files with 415 additions and 83 deletions

View File

@@ -1,16 +1,120 @@
<script setup lang="ts">
import { onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { ref, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { showLoadingToast, closeToast, showToast } from 'vant';
import { useConnectionStore } from '../stores/connection';
const router = useRouter();
const route = useRoute();
const connectionStore = useConnectionStore();
// Check if already logged in
onMounted(() => {
const isProcessing = ref(false);
// API base URL
const apiUrl = import.meta.env.VITE_API_URL || 'http://192.168.1.5:3000';
/**
* 检测是否在微信环境中
*/
function isWechatBrowser(): boolean {
const ua = navigator.userAgent.toLowerCase();
return ua.includes('micromessenger');
}
/**
* 用code完成登录
*/
async function loginWithCode(code: string) {
isProcessing.value = true;
showLoadingToast({ message: '登录中...', forbidClick: true });
try {
const response = await fetch(`${apiUrl}/api/mp/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code }),
});
const result = await response.json();
closeToast();
if (result.success && result.data) {
// 设置用户信息
connectionStore.setUser(
result.data.userId,
result.data.userName,
result.data.sessionToken
);
showToast({ message: '登录成功!', type: 'success' });
// 跳转到投票页面
router.replace('/vote');
} else {
showToast({ message: result.error || '登录失败', type: 'fail' });
isProcessing.value = false;
}
} catch (err) {
closeToast();
showToast({ message: '网络错误,请重试', type: 'fail' });
isProcessing.value = false;
}
}
/**
* 跳转到微信授权页面
*/
async function redirectToWechatAuth() {
try {
// 获取授权URL
const currentUrl = window.location.href.split('?')[0]; // 移除已有的query参数
const response = await fetch(
`${apiUrl}/api/mp/auth-url?redirect_uri=${encodeURIComponent(currentUrl)}&scope=snsapi_base`
);
const result = await response.json();
if (result.success && result.data?.authUrl) {
// 跳转到微信授权页面
window.location.href = result.data.authUrl;
} else {
console.error('[HomeView] Failed to get auth URL:', result.error);
showToast({ message: '获取授权链接失败', type: 'fail' });
}
} catch (err) {
console.error('[HomeView] Failed to redirect to auth:', err);
showToast({ message: '网络错误', type: 'fail' });
}
}
/**
* 处理微信授权流程
*/
async function handleWechatAuth() {
// 检查URL中是否有code参数授权回调
const code = route.query.code as string;
if (code) {
// 有code用code完成登录
console.log('[HomeView] Got code from callback, logging in...');
await loginWithCode(code);
} else {
// 无code跳转到授权页面
console.log('[HomeView] No code, redirecting to auth...');
await redirectToWechatAuth();
}
}
// 页面加载时检查登录状态和微信环境
onMounted(async () => {
// 已登录则直接跳转
if (connectionStore.userId && connectionStore.userName && connectionStore.userName !== '访客') {
// Already logged in, redirect to vote page
router.replace('/vote');
return;
}
// 在微信环境中自动处理授权
if (isWechatBrowser()) {
await handleWechatAuth();
}
});
</script>