建议大家先去看我第一篇小兔鲜的文章,强烈建议,非常建议,十分建议,从头开始看更完整。
最近正在学习vue3小兔鲜,
下面是学习笔记
目标:登录组件在书写一级路由的时候已经准备,添加路由链接跳转到登录页即可。
src/components/app-topnav.vue
<li><RouterLink to="/login">请先登录RouterLink>li>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ismlX4gn-1668072991723)(media/01-16459813154986.png)]
静态结构参考代码 - CV
小兔鲜
欢迎登录
进入网站首页
目标: 实现登录页面的整体大结构布局
src/views/Login/components/login-form.vue
<script setup lang="ts">
//
script>
<template>
<div class="account-box">
<div class="form">
<div class="form-item">
<div class="input">
<i class="iconfont icon-user">i>
<input type="text" placeholder="请输入用户名或手机号" />
div>
div>
<div class="form-item">
<div class="input">
<i class="iconfont icon-lock">i>
<input type="password" placeholder="请输入密码" />
div>
div>
<div class="form-item">
<div class="agree">
<XtxCheckBox />
<span>我已同意span>
<a href="javascript:;">《隐私条款》a>
<span>和span>
<a href="javascript:;">《服务条款》a>
div>
div>
<a href="javascript:;" class="btn">登录a>
div>
<div class="action">
<img
src="https://qzonestyle.gtimg.cn/qzone/vas/opensns/res/img/Connect_logo_7.png"
alt=""
/>
<div class="url">
<a href="javascript:;">忘记密码a>
<a href="javascript:;">免费注册a>
div>
div>
div>
template>
<style lang="less" scoped>
// 账号容器
.account-box {
.toggle {
padding: 15px 40px;
text-align: right;
a {
color: @xtxColor;
i {
font-size: 14px;
}
}
}
.form {
padding: 0 20px;
&-item {
margin-bottom: 28px;
.input {
position: relative;
height: 36px;
> i {
width: 34px;
height: 34px;
background: #cfcdcd;
color: #fff;
position: absolute;
left: 1px;
top: 1px;
text-align: center;
line-height: 34px;
font-size: 18px;
}
input {
padding-left: 44px;
border: 1px solid #cfcdcd;
height: 36px;
line-height: 36px;
width: 100%;
&.error {
border-color: @priceColor;
}
&.active,
&:focus {
border-color: @xtxColor;
}
}
.code {
position: absolute;
right: 1px;
top: 1px;
text-align: center;
line-height: 34px;
font-size: 14px;
background: #f5f5f5;
color: #666;
width: 90px;
height: 34px;
cursor: pointer;
}
}
> .error {
position: absolute;
font-size: 12px;
line-height: 28px;
color: @priceColor;
i {
font-size: 14px;
margin-right: 2px;
}
}
}
.agree {
a {
color: #069;
}
}
.btn {
display: block;
width: 100%;
height: 40px;
color: #fff;
text-align: center;
line-height: 40px;
background: @xtxColor;
&.disabled {
background: #cfcdcd;
}
}
}
.action {
padding: 20px 40px;
display: flex;
justify-content: space-between;
align-items: center;
.url {
a {
color: #999;
margin-left: 10px;
}
}
}
}
style>
登录模块入口组件 src/views/Login/index.vue
在主体登录区域引入并使用表单组件。
....
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mqc5JFik-1668072991725)(media/05-1627943191361.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YjHBxdKU-1668072991727)(assets/image-20210803065321123.png)]
我已同意
Message 组件支持 函数 和 组件 两种调用方式:
src\components\XtxUI\index.ts
组件库做统一出口// 做统一出口
export { message } from "./Message/index";
目标:校验之前我们已经实现了很多次,这里弱化验证写法,简单做一个非空检验即可。
<script setup lang="ts">
import { reactive } from "vue";
import { message } from "@/components/XtxUI";
const form = reactive({
account: "",
password: "",
});
const loginBtn = () => {
if (!form.account) {
message({ type: "error", text: "用户名或手机号不能为空" });
return;
}
if (!form.password) {
message({ type: "error", text: "密码不能为空" });
return;
}
if (!isAgree.value) {
message({ type: "error", text: "请同意许可" });
return;
}
console.log("通过校验,可以发送请求");
};
script>
// 绑定 form 响应式数据到表单元素中
<input v-model.trim="form.account" type="text" placeholder="请输入用户名或手机号" />
<input v-model.trim="form.password" type="password" placeholder="请输入密码" />
<XtxCheckBox v-model="isAgree">我已同意XtxCheckBox>
// 登录按钮
<a href="javascript:;" class="btn" @click="loginBtn">登录a>
新建文件: src\store\modules\member.ts
import { defineStore } from "pinia";
const useMemberStore = defineStore("member",{
// 状态
state: () => ({}),
// 计算
getters: {},
// 方法
actions: {},
});
export default useMemberStore;
修改文件:src\store\index.ts
import { defineStore } from 'pinia';
import useHomeStore from './modules/home';
+ import useMemberStore from './modules/member';
// 定义总Store,管理所有模块Store
const useStore = defineStore('main', {
// state 存放模块 Store
state: () => ({
home: useHomeStore(),
+ member: useMemberStore(),
}),
});
// 默认导出
export default useStore;
本节目标:
实现账户名密码登录功能
基本信息
Path: /login
Method: POST
接口描述:
登录成功后,后台返回的 token,请在本地保存,并在每次请求接口时在 Header 中携带上。
请求参数
Body
名称 | 类型 | 是否必须 | 默认值 | 备注 | 其他信息 |
---|---|---|---|---|---|
account | string | 必须 | 用户名或手机号 | undefined: ceshi | |
password | string | 必须 | 密码 | undefined: 123456 |
修改文件:src/store/modules/member.ts
import { http } from "@/utils/request";
import { defineStore } from "pinia";
const useMemberStore = defineStore("member",{
// 状态
state: () => ({
profile: {},
}),
// 计算
getters: {},
// 方法
actions: {
// 用户名和密码登录
async login(data: { account: string; password: string }) {
const res = await http("POST", "/login", data);
console.log("/login", res.data.result);
},
},
});
export default useMemberStore;
3)登录表单调用
login-form.vue
新增类型文件:src\types\api\member.d.ts
export interface Profile {
id: string;
account: string;
mobile: string;
token: string;
avatar: string;
nickname: string;
gender: string;
birthday: string;
cityCode: string;
provinceCode: string;
profession: string;
}
应用类型:
import type { Profile } from "@/types";
import { http } from "@/utils/request";
import { defineStore } from "pinia";
import { message } from "@/components/XtxUI";
// 非 vue 组件,导入路由实例
import router from "@/router";
const useMemberStore = defineStore("member", {
// 状态
state: () => ({
// 用户信息
profile: {} as Profile,
}),
// 计算
getters: {
// 是否登录
isLogin(): boolean {
return Boolean(this.profile.token);
},
},
// 方法
actions: {
// 用户名和密码登录
async login(data: { account: string; password: string }) {
const res = await http<Profile>("POST", "/login", data);
// 1. 保存用户信息到 state 中
this.profile = res.data.result;
// 2. 请求成功给用户提示
message({ type: "success", text: "登录成功" });
// 3. 跳转页面
router.push('/')
},
},
});
export default useMemberStore;
登录成功后,把用户信息保存到本地。(本地和pinia都保存)
用户信息渲染: Layout/components/app-topnav.vue
<script setup lang="ts">
import useStore from "@/store";
import { storeToRefs } from "pinia";
const { member } = useStore();
// storeToRefs 解构出来的数据还能保持响应式
const { profile } = storeToRefs(member);
script>
<template>
<nav class="app-topnav">
<div class="container">
<ul>
<template v-if="member.isLogin">
<li>
<a href="javascript:;">
<i class="iconfont icon-user">i>
{{ profile.nickname || profile.account || profile.mobile }}
a>
li>
<li><a href="javascript:;">退出登录a>li>
template>
<template v-else>
<li><RouterLink to="/login">请先登录RouterLink>li>
<li><a href="javascript:;">免费注册a>li>
template>
...
ul>
div>
nav>
template>
本节目标:
实现账户名密码登录功能
1 提供 actions, 清空用户数据 (本地和pinia都清空)
actions: {
// 退出登录
async logout() {
// 1. 清理 Pinia 用户信息
this.profile = {} as Profile;
// 2. 提示用户
message({ type: 'success', text: '退出成功' });
// 3. 跳转页面
router.push("/login");
},
},
<template v-if="profile.isLogin">
<li>
<a href="javascript:;"><i class="iconfont icon-user">i>
{{ profile.nickname || profile.account || profile.mobile }}
a>
li>
<li><a @click="member.logout()" href="javascript:;">退出登录a>li>
template>
src\views\Layout\components\app-topnav.vue
请先登录
登录成功后跳转处理
const useMemberStore = defineStore("member",{
...
// 方法
actions: {
// 用户名密码登录
async login(data: { account: string; password: string }) {
...
// 在非 .vue 组件中 useRoute() 返回 undefined,没法获取当前路由信息
// 解决方案,通过 router 路由实例 currentRoute 获取
const { target = "/" } = router.currentRoute.value.query;
// 跳转到指定地址
router.push(target as string);
},
},
});
完善请求拦截器 utils/request.ts
// 官方说明:https://pinia.vuejs.org/core-concepts/outside-component-usage.html
// ❌ 非组件中,Pinia 常见错误写法
// const { member } = useStore();
// 添加请求拦截器
instance.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
// ✅ 在组件外,哪里使用,写哪里(消费前获取)
const { member } = useStore();
// 1. 获取token
const { token } = member.profile;
// 2. token 和 headers 的非空判断
if (token && config.headers) {
// 3. 请求头中携带 token 信息
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}
);
响应拦截器,添加错误提示
import { message } from '@/components/Message'
// 添加响应拦截器
instance.interceptors.response.use(
function (response) {
// 如果请求成功成功 2xx 就直接返回 data 中的数据
return response
},
function (error) {
// 对响应错误做点什么
if (error.code === 'ERR_NETWORK') {
// 无网络,错误提示
message({ type: 'error', text: '亲,换个网络试试~' });
} else {
// 有网络,但后端认为有错误,提示后端响应的错误
message({ type: 'error', text: error.response.data.message });
}
// 控制台显示错误
return Promise.reject(error);
})
本节目标:
掌握第三方登录的实现流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NxRV3K20-1668072991731)(media/06-1628025535476.png)]
1)参考文档
2)大概步骤
QQ
登录的逻辑(登录页面,回跳页面)QQ
互联上进行身份认证
,并且审核通过
QQ
互联上创建应用,应用需要域名,备案号,回调地址等应用ID
回调地址
, 应用key
后端使用。# 测试用 appid
# 100556005
# 测试用 redirect_uri
# http://www.corho.com:8080/#/login/callback
vite.config.ts
配置。host
文件,访问本地服务器。目标:浏览器访问 http://www.corho.com:8080/#/login/callback 地址,能打开正在开发的本地
Vue
项目。
vite.config.ts
配置。host
文件。vite
配置修改 vite.config.ts
文件:
export default defineConfig({
// 配置开发服务器
server: {
// QQ三方登录的回调uri为:http://www.corho.com:8080/#/login/callback
// vite 中配置: www.corho.com:8080
host: "www.corho.com",
port: 8080,
// 其他有价值的配置项
open: true, // 帮我们打开浏览器
cors: true, // 允许开发时 ajax 跨域
},
...
});
host
文件提醒:修改电脑配置,需要先退出 360 或 各种管家 各种 杀毒软件
提醒:如果修改 hosts 文件有弹窗警告,点击信任(因为这是我们自己进行的安全操作)
1. 找到 C:\Windows\System32\drivers\etc 下 hosts 文件
2. 在文件中加入 127.0.0.1 www.corho.com
3. 保存即可
# 如果提示没有权限
1. 将hosts文件移到桌面,然后进行修改,确认保存。
2. 将桌面hosts文件替换c盘文件
1. 打开命令行窗口
2. 输入:sudo vim /etc/hosts
3. 按下:i 键
4. 输入:127.0.0.1 www.corho.com
5. 按下:esc
6. 按下:shift + :
7. 输入:wq 回车即可
步骤验证:浏览器访问 http://www.corho.com:8080/#/ 能看到自己开发的 Vue3 项目表示成功。
由于本地我们的网站是访问
http://localhost:3000
而回调地址的域名是
http://www.corho.com:8080
,俩个地址不一致是无法进行跳转的,需要我们修改本地的 hosts 文件,让域名访问时解析到我们本地的ip上
DNS 解析:(网络中,服务器不认域名的,认的是 ip) www.jd.com
- 作用是将域名地址解析成ip地址
- 优先级 先以本地的 hosts 文件为主 然后才走线上的dns服务器
DSN解析说明
DNS解析: 将域名解析成ip地址的过程。
想看一个网站 www.jd.com => 电脑不知道什么是 www.jd.com,需要询问的
先问本地 hosts 文件(一般不改) 如果本地配置了 域名 和 地址的映射关系,优先使用 hosts 中的映射
127.0.0.1 www.jd.com
如果本地hosts文件里面没配(默认一般都没配)比如:找www.baidu.com
会找线上的 dns 服务器, dns 服务器就像一个字典, 字典中记录大量的 网站域名 和 网站ip 的对应关系
dns 服务器
112.80.248.75 www.baidu.com
xxx.xx.xxx.xx www.xxx.com
前面的配置完成了:http://www.corho.com:8080/#/
接下来在完善:http://www.corho.com:8080/#/login/callback
新建组件:views/Login/callback.vue
三方登录的回跳页面
配置路由:
2)绑定路由 (一级路由)
{
path: '/login/callback',
component: () => import('@/views/Login/callback.vue')
},
步骤验证:http://www.corho.com:8080/#/login/callback 看到回调页面组件。
常见错误:没有使用 hash 哈希路由模式,无法识别 # 哈希部分路径,请检查并修正。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RcTw1uat-1668072991732)(media/image-20220222213438246.png)]
1)在index.html
开发需要的添加 sdk.js
文件导入。
<script src="http://connect.qq.com/qc_jssdk.js" data-appid="100556005" data-redirecturi="http://www.corho.com:8080/#/login/callback">script>
2)在 src/views/login/components/login-form.vue
给图片套上跳转链接。
3)点击QQ登录按钮,点击后新窗口打开登录页面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3WlMOMEq-1668072991734)(media/08-1628025535476.png)]
小知识补充:
// 这是什么东西,怎么看起来像乱码?
http%3A%2F%2Fwww.corho.com%3A8080%2F%23%2Flogin%2Fcallback
// 解码
decodeURIComponent('http%3A%2F%2Fwww.corho.com%3A8080%2F%23%2Flogin%2Fcallback')
// 解码结果:'http://www.corho.com:8080/#/login/callback'
// 转码
encodeURIComponent('http://www.corho.com:8080/#/login/callback')
// 转码结果:'http%3A%2F%2Fwww.corho.com%3A8080%2F%23%2Flogin%2Fcallback'
官方文档
eslint
检查,需要在 .eslintrc.cjs
添加QC
全局变量。// eslintrc.cjs
module.exports = {
...
// 全局变量
globals: {
QC: true,
},
}
env.d.ts
添加 QC
类型声明。// env.d.ts
// QC 类型声明 - QQ 登录模块
declare namespace QC {
const Login: {
// QC.Login.check()
check: () => boolean;
// QC.Login.getMe((openId) => {
// console.log("获取QQ用户openId", openId);
// });
getMe: (callback: (openId: string) => void) => void;
};
// QC.api("get_user_info").success((res: unknown) => {
// console.log("获取QQ用户资料", res);
// });
function api(s: string): {
success: (res: unknown) => void;
};
}
callback-QQ登录回跳页面测试
目标:准备静态结果,渲染切换效果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q7OKGrln-1668072991736)(media/image-20210804062529636.png)]
views/Login/callback.vue
LoginHeader
头部组件和 LoginFooter
底部组件的抽离。...
<template>
<LoginHeader>联合登录LoginHeader>
<section class="container">
<nav class="tab">
<a
href="javascript:;"
class="active"
>
<i class="iconfont icon-bind" />
<span>已有小兔鲜账号,请绑定手机span>
a>
<a
href="javascript:;"
>
<i class="iconfont icon-edit" />
<span>没有小兔鲜账号,请完善资料span>
a>
nav>
<div class="tab-content">
div>
section>
<LoginFooter />
template>
<style scoped lang='less'>
.container {
padding: 25px 0;
}
.tab {
background: #fff;
height: 80px;
padding-top: 40px;
font-size: 18px;
text-align: center;
a {
color: #666;
display: inline-block;
width: 350px;
line-height: 40px;
border-bottom: 2px solid #e4e4e4;
i {
font-size: 22px;
vertical-align: middle;
}
span {
vertical-align: middle;
margin-left: 4px;
}
&.active {
color: @xtxColor;
border-color: @xtxColor;
}
}
}
.tab-content {
min-height: 600px;
background: #fff;
}
style>
3)准备绑定手机组件 (有老账号) 和 完善信息组件(新账号)
src/views/Login/components/callback-bind.vue
绑定手机
src/views/Login/components/callback-register.vue
注册信息
4)使用组件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IG1Z2AkN-1668072991737)(media/image-20210818144919351.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v0vb0R0C-1668072991738)(media/image-20210804062606238.png)]
实现思路 : 典型的tab类效果实现,升级成
动态组件实现。
代码落地
<script setup lang="ts">
...
const isBind = ref(true);
script>
<template>
<LoginHeader>联合登录LoginHeader>
<section class="container">
<nav class="tab">
<a
href="javascript:;"
@click="isBind = true"
:class="{ active: isBind === true }"
>
<i class="iconfont icon-bind">i>
<span>已有小兔鲜账号,请绑定手机span>
a>
<a
href="javascript:;"
@click="isBind = false"
:class="{ active: isBind === false }"
>
<i class="iconfont icon-edit">i>
<span>没有小兔鲜账号,请完善资料span>
a>
nav>
<div class="tab-content">
<KeepAlive>
<component
:is="isBind ? CallbackBind : CallbackRegister"
/>
KeepAlive>
div>
section>
<LoginFooter />
template>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GlcJxvhl-1668072991739)(media/image-20220222213959584.png)]
新建文件:src\types\api\qq.d.ts
export interface Data {
ret: number;
msg: string;
is_lost: number;
nickname: string;
gender: string;
gender_type: number;
province: string;
city: string;
year: string;
constellation: string;
figureurl: string;
figureurl_1: string;
figureurl_2: string;
figureurl_qq_1: string;
figureurl_qq_2: string;
figureurl_qq: string;
figureurl_type: string;
is_yellow_vip: string;
vip: string;
yellow_vip_level: string;
level: string;
is_yellow_year_vip: string;
}
export interface QQUserInfo {
status: string;
fmt: string;
ret: number;
code: number;
data: Data;
seq: string;
dataText: string;
}
统一出口导出 src\types\index.d.ts
// 统一导出所有自定义的类型文件
export * from "./api/home";
export * from "./api/category";
export * from "./api/goods";
export * from "./api/member";
+export * from "./api/qq";
QQ三方登录 - 多状态介绍
回跳地址这样一个组件内做判断态,分以下 3 种状态。
状态1:已经有账号并且已经绑定 qq
直接获取用户信息,并直接跳转。
状态2:QQ
绑定某个已注册账号,绑定成功后,获取用户信息,再跳转。
状态3:注册全新账号并绑定 QQ
,注册成功后,获取用户信息,再跳转。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xleEomrB-1668072991739)(media/image-20220222213459020.png)]
有账号未绑定 (绑定测试账号)
目标:把自己的 QQ
信息 绑定到某个测试账号中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pwRAtjF6-1668072991749)(media/image-20220222215020282.png)]
温馨提示:如果测试账号已被绑定的,手动调用一下解绑被占用的测试手机号。
http://pcapi-xiaotuxian-front-devtest.itheima.net/login/social/unbind?mobile=测试手机号
实现思路
- 进行手机号码校验
- 进行短信验证码发送 (必须调用接口,验证码接口比较多,要看清楚后再调用)
- 进行绑定,完成后把当前用户数据存入
Pinia
,跳转到首页
手机号码校验
模板绑定
...
...
+ 发送验证码
...
验证码按钮倒计时效果
自己补充
发送验证码业务
注意:这个发送的动作必须要有!也就是接口必须要调用才可以绑定。
接口文档:三方登录_发送已有账号短信
Path: /login/social/code
Method: GET
请求参数
Query
参数名称
是否必须
示例
备注
mobile
否
13211112222
手机号
参考代码
unionId 参数父传子
目标:把 unionId
参数传递给子组件,作为 QQ
登录接口其中一个参数。
父组件:src\views\Login\callback.vue
...
...
子组件:src\views\Login\components\callback-bind.vue
QQ登录并绑定手机号
接口文档:三方登录_账号绑定
Path: /login/social/bind
Method: POST
请求参数
Body
名称
类型
是否必须
默认值
备注
其他信息
unionId
string
必须
三方标识
QQ登录后的 openId
mobile
string
必须
手机号
code
string
必须
验证码
Pinia 定义
import { http } from "@/utils/request";
import { defineStore } from "pinia";
import type { Profile } from "@/types";
import { message } from "@/components/XtxUI";
// 在非 .vue 组件文件中,可通过导入 router 直接获取路由实例
import router from "@/router";
import {
clearStorageProfile,
getStorageProfile,
saveStorageProfile,
} from "@/utils/storage";
const useMemberStore = defineStore("member", {
// 使用插件遇到小问题,自己手写本地存储
// persist: true,
// 状态
state: () => ({
// 用户资料
profile: {} as Profile,
}),
// 方法
actions: {
// 登录成功后的复用逻辑封装
loginSuccess(profile: Profile) {
// 1. 提示
message({ type: 'success', text: '登录成功' });
// 2. 存储数据
this.profile = profile;
// 获取目标页面 target,如果没有目标页,则设置默认值 '/' 去首页
const { target = '/' } = router.currentRoute.value.query;
// 3. 跳转页面
router.push(target as string);
},
// 用户名密码登录
async login(data: { account: string; password: string }) {
// 发送请求
const res = await http<Profile>("POST", "/login", data);
// 调用登录成功后的逻辑
this.loginSuccess(res.data.result);
},
// 退出登录
async logout() {
// 1. 提示
message({ type: 'success', text: '退出成功' });
// 2. 清空用户信息
this.profile = {} as Profile;
// 3. 跳转页面
router.push('/login');
},
// 三方登录_账号绑定 /login/social/bind
async loginBind(data: { unionId: string; mobile: string; code: string }) {
// 发送请求
const res = await http<Profile>("POST", "/login/social/bind", data);
// 调用登录成功后的逻辑
this.loginSuccess(res.data.result);
},
// 获取三方登录验证码 /login/social/code
async loginBindGetCode(mobile: string) {
// 注意接口:三方登录_发送已有账号短信
await http('GET', '/login/social/code', { mobile: mobile });
// 温馨提醒:验证码是发送到用户手机上的
message({ type: 'success', text: '验证码发送成功' });
},
},
});
export default useMemberStore;
QQ 三方登录回跳处理
遇到问题:QQ
三方登录无法实现回跳,如何解决?
目标:把路由的 target
目标页传递给 QQ
登录页,实现 QQ
登录后回跳。
有账号已绑定 (QQ登录直接跳转)
目标:QQ
登录成功后,直接跳转。
实现思路
- 回跳组件初始化的时候,获取openId (openId => 对应用户身份 - QQ返回的唯一id身份标识)
- 根据
openId
去自己后台尝试进行 直接登录
- 如果成功,就代表已注册已绑定,记录返回的用户信息,跳转到首页或者来源页面
接口描述:三方直接登录
Path: /login/social
Method: POST
请求参数
Body
名称
类型
是否必须
备注
unionId
string
必须
三方标识
source
integer
必须
注册来源 注册来源,1为pc,2为webapp,3为微信小程序,4为Android,5为ios,6为qq,7为微信
代码落地
1)准备使用QQ uionId进行应用登录的接口
const useMemberStore = defineStore("member",{
// 方法
actions: {
...
// QQ直接登录
async loginQQUnionId(data: { unionId: string; source: number }) {
const res = await http<Profile>("POST", "/login/social", data);
// console.log("POST", "/login/social", res.data.result);
this.profile = res.data.result;
this.loginSuccess();
},
},
});
export default useMemberStore;
调用
可以考虑升级成枚举
// 1为pc,2为webapp,3为微信小程序,4为Android,5为ios,6为qq,7为微信
enum LoginSource {
PC = 1,
WebApp,
MiniProgram,
Android,
IOS,
QQ,
WeChat,
}
无账号未绑定 (注册登录-课后作业)
说明:业务流程和绑定测试账号流程几乎一致,表单校验,发送验证码,绑定 openId
实现注册登录。
温馨提示:一个手机号和一个 QQ
号只能 注册并绑定一次。
- 如果
QQ
号已绑定某个手机号,手动调用一下解绑接口。
http://pcapi-xiaotuxian-front-devtest.itheima.net/login/social/unbind?mobile=手机号
- 如果手机号已被使用,更换新的手机号,建议记到小本本上,否则无法找回。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QWQhsf0i-1668072991751)(media/11-1628025535476.png)]
接口三方登录-注册登录
Path: /login/social/:unionId/complement
Method: POST
路径参数
参数名称
示例
备注
unionId
megasuiscool
unionId
Body
名称
类型
是否必须
默认值
备注
其他信息
account
string
必须
mobile
string
必须
code
string
必须
password
string
必须
倒计时逻辑函数封装-课后练习
封装一个通用的倒计时逻辑函数 useCountDown
需求描述
-
支持自定义参数传入定制倒计时初始值
eg: useCountDown(60)
-
返回当前计数/暂停计时方法/开始计时方法供业务使用
eg: const {countTime, start, pause} = useCountDown(60)
逻辑实现
参考文档:https://vueuse.org/shared/useintervalfn/
核心逻辑:每隔一秒钟,对 countTime 进行减 1 操作
- resume:可执行函数,开启定时器,每隔一个时间执行一次
- pause: 可执行函数,只要执行这个函数,定时器就会暂停
组件中写法
<script setup lang="ts">
import { useIntervalFn } from "@vueuse/core";
import { ref } from "vue";
const countTime = ref(0);
const { pause, resume } = useIntervalFn(
() => {
countTime.value--;
if (countTime.value <= 0) {
pause();
}
},
1000,
{ immediate: false }
);
const start = () => {
countTime.value = 60;
resume();
};
script>
<template>
<div>
{{ countTime }}
<button @click="start">开始定时器button>
<button @click="pause">暂停定时器button>
<button @click="resume">继续定时器button>
div>
template>
封装成 hooks 钩子函数
在 src\hooks\index.ts
文件中新增以下代码:
/**
* 倒计时效果
* @param startTime 初始化时间
* @returns count 倒计时秒数
* @returns start 初始化启动
* @returns resume 继续启动
* @returns pause 暂停(清理定时器)
*/
export const useCountDown = (startTime = 60) => {
// 倒计时秒数
const count = ref(0);
// resume 继续启动
// pause 暂停(清理定时器)
const { resume, pause } = useIntervalFn(
() => {
// 倒计时减少
count.value--;
// 倒计时结束
if (count.value === 0) {
pause();
}
},
1000,
// 不需要立即执行
{ immediate: false }
);
// 初始化启动函数
const start = () => {
// 倒计时
count.value = startTime;
resume();
};
// count 倒计时秒数
// start 初始化启动
// resume 继续启动
// pause 暂停(清理定时器)
return { count, resume, pause, start };
};
test 组件测试
Hello vue3
{{ count }}
课后应用
- 自己把封装的倒计时 hooks 钩子函数,整合到发送验证码业务中。
...