Vue 开发 Web 端的三种登录方式:微信扫码登录、手机验证码登录、账号密码登录。
·
我们来详细讲解如何使用 Vue 3 开发 Web 端的这三种核心登录方式:微信扫码登录、手机验证码登录、账号密码登录。
我将提供清晰的代码结构、关键代码片段以及详尽的讲解,帮助你理解整个流程。
项目结构与准备工作
假设我们有一个 Vue 3 项目(使用 Vite 创建),并安装了 axios 用于网络请求,vue-router 用于路由管理。
src/
|-- api/
| |-- login.js # 封装所有登录相关的 API 请求
|-- views/
| |-- Login.vue # 登录页面组件
|-- router/
| |-- index.js # 路由配置
|-- store/
| |-- user.js # (可选) 使用 Pinia 或 Vuex 管理用户状态
|-- main.js
1. 后端接口约定
在开始前端开发前,你需要和后端约定好以下接口:
- 微信登录:
GET /api/wechat/login/url: 获取微信登录二维码的跳转 URL。POST /api/wechat/login/callback: 微信扫码成功后,后端接收code和state进行处理,并返回token。
- 手机验证码登录:
POST /api/sms/send: 发送短信验证码,需要手机号作为参数。POST /api/sms/login: 使用手机号和验证码进行登录,返回token。
- 账号密码登录:
POST /api/account/login: 使用账号和密码进行登录,返回token。
2. api/login.js 封装请求
将所有网络请求封装起来,便于管理和复用。
// src/api/login.js
import axios from 'axios'; // 假设你已配置好 axios 实例
// 获取微信登录二维码 URL
export const getWechatLoginUrl = () => {
return axios.get('/api/wechat/login/url');
};
// 发送短信验证码
export const sendSmsCode = (phone) => {
return axios.post('/api/sms/send', { phone });
};
// 手机验证码登录
export const loginBySms = (phone, code) => {
return axios.post('/api/sms/login', { phone, code });
};
// 账号密码登录
export const loginByAccount = (username, password) => {
return axios.post('/api/account/login', { username, password });
};
views/Login.vue 组件实现
这是我们的核心登录页面,它将包含三种登录方式的切换和逻辑处理。
<template> 部分
<template>
<div class="login-container">
<div class="login-box">
<div class="tabs">
<span @click="activeTab = 'account'" :class="{ active: activeTab === 'account' }">账号登录</span>
<span @click="activeTab = 'sms'" :class="{ active: activeTab === 'sms' }">短信登录</span>
<span @click="activeTab = 'wechat'" :class="{ active: active-tab === 'wechat' }">微信登录</span>
</div>
<!-- 账号密码登录表单 -->
<div v-if="activeTab === 'account'" class="form-section">
<input type="text" v-model="accountForm.username" placeholder="请输入账号" />
<input type="password" v-model="accountForm.password" placeholder="请输入密码" />
<button @click="handleAccountLogin">登录</button>
</div>
<!-- 手机验证码登录表单 -->
<div v-if="activeTab === 'sms'" class="form-section">
<input type="text" v-model="smsForm.phone" placeholder="请输入手机号" />
<div class="sms-code-wrapper">
<input type="text" v-model="smsForm.code" placeholder="请输入验证码" />
<button @click="handleSendSms" :disabled="isCountingDown">
{{ countdownText }}
</button>
</div>
<button @click="handleSmsLogin">登录</button>
</div>
<!-- 微信扫码登录 -->
<div v-if="activeTab === 'wechat'" class="wechat-section">
<p>请使用微信扫描二维码登录</p>
<div id="wechat-login-container"></div>
</div>
</div>
</div>
</template>
<style> 部分 (简单样式)
<style scoped>
.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.login-box {
width: 350px;
padding: 20px;
border: 1px solid #ccc;
border-radius: 8px;
}
.tabs {
display: flex;
justify-content: space-around;
margin-bottom: 20px;
}
.tabs span {
cursor: pointer;
padding-bottom: 5px;
}
.tabs span.active {
color: #409eff;
border-bottom: 2px solid #409eff;
}
.form-section input {
width: 100%;
padding: 10px;
margin-bottom: 15px;
box-sizing: border-box;
}
.sms-code-wrapper {
display: flex;
}
.sms-code-wrapper input {
flex-grow: 1;
}
.sms-code-wrapper button {
margin-left: 10px;
}
.wechat-section {
text-align: center;
}
#wechat-login-container {
display: inline-block; /* 必须,否则 WxLogin 可能无法正确渲染 */
}
</style>
<script setup> 部分 (Vue 3 Composition API)
这是所有逻辑的核心。
<script setup>
import { ref, reactive, watch, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { getWechatLoginUrl, sendSmsCode, loginBySms, loginByAccount } from '@/api/login';
// 引入微信 JS-SDK,你可能需要先 npm install weixin-js-sdk
// 对于扫码登录,我们使用微信官方提供的 JS 构造器
// 需要在 index.html 中引入: <script src="http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"></script>
const router = useRouter();
const activeTab = ref('account');
// --- 账号密码登录 ---
const accountForm = reactive({
username: '',
password: '',
});
const handleAccountLogin = async () => {
if (!accountForm.username || !accountForm.password) {
alert('请输入账号和密码');
return;
}
try {
const res = await loginByAccount(accountForm.username, accountForm.password);
handleLoginSuccess(res.data.token);
} catch (error) {
console.error('账号登录失败:', error);
alert('账号或密码错误');
}
};
// --- 手机验证码登录 ---
const smsForm = reactive({
phone: '',
code: '',
});
const isCountingDown = ref(false);
const countdown = ref(60);
const countdownText = ref('获取验证码');
let timer = null;
const handleSendSms = async () => {
if (!/^1[3-9]\d{9}$/.test(smsForm.phone)) {
alert('请输入正确的手机号');
return;
}
isCountingDown.value = true;
countdownText.value = `${countdown.value}s后重试`;
timer = setInterval(() => {
countdown.value--;
countdownText.value = `${countdown.value}s后重试`;
if (countdown.value <= 0) {
clearInterval(timer);
isCountingDown.value = false;
countdown.value = 60;
countdownText.value = '获取验证码';
}
}, 1000);
try {
await sendSmsCode(smsForm.phone);
alert('验证码已发送');
} catch (error) {
console.error('发送验证码失败:', error);
alert('发送失败,请稍后重试');
// 发送失败时也应该重置倒计时
clearInterval(timer);
isCountingDown.value = false;
countdown.value = 60;
countdownText.value = '获取验证码';
}
};
const handleSmsLogin = async () => {
if (!smsForm.phone || !smsForm.code) {
alert('请输入手机号和验证码');
return;
}
try {
const res = await loginBySms(smsForm.phone, smsForm.code);
handleLoginSuccess(res.data.token);
} catch (error) {
console.error('短信登录失败:', error);
alert('登录失败,验证码错误或已过期');
}
};
// --- 微信扫码登录 ---
const initWechatLogin = async () => {
try {
// 1. 从后端获取微信登录所需的参数
const res = await getWechatLoginUrl();
const { appid, scope, redirect_uri, state, style, href } = res.data;
// 2. 使用 WxLogin 构造器生成二维码
// WxLogin 是全局对象,来自我们引入的 JS 文件
new WxLogin({
self_redirect: false, // 是否内嵌二维码,false 表示跳转
id: "wechat-login-container", // 容器 ID
appid: appid,
scope: scope,
redirect_uri: redirect_uri, // 扫码成功后,微信会携带 code 和 state 跳转到这个地址
state: state,
style: style, // "black" or "white"
href: href, // 自定义样式表的 URL
});
} catch (error) {
console.error('初始化微信登录失败:', error);
}
};
// 监听 tab 切换,当切换到微信登录时才初始化
watch(activeTab, (newVal) => {
if (newVal === 'wechat') {
// DOM 更新后再执行,确保容器已渲染
nextTick(() => {
initWechatLogin();
});
}
});
// --- 公共登录成功处理 ---
const handleLoginSuccess = (token) => {
// 1. 保存 token (例如到 localStorage 或 Pinia/Vuex store)
localStorage.setItem('token', token);
// (可选) 可以在这里保存用户信息到 Pinia/Vuex
// userStore.setUserInfo(...)
// 2. 跳转到首页或其他页面
router.push('/');
alert('登录成功!');
};
// 处理微信登录回调
// 微信扫码成功后,会跳转到你在后端配置的 redirect_uri,
// 页面 URL 会变成这样:http://yourdomain.com/login-callback?code=xxx&state=yyy
// 你需要一个专门的页面或在当前页面处理这个回调
onMounted(() => {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
if (code && state) {
// 通常这里会调用后端的 callback 接口,用 code 换取 token
// loginByWechatCode(code, state).then(res => handleLoginSuccess(res.data.token));
// 这里只是演示,实际项目需要一个专门的回调处理逻辑或页面
console.log('接收到微信回调:', { code, state });
}
});
</script>
代码讲解与核心要点
1. 账号密码登录
- 流程: 用户输入 -> 点击登录 -> 调用
loginByAccountAPI -> 成功后调用handleLoginSuccess。 - 代码:
handleAccountLogin函数负责整个流程,非常直接。
2. 手机验证码登录
- 流程:
- 用户输入手机号,点击“获取验证码”。
- 前端进行手机号格式校验。
- 调用
sendSmsCodeAPI,后端发送短信。 - 前端启动一个 60 秒倒计时,防止用户频繁点击。
- 用户收到短信,输入验证码,点击登录。
- 调用
loginBySmsAPI,后端校验验证码并完成登录。
- 代码:
handleSendSms函数处理发送和倒计时逻辑。isCountingDown、countdown等ref变量用于控制按钮状态和文本。
3. 微信扫码登录 (PC端)
- 核心原理: 这是基于 OAuth 2.0 协议的。前端的角色是引导用户到微信的授权页面。
- 流程:
- 前端向后端请求参数: 前端不应暴露 AppID、AppSecret 等敏感信息。前端先调用
getWechatLoginUrl接口,后端会返回初始化二维码所需的参数,包括appid、scope(授权范围)、redirect_uri(扫码成功后的回调地址)和state(一个随机字符串,用于防止 CSRF 攻击)。 - 前端生成二维码:
- 首先,在
public/index.html中引入微信官方的 JS 文件:<script src="http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"></script>。 - 在 Vue 组件中,调用
new WxLogin({...})构造函数。它会在指定的容器 (id: "wechat-login-container") 内生成一个 iframe,里面就是微信的登录二维码。
- 首先,在
- 用户扫码授权: 用户使用手机微信扫码,并在手机上确认登录。
- 微信重定向: 确认后,微信服务器会将页面重定向到你在后端配置的
redirect_uri,并在 URL 参数中附带code和state。 - 后端用
code换token: 你的前端回调页面(或逻辑)拿到code后,需要再将其发送给后端(例如POST /api/wechat/login/callback)。后端用这个code和你的AppSecret去请求微信服务器,换取用户的access_token和openid,最终生成你自己系统的token并返回给前端。
- 前端向后端请求参数: 前端不应暴露 AppID、AppSecret 等敏感信息。前端先调用
- 代码:
initWechatLogin负责从后端获取参数并初始化二维码。watch(activeTab, ...)确保只有在用户切换到微信登录时才加载二维码,避免不必要的性能开销。onMounted中的代码演示了如何处理回调 URL 中的code和state。在实际项目中,你可能需要一个专门的路由和页面来处理这个回调,用户体验会更好。
更多推荐


所有评论(0)