Skip to content

三方登录与组件测试(一) ​

本页对应 三方登录与组件测试,共 6 段视频,约 1 小时 18 分。

学习目标 ​

  1. 能够复述 QQ 三方登录的完整流程,并区分 H5 唤起 App 与 PC 扫码登录、openId 直接登录、未绑定手机时进入绑定流程等分支。
  2. 能够通过配置 host 映射、Vite 的端口与 host、路由白名单,实现从登录页跳转 QQ 登录并回跳到本地 login-callback 页面。
  3. 能够引入 QQ JS SDK,使用 QC.Login.check 与 QC.Login.getMe 获取 openId,并为全局 QC 对象补充 TypeScript 类型和 ESLint 全局声明。
  4. 能够编写使用 openId 登录的 API,并在登录失败时通过状态控制展示绑定手机号界面。
  5. 能够封装验证码 composable,在登录页和绑定手机页复用发送验证码、倒计时和表单校验逻辑。
  6. 能够实现绑定手机 API、复用登录成功逻辑、存储并消费回跳地址,并能配置 .env.development 与 .env.production 区分开发/生产环境的回跳域名和标题。

知识点 ​

先看清流程:点下按钮之后发生了什么 ​

为什么需要:QQ 登录不是“点一下按钮就完事”,中间要跨到 QQ 的页面,再回跳到自己系统,回跳后还要判断用户有没有绑定过手机号。流程没理清,代码一定写漏。

怎么想:把流程拆成“去 QQ”和“回自己系统”两段。去 QQ 时有两种形态:H5(M 端)页面会提示唤起手机上的 QQ 应用,授权后回到 H5 页面;PC 页面则直接展示扫码页,用手机 QQ 扫码并点“同意登录”,再回跳。

怎么做:回跳之后,页面能通过 QQ 提供的 JS API 拿到一个 openId 字符串,把它当作登录标识,向自己的后台发登录请求。后台判断这个 openId 是否已经和系统账号绑定过:绑定过就直接登录成功,按原有登录成功逻辑存用户信息、回到之前的页面;没绑定过就展示绑定手机号界面,绑定成功后同样存用户信息并回跳。下一次再用 QQ 登录,就能凭 openId 直接登录。

易错点:openId 只是第三方身份标识,不是自己系统的 token;未绑定时不是“登录失败”这么简单,而是要进入绑定流程。

线上演示与解绑:怎么反复测试绑定 ​

为什么需要:绑定过一次手机号后,再测就永远走“直接登录成功”分支,绑定界面测不到了。

怎么做:先登录拿到 token,打开接口文档里的“三方登录解绑”接口,点调试,在 userInfo 位置填要解绑的手机号,把 token 放到 Authorization 里。注意 token 前面要加 Bearer 和一个空格,再发送请求。解绑成功后回到系统退出登录,再次 QQ 登录就会重新走到绑定手机号。

易错点:漏写 Bearer 会鉴权失败;重复解绑会提示信息已解除,属于正常现象。

准备工作与实现步骤拆分 ​

为什么需要:QQ 互联要求实名认证、合法域名,回调地址不能是 localhost 或 127.0.0.1,所以本地开发必须做域名映射。

怎么做:在 QQ 互联注册、实名、创建 web 应用,配置回调地址,拿到 appId。课程里已统一准备好 appId 和约定好的回调地址。整体分四步:① 跳转 QQ 并回跳;② 回跳页用 openId 登录,失败则展示绑定手机界面;③ 把发送验证码封装成 composable;④ 实现绑定手机号。

第一步:让回调域名指向本地 ​

为什么需要:QQ 回跳的是固定线上域名,本地要“接住”它。

怎么想:改 hosts 把该域名映射到 127.0.0.1,再把 Vite 端口改成 80(回调地址没有端口号),最后把回调路由加入登录白名单。

怎么做:macOS 下执行 sudo vim /etc/hosts,加入映射后 Esc → :wq 保存,再刷新本地 DNS。Vite 配置:

ts
server: {
  port: 80,
  // host 打开,允许通过域名访问
  host: '0.0.0.0'
}

白名单里补上 /login/callback。

易错点:访问域名仍看到线上页面,说明 hosts 没生效或 DNS 没刷新;访问 /login/callback 被重定向到 /login,说明白名单没加。

第二步:生成跳转 QQ 的地址 ​

怎么做:在 index.html 引入 QQ 互联提供的 JS 文件,页面挂载时调用:

ts
QC.login({ btn: 'qq' })

它会生成一个 QQ 登录按钮。打开控制台找到它 window.open 的地址,复制出来,把临时按钮改成 <a :href="地址"> 即可直接跳转。验证时扫码同意,能回到 /login/callback 就说明回跳成功。

易错点:临时按钮和 id 用完要删掉;这段代码只是用来“取地址”,不要留在正式逻辑里。

第三步:回跳页拿 openId 登录 ​

怎么做:给 QC 补类型,解决编辑器报错:

ts
type QCType = {
  login: {
    check: () => boolean
    getMe: (cb: (openId: string) => void) => void
  }
}
declare const QC: QCType

ESLint 提示未定义时,在文件顶部加 /* global QC */。挂载时先 QC.login.check() 判断是否登录过,再用 QC.login.getMe 回调拿到 openId,调用 loginByQQ(openId)。失败时把 isNeedBind 置为 true 并记录 openId,用 v-if 控制绑定手机界面显示。

易错点:openId 要存下来,绑定手机时还要用;接口里的来源参数 source 默认传 QQ。

第四步:验证码 composable 与绑定手机 ​

怎么做:封装 useMobileCode(mobile: Ref<string>, type: CodeType = 'login'),返回 onSend、倒计时 time 和 form 实例。登录页调用时不传第二个参数;绑定页传绑定类型。绑定接口传 mobile、code、openId,成功后与 openId 登录成功共用 loginSuccess(存用户信息、跳个人中心、提示成功)。跳 QQ 前把当前回跳地址存进 store 的 returnUrl,登录成功后优先跳它,跳完清空。

易错点:returnUrl 不清空会导致下次登录跳错页面。

部署:区分开发环境与生产环境 ​

为什么需要:开发环境回调域名是映射到本地的域名,生产环境是 HTTPS 的线上域名,标题也要能区分。

怎么做:新建 .env.development 和 .env.production,变量以 VITE_ 开头,例如 VITE_APP_CALLBACK、VITE_APP_TITLE;在 global.d.ts 里扩展 ImportMetaEnv 类型,代码中用 import.meta.env.VITE_APP_CALLBACK 读取,HTML 模板按 Vite 规则引用。用 pnpm dev 和 npm run build + npm run preview 分别验证打印值不同。

易错点:变量名不以 VITE_ 开头不会暴露给客户端;不扩展类型就没有提示。

本节目标与整体流程 ​

本节要完成第三方登录的第一步:去 QQ 登录,并且登录成功后回跳到自己的系统页面。整体流程是:登录页放一个 QQ 登录入口 → 点击后跳到 QQ 授权页/扫码页 → 用户授权 → QQ 按配置的回调地址跳回本项目 → 进入 /login-callback 路由。此时还不处理 openId 登录,只验证“能去 QQ、能回跳”。

为什么需要先做这一步:后面拿 openId 登录、绑定手机号,都必须建立在“QQ 授权后能回到我们页面”的基础上。怎么想:QQ 互联配置的回调地址是固定的合法域名,不能是 localhost、127.0.0.1,所以本地开发要设法让这个域名指向本机服务。怎么做:改 hosts、调 Vite 端口和 host、给回调路由加白名单、引入 QQ SDK 拿到授权地址。易错点:只改了 hosts 没改端口,或回调路由被登录拦截,都会导致“回跳失败”。

本节笔记未附图片;以下按转写稿中的界面演示复现。

前置准备:QQ 互联与回调域名 ​

为什么需要:QQ 登录不是直接写个按钮就行,必须先在 QQ 互联注册、实名认证,并拥有合法域名;创建 Web 应用后配置回调地址,才能拿到 appId 并完成授权回跳。怎么想:开发阶段大家通常没有合法域名,所以课程已准备好 appId 和约定好的回调地址,我们按约定使用即可。怎么做:在 QQ 互联创建 Web 应用,配置回调地址;本地开发时把该回调域名映射到本机。易错点:回调地址不能写 localhost 或 127.0.0.1;回调地址必须和 QQ 互联里配置的一致,路径也不能随意改。

第一步:配置 hosts,让回调域名指向本机 ​

为什么需要:QQ 授权成功后访问的是线上合法域名,本地要截住这个域名,让它指向 127.0.0.1。怎么想:hosts 文件负责域名到 IP 的映射,把 QQ 互联配置的回调域名映射到本机,就能访问本地服务。怎么做:编辑 hosts,加入映射;修改后刷新本地 DNS 解析。示例中域名请替换成笔记里约定的回调域名:

txt
127.0.0.1  <QQ互联配置的回调域名>

macOS/Linux 可用 sudo vim /etc/hosts,输入 i 编辑,保存时按 Esc,输入 :wq。易错点:hosts 需要管理员权限;改完不刷新 DNS 可能仍访问线上;如果还能看到线上页面,说明映射没生效或访问的不是本地服务。

第二步:配置 Vite 端口 80 与域名访问 ​

为什么需要:QQ 回调域名通常不带端口,而 Vite 默认是 5173,所以本地服务要监听 80 端口,并允许通过域名访问。怎么想:回调地址没有端口,本地就不能让用户再补 :5173。怎么做:在 vite.config.ts 的 server 中配置端口和 host,按项目实际写法配置为允许域名/IP 访问:

ts
server: {
  port: 80,
  host: '0.0.0.0'
}

保存后重启开发服务,再访问回调域名,若能看到自己的项目页面,说明 hosts 和 Vite 配置成功。易错点:80 端口在部分系统需要权限;端口被占用会启动失败;只改端口不改 host,可能仍无法通过域名访问。

第三步:给 /login-callback 加白名单 ​

为什么需要:项目之前做了登录权限拦截,除根路由外很多页面会被重定向到登录页。QQ 回跳地址是 /login-callback,如果不加白名单,会被拦截,看不到回调页面。怎么想:回调页在“尚未完成登录”时就要能访问,因此必须放进白名单。怎么做:在路由守卫的白名单中加入回调路由:

ts
const whiteList = ['/login', '/login-callback']

加完后访问 /login-callback,即使页面是空白,只要没有被重定向到 /login,就说明回跳地址已准备好。易错点:白名单路径要和路由完全一致;加错成 /login-callback/ 或拼写错误都不生效。

第四步:引入 QQ SDK,拿到跳转地址并改成 A 链接 ​

为什么需要:QQ 登录跳转地址不是手写的,需要 QQ SDK 生成。怎么想:先临时用 QC.Login 生成 QQ 按钮,观察它打开的地址,再把地址复制到自己的 A 标签上,避免保留弹窗逻辑。怎么做:在 index.html 中引入 QQ 互联提供的 SDK 脚本(地址以笔记/QQ 互联后台为准):

html
<!-- 粘贴 QQ 互联提供的 SDK 脚本 -->
<script src="<QQ互联提供的 SDK 地址>"></script>

在登录组件临时写:

ts
onMounted(() => {
  QC.Login({ btnId: 'qq' })
})

给按钮加 id="qq",运行后点击生成的 QQ 按钮,浏览器会 window.open 一个授权地址。复制该地址,删除临时 QC.Login 代码,把按钮改成 A 标签:

html
<a href="<复制出来的QQ授权地址>">QQ登录</a>

易错点:不要保留弹窗按钮;授权地址参数要完整复制;SDK 未加载时 QC 不存在;本节临时出现的 QC 类型/ESLint 报错可先不处理。

调试与验证 ​

访问本地回调域名,确认打开的是自己的项目;访问 /login-callback,若被重定向到 /login,回到白名单补上;点击 QQ 登录 A 链接,应跳到 QQ 扫码/授权页;扫码并同意后,地址应回到 /login-callback。看到回跳成功,就完成了本节目标。易错点:扫码后回跳仍到线上,通常是 hosts 或 DNS 未生效;回跳 404/空白,检查路由和白名单;授权页提示回调地址错误,检查 QQ 互联配置与本地访问域名是否完全一致。

这一节要解决什么问题 ​

上一节已经能跳转到 QQ 并回跳到 /login-callback,这一节要在回调页拿到 openId,用它向后台发起登录。若该 openId 之前绑定过手机号,就直接登录成功;若没绑定过,就展示绑定手机号界面。

  • 为什么需要:QQ 回调页没有账号密码,openId 是 QQ 侧提供给我们的登录凭证。
  • 怎么想:先取 openId,再调“用 openId 登录”的接口;成功走登录成功逻辑,失败则记录 openId 并显示绑定手机界面。
  • 怎么做:建 /login-callback 组件与路由 → 用 QC.Login.check、QC.Login.getMe 取 openId → 补 QC 类型 → 封装 loginByQQ → 用 isNeedBind 控制绑定界面。
  • 易错点:/login-callback 需要加入路由白名单;getMe 的 openId 在回调参数里;登录失败不一定是接口异常,常见原因是“未绑定手机”。

先建回调路由和组件 ​

  • 为什么需要:QQ 登录成功后会回到固定回调地址,必须有对应组件承接。
  • 怎么想:在 login 目录下新建 login-callback.vue,在路由中配置 /login-callback。
  • 怎么做:路由指向该组件,标题可设为“三方登录”。上一节已把 login-callback 加入白名单,否则会被登录拦截。
  • 易错点:路径要和 QQ 互联配置的回调地址一致;不要漏掉白名单。
ts
{
  path: '/login-callback',
  component: () => import('@/views/login/login-callback.vue'),
  meta: { title: '三方登录' }
}

用 QC 获取 openId ​

  • 为什么需要:回调页要靠 QQ 的 JS API 取出当前登录用户的 openId。
  • 怎么想:先 QC.Login.check() 判断是否已 QQ 登录,返回 true 再调用 QC.Login.getMe(cb),从回调参数拿 openId。
  • 怎么做:在 onMounted 中调用,并先打印验证。
  • 易错点:check 返回 false 时不要继续取;getMe 的参数是回调函数,openId 是字符串。
ts
onMounted(() => {
  if (QC.Login.check()) {
    QC.Login.getMe((openId: string) => {
      console.log('openId', openId)
    })
  }
})

给 QC 补类型和 ESLint 全局声明 ​

  • 为什么需要:QC 是 JS SDK 注入的全局对象,TS 会报“找不到 QC”,ESLint 会报未定义。
  • 怎么想:在 global.d.ts 里定义 QCType 并声明全局变量;ESLint 用注释声明全局。
  • 怎么做:
ts
type QCType = {
  Login: {
    check: () => boolean
    getMe: (cb: (openId: string) => void) => void
  }
}
declare const QC: QCType
ts
/* global QC */
  • 易错点:check 返回 boolean,getMe 回调参数是 string;ESLint 注释放文件顶部。

封装 loginByQQ 并处理成功/失败 ​

  • 为什么需要:组件里不应直接写请求细节,接口统一放 API 层。
  • 怎么想:接口是 POST,需要 openId 和来源 source;来源默认 QQ,所以函数只暴露 openId。
  • 怎么做:在 user.ts 增加 loginByQQ,组件中调用;成功走登录成功逻辑,失败设置 isNeedBind = true 并保存 openId。
  • 易错点:source 默认值写 QQ;失败分支要保存 openId,绑定手机时还要用。
ts
export const loginByQQ = (openId: string) =>
  request.post('/login', { openId, source: 'QQ' })
ts
const openId = ref('')
const isNeedBind = ref(false)

QC.Login.getMe(async (id: string) => {
  openId.value = id
  try {
    const res = await loginByQQ(id)
    // 登录成功:按之前登录成功逻辑处理
  } catch {
    isNeedBind.value = true
  }
})

用 isNeedBind 控制绑定手机界面 ​

  • 为什么需要:未绑定手机时不能直接完成登录,要引导用户绑定。
  • 怎么想:isNeedBind 为 true 才显示绑定手机结构;样式复用登录页样式。
  • 怎么做:模板用 v-if="isNeedBind";把登录页样式抽到 login.scss,回调页也引入。
  • 易错点:绑定界面需要拿到刚才保存的 openId;未绑定测试时登录失败并展示界面是正常现象。

调试与验证 ​

刷新 /login-callback,控制台应打印 openId;用未绑定过的 QQ 登录,会进入失败分支并显示绑定手机界面。若没有显示,先检查 openId 是否拿到、接口参数是否传对、isNeedBind 是否在 catch 中置为 true。这样,本节“用 openId 登录,未绑定时展示手机绑定界面”的逻辑就完成了。

代码示例 ​

示例一:引入 QQ JS SDK,把登录按钮改成跳转 QQ 的 A 标签 ​

html
<!-- index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>登录 - 优医问诊</title>
    <style>
      .login-page { padding: 40px 20px; }
      .btn {
        width: 100%; height: 44px; margin-bottom: 12px;
        border: 1px solid #ccc; border-radius: 22px; background: #fff;
      }
      .qq-login {
        display: block; width: 44px; height: 44px; margin: 24px auto 0;
        border-radius: 50%; background: #12b7f5; color: #fff;
        text-align: center; line-height: 44px; text-decoration: none;
      }
    </style>
  </head>
  <body>
    <div class="login-page">
      <button class="btn">密码登录</button>
      <button class="btn">短信登录</button>

      <!-- 把原来的按钮改造成一个 A 标签,href 稍后用 JS 填上 -->
      <a id="qqLogin" class="qq-login" href="javascript:;">QQ</a>

      <!-- 临时用来生成按钮的容器 -->
      <div id="qq" style="display:none"></div>
    </div>

    <!-- ① 引入 QQ 互联提供的 JS SDK:appid 与回调地址在 QQ 互联后台配置 -->
    <script
      type="text/javascript"
      src="https://connect.qq.com/qc_jssdk.js"
      data-appid="你的APPID"
      data-redirecturi="http://consult.passion.cn/login/call-back"
    ></script>

    <script>
      // ② 用 QC.Login 生成一个 QQ 登录按钮(目的只是拿到它的跳转地址)
      QC.Login({ btnId: 'qq' });

      // ③ 在浏览器里找到生成的按钮,把它的 href 复制出来写死在这里
      //    下面这一串就是"复制出来的地址",真实项目里替换成你复制的那一条
      var QQ_LOGIN_URL = 'https://graph.qq.com/oauth2.0/...';

      document.getElementById('qqLogin').href = QQ_LOGIN_URL;

      // ④ 地址拿到后,QC.Login 这段临时代码和 #qq 容器都可以删掉
    </script>
  </body>
</html>

关键行与易错点

  • 引入 qc_jssdk.js 之后,全局才多出一个 QC 对象;data-appid 和 data-redirecturi 必须与 QQ 互联后台配置完全一致。
  • 回调地址不能是 localhost / 127.0.0.1,必须是合法域名,所以开发时要用 hosts 映射 + 端口 80 来“骗”过它。
  • QC.Login({ btnId: 'qq' }) 生成的是一个会 window.open 弹窗的按钮,我们不要弹窗,只要它 href 上的跳转地址,最终用 A 标签直接跳转。
  • 生成按钮那段代码是临时的,拿到地址后要删掉,否则页面上会多出一个按钮。
  • 回跳地址里带了路由 /login/call-back,这个路由之前被登录拦截了,必须加进白名单才能正常进入。

示例二:回调页拿到 openId 登录,失败时展示绑定手机号界面 ​

vue
<!-- src/views/login/login-call-back.vue -->
<template>
  <div class="login-call-back">
    <!-- 只有"未绑定手机号"(登录失败)时才显示 -->
    <div class="bind-mobile" v-if="isNeedBind">
      <h3>绑定手机号</h3>
      <van-field v-model="mobile" placeholder="请输入手机号" />
      <van-field v-model="code" placeholder="请输入验证码">
        <template #button>
          <span @click="onSend">发送验证码</span>
        </template>
      </van-field>
      <van-button type="primary" block @click="bind">立即绑定</van-button>
    </div>
  </div>
</template>

<script setup lang="ts">
/* global QC */
import { onMounted, ref } from 'vue'
import { loginByQQ, bindMobile } from '@/services/user'

const openId = ref('')        // QQ 返回的 openId,绑定手机号时还要用
const isNeedBind = ref(false) // 是否需要展示绑定手机号界面
const mobile = ref('')
const code = ref('')

onMounted(() => {
  // ① 先判断是否已经使用 QQ 登录过
  if (QC.Login.check()) {
    // ② 通过回调拿到 openId
    QC.Login.getMe((id: string) => {
      console.log(id)
      openId.value = id

      // ③ 用 openId 向后台发登录请求
      loginByQQ(id)
        .then((res) => {
          loginSuccess(res) // 之前绑定过,直接登录成功
        })
        .catch(() => {
          // 登录失败 = 没绑定过手机号,展示绑定界面
          isNeedBind.value = true
        })
    })
  }
})

// ④ 绑定手机号,成功后走和正常登录一样的逻辑
async function bind() {
  const res = await bindMobile({
    mobile: mobile.value,
    code: code.value,
    openId: openId.value
  })
  loginSuccess(res)
}
</script>
ts
// src/types/global.d.ts
type QCType = {
  Login: {
    check: () => boolean                          // 是否使用 QQ 登录过
    getMe: (cb: (openId: string) => void) => void // 回调里拿 openId
  }
}

declare const QC: QCType

关键行与易错点

  • 必须先 check() 再 getMe(),getMe 是回调式的,openId 只能在回调参数里拿,不能当返回值用。
  • openId 一定要存进 ref,因为后面绑定手机号还要把它传给接口;只 console.log 是不够的。
  • QC 是 JS 文件挂上去的全局变量,TS 会报“找不到名称 QC”:用 declare const QC: QCType 声明类型,再在文件顶部加 /* global QC */ 让 ESLint 也认它是全局变量。
  • 登录失败(catch)不是异常,而是“没绑定过手机号”的业务信号,用 isNeedBind 控制绑定界面显示,而不是直接提示错误。
  • 登录成功和绑定成功要走同一套逻辑(存用户信息 + 跳转 + 提示),所以抽成 loginSuccess,两处复用。

示例三:用环境变量区分开发 / 生产环境的回调域名与标题 ​

bash
# .env.development —— 只在 pnpm dev 时生效
VITE_APP_CALLBACK=http://consult.passion.cn
VITE_APP_TITLE=优医问诊-DEV
bash
# .env.production —— 只在 pnpm build 之后生效
VITE_APP_CALLBACK=https://consult.itheima.net
VITE_APP_TITLE=优医问诊
ts
// vite.config.ts
import { defineConfig } from 'vite'

export default defineConfig({
  server: {
    port: 80,      // QQ 回跳域名不带端口号,所以本地开发也改成 80
    host: true     // 允许用域名(配合 hosts 映射)访问本地服务
  }
})
ts
// src/types/global.d.ts —— 扩展环境变量类型,才有提示
interface ImportMetaEnv {
  readonly VITE_APP_CALLBACK: string
  readonly VITE_APP_TITLE: string
}
ts
// src/main.ts —— 使用环境变量
console.log(import.meta.env.VITE_APP_CALLBACK)

关键行与易错点

  • 变量名必须以 VITE_ 开头,否则不会暴露到 import.meta.env 上。
  • 文件名后缀决定生效时机:.env.development 只给 dev,.env.production 只给 build;改完 .env 必须重启 dev server 才生效。
  • port: 80 是因为 QQ 后台配置的回调域名没有端口号,本地也要“无端口”才能对上;host: true 才能用 hosts 里映射的域名访问。
  • 不扩展 ImportMetaEnv 的话,import.meta.env.VITE_APP_CALLBACK 会没提示甚至报错。
  • 同一个域名在两套环境值不同,index.html 里的 QQ SDK data-redirecturi 和页面标题都应读这个变量,而不是写死。

常见错误 ​

  1. 回跳后页面被弹回 /login,看不到回调页

    • 现象:QQ 授权成功后地址栏变成 /login/callback,但页面瞬间跳转到登录页。
    • 原因:全局路由守卫对除根路由外的地址做登录拦截,/login/callback 没有进白名单。
    • 修法:在 router/index.ts 的白名单数组里补上 '/login/callback',再手动访问该地址确认能停留在白页。
  2. 配好 hosts 后访问回调域名,看到的仍是线上页面或打不开

    • 现象:域名解析到了线上站点,或本地 5173 端口访问不到。
    • 原因:三件事只做了一件——hosts 映射没生效/DNS 缓存未刷新;Vite 端口不是 80,而回调域名不带端口;没开 server.host 只允许 localhost 访问。
    • 修法:确认 hosts 写入并刷新 DNS;server.port 改为 80;server.host 打开允许域名访问;重启 dev server 后用域名访问,能看到自己项目标题才算成功。
  3. QC is not defined,或编辑器满屏红波浪线

    • 现象:组件里用 QC.Login 报未定义;TS 提示属性不存在;ESLint 报 no-undef。
    • 原因:index.html 没引入 QQ 互联的 SDK 脚本;TS 没有该全局对象的类型声明;ESLint 不知道它是全局变量。
    • 修法:先在 index.html 引入 SDK;在 global.d.ts 里声明 QC 的类型(login.check 返回 boolean、login.getMe(cb) 回调参数为 string 的 openId);文件顶部加 /* global QC */ 注释消除 ESLint 报错。
  4. 点“QQ 登录”弹出小窗口,而不是整页跳转

    • 现象:点击后 window.open 开了一个新窗口,回跳后新窗口里才是自己系统。
    • 原因:直接用了 QC.Login 渲染按钮,其内部实现就是 window.open,而需求是当前页跳转。
    • 修法:在 DevTools 里找到该按钮绑定的事件源码,复制其中生成的 URL,把按钮改成 <a :href="url">,然后删除 QC.Login 的临时调用代码和多余 id。
  5. 用同一个 QQ 反复测试,永远进不了“绑定手机”分支

    • 现象:第二次登录直接成功,无法再验证绑定流程。
    • 原因:openId 已与手机号绑定,服务端直接放行。
    • 修法:调三方登录解绑接口,userInfo 填要解绑的手机号,Authorization 必须是 Bearer + 空格 + token,发送成功后重新扫码即可回到绑定流程。
  6. import.meta.env.VITE_XXX 打印出 undefined

    • 现象:main.ts 里打印回调域名是 undefined,或仍是旧值。
    • 原因:变量名没加 VITE_ 前缀不会被注入;文件名写错(必须是 .env.development / .env.production);改完没重启。
    • 修法:统一 VITE_ 前缀、核对文件名、重启 dev server;用 pnpm dev 与 build + preview 各跑一次对比打印结果。
  7. 环境变量在 TS 里没提示、报类型错误;在 index.html 里取值不生效

    • 现象:import.meta.env.VITE_APP_CALLBACK 标红;HTML 里写变量名原样输出。
    • 原因:ImportMetaEnv 接口未扩展;HTML 模板不支持 JS 表达式,只认占位符语法。
    • 修法:在 global.d.ts 中扩展 ImportMetaEnv(callback、title 均为 string);HTML 中用 %VITE_XXX% 占位写法。
  8. 绑定成功后没有回到进入登录页时的那一页

    • 现象:总是跳到 /user,丢失了来源页。
    • 原因:跳 QQ 前没记录来源地址;或记录后成功后没清理,下次登录被旧值污染。
    • 修法:点击 QQ 登录时把当前路由存进 user store 的 returnUrl;loginSuccess 里优先 replace(returnUrl),无值才去 /user,跳转后立即清空该字段。

练习 ​

  1. 从零跑通“回跳”这一段:配置 hosts 与 Vite(端口 80、允许域名访问),把 /login/callback 加进白名单,并用浏览器地址栏直接访问该路由验证不再被拦截。写出你验证成功的三条判据。

  2. 打开 DevTools,定位 QC.Login 生成的按钮,复制其跳转 URL,把登录页按钮改造成 <a> 标签;随后删除临时代码。说明删除前后各发生了什么变化(网络请求、窗口行为)。

  3. 为 QC 补齐类型声明与 ESLint 全局声明,要求:check() 返回 boolean,getMe(cb) 的回调参数是 string。补完后在组件里取出 openId 并打印,确认无红波浪线。

  4. 把发送验证码逻辑抽成 useMobileCode(mobile, type = 'login'):说明为什么 mobile 要声明为 Ref<string>、为什么 type 要有默认值,以及返回值中为什么要暴露 form 实例和倒计时。

  5. 配置 .env.development 与 .env.production 两个回调域名变量,并分别在 main.ts 与 index.html(标题或脚本参数)中使用;用 pnpm dev 和 build + preview 各验证一次,写出你看到的差异。

课件图示 ​

课件第3页:三方登录-QQ登录流程 去QQ登录且回跳 使用openId登录 验证码 comp

课件第9页:三方登录-验证码composable 约定入参返参 登录页使用 绑定手机页使用

课件第7页:三方登录-使用openId登录 获取openId 给QC添加类型 使用openI

课件第5页:三方登录-去QQ登录且回跳 配置host和白名单 设置跳转QQ地址 hosts

课件第11页:三方登录-绑定手机号 绑定手机API 实现绑定手机号 回跳来源页

课件第13页: 什么是开发环境和生产环境?  pnpm dev 开发环境  pnpm b

原视频对照 ​

#视频时长
1三方登录-QQ登录流程和演示08:08
2三方登录-去QQ登录且回跳18:11
3三方登录-使用openId登录17:02
4三方登录-验证码composable10:59
5三方登录-绑定手机12:58
6部署-开发环境与生产环境-01如何区分11:18

小结 ​

  • 整条链路是“跳出去 → 跳回来 → 换凭证 → 补绑定”四段,任何一段的域名、端口、白名单对不上,都会表现为“回跳失败”这一个笼统现象,排查要按段定位。
  • 回调地址是第三方平台写死的,本地开发只能靠 hosts + 80 端口 + 允许域名访问去“伪造”线上环境,这三项是一个整体,缺一不可。
  • 第三方 SDK 往往只提供“生成按钮”的封装,真正的跳转 URL 才是可复用的产物;拿到 URL 后自己控制跳转,行为才可控。
  • 引入外部全局脚本必然带来 TS 类型缺失和 ESLint 未定义两类报错,正确姿势是“声明类型 + 声明全局”,而不是用 any 或关闭规则掩盖。
  • 登录成功后的收尾动作(存用户信息、回跳、提示)在密码登录、openId 登录、绑定手机登录三处完全一致,抽成 loginSuccess 是消除重复的关键,也让后续加微信登录零成本。
  • 验证码这类“多入口同逻辑”的能力,用 composable 承载入参与出参约定,比复制代码更安全:类型参数(login / bindMobile)让同一函数服务不同场景。
  • 环境差异(回调域名、支付回调、标题标识)必须靠环境变量文件隔离,靠手工改代码区分环境迟早会带上线事故;同时别忘了给变量补类型,否则等于放弃了 TS 的保护。
  • 反复测试的阻碍往往来自“数据已被写过”,善用解绑接口与清理 store 中的 returnUrl,才能保证每次都是从零开始的干净用例。