diff --git a/package-lock.json b/package-lock.json index 334766c..9563571 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "wechat-pc-frontend", "version": "1.0.0", "dependencies": { - "@batiao/batiao-sdk-vue-vite": "^1.0.357", + "@batiao/batiao-sdk-vue-vite": "^1.0.386", "@element-plus/icons-vue": "^2.3.1", "axios": "^1.6.8", "body-parser": "^2.3.0", @@ -80,9 +80,9 @@ } }, "node_modules/@batiao/batiao-sdk-vue-vite": { - "version": "1.0.357", - "resolved": "http://maven.batiao8.com/repository/npm-releases/@batiao/batiao-sdk-vue-vite/-/batiao-sdk-vue-vite-1.0.357.tgz", - "integrity": "sha512-CGp16f+z7kLJSZocrj8wuttwUI4LuhAM3F9EE8+LManpsaGG5GPeDCfIfmYI9am97vnn4Fl1J0JGSSZiafjZyg==", + "version": "1.0.395", + "resolved": "http://maven.batiao8.com/repository/npm-releases/@batiao/batiao-sdk-vue-vite/-/batiao-sdk-vue-vite-1.0.395.tgz", + "integrity": "sha512-i4tdbKk4RAcmOyL4HeXr4p2tgzaAcJG9DJc2UyzKteOWtsaJbG9jAGbuaq/XxfWqCNP1nGRakrltVMpQzRbWOQ==", "license": "UNLICENSED", "dependencies": { "quickjs-emscripten": "^0.32.0" diff --git a/src/App.vue b/src/App.vue index 68fd092..9fc9825 100644 --- a/src/App.vue +++ b/src/App.vue @@ -17,24 +17,22 @@ import AppFooter from '@/components/common/AppFooter.vue' import PaymentDialog from '@/components/PaymentDialog.vue' import { useUserStore } from '@/stores/user' import api from '@/api' +import { handleWxLoginCallback } from '@/utils/wxLogin' const userStore = useUserStore() const paymentDialog = ref(null) -onMounted(() => { +onMounted(async () => { + // 1. 尝试处理微信登录回调 + const isWxLoginSuccess = await handleWxLoginCallback() + if (isWxLoginSuccess) { + // 微信登录成功后拉取用户信息 + userStore.fetchProfile().catch(() => { }) + return + } + + // 2. 正常 token 验证 if (localStorage.getItem('token') && !userStore.tokenChecked) { - // 用静默验证:401时不跳转登录页(避免注册/登录页被拦截) - // api.auth.profileSilent().then(res => { - // userStore.username = res.username - // userStore.isVip = res.is_vip - // userStore.avatarUrl = res.avatar_url - // userStore.membershipExpireAt = res.membership_expire_at - // userStore.tokenChecked = true - // userStore.tokenValid = true - // }).catch(() => { - // userStore.tokenChecked = true - // userStore.tokenValid = false - // }) userStore.fetchProfile().catch(() => { }) } }) diff --git a/src/api/authService.js b/src/api/authService.js index 473a75d..f05b735 100644 --- a/src/api/authService.js +++ b/src/api/authService.js @@ -1,5 +1,6 @@ import { get, postJson } from "@/utils/requestService"; import appConfig from "@/utils/appConfig"; +import config from "@/config.json"; export default { /** @@ -29,7 +30,21 @@ export default { }, /** - * 3. 获取微信扫码登录二维码 + * 3. 微信开放平台 code 登录 + */ + async loginByWxCode(code) { + return await postJson(appConfig.baseURL, "/user/login", { + type: "weixin", + data: { + // appid: config.wxAppId, + code: code, + code_type: "wx-web" + } + }); + }, + + /** + * 4. 获取微信扫码登录二维码 (可能废弃) */ async getLoginCode(data) { return await postJson(appConfig.baseURL, "/weixin/qrcode", data); diff --git a/src/components/LoginDialog.vue b/src/components/LoginDialog.vue index a90a924..dac46e4 100644 --- a/src/components/LoginDialog.vue +++ b/src/components/LoginDialog.vue @@ -29,18 +29,11 @@ -
@@ -49,7 +42,8 @@ 我已阅读并同意《用户协议》《隐私政策》
- +
@@ -74,6 +68,7 @@ import api from '@/api' import authService from '@/api/authService' import { useUserStore } from '@/stores/user' import BaseDialog from '@/components/common/BaseDialog.vue' +import { initWechatLogin } from '@/utils/wxLogin' const emit = defineEmits(['success']) const userStore = useUserStore() @@ -83,9 +78,6 @@ const loading = ref(false) const sendingCode = ref(false) const countdown = ref(0) let timer = null -let qrTimer = null -const qrcode = ref('') -const qrKey = ref('') const showError = ref(false) const loginType = ref('phone') @@ -101,50 +93,14 @@ function open() { // fetchQrCode() } -async function fetchQrCode() { - try { - // 确保请求二维码前本地有 Token (若缺失则自动拉取游客 Token) - if (!localStorage.getItem('token')) { - const configRes = await authService.getUserConfig() - if (configRes && configRes.data && configRes.data.token) { - localStorage.setItem('token', configRes.data.token) - } - } - const res = await authService.getLoginCode() - if (res && res.data) { - qrKey.value = res.data.key - qrcode.value = res.data.qrcode - - if (qrTimer) clearInterval(qrTimer) - qrTimer = setInterval(checkQrLogin, 2000) - } - } catch (err) { - console.error('获取微信登录二维码失败:', err) - } -} - -async function checkQrLogin() { - if (loginType.value !== 'wechat' || !visible.value) return - try { - const res = await authService.askLoginCode({ key: qrKey.value }) - if (res && res.code === 0 && res.data && res.data.token) { - localStorage.setItem('token', res.data.token) - if (qrTimer) { - clearInterval(qrTimer) - qrTimer = null - } - visible.value = false - emit('success') - ElMessage.success('微信登录成功') - window.location.reload() - } - } catch (err) { - // 忽略轮询中的错误 - } -} - function toggleLoginType() { loginType.value = loginType.value === 'phone' ? 'wechat' : 'phone' + if (loginType.value === 'wechat') { + // 初始化微信开放平台登录 + setTimeout(() => { + initWechatLogin('wechat-login-container') + }, 100) + } } function startCountdown() { @@ -554,7 +510,15 @@ defineExpose({ open }) align-items: center; justify-content: center; margin-top: 40px; - margin-bottom: 20px; + + + + #wechat-login-container { + height: 312px; + display: flex; + justify-content: center; + align-items: center; + } .qrcode-img { width: 150px; diff --git a/src/config.json b/src/config.json index 02b17a2..da03ec6 100644 --- a/src/config.json +++ b/src/config.json @@ -24,5 +24,7 @@ "ignoreCodeErrorList": ["GET&/api/weixin/qrcode","GET&/api/app/code", "GET&/api/order"], "hideConsole": true, "downloadSuccessCode":[200,201,202,203,204,205,206], - "configIgnoreParams": ["inputvalue"] + "configIgnoreParams": ["inputvalue"], + "wxAppId": "wx099ee8f75a70b571", + "wxRedirectUri": "https://dashi666.com/" } \ No newline at end of file diff --git a/src/utils/requestService.js b/src/utils/requestService.js index a5b6104..8d5ca0c 100644 --- a/src/utils/requestService.js +++ b/src/utils/requestService.js @@ -39,6 +39,7 @@ const setheader = (dataType) => { header["x-channel"] = "official"; header["x-version"] = "1.0.0"; header["x-platform"] = "web"; + header["x-app-id"] = "10037"; // 添加 appid 以满足微信鉴权后端要求 let deviceId = localStorage.getItem("device_id"); if (!deviceId) { deviceId = appUtils.generateUUID(); diff --git a/src/utils/wxLogin.js b/src/utils/wxLogin.js new file mode 100644 index 0000000..876d41f --- /dev/null +++ b/src/utils/wxLogin.js @@ -0,0 +1,83 @@ +import config from '@/config.json' + +function loadWXSDK() { + return new Promise((resolve, reject) => { + if (window.WxLogin) { + resolve() + return + } + const script = document.createElement('script') + script.src = 'https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js' + script.onload = resolve + script.onerror = () => reject(new Error('微信SDK加载失败')) + document.head.appendChild(script) + }) +} + +// 生成 UUID (简单实现) +function generateUUID() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = (Math.random() * 16) | 0, + v = c == 'x' ? r : (r & 0x3) | 0x8 + return v.toString(16) + }) +} + +export async function initWechatLogin(containerId = 'wechat-login-container') { + await loadWXSDK() + + const stateUUID = generateUUID() + // 将 state 存入 localStorage 用于防范 CSRF + localStorage.setItem('wx_state', stateUUID) + + // 动态获取当前的完整基础 URL 作为重定向地址(包括端口号),这样无论本地用什么端口,都能精准跳回来 + const WXLOGIN_APPID = config.wxAppId + const WXLOGIN_REDIRECT_URI = config.wxRedirectUri + + try { + const customCss = '.impowerBox .title { opacity: 0 !important; margin-top:20px !important;}.impowerBox .info { margin-bottom:20px !important;} .impowerBox .qrcode { width: 250px !important; height: 250px !important; margin: 0 auto !important; } .impowerBox .qrcode img { width: 250px !important; height: 250px !important; }'; + + new window.WxLogin({ + self_redirect: true, // 本地调试时改为 true,让 iframe 自己跳转,避免跨域阻止 + id: containerId, + appid: WXLOGIN_APPID, + scope: 'snsapi_login', + redirect_uri: encodeURIComponent(WXLOGIN_REDIRECT_URI), + state: stateUUID, + style: 'black', + href: 'data:text/css;base64,' + btoa(customCss) + }) + } catch (err) { + console.error('初始化微信登录失败:', err) + } +} + +export async function handleWxLoginCallback() { + const urlParams = new URLSearchParams(window.location.search) + const code = urlParams.get('code') + const state = urlParams.get('state') + + if (code && state) { + const savedState = localStorage.getItem('wx_state') + if (savedState !== state) { + console.warn('微信登录 state 校验失败,可能存在跨站请求伪造 (CSRF) 攻击风险。') + return false + } + + try { + const authService = (await import('@/api/authService')).default + const res = await authService.loginByWxCode(code) + if (res && res.code === 0 && res.data && res.data.token) { + localStorage.setItem('token', res.data.token) + // 清理 URL 参数 + const url = new URL(window.location.href) + url.search = '' + window.history.replaceState({}, document.title, url.toString()) + return true + } + } catch (err) { + console.error('微信扫码登录失败:', err) + } + } + return false +}