微信重定向被浏览器拦截
按照微信官方文档,当我们扫码二维码,点击确定时,会被浏览器拦截。
源码如下
<template>
<div>
<div v-show="formContainerVisible">
<el-form
v-show="getShow"
ref="formLogin"
:model="loginData.loginForm"
:rules="LoginRules"
class="login-form"
label-position="top"
label-width="120px"
size="large"
>
<el-row style="margin-right: -10px; margin-left: -10px">
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item>
<LoginFormTitle style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
<el-input
v-model="loginData.loginForm.tenantName"
:placeholder="t('login.tenantNamePlaceholder')"
:disabled="!tenantNameInputable"
:prefix-icon="iconHouse"
link
type="primary"
/>
</el-form-item>
</el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item prop="username">
<el-input
v-model="loginData.loginForm.username"
:placeholder="t('login.usernamePlaceholder')"
:prefix-icon="iconAvatar"
/>
</el-form-item>
</el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item prop="password">
<el-input
v-model="loginData.loginForm.password"
:placeholder="t('login.passwordPlaceholder')"
:prefix-icon="iconLock"
show-password
type="password"
@keyup.enter="getCode()"
/>
</el-form-item>
</el-col>
<el-col
:span="24"
style="padding-right: 10px; padding-left: 10px; margin-top: -20px; margin-bottom: -20px"
>
<el-form-item>
<el-row justify="space-between" style="width: 100%">
<el-col :span="6">
<el-checkbox v-model="loginData.loginForm.rememberMe">
{{ t('login.remember') }}
</el-checkbox>
</el-col>
<el-col :offset="6" :span="12">
<!-- <el-link style="float: right" type="primary">{{ t('login.forgetPassword') }}</el-link> -->
</el-col>
</el-row>
</el-form-item>
</el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item>
<XButton
:loading="loginLoading"
:title="t('login.login')"
class="w-[100%]"
type="primary"
@click="getCode()"
/>
</el-form-item>
</el-col>
<Verify
ref="verify"
:captchaType="captchaType"
:imgSize="{ width: '400px', height: '200px' }"
mode="pop"
@success="handleLogin"
/>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item>
<el-row :gutter="5" justify="space-between" style="width: 100%">
<el-col :span="24">
<XButton
:title="t('login.btnMobile')"
class="w-[100%]"
@click="setLoginState(LoginStateEnum.MOBILE)"
/>
</el-col>
<!-- <el-col :span="8">
<XButton
:title="t('login.btnQRCode')"
class="w-[100%]"
@click="setLoginState(LoginStateEnum.QR_CODE)"
/>
</el-col> -->
<!-- <el-col :span="12">
<XButton
:title="t('邮箱登录')"
class="w-[100%]"
@click="setLoginState(LoginStateEnum.REGISTER)"
/>
</el-col> -->
</el-row>
</el-form-item>
</el-col>
<!-- <el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider> -->
<!-- <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item>
<div class="w-[100%] flex justify-between">
<Icon
v-for="(item, key) in socialList"
:key="key"
:icon="item.icon"
:size="30"
class="anticon cursor-pointer"
color="#999"
@click="doSocialLogin(item.type)"
/>
</div>
</el-form-item>
</el-col> -->
<!-- 个人微信登录 -->
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item>
<div class="w-[100%] flex items-center justify-center">
<el-image
class="w-12 h-12 cursor-pointer"
:src="iconWx"
@click="getWxLogin()"
/>
</div>
</el-form-item>
</el-col>
<!-- <el-divider content-position="center">萌新必读</el-divider>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item>
<div class="w-[100%] flex justify-between">
<el-link href="https://doc.iocoder.cn/" target="_blank">📚开发指南</el-link>
<el-link href="https://doc.iocoder.cn/video/" target="_blank">🔥视频教程</el-link>
<el-link href="https://www.iocoder.cn/Interview/good-collection/" target="_blank">
⚡面试手册
</el-link>
<el-link href="http://static.yudao.iocoder.cn/mp/Aix9975.jpeg" target="_blank">
🤝外包咨询
</el-link>
</div>
</el-form-item>
</el-col> -->
</el-row>
</el-form>
</div>
<!--个人微信登录-->
<div v-show="loginContainerVisible">
<el-row>
<div class="w-[100%] flex items-center justify-center" style="font-size:20px;font-weight:bold;margin-bottom:20px;"><span>微信扫码登录</span></div>
</el-row>
<el-row>
<div id="login_container" style="width:300px;height:400px"></div>
</el-row>
<el-row>
<div class="mt-15px w-[100%]">
<XButton :title="t('login.hasUser')" class="w-[100%]" @click="backLogin()" />
</div>
</el-row>
</div>
</div>
</template>
<script lang="ts" setup>
import iconWx from '@/assets/imgs/icon_wx.png'
import { ElLoading } from 'element-plus'
import LoginFormTitle from './LoginFormTitle.vue'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import { useIcon } from '@/hooks/web/useIcon'
import * as authUtil from '@/utils/auth'
import { usePermissionStore } from '@/store/modules/permission'
import * as LoginApi from '@/api/login'
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
import { UserPointsOrderApi } from '@/api/xmg/wechat/userpointsorder'
import router from '@/router'
defineOptions({ name: 'LoginForm' })
const { t } = useI18n()
const message = useMessage()
const iconHouse = useIcon({ icon: 'ep:house' })
const iconAvatar = useIcon({ icon: 'ep:avatar' })
const iconLock = useIcon({ icon: 'ep:lock' })
const formLogin = ref()
const { validForm } = useFormValid(formLogin)
const { setLoginState, getLoginState,handleBackLogin} = useLoginState()
const { currentRoute, push } = useRouter()
const permissionStore = usePermissionStore()
const redirect = ref<string>('')
const loginLoading = ref(false)
const verify = ref()
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
const formContainerVisible = ref(true); // 控制显示状态的响应式属性
const loginContainerVisible = ref(false); // 控制显示状态的响应式属性
const backLogin=()=>{
router.push({ path: '/' })
}
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
const LoginRules = {
tenantName: [required],
username: [required],
password: [required]
}
const tenantNameInputable=ref(true) //租户输入框是否可以输入
const loginData = reactive({
isShowPassword: false,
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
loginForm: {
tenantName: import.meta.env.VITE_APP_DEFAULT_LOGIN_TENANT || '',
username: import.meta.env.VITE_APP_DEFAULT_LOGIN_USERNAME || '',
password: import.meta.env.VITE_APP_DEFAULT_LOGIN_PASSWORD || '',
captchaVerification: '',
rememberMe: true // 默认记录我。如果不需要,可手动修改
}
})
const socialList = [
{ icon: 'ant-design:wechat-filled', type: 30 },
{ icon: 'ant-design:dingtalk-circle-filled', type: 20 },
{ icon: 'ant-design:github-filled', type: 0 },
{ icon: 'ant-design:alipay-circle-filled', type: 0 }
]
// 获取验证码
const getCode = async () => {
// 情况一,未开启:则直接登录
if (loginData.captchaEnable === 'false') {
await handleLogin({})
} else {
// 情况二,已开启:则展示验证码;只有完成验证码的情况,才进行登录
// 弹出验证码
verify.value.show()
}
}
// 获取租户 ID
const getTenantId = async () => {
if (loginData.tenantEnable === 'true') {
const res = await LoginApi.getTenantIdByName(loginData.loginForm.tenantName)
authUtil.setTenantId(res)
}
}
// 记住我
const getLoginFormCache = () => {
const loginForm = authUtil.getLoginForm()
if (loginForm) {
loginData.loginForm = {
...loginData.loginForm,
username: loginForm.username ? loginForm.username : loginData.loginForm.username,
password: loginForm.password ? loginForm.password : loginData.loginForm.password,
rememberMe: loginForm.rememberMe,
tenantName: loginForm.tenantName ? loginForm.tenantName : loginData.loginForm.tenantName
}
}
}
// 根据域名,获得租户信息
const getTenantByWebsite = async () => {
const website = location.host
const res = await LoginApi.getTenantByWebsite(website)
if (res) {
loginData.loginForm.tenantName = res.name
authUtil.setTenantId(res.id)
tenantNameInputable.value=false
}
}
const loading = ref() // ElLoading.service 返回的实例
//个人微信登录
const getWxLogin = async () => {
const comboParams1 = {
operateType: 'login'
};
const data = await UserPointsOrderApi.getWxLogin(comboParams1);
console.info("datadatadata",data);
await initWxLogin(data);
loginContainerVisible.value=true;
formContainerVisible.value=false;
};
// 初始化 WxLogin 实例
// 初始化逻辑
const initWxLogin = async (data: any) => {
await loadWxLogin();
// 覆盖微信SDK的iframe生成逻辑
// @ts-ignore
window.WxLogin = function (config) {
const iframe = document.createElement('iframe');
iframe.sandbox = 'allow-scripts allow-same-origin allow-forms allow-popups allow-top-navigation';
iframe.src = `https://open.weixin.qq.com/connect/qrconnect?appid=${config.appid}&redirect_uri=${encodeURIComponent(config.redirect_uri)}&response_type=code&scope=snsapi_login`;
document.getElementById(config.id).appendChild(iframe);
};
new WxLogin({
id: 'login_container',
self_redirect: false,
appid: data.appId,
scope: 'snsapi_login', // 强制指定该参数
redirect_uri: encodeURIComponent(data.redirect_uri),
state: data.state,
style: 'black',
href: 'data:text/css;base64,' + btoa(`
.impowerBox .qrcode { width: 260px !important; margin-top: 20px }
.wrp_code { padding: 0 !important }
`)
});
};
// 加载 wxLogin.js 脚本并初始化 WxLogin 实例
const loadWxLogin = async () => {
return new Promise((resolve, reject) => {
if (typeof WxLogin !== 'undefined') return resolve(); // 已加载则跳过
const script = document.createElement('script');
script.src = 'https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js?t=' + Date.now();
script.onload = () => resolve();
script.onerror = (err) => reject(err);
document.body.appendChild(script);
});
};
watch(
() => currentRoute.value,
(route: RouteLocationNormalizedLoaded) => {
redirect.value = route?.query?.redirect as string
},
{
immediate: true
}
)
onMounted(() => {
getLoginFormCache()
getTenantByWebsite()
// 新增:自动跳转到手机登录表单
setLoginState(LoginStateEnum.MOBILE)
// 动态加载外部 JavaScript 文件
const script = document.createElement('script');
script.src = 'http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js';
document.head.appendChild(script);
})
</script>
<style lang="scss" scoped>
:deep(.anticon) {
&:hover {
color: var(--el-color-primary) !important;
}
}
.login-code {
float: right;
width: 100%;
height: 38px;
img {
width: 100%;
height: auto;
max-width: 100px;
vertical-align: middle;
cursor: pointer;
}
}
/* 修复iframe可能引起的布局问题 */
#iframe_container {
display: block !important;
margin: 0 auto;
overflow: hidden;
}
</style>
解决方法
确保设置了 self_redirect: false
并正确添加了 iframe 的 sandbox 属性。以下是修改后的代码:
<template>
<div>
<!-- 原有表单部分保持不变 -->
<div v-show="formContainerVisible">
<!-- 原有表单内容 -->
</div>
<!-- 个人微信登录部分 -->
<div v-show="loginContainerVisible">
<el-row>
<div class="w-[100%] flex items-center justify-center" style="font-size:20px;font-weight:bold;margin-bottom:20px;">
<span>微信扫码登录</span>
</div>
</el-row>
<el-row>
<!-- 修改为使用iframe容器 -->
<iframe
id="wx_login_container"
sandbox="allow-scripts allow-same-origin allow-top-navigation"
style="width:300px;height:400px;border:none;margin:0 auto;display:block"
></iframe>
</el-row>
<el-row>
<div class="mt-15px w-[100%]">
<XButton :title="t('login.hasUser')" class="w-[100%]" @click="backLogin()" />
</div>
</el-row>
</div>
</div>
</template>
<script lang="ts" setup>
// 其他导入保持不变...
// 修改后的微信登录方法
const getWxLogin = async () => {
const comboParams1 = {
operateType: 'login'
};
const data = await UserPointsOrderApi.getWxLogin(comboParams1);
await initWxLogin(data);
loginContainerVisible.value = true;
formContainerVisible.value = false;
};
// 修改后的初始化微信登录方法
const initWxLogin = async (data: any) => {
await loadWxLogin();
// 创建iframe并设置属性
const iframe = document.getElementById('wx_login_container') as HTMLIFrameElement;
if (iframe) {
// 清空iframe内容
iframe.src = '';
// 构建微信登录URL
const wxLoginUrl = `https://open.weixin.qq.com/connect/qrconnect?appid=${data.appId}&redirect_uri=${encodeURIComponent(data.redirect_uri)}&response_type=code&scope=snsapi_login&state=${data.state}&self_redirect=false`;
// 设置iframe的src
iframe.src = wxLoginUrl;
// 添加样式
const style = document.createElement('style');
style.textContent = `
.impowerBox .qrcode { width: 260px !important; margin-top: 20px }
.wrp_code { padding: 0 !important }
`;
iframe.contentDocument?.head.appendChild(style);
}
};
// 加载微信登录脚本
const loadWxLogin = async () => {
return new Promise((resolve, reject) => {
if (typeof WxLogin !== 'undefined') return resolve();
const script = document.createElement('script');
script.src = 'https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js?t=' + Date.now();
script.onload = () => resolve();
script.onerror = (err) => reject(err);
document.body.appendChild(script);
});
};
// 其他代码保持不变...
</script>
主要修改点:
- 将原来的
div#login_container
替换为iframe#wx_login_container
,并设置了必要的 sandbox 属性 - 修改了
initWxLogin
方法,直接使用 iframe 加载微信登录页面 - 在 iframe 的 URL 中包含了
self_redirect=false
参数 - 保留了样式自定义功能,通过动态添加 CSS 到 iframe 中
这种实现方式更符合微信官方推荐的做法,同时也避免了跨域问题。iframe 的 sandbox 属性设置为 allow-scripts allow-same-origin allow-top-navigation
可以确保必要的功能正常运行,同时保持安全性。
注意:微信登录的回调地址必须与微信开放平台配置的域名完全一致,否则会报错。确保你的 redirect_uri
参数是正确的。
效果
丝滑跳转