主题
三方登录与组件测试(一)
本页对应 三方登录与组件测试,共 6 段视频,约 1 小时 18 分。
学习目标
- 能够复述 QQ 三方登录的完整流程,并区分 H5 唤起 App 与 PC 扫码登录、openId 直接登录、未绑定手机时进入绑定流程等分支。
- 能够通过配置 host 映射、Vite 的端口与 host、路由白名单,实现从登录页跳转 QQ 登录并回跳到本地
login-callback页面。 - 能够引入 QQ JS SDK,使用
QC.Login.check与QC.Login.getMe获取 openId,并为全局QC对象补充 TypeScript 类型和 ESLint 全局声明。 - 能够编写使用 openId 登录的 API,并在登录失败时通过状态控制展示绑定手机号界面。
- 能够封装验证码 composable,在登录页和绑定手机页复用发送验证码、倒计时和表单校验逻辑。
- 能够实现绑定手机 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: QCTypeESLint 提示未定义时,在文件顶部加 /* 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: QCTypets
/* 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=优医问诊-DEVbash
# .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 SDKdata-redirecturi和页面标题都应读这个变量,而不是写死。
常见错误
回跳后页面被弹回
/login,看不到回调页- 现象:QQ 授权成功后地址栏变成
/login/callback,但页面瞬间跳转到登录页。 - 原因:全局路由守卫对除根路由外的地址做登录拦截,
/login/callback没有进白名单。 - 修法:在
router/index.ts的白名单数组里补上'/login/callback',再手动访问该地址确认能停留在白页。
- 现象:QQ 授权成功后地址栏变成
配好 hosts 后访问回调域名,看到的仍是线上页面或打不开
- 现象:域名解析到了线上站点,或本地 5173 端口访问不到。
- 原因:三件事只做了一件——hosts 映射没生效/DNS 缓存未刷新;Vite 端口不是 80,而回调域名不带端口;没开
server.host只允许 localhost 访问。 - 修法:确认 hosts 写入并刷新 DNS;
server.port改为80;server.host打开允许域名访问;重启 dev server 后用域名访问,能看到自己项目标题才算成功。
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 报错。
- 现象:组件里用
点“QQ 登录”弹出小窗口,而不是整页跳转
- 现象:点击后
window.open开了一个新窗口,回跳后新窗口里才是自己系统。 - 原因:直接用了
QC.Login渲染按钮,其内部实现就是window.open,而需求是当前页跳转。 - 修法:在 DevTools 里找到该按钮绑定的事件源码,复制其中生成的 URL,把按钮改成
<a :href="url">,然后删除QC.Login的临时调用代码和多余 id。
- 现象:点击后
用同一个 QQ 反复测试,永远进不了“绑定手机”分支
- 现象:第二次登录直接成功,无法再验证绑定流程。
- 原因:openId 已与手机号绑定,服务端直接放行。
- 修法:调三方登录解绑接口,
userInfo填要解绑的手机号,Authorization必须是Bearer+ 空格 + token,发送成功后重新扫码即可回到绑定流程。
import.meta.env.VITE_XXX打印出undefined- 现象:
main.ts里打印回调域名是 undefined,或仍是旧值。 - 原因:变量名没加
VITE_前缀不会被注入;文件名写错(必须是.env.development/.env.production);改完没重启。 - 修法:统一
VITE_前缀、核对文件名、重启 dev server;用pnpm dev与build + preview各跑一次对比打印结果。
- 现象:
环境变量在 TS 里没提示、报类型错误;在
index.html里取值不生效- 现象:
import.meta.env.VITE_APP_CALLBACK标红;HTML 里写变量名原样输出。 - 原因:
ImportMetaEnv接口未扩展;HTML 模板不支持 JS 表达式,只认占位符语法。 - 修法:在
global.d.ts中扩展ImportMetaEnv(callback、title 均为 string);HTML 中用%VITE_XXX%占位写法。
- 现象:
绑定成功后没有回到进入登录页时的那一页
- 现象:总是跳到
/user,丢失了来源页。 - 原因:跳 QQ 前没记录来源地址;或记录后成功后没清理,下次登录被旧值污染。
- 修法:点击 QQ 登录时把当前路由存进 user store 的
returnUrl;loginSuccess里优先replace(returnUrl),无值才去/user,跳转后立即清空该字段。
- 现象:总是跳到
练习
从零跑通“回跳”这一段:配置 hosts 与 Vite(端口 80、允许域名访问),把
/login/callback加进白名单,并用浏览器地址栏直接访问该路由验证不再被拦截。写出你验证成功的三条判据。打开 DevTools,定位
QC.Login生成的按钮,复制其跳转 URL,把登录页按钮改造成<a>标签;随后删除临时代码。说明删除前后各发生了什么变化(网络请求、窗口行为)。为
QC补齐类型声明与 ESLint 全局声明,要求:check()返回 boolean,getMe(cb)的回调参数是 string。补完后在组件里取出 openId 并打印,确认无红波浪线。把发送验证码逻辑抽成
useMobileCode(mobile, type = 'login'):说明为什么mobile要声明为Ref<string>、为什么type要有默认值,以及返回值中为什么要暴露form实例和倒计时。配置
.env.development与.env.production两个回调域名变量,并分别在main.ts与index.html(标题或脚本参数)中使用;用pnpm dev和build + preview各验证一次,写出你看到的差异。
课件图示






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