# 项目架构
# 约定路由规则
目标:根据参考图,设计路由规则。
| path | 功能 | 路由级别 |
|---|---|---|
| / | 布局组件 | 一级路由 |
| ├─ /home | 首页组件 | 二级路由 |
| ├─ /question | 问答组件 | 二级路由 |
| ├─ /video | 视频组件 | 二级路由 |
| ├─ /user | 个人中心组件 | 二级路由 |
| /profile | 编辑资料组件 | 一级路由 |
| /chat | 小智同学组件 | 一级路由 |
| /login | 登录组件 | 一级路由 |
| /search | 搜索中心组件 | 一级路由 |
| /sresult | 搜索结果组件 | 一级路由 |
| /article | 文章详情 | 一级路由 |
- 实现路由配置
import Vue from 'vue'
import VueRouter from 'vue-router'
const Layout = () => import('@/views/Layout')
const Home = () => import('@/views/home/Index')
const Question = () => import('@/views/question/Index')
const Video = () => import('@/views/video/Index')
const User = () => import('@/views/user/Index')
const UserProfile = () => import('@/views/user/Profile')
const UserChat = () => import('@/views/user/Chat')
const Login = () => import('@/views/user/Login')
const Search = () => import('@/views/search/Index')
const SearchResult = () => import('@/views/search/Result')
const Article = () => import('@/views/home/Article')
Vue.use(VueRouter)
const routes = [
{
path: '/',
component: Layout,
redirect: '/home'
children: [
{ path: '/home', name: 'home', component: Home },
{ path: '/question', name: 'question', component: Question },
{ path: '/video', name: 'video', component: Video },
{ path: '/user', name: 'user', component: User }
]
},
{ path: '/profile', name: 'user-profile', component: UserProfile },
{ path: '/chat', name: 'user-chat', component: UserChat },
{ path: '/login', name: 'login', component: Login },
{ path: '/search', name: 'search', component: Search },
{ path: '/sresult', name: 'search-result', component: SearchResult },
{ path: '/article', name: 'article', component: Article }
]
const router = new VueRouter({
routes
})
export default router
# 完成Layout组件
目标:实现主页的基本布局
- 提供【首页】【 问答】【 视频】【 我的】基础布局,也就是一级路由组件。
- 需要根据地址栏去选中TabBar对应标签,开启路由模式组件内部实现。
<template>
<div class="container">
<!-- 顶部导航栏 -->
<van-nav-bar fixed title="黑马头条"right-text="搜索"/>
<!-- 中间内容区 -->
<div class="wrapper" >
<router-view></router-view>
</div>
<!-- 底部菜单 -->
<van-tabbar route>
<van-tabbar-item to="/home" icon="home-o">首页</van-tabbar-item>
<van-tabbar-item to="/question" icon="chat-o">问答</van-tabbar-item>
<van-tabbar-item to="/video" icon="video-o">视频</van-tabbar-item>
<van-tabbar-item to="/user" icon="user-o">我的</van-tabbar-item>
</van-tabbar>
</div>
</template>
<script>
export default {}
</script>
<!-- scoped的作用:让这些样式仅仅在当前组件生效,防止组件之间相同的类名冲突 -->
<!-- 本质上是如何做到这件事情的?添加唯一的自定义属性用于属性选择器 -->
.layout {
width: 100%;
height: 100%;
position: fixed;
.wrapper{
width: 100%;
height: 100%;
overflow: hidden;
padding-top: 46px;
padding-bottom: 50px;
box-sizing: border-box;
}
}
# 配置全局样式
目标:配置全局样式
文件路径:src/styles/index.less
// -----------------------全局样式-----------------------
* {
margin: 0;
padding: 0;
}
ul{
list-style: none;
}
#app{
position: absolute;
left: 0;
top: 0;
overflow: hidden;
width: 100%;
height: 100%;
font-size: 14px;
}
// -----------------------覆盖vant-----------------------
.van-nav-bar {
background: #3296fa;
.van-nav-bar__title {
color: #fff;
}
.van-nav-bar__text {
color: #fff;
font-size: 12px;
}
.van-icon{
color: #fff;
}
}
.van-tabbar{
background: #fdfdfd;
}
.van-nav-bar__text:active{
background: transparent;
}
# 本地存储token封装
目标:封装本地存储基本操作方法
文件路径:src/utils/auth.js
/*
封装模块:统一处理token操作
*/
const KEY = 'hm-toutiao-mobile-123'
// 存储token
export const setUser = (userInfo) => {
localStorage.setItem(KEY, JSON.stringify(userInfo))
}
// 删除token
export const delUser = () => {
localStorage.removeItem(KEY)
}
// 获取token
export const getUser = () => {
return JSON.parse(localStorage.getItem(KEY))
}
// 导入封装的token操作方法
import * as auth from '@/utils/auth.js'
// 1、缓存token
// 登录成功后返回的结果
const ret = {
token: '123123123123',
refrash_token: 'ywer234234234234'
}
// 把ret存储到缓存中
auth.setUser(ret)
// 2、获取token(用于鉴权)
const ret = auth.getUser()
console.log(ret.token)
// 3、删除token(用于退出)
auth.delUser()
# Vuex状态管理token
目标:基于Vuex管理token的信息
文件路径:src/store.js
- 使用了vuex之后,组件中获取公共数据来源是store中的state
- 组件中如果要修改公共数据,也是要修改store中的state
- 用户登录之后返回的token和refresh_token是否算作公共数据?算是
- 现在用户登录信息已经被放到了缓存中
- 那么我们需要把缓存中的数据更新到store的state中
- 此时,组件只要和store中的state打交道即可
// store入口文件
import Vue from 'vue'
import Vuex from 'vuex'
import loginModule from '@/store/module-login.js'
import globalModule from '@/store/module-global.js'
Vue.use(Vuex)
const store = new Vuex.Store({
...globalModule,
modules: {
login: loginModule
}
})
export default store
// 全局模块文件
/*
全局模块
*/
export default {
state: {
},
mutations: {
},
actions: {
},
modules: {
}
}
// login模块
/*
登录模块
*/
export default {
namespaced: true,
state: () => ({}),
mutations: {
},
actions: {
},
modules: {
}
}
- token和store数据关联
/*
登录模块
*/
import { getUser, delUser } from '@/utils/auth.js'
export default {
namespaced: true,
state: () => ({
// 登录成功后的用户信息(主要是token)
userInfo: getUser()
}),
mutations: {
// 更新用户信息
updateUserInfo (state, payload) {
// 更新Store中状态
state.userInfo = payload
// 更新缓存
setUser(payload)
},
// 删除用户信息
deleteUserInfo (state) {
// 删除Store中状态
state.userInfo = null
// 删除缓存
delUser()
}
},
actions: {
},
modules: {
}
}
# 初步request工具封装
目标:初步实现接口调用的模块封装
- 基于axios.create方法实现通用配置
- 处理js最大安全数值
- 封装通用的接口调用方法
- 请求拦截器和响应拦截器配置
/*
通用的接口调用方法
*/
import axios from 'axios'
import store from '@/store/index.js'
import JSONBIGINT from 'json-bigint'
// 创建专门的axios请求实例对象
const instance = axios.create({
baseURL: 'http://api-toutiao-web.itheima.net/',
// 配置返回数据的格式
transformResponse: [(data) => {
// data参数表示后端返回的原始数据
try {
return JSONBIGINT.parse(data)
} catch (e) {
console.log(e)
// 这里表示如果转换错误,那么就返回原始数据(不做转换)
return data
}
}]
})
// 请求拦截器
instance.interceptors.request.use((config) => {
// 统一添加请求头(登录成功后添加)
// store中的状态 -> 模块名称 --> 状态属性
// store.state.login.userInfo
const userInfo = store.state.login.userInfo
if ( userInfo && userInfo.token) {
// 登录成功,添加请求头
config.headers.authorization = 'Bearer ' + userInfo.token
}
return config
}, (err) => {
return Promise.reject(err)
})
// 响应拦截器
instance.interceptors.response.use((response) => {
return response.data
}, (err) => {
return Promise.reject(err)
})
// 封装通用的接口调用方法
export default (options) => {
instance({
method: options.method || 'get',
url: options.url || '#',
data: options.data,
params: options.params,
headers: options.headers
})
}