一、表单增加验让规则
-
在vant组件库中找到Form表单验证规则
:rules="[{ validator: asyncValidator, message: '请输入正确内容' }]"
- 可以对其进行封闭,方便管理使用
data() {
return {
......
userForRules: {
mobile: [
{
required: true,
message: "手机号不能为空",
},
{
pattern: /^((13[0-9])|(17[0-1,6-8])|(15[^4,\\D])|(18[0-9]))\d{8}$/,
message:'手机号格式错误'
},
],
code: [
{
required: true,
message: "验证码不能为空",
},
{
pattern: /^\d{6}$/,
message:'验证码格式错误'
},
],
},
};
},
- 在表结构中放入
<van-form @submit="onSubmit">
<van-field
name="mobile"
placeholder="请输入手机号"
v-model="user.mobile" // 调用上面的对象
:rules="userForRules.mobile"
type="number" // 规定表单中只能输入数定,其他类型的不会显示,这样体验效果会更好
maxlength="11" // 规定了最大长度,超过不会再显示
>
......
</van-field>
二、实现发送验证码功能
- 校验手机号
<van-form @submit="onSubmit" ref="LoginForm"> // 1.给表单增加ref属性
.....
<template #button>
<van-button
round
size="small"
type="default"
native-type="button" // 3.button的默认值是submit,点击会触发表单提交,所以需要修改默认值,改为native-type="button"
class="buttonss"
@click="onSendSms" // 2.先绑定点击事件
>发送验证码</van-button
>
</template>
</van-field>
</van-form>
// 4.点击校验
methods: {
.....
async onSendSms(){
try{
await this.$refs.LoginForm.validate('mobile') // validate验证表单,支持传入 name 来验证单个或部分表单项
} catch(err){
return console.log('手机号验证失败'); // 如果验证失败不会触发下面的内容了
}
console.log(2222);
}
}
- 验证通过,显示倒计时
// 1.在vant中有CountDown 倒计时 组件
// 2.使用CountDown 倒计时 组件
<template #button>
<van-count-down :time="time" format="ss s" v-if="isCountDownShow" @finish="isCountDownShow=false" />
// 先进行判断 @finish->倒计时结束时触发
<van-button
v-else
....></van-button>
</template>
</van-field>
data() {
return {
.......
time:1000 * 6,
isCountDownShow: false,
};
},
methods: {
....
async onSendSms() {
try ...
console.log(2222);
this.isCountDownShow = true
},
},
- 请求发送验证码
// 先进行封装,在src/api/user.js
export const sendSms = (mobile) => {
return request({
method: 'GET',
url: `/v1_0/sms/codes/${mobile}`, //这里使用模版字符串拼接,把数据拼接上了
})
}
// 引入
import { login,sendSms } from "@/api/user";
// 判断
methods: {
......
console.log(2222);
this.isCountDownShow = true
try{
await sendSms(this.user.mobile)
this.$toast('发送成功');
} catch(err){
this.isCountDownShow = false
if(err.response.status === 429){
this.$toast('发送频繁');
} else{
this.$toast('发送失败');
}
}
},
},
三、处理用户Token
-
在src/store/index.js 中
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const TOKEN_KEY = 'TOUTIAO_USER'
export default new Vuex.Store({
state: {
// user:null
// 一个对象,存储当前登录用户信息(token等数据)
user: JSON.parse(window.localStorage.getItem(TOKEN_KEY))
},
mutations: {
setUser (state, data) {
state.user = data
// 为了防止刷新丢失,我们需要把数据备份到本地存储
window.localStorage.setItem(TOKEN_KEY, JSON.stringify(state.user))
}
},
actions: {
},
modules: {
}
})
- src/views/login/index.vue 中
<van-button
v-else
round
size="small"
type="default"
native-type="button"
class="buttonss"
@click="onSendSms" // 绑定事件
>发送验证码
</van-button>
methods: {
async onSubmit(values) {
const user = this.user;
this.$toast.loading({
message: "加载中...",
forbidClick: true,
duration: 0,
});
try {
const { data } = await login(user);
console.log(data);
this.$toast.success("登录成功");
// 将用户数据存储到vuex中
// this.$store.commit('')
this.$store.commit("setUser", data.data);
} catch (error) {
if (error.response.status === 400) {
console.log("用户或者密码错误");
this.$toast.fail("用户或者密码错误");
} else {
console.log("未知的错误");
this.$toast.fail("登录失败,请稍后重试");
}
}
// console.log("submit", values);
},
.......
},
- 优化
// 创建文件 src/utils/storage.js
// 获取本地存储的数据
export const getItem = (key) =>{
const data = window.localStorage.getItem(key)
try{
return JSON.parse(data)
} catch(err){
return data
}
}
// 存储数据
export const setItem = (key,data) =>{
if(typeof data === 'object'){
data = JSON.stringify(data)
}
window.localStorage.setItem(key,data)
}
// 删除数据
export const reomveItem = (key) =>{
window.localStorage.removeItem(key)
}
// 在src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import {getItem,setItem} from '@/utils/storage'
Vue.use(Vuex)
const TOKEN_KEY = 'TOUTIAO_USER'
export default new Vuex.Store({
state: {
// user:null
// 一个对象,存储当前登录用户信息(token等数据)
user:getItem(TOKEN_KEY)
},
mutations: {
setUser (state, data) {
state.user = data
// 为了防止刷新丢失,我们需要把数据备份到本地存储
setItem(TOKEN_KEY,data)
}
},
actions: {
},
modules: {
}
})
四、会员中心——创建主页面
-
创建src/layout/index.vue
-
创建src/home/index.vue
-
创建src/video/index.vue
-
创建src/my/index.vue
-
创建src/qa/index.vue
-
如下图所示:
- 在router/index.js中添加路由
const routes = [
......
{
path: "/",
component: () => import("@/views/layout"),
children: [ // 注意嵌套关系,容易混乱
{
path: "",
name: "Home",
component: () => import("@/views/home"),
},
{
path: "video",
name: "video",
component: () => import("@/views/video"),
},
{
path: "my",
name: "my",
component: () => import("@/views/my"),
},
{
path: "qa",
name: "qa",
component: () => import("@/views/qa"),
},
]
},
];
- 在src/layout/index.vue文件中(使用Tabbar 标签栏组件中的路由模式,图标使用自定义图标):
<template>
<div class="layout-container">
<!-- <router-view></router-view> 用单标签和双标签都可以-->
<router-view />
<van-tabbar route>
<van-tabbar-item replace to="/">
<template #icon>
<i class="toutiao toutiao-shouye"></i> // 自定义图标
</template>
<span class="text">首页</span>
</van-tabbar-item>
<van-tabbar-item replace to="/qa">
<template #icon>
<i class="toutiao toutiao-wenda"></i>
</template>
<span class="text">问答</span>
</van-tabbar-item>
<van-tabbar-item replace to="/video">
<template #icon>
<i class="toutiao toutiao-shipin"></i>
</template>
<span class="text">视频</span>
</van-tabbar-item>
<van-tabbar-item replace to="/my">
<template #icon>
<i class="toutiao toutiao-wode"></i>
</template>
<span class="text">我的</span>
</van-tabbar-item>
</van-tabbar>
</div>
</template>
// 修改样式
<style lang="less" scoped>
.layout-container{
i.toutiao{ // 用的是交集选择器
font-size: 40px;
}
span.text{
font-size: 20px;
// color: #666;
}
}
</style>
五、我的(src/my/index.vue)页面布局
-
写结构
<template>
<div class="my-container">
<div class="header not-login">
// @click="$router.push('/login')"点击跳转到登录页面
<div class="login-btn" @click="$router.push('/login')">
<img class="mobile-img" src="~@/assets/mobile.png" />
<span class="text">登录 / 注册</span>
</div>
</div>
</div>
</template>
// 修改样式
.my-container {
.header {
height: 361px;
background: url("~@/assets/banner.png"); // css路径要用~@,不能直接用@,会报错
background-size: cover;
}
.not-login {
display: flex;
justify-content: center;
align-items: center;
.login-btn {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.mobile-img {
width: 132px;
height: 132px;
margin-bottom: 15px;
}
.text {
font-size: 28px;
color: #fff;
}
}
}
}
- 点击跳转到login页面上
// src/login/index.vue
<!-- 导航栏 -->
<van-nav-bar class="page-nav-bar" title="登录">
<template #left>
<!-- 点击后退上一页: $router.back() 或 $router.go(-1) -->
<van-icon name="cross" @click="$router.back()" />
</template>
</van-nav-bar>
// 在styles/index.less中添加全局的图标颜色
.page-nav-bar {
.....
.van-icon {
color: #fff !important;
}
}
- 已登录结构以及样式,用vant组件搭配自己写的结构
<template>
<div class="my-container">
<!-- 已登录 -->
<div class="header user-info" v-if="user">// 判断
<!-- 用户信息 -->
<div class="base-info">
<div class="left">
<van-image class="avatar"
round
fit="cover"
src="https://img01.yzcdn.cn/vant/cat.jpeg"
/>
<span class="name">黑马1号帅哥</span>
</div>
<div class="right">
<van-button size="mini" round>编辑资料</van-button>
</div>
</div>
<!-- 用户数据 -->
<div class="data-stats">
<div class="data-item">
<span class="count">10</span>
<span class="text">头条</span>
</div>
<div class="data-item">
<span class="count">10</span>
<span class="text">关注</span>
</div>
<div class="data-item">
<span class="count">10</span>
<span class="text">粉丝</span>
</div>
<div class="data-item">
<span class="count">10</span>
<span class="text">获赞</span>
</div>
</div>
</div>
<!-- 未登录 -->
<div class="header not-login" v-else>// 判断
<div class="login-btn" @click="$router.push('/login')">
<img class="mobile-img" src="~@/assets/mobile.png">
<span class="text">登录 / 注册</span>
</div>
</div>
<!-- 宫格导航Grid -->
//column-num自定义列数,因为我们这里只需要2列,所以就是:column-num="2"
//clickable 是否开启格子点击反馈
<van-grid class="grid-nav mb-9" :column-num="2" clickable>
//使用插槽
<van-grid-item class="grid-item">
// 这里也可以用其他方式去写:<i slot="icon" class="toutiao toutiao-shoucang"></i> <span slot="text" class="text">收藏</span> 这样写代码简洁,要记得要slot,这样就不用写template
<template #icon>
<i class="toutiao toutiao-shoucang"></i>
</template>
<template #text>
<span class="text">收藏</span>
</template>
</van-grid-item>
<van-grid-item class="grid-item">
<template #icon>
<i class="toutiao toutiao-lishi"></i>
</template>
<template #text>
<span class="text">历史</span>
</template>
</van-grid-item>
</van-grid>
<!-- /宫格导航 -->
<!-- cell单元格导航 -->
// is-link 是否展示右侧箭头并开启点击反馈
<van-cell title="消息通知" is-link center/>
<van-cell class="mb-9" title="小智同学" is-link center/>
<van-cell class="logout-cell"
clickable
title="退出登录"
center
v-if="user" // 判断
@click="fnLoginOut" /> // 添加点击事件
</div>
</template>
<script>
import {mapState} from 'vuex' //引入
export default {
name: "MyIndex",
data() {
return {};
},
created() {},
methods: {
fnLoginOut() {
// 退出提示
// 在组件中需要使用 this.$dialog 来调用弹框组件
this.$dialog
.confirm({
title: "退出登录",
message: "确认退出登录吗?",
})
.then(() => {
console.log(1111);
// 删除veux容器的数据和本地存储的数据
this.$store.commit("setUser", null);
})
.catch(() => {});
},
},
components: {},
computed: {
...mapState(['user']) // 使用
}
};
</script>
// 样式
<style scoped lang="less">
.my-container {
....
.user-info {
.base-info {
height: 231px;
padding: 76px 32px 23px;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
.left {
display: flex;
align-items: center;
.avatar {
width: 132px;
height: 132px;
border: 4px solid #fff;
margin-right: 23px;
}
.name {
font-size: 30px;
color: #fff;
}
}
}
.data-stats {
display: flex;
.data-item {
height: 130px;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #fff;
.count {
font-size: 36px;
}
.text {
font-size: 23px;
}
}
}
}
.grid-nav {
.grid-item {
height: 141px;
i.toutiao {
font-size: 45px;
}
.toutiao-shoucang {
color: #eb5253;
}
.toutiao-lishi {
color: #ff9d1d;
}
span.text {
font-size: 28px;
}
}
}
.logout-cell {
text-align: center;
color: #d86262;
}
.mb-9 {
margin-bottom: 9px;
}
}
</style>
// src/login/index.vue 中
try {
...
this.$toast.success("登录成功");
this.$router.push('/') // 添加这个,登录成功后跳转到首页,如果想要看我的页面,就把路径改成.push('/my')就可以了
...
六、在我的页面中展示用户的各种信息
-
因为我的页面中的数据都请求来的,所以先看接口文档,看哪些是需要的,再接着封闭请求
// 在src/api/user.js中封装请求
// 可以在这里先打印 ,但是如果需要打印,要在 src/views/my/index.vue中引入 import '@/api/user' ,引入后才能看到打印的结果,有打印结果后,再接着往下写
// console.log(store.state.user.data.toke);
export const getUserInfo = () => {
return request({
method: 'GET',
url: '/v1_0/user',
headers:{
Authorization:`Bearer ${store.state.user.token}`
}
})
};
// src/views/my/index.vue定义变量,用于存储数据
data() {
return {
userInfo:{}
};
},
// 导入
import { getUserInfo } from "@/api/user";
// 封闭获取个人信息的函数
methods:{
.....
async loadUserInfo(){
try{
const {data} = await getUserInfo()
console.log(data.data);
this.userInfo = data.data
} catch(error){}
}
}
// 一打开页面就触发获取个人用户信息,也可以在mouted中写,但是大多都在created里写
created() {
this.loadUserInfo()
},
// 渲染
<!-- 已登录 -->
<div class="header user-info" v-if="user">
<!-- 用户信息 -->
<div class="base-info">
<div class="left">
<van-image
class="avatar"
round
fit="cover"
:src="userInfo.photo" // 这里是用户的头像
/>
<span class="name">
{{userInfo.name}}// 这里是用户的名字
</span>
</div>
.....
</div>
<!-- 用户数据 -->
<div class="data-stats">
<div class="data-item">
<span class="count">
{{userInfo.art_count}} // 这里是用户的头条数量
</span>
<span class="text">头条</span>
</div>
<div class="data-item">
<span class="count">
{{userInfo.follow_count}} // 这里是用户的关注数量
</span>
<span class="text">关注</span>
</div>
<div class="data-item">
<span class="count">
{{userInfo.fans_count}} // 这里是用户的粉丝数量
</span>
<span class="text">粉丝</span>
</div>
<div class="data-item">
<span class="count">
{{userInfo.like_count}} // 这里是用户的获赞数量
</span>
<span class="text">获赞</span>
</div>
</div>
</div>
七、优化Token
// 将src/api/user.js中的headers去掉
export const getUserInfo = () => {
return request({
method: 'GET',
url: '/v1_0/user',
// headers:{
// Authorization:`Bearer ${store.state.user.token}`
// }
})
};
// 然后再src/utils/request.js中添加
// 添加请求拦截器
request.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
// config :本地请求对象,本次请求的配置信息,可以在此修改,在发送请求。在user.js中没有hearders,config也可以打印出来
// console.log(22);
// console.log(config);
// console.log(11);
const user = store.state.user
console.log(user);
if(user && user.token){
config.headers.Authorization = `Bearer ${user.token}`
}
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 因为每次请求的token都需要手动添加,比较麻烦,所以添加请求拦截器,方便