# 项目架构

# 约定路由规则

目标:根据参考图,设计路由规则。

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
  })
}