终于解决了,微信扫码登录重定向被浏览器拦截

微信重定向被浏览器拦截

在这里插入图片描述
按照微信官方文档,当我们扫码二维码,点击确定时,会被浏览器拦截。
源码如下

<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>

主要修改点:

  1. 将原来的 div#login_container 替换为 iframe#wx_login_container,并设置了必要的 sandbox 属性
  2. 修改了 initWxLogin 方法,直接使用 iframe 加载微信登录页面
  3. 在 iframe 的 URL 中包含了 self_redirect=false 参数
  4. 保留了样式自定义功能,通过动态添加 CSS 到 iframe 中

这种实现方式更符合微信官方推荐的做法,同时也避免了跨域问题。iframe 的 sandbox 属性设置为 allow-scripts allow-same-origin allow-top-navigation 可以确保必要的功能正常运行,同时保持安全性。

注意:微信登录的回调地址必须与微信开放平台配置的域名完全一致,否则会报错。确保你的 redirect_uri 参数是正确的。

效果

丝滑跳转
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

<think>好的,用户现在问的是“请在微信客户端打开链接”是怎么回事。之前他们问过关于微信登录Scope参数错误的问题,现在这个问题可能也是相关的,可能是在开发或者使用微信相关功能时遇到的。首先,我需要回忆微信生态中为什么会提示这样的信息。 首先,用户可能在非微信客户端内尝试打开一个专为微信设计的链接,比如微信授权登录的页面或者微信内的H5应用。微信的某些功能只能在微信内置浏览器中运行,因为涉及到微信的JS-SDK、授权机制或者支付功能,这些都依赖于微信客户端的环境。 接下来,我需要分析可能的原因。常见的情况包括:链接是微信OAuth授权链接,只能在微信内打开;或者链接使用了微信的JS-SDK,需要微信环境才能调用API;或者涉及微信支付,必须在微信客户端内完成。此外,微信网页可能配置了安全域名,如果直接在外部浏览器打开,会被阻止。 然后,用户遇到这个提示时的具体场景是什么?如果是开发者,可能在测试时遇到,需要确认他们是否正确在微信客户端内测试。如果是普通用户,可能通过其他渠道获得了链接,尝试在外部浏览器打开,导致无法访问。 接下来,要整理解决方法。首先,确保在微信内打开链接,比如复制链接到微信对话中点击。如果是网页开发,可能需要配置安全域名,或者检查是否使用了微信特定的API。另外,测试号或正式号的配置是否正确,是否有遗漏的步骤。 还需要考虑用户可能的深层需求。比如用户可能是在开发过程中遇到这个问题,需要调试或者配置正确的环境。或者是普通用户遇到了访问限制,需要指导他们如何正确操作。这时候需要分情况给出建议,针对开发者和普通用户的不同解决方案。 另外,可能涉及到的技术点包括微信JS-SDK的使用、安全域名的配置、OAuth2.0的授权流程等。需要解释清楚为什么微信会限制链接只能在客户端打开,以及如何正确配置以避免这个问题。 最后,要确保回答清晰,分点说明原因和解决方法,并给出示例或参考链接,帮助用户进一步解决问题。同时,提醒用户检查常见的配置错误,如域名是否备案,是否在微信后台正确设置等。</think>当你在访问某个链接时看到提示“**请在微信客户端打开链接**”,通常是因为该链接被设计为**只能在微信内置浏览器微信客户端)中运行**。以下是详细解释及解决方法: --- ### **1. 为什么会出现这个提示?** #### **(1) 功能依赖微信环境** - **微信授权登录**:链接可能是微信 OAuth2.0 授权页,需通过微信客户端获取用户的 `code` 或 `openid`。 - **微信支付**:支付流程依赖微信客户端的支付接口。 - **JS-SDK 功能**:调用微信分享、定位、等功能的网页需运行在微信内置浏览器中。 - **安全域名限制**:网页可能配置了微信的**业务域名**或**JS接口安全域名**,仅允许在微信内访问。 #### **(2) 链接被加密或限制** - 部分微信服务(如小程序、公众号文章)的链接可能被微信加密,无法在外部浏览器直接打开。 --- ### **2. 常见场景** #### **(1) 用户端场景** - 用户在手机浏览器、QQ、其他 App 中点击了微信专属链接(如公众号菜单链接、微信支付链接)。 - 用户通过 PC 端微信打开了链接,但链接要求必须在手机微信客户端内访问。 #### **(2) 开发者端场景** - 开发者在本地浏览器调试微信网页,未通过微信客户端访问。 - 网页中使用了微信 JS-SDK(如 `wx.config`),但未在微信环境中运行。 --- ### **3. 解决方法** #### **对于普通用户**: 1. **复制链接到微信内打开**: - 长按链接 → 复制 → 打开微信 → 粘贴至聊天框 → 点击链接。 2. **从公众号菜单访问**: - 如果链接来自公众号菜单,直接通过公众号菜单进入。 3. **检查链接来源**: - 部分链接仅限特定用户或场景使用(如企业微信内部链接),需确认是否有访问权限。 #### **对于开发者**: 1. **在微信客户端内调试**: - 本地开发时,使用微信开发者工具或通过手机微信访问调试页面。 - 使用微信测试号或正式公众号配置**JS接口安全域名**(需备案域名)。 2. **配置安全域名**: - 登录[微信公众平台](https://mp.weixin.qq.com/) → 设置 → **公众号设置** → 功能设置 → **JS接口安全域名**、**网页授权域名**。 - 确保链接的域名与配置的域名**完全一致**(包括子域名)。 3. **检查 JS-SDK 调用**: - 若使用微信分享、支付等功能,需在微信内置浏览器中初始化 `wx.config`。 - 示例代: ```javascript wx.config({ debug: false, appId: '公众号AppID', timestamp: '生成的时间戳', nonceStr: '随机字符串', signature: '签名', jsApiList: ['updateAppMessageShareData', 'chooseImage'] // 需声明的JS接口 }); ``` --- ### **4. 其他注意事项** - **域名备案**:微信要求所有配置的业务域名必须已完成 ICP 备案。 - **链接重定向**: - 避免多次重定向,可能导致微信安全策略拦截。 - 确保最终访问的页面域名与配置的安全域名一致。 - **微信版本兼容性**:旧版微信客户端可能不支持部分新功能,需测试兼容性。 --- ### **5. 示例:生成微信内可访问的链接** 若你需要用户通过微信打开网页,可生成以下格式的链接(如公众号菜单链接): ```url https://mp.weixin.qq.com/mp/homepage?__biz=公众号biz参数&hid=页面ID&sn=页面SN参数#wechat_redirect ``` - 需通过微信公众平台后台生成合法链接。 --- 通过以上方法,可解决“请在微信客户端打开链接”的问题。如果仍无法解决,建议检查域名配置或联系微信客服。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小码哥(xmgcode88)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值