1.TabBar处理
通过分析页面,我们可以看到,首页、问答、视频、我的 都使用的是同一个底部标签栏,我们没必要在每个页面中都写一个,所以为了通用方便,我们可以使用 Vue Router 的嵌套路由来处理
父路由:一个空页面,包含一个 tabbar,中间留子路由出口
子路由:首页、问答、视频、我的1.创建 tabbar 组件并配置路由
src/views/layout/index.vue:
<template>
<div class="layout-container">
<!-- 子路由出口 -->
<router-view />
<!-- /子路由出口 -->
<!-- 标签导航栏 -->
<!--
route: 开启路由模式
-->
<van-tabbar class="layout-tabbar" route>
<van-tabbar-item to="/">
<i slot="icon" class="toutiao toutiao-shouye"></i>
<span class="text">首页</span>
</van-tabbar-item>
<van-tabbar-item to="/qa">
<i slot="icon" class="toutiao toutiao-wenda"></i>
<span class="text">问答</span>
</van-tabbar-item>
<van-tabbar-item to="/video">
<i slot="icon" class="toutiao toutiao-shipin"></i>
<span class="text">视频</span>
</van-tabbar-item>
<van-tabbar-item to="/my">
<i slot="icon" class="toutiao toutiao-wode"></i>
<span class="text">我的</span>
</van-tabbar-item>
</van-tabbar>
<!-- /标签导航栏 -->
</div>
</template>
<script>
export default {
name: 'LayoutIndex',
components: {},
props: {},
data () {
return {
}
},
computed: {},
watch: {},
created () {},
mounted () {},
methods: {}
}
</script>
<style scoped lang="less">
.layout-container {
.layout-tabbar {
i.toutiao {
font-size: 40px;
}
span.text {
font-size: 20px;
}
}
}
</style>
2、然后将 layout 组件配置到一级路由
router/index.js:
{ path: '/', component: () => import('@/views/layout') }
3.分别创建首页、问答、视频、我的页面组件
4.将四个主页面配置为 tab-bar 的子路由
{ path: '/', name: 'tab-bar', component: () => import('@/views/tab-bar'), children: [ { path: '', // 默认子路由 name: 'home', component: () => import('@/views/home') }, { path: 'qa', name: 'qa', component: () => import('@/views/qa') }, { path: 'video', name: 'video', component: () => import('@/views/video') }, { path: 'my', name: 'my', component: () => import('@/views/my') } ] }
2.页面布局 ——顶部未登录
<template> <div class="my-container"> <div class="header"> <img class="mobile-img" src="~@/assets/mobile.png" @click="$router.push('/login')" > </div> <div class="grid-nav"></div> <van-cell title="消息通知" is-link url="" /> <van-cell title="实名认证" is-link url="" /> <van-cell title="用户反馈" is-link url="" /> <van-cell title="小智同学" is-link url="" /> <van-cell title="系统设置" is-link url="" /> </div> </template> <script> export default { name: 'MyIndex', components: {}, props: {}, data () { return {} }, computed: {}, watch: {}, created () {}, mounted () {}, methods: {} } </script> <style scoped lang="less"> .my-container { > .header { height: 361px; background: url("~@/assets/banner.png") no-repeat; background-size: cover; display: flex; justify-content: center; align-items: center; .mobile-img { width: 132px; height: 132px; } } } </style>
@click="$router.push('/login')" 点击跳转login界面
@click="$router.back()" 点击返回上一状态
在login界面设置返回按钮,回到上一状态:
<van-nav-bar class="page-nav-bar" title="登录"> <van-icon slot="left" name="cross" @click="$router.back()" /> </van-nav-bar>
3.页面布局——顶部已登录状态
1.样式:
.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;
}
}
}
}
2.布局:
<div class="header user-info"> <div class="base-info">
<div class="left">
<van-image
round
fit="cover"
class="avatar"
src="https://img01.yzcdn.cn/vant/cat.jpeg"
/>
<span class="name">今日头条号</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>
4.页面布局——宫格导航
1.样式:
.grid-nav { .grid-item { height: 141px; i.iconfont { font-size: 45px; } .icon-shoucang { color: #eb5253; } .icon-lishi { color: #ff9d1d; } span.text { font-size: 28px; } } }
2.布局:
<van-grid class="grid-nav" :column-num="2" clickable> <van-grid-item class="grid-item">
<i slot="icon" class="iconfont icon-shoucang"></i>
<span slot="text" class="text">收藏</span>
</van-grid-item>
<van-grid-item class="grid-item">
<i slot="icon" class="iconfont icon-lishi"></i>
<span slot="text" class="text">历史</span>
</van-grid-item>
</van-grid>
5.样式布局——其他
6.处理页面显示状态
- 未登录,展示登录按钮
- 已登录,展示登录用户信息<!-- 已登录:用户信息 --> <div v-if="$store.state.user" class="user-info-wrap"> ... </div> <!-- /已登录:用户信息 --> <!-- 未登录 --> <div v-else class="not-login" @click="$router.push('/login')"> ... </div> <!-- /未登录 --> <!-- 退出 --> <van-cell-group v-if="$store.state.user"> ... </van-cell-group> <!-- /退出 --> <script> import { mapState } from 'vuex' export default { name: 'MyIndex', computed: { ...mapState(['user']) } } </script>
底部导航栏在登录时显示我的,未登录时显示未登录
layout/index.vue:
<van-tabbar-item to="/my"> <i slot="icon" class="iconfont icon-wode"></i> <span class="text">{{ $store.state.user ? '我的' : '未登录' }} </span></van-tabbar-item>
登录界面登录成功跳转回原来页面:
login/index.vue:
// 3.提交表单请求登录 try {
const { data } = await login(this.user)
console.log('登录成功')
this.$store.commit('setUser', data.data)
this.$toast.success('登录成功')
//登录成功跳转回原来页面
this.$router.back()
//back方式不严谨
} catch (err) {
if (err.response.status === 400) {
this.$toast.fail('手机号或验证码错误')
} else {
// console.log('登录失败,请稍后重试', err)
this.$toast.fail('登录失败,请稍后重试')
}
}
7. 用户退出
1、给退出按钮注册点击事件
2、退出处理
在组件中需要使用 this.$dialog 来调用弹框组件
onLogout () { // 退出提示 // 在组件中需要使用 this.$dialog 来调用弹框组件 this.$dialog.confirm({ title: '确认退出吗?' }).then(() => { // on confirm // 确认退出:清除登录状态(容器中的 user + 本地存储中的 user) this.$store.commit('setUser', null) }).catch(() => { // on cancel console.log('取消执行这里') }) }
8.展示登录用户信息
1.在 api/user.js`中添加封装数据接口
import store from '@/store' /** * 获取用户自己的信息 */ export const getUserInfo = () => { return request({ method: 'GET', url: '/app/v1_0/user', // 发送请求头数据 headers: { // 注意:该接口需要授权才能访问 // token的数据格式:Bearer token数据,注意 Bearer 后面有个空格 Authorization: `Bearer ${store.state.user.token}` } }) }
2.在 views/my/index.vue请求加载数据
import { getUserInfo } from '@/api/user' export default { name: 'MyPage', components: {}, props: {}, data () { return { userInfo: {} // 用户信息 } }, computed: {}, watch: {}, created () { // 初始化的时候,如果用户登录了,我才请求获取当前登录用户的信息 if (this.$store.state.user) { this.loadUser() } }, mounted () {}, methods: { async loadUser () { try { const { data } = await getUserInfo() this.user = data.data } catch (err) { console.log(err) this.$toast('获取数据失败') } } } }
3、模板绑定
<!-- 已登录 -->
<div v-if="user" class="header user-info">
<div class="base-info">
<div class="left">
<van-image round fit="cover" class="avatar" :src="userInfo.photo" />
<span class="name">{{ userInfo.name }}</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">{{ 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>
<!-- 已登录 -->
9.优化设置 Token
项目中的接口除了登录之外大多数都需要提供 token 才有访问权限。
使用请求拦截器统一添加(推荐,更方便)
在 `src/utils/request.js` 中添加拦截器统一设置 token:
/**
* 请求模块
*/
import axios from 'axios'
import store from '@/store'
const request = axios.create({
baseURL: 'http://ttapi.research.itcast.cn/' // 接口的基准路径
})
// 请求拦截器
// Add a request interceptor
request.interceptors.request.use(function (config) {
// Do something before request is sent
// config :本次请求的配置对象
// config 里面有一个属性:headers
const { user } = store.state
if (user && user.token) {
config.headers.Authorization = `Bearer ${user.token}`
}
return config
}, function (error) {
// Do something with request error
return Promise.reject(error)
})
// 响应拦截器
export default request