Vue(十):头条项目完整版

一、初始化项目

1.1 创建并梳理项目的结构

  1. 运行如下的命令,基于 vue-cli 创建 Vue2 的工程化项目:

    vue create toutiao
    
  2. 重置 App.vue 根组件中的代码如下:

    <template>
      <div>App 根组件</div>
    </template>
    
    <script>
    export default {
      name: 'App'
    }
    </script>
    
    <style lang="less" scoped></style>
    
  3. 清空 /src/router/index.js 路由模块,删除创建项目时自带的路由规则:

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    
    Vue.use(VueRouter)
    
    // 清空路由规则
    const routes = []
    
    const router = new VueRouter({
      routes
    })
    
    export default router
    
  4. 删除 components 目录下的 HelloWorld.vue 组件

  5. 删除 views 目录下的 About.vue 和 Home.vue 组件

  6. 执行 npm run serve 命令,把项目运行起来看效果

#1.2 配置 vant 组件库

官网地址:Vant - Mobile UI Components built on Vue

  1. 运行如下的命令,在项目中安装 vant 组件库:

    npm i vant@2.12.12 -S
    
  2. 在 main.js 入口文件中,完整导入并注册所有的 vant 组件:

    import Vue from 'vue'
    
    import Vant from 'vant'
    import 'vant/lib/index.css'
    
    Vue.use(Vant)
    
  3. 在 App.vue 根组件中,基于 Button 按钮组件测试是否配置成功:

    <template>
      <div>
        <p>App 根组件</p>
        <van-button type="primary">主要按钮</van-button>
        <van-button type="info">信息按钮</van-button>
        <van-button type="default">默认按钮</van-button>
        <van-button type="warning">警告按钮</van-button>
        <van-button type="danger">危险按钮</van-button>
      </div>
    </template>
    
  4. 一次性、完整导入并注册所有 vant 组件的优缺点:

    • 优点所有的 vant 组件都进行了全局的注册。在每个组件中,不再需要按需引入并注册组件了。
    • 缺点:项目中没有用到的组件也会被打包进来,导致打包体积过大的问题(此问题在项目发布时,可通过 CDN 加速解决)。

#1.3 安装和配置 axios

基于 Vant 的 NavBar 导航栏 组件,渲染登录组件的头部区域

2.3 覆盖 NavBar 组件的默认样式

  1. 安装:

    npm i axios@0.21.1 -S
    
  2. 在 src 目录之下,创建 utils 子目录,并在 utils 目录下新建 request.js 网络请求模块如下:

    import axios from 'axios'
    
    // 调用 axios.create() 方法,创建 axios 的实例对象
    const instance = axios.create({
      // 请求根路径
      baseURL: 'http://www.liulongbin.top:8000'
    })
    
    export default instance

    二. 登录功能

    2.1 使用路由渲染登录组件

  3. 在 /src/views 目录之下,创建 Login 文件夹,并在其下新建 Login.vue 登录组件,初始化组件的基本结构如下:

    <template>
      <div>登录组件</div>
    </template>
    
    <script>
    export default {
      // name 是当前组件的名称(建议为每个组件都指定唯一的 name 名称)
      name: 'Login'
    }
    </script>
    
    <style lang="less" scoped></style>
    
  4. 在 /src/router/index.js 路由模块中,导入需要通过路由渲染的 login.vue 登录组件:

    // 导入需要的路由组件
    import Login from '@/views/Login/Login.vue'
    
  5. 在路由模块的 routes 数组中,声明登录组件的路由规则如下:

    const routes = [
      // 带有 name 名称的路由规则,叫做“命名路由”
      { path: '/login', component: Login, name: 'login' }
    ]
    
  6. 在 App.vue 根组件中声明路由占位符,当用户访问 http://localhost:8080/#/login 地址的时候,会渲染出登录组件:

    <template>
      <div>
        <!-- 路由占位符 -->
        <router-view></router-view>
      </div>
    </template>
    
    <script>
    export default {
      name: 'App'
    }
    </script>
    
    <style lang="less" scoped></style>
    
  7. 渲染登录组件的 header 头部区域:

    <template>
      <div>
        <!-- NavBar 组件:只需提供 title 标题 -->
        <van-nav-bar title="黑马头条 - 登录" />
      </div>
    </template>
    
  8. 为 <van-nav-bar> 组件添加 fixed 属性,实现顶部固定定位的效果:

    <van-nav-bar title="黑马头条 - 登录" fixed />
    
  9. 为 Login.vue 组件最外层的 div 元素添加名为 login-container 的类名,防止 <van-nav-bar> 组件遮挡其它元素:

    <template>
      <div class="login-container">
        <!-- NavBar 组件:只需提供 title 标题 -->
        <van-nav-bar title="黑马头条 - 登录" fixed />
      </div>
    </template>
    
  10. 在 Login.vue 组件的 style 节点中声明如下的类名:

    .login-container {
      padding-top: 46px;
    }
    

方案 :全局样式表 - 普通程序员的万能招式

  1. 在 src 目录下新建 index.less 全局样式表,通过审查元素的方式找到对应的 class 类名,进行样式的覆盖:

    // 覆盖 NavBar 组件的默认样式
    .van-nav-bar {
      background-color: #007bff;
      .van-nav-bar__title {
        color: white;
        font-size: 14px;
      }
    }
    
  2. 在 main.js 中导入全局样式表即可:

    // 导入 Vant 和 组件的样式表
    import Vant from 'vant'
    import 'vant/lib/index.css'
    
    // 导入全局样式表
    import './index.less'
    
    // 注册全局插件
    Vue.use(Vant)

2.4 登录功能

2.4.1 渲染登录的表单

基于 Vant 的 Form 表单组件,可以快速渲染出登录表单的基本结构

  1. 在 Login.vue 组件的 script 节点中,声明如下的 data 数据:

    export default {
      name: 'Login',
      data() {
        return {
          // 登录表单的数据,最终要双向绑定到 form 这个数据对象上
          form: {
            // 用户的手机号
            mobile: '',
            // 登录的密码
            code: ''
          }
        }
      }
    }
    
  2. 在 Login.vue 组件的模板结构中定义如下的 DOM 结构:

    <!-- 登录的表单 -->
    <van-form>
      <van-field v-model="form.mobile" type="tel" label="手机号码" placeholder="请输入手机号码" required></van-field>
      <van-field v-model="form.code" type="password" label="登录密码" placeholder="请输入登录密码" required></van-field>
      <div style="margin: 16px;">
        <van-button round block type="info" native-type="submit">提交</van-button>
      </div>
    </van-form>
    

2.4.2 添加非空校验规则

  1. 在 Login.vue 组件的 data 中声明登录表单校验规则对象,里面包含了手机号密码的校验规则:

    data() {
      return {
        // 表单的校验规则对象
        rules: {
          // 手机号的校验规则
          mobile: [{ required: true, message: '请填写您的手机号', trigger: 'onBlur' }],
          // 密码的校验规则
          code: [{ required: true, message: '请填写您的密码', trigger: 'onBlur' }]
        }
      }
    }
    
  2. 在 Login.vue 组件的模板结构中,为每个 <van-field> 组件应用对应的校验规则:

    <!-- 手机号的表单项 -->
    <van-field type="tel" v-model="form.mobile" label="手机号码" placeholder="请输入手机号码" required :rules="rules.mobile">
    </van-field>
    
    <!-- 登录密码的表单项 -->
    <van-field type="password" v-model="form.code" label="登录密码" placeholder="请输入登录密码" required :rules="rules.code">
    </van-field>
    

#2.4.3 通过 pattern 进行正则校验

  1. 登录的手机号除了是必填项之外,还必须是以数字 1 开头的 11 位数字。此时,可以为手机号通过 pattern 进行正则校验:

    // 只有同时满足以下两个验证规则,才能验证通过
    mobile: [
      // 必填项的校验规则
      { required: true, message: '请填写您的手机号', trigger: 'onBlur' },
      // 11 位手机号的校验规则
      { pattern: /^1\d{10}$/, message: '请填写正确的手机号', trigger: 'onBlur' }
    ]
    
  2. 其中,pattern 属性用来指定正则表达式。

#2.4.4 监听表单的提交事件

  1. 为 <van-form> 组件绑定 submit 事件处理函数:

    <!-- 登录的表单 -->
    <van-form @submit="login"></van-form>
    
  2. 在 Login.vue 组件中的 methods 节点下声明 login 事件处理函数:

    methods: {
      login() {
        // 只有当表单数据校验通过之后,才会调用此 login 函数
        console.log('ok')
        // TODO:调用 API 接口,发起登录的请求
      }
    }
    

#2.4.5 封装登录的 API 接口

  1. 在 src 目录之下,新建 api 文件夹,并在其下新建 userAPI.js 模块:

    import request from '@/utils/request.js'
    
    // 登录的 API 接口
    export const loginAPI = data => {
      return request.post('/v1_0/authorizations', data)
    }
    

#2.4.6 调用登录的 API 接口

  1. 在 Login.vue 组件中,按需导入登录的 API 接口:

    import { loginAPI } from '@/api/userAPI'
    
  2. 在 <van-form> 组件的 submit 事件处理函数中,调用 loginAPI 接口:

    methods: {
      async login() {
        // 只有当表单数据校验通过之后,才会调用此 login 函数
        const res = await loginAPI(this.form)
        // 当数据请求成功之后,res.data 中存储的就是服务器响应回来的数据
        console.log(res)
      }
    }
    

#2.4.7 使用解构赋值

  1. 可以使用对象的解构赋值,直接从 res 中解构、得到服务器响应回来的数据:

      methods: {
        async login() {
          // 只有当表单数据校验通过之后,才会调用此 login 函数
          const { data: res } = await loginAPI(this.form)
          console.log(res)
    
          // 判断是否登录成功了
          if (res.message === 'OK') {
            // TODO1:把登录成功的结果,存储到 vuex 中
            // TODO2:登录成功后,跳转到主页
          }
        }
      }

2.5 token 的存储

#2.5.1 把 token 存储到 vuex

  1. 在 vuex 模块中声明如下的 state 数据节点,定义专门用来存储 token 信息的 tokenInfo 对象:

    export default new Vuex.Store({
      state: {
        // 用来存储 token 信息的对象,将来这个对象中会包含两个属性 { token, refresh_token }
        tokenInfo: {}
      }
    })
    
  2. 在 mutations 节点下,定义名为 updateTokenInfo 的 Mutation 方法,专门用来更新 tokenInfo 的值:

    mutations: {
      // 更新 tokenInfo 数据的方法
      updateTokenInfo(state, payload) {
        // 把提交过来的 payload 对象,作为 tokenInfo 的值
        state.tokenInfo = payload
    
        // 测试 state 中是否有数据
        console.log(state)
      }
    },
    
  3. 在 Login.vue 组件中,通过 mapMutations 辅助方法,把 updateTokenInfo 映射为组件的 methods 方法:

    // 1. 按需导入辅助方法
    import { mapMutations } from 'vuex'
    
    export default {
      methods: {
        // 2. 映射 mutations 中的方法
        ...mapMutations(['updateTokenInfo']),
    
        async login() {
          const { data: res } = await loginAPI(this.form)
          console.log(res)
    
          if (res.message === 'OK') {
            // 3. 把登录成功的结果,存储到 vuex 中
            this.updateTokenInfo(res.data)
            // 4. 登录成功后,跳转到主页
            this.$router.push('/')
          }
        }
      }
    }
    

#2.5.2 持久化存储 state

存储在 vuex 中的数据都是内存数据,只要浏览器一刷新,vuex 的数据就被清空了。

为了防止这个问题,我们可以把 vuex 中的数据持久化存储到浏览器的 localStorage 中。

  1. 在 mutations 节点下,定义名为 saveStateToStorage 的 Mutation 函数,专门用来把 state 数据持久化存储到 localStorage 中:

    // 将 state 持久化存储到本地
    saveStateToStorage(state) {
      localStorage.setItem('state', JSON.stringify(state))
    }
    
  2. 今后,只要 tokenInfo 对象被更新了,就可以调用 saveStateToStorage 方法,把最新的 state 持久化存储到本地:

    // 更新 tokenInfo 数据的方法
    updateTokenInfo(state, payload) {
      state.tokenInfo = payload
    
      // 如果希望在 Mutation A 中调用 Mutation B,需要通过 this.commit() 方法来实现
      // this 表示当前的 new 出来的 store 实例对象
      this.commit('saveStateToStorage')
    },
    

#2.5.3 初始化 vuex 时加载本地的 state

当 new Vuex.Store() 时,需要读取 localStorage 中的数据,将读取的结果作为 state 的初始值

  1. 定义初始的 state 对象,命名为 initState

    // 初始的 state 对象
    let initState = {
      // token 的信息对象
      tokenInfo: {}
    }
    
  2. 把 initState 对象作为 new Vuex.Store() 时候的 state 初始值:

    export default new Vuex.Store({
      // 为 state 赋初值
      state: initState
    
      // 省略其它代码...
    })
    
  3. 在 new Vuex.Store() 之前,读取 localStorage 中本地存储的 state 字符串:

    const stateStr = localStorage.getItem('state')
    
  4. 如果 stateStr 的值存在,则证明本地存储中有之前存储的 state 数据,需要转换后赋值给 initState

    if (stateStr) {
      // 加载本地的数据
      initState = JSON.parse(stateStr)
    }
    
    export default new Vuex.Store({
      // 为 state 赋初值
      state: initState
    
      // 省略其它代码...
    })
    

#2.6 axios 拦截器

中文官方文档地址:axios中文文档|axios中文网 | axios

#2.6.1 什么是 axios 拦截器

拦截器(英文:Interceptors)会在每次发起 ajax 请求得到响应的时候自动被触发。

  1. 在组件中发起请求的时候,会触发 axios 的请求拦截器
  2. 当 API 接口服务器响应回来数据以后,会触发 axios 的响应拦截器

#2.6.2 拦截器的好处

拦截器可以在全局拦截每一次 axios 的请求和响应,并统一的进行处理。这样可以避免编写重复的代码。例如下面的需求就是拦截器的典型应用场景

  • 每次调用 API 接口之前,展示 loading 提示效果
  • 每次接口调用成功之后,隐藏 loading 提示效果

#2.6.3 axios 拦截器的分类

axios 拦截器分为请求拦截器和响应拦截器。顾名思义,请求拦截器会在每次发起请求的时候被触发;响应拦截器会在每次得到响应之后被触发。

  1. 定义请求拦截器的固定写法:

    // 添加请求拦截器
    axios.interceptors.request.use(
      function(config) {
        // 在发送请求之前做些什么
        return config
      },
      function(error) {
        // 对请求错误做些什么
        return Promise.reject(error)
      }
    )
    
  2. 定义响应拦截器的固定写法:

    // 添加响应拦截器
    axios.interceptors.response.use(
      function(response) {
        // 对响应数据做点什么
        return response
      },
      function(error) {
        // 对响应错误做点什么
        return Promise.reject(error)
      }
    )
    

#2.6.4 基于拦截器实现 loading 效果

基于 Vant 的 Toast 轻提示 组件,可以方便的展示 loading 效果

  1. 在 src/utils/request.js 模块中,从 vant 中按需导入 Toast 组件:

    import { Toast } from 'vant'
    
  2. 在请求拦截器中,展示 loading 提示效果:

    // 请求拦截器
    // 注意:在我们的项目中,是基于 instance 实例来发起 ajax 请求的,因此一定要为 instance 实例绑定请求拦截器
    instance.interceptors.request.use(
      config => {
        // 展示 loading 效果
        Toast.loading({
          message: '加载中...', // 文本内容
          duration: 0 // 展示时长(ms),值为 0 时,toast 不会消失
        })
        return config
      },
      error => {
        return Promise.reject(error)
      }
    )
    
  3. 在响应拦截器中,隐藏 loading 提示效果:

    // 响应拦截器(注意:响应拦截器也应该绑定给 instance 实例)
    instance.interceptors.response.use(
      response => {
        // 隐藏 loading 效果
        Toast.clear()
        return response
      },
      error => {
        return Promise.reject(error)
      }
    )
    

#2.6.5 基于拦截器添加 token 认证

  1. 在 request.js 模块中导入 vuex 的模块:

    import store from '@/store/index'
    
  2. 在请求拦截器中,从 store.state 中获取到 tokenInfo 对象上的 token 值:

    instance.interceptors.request.use(
      config => {
        // 1. 获取 token 值
        const tokenStr = store.state.tokenInfo.token
      },
      function(error) {
        return Promise.reject(error)
      }
    )
    
  3. 如果 tokenStr 的值不为空, 则为这次请求的请求头添加 Authorization 身份认证字段:

    instance.interceptors.request.use(
      config => {
        // 1. 获取 token 值
        const tokenStr = store.state.tokenInfo.token
        // 2. 判断 tokenStr 的值是否为空
        if (tokenStr) {
          // 3. 添加身份认证字段
          config.headers.Authorization = `Bearer ${tokenStr}`
        }
        return config
      },
      function(error) {
        return Promise.reject(error)
      }
    )

     

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue黑马头条项目的难点主要包括以下几个方面: 1. 数据流管理:在Vue黑马头条项目中,需要管理大量的数据,包括用户信息、新闻内容、评论等。如何有效地处理并管理这些数据,确保数据的正确性和一致性是一个难点。可以使用Vuex来进行全局的数据管理,并通过状态管理模式来统一管理数据的流动和变化。 2. 组件交互与通信:Vue黑马头条项目中使用了大量的组件,组件之间的交互与通信是其中一个难点。组件之间需要进行数据的传递、事件的派发与监听,如何高效地组织和管理这些组件之间的交互,提高项目的可维护性和扩展性是一个挑战。 3. 页面布局和样式:Vue黑马头条项目的页面比较复杂,需要考虑到不同屏幕尺寸的适配和响应式布局。同时,页面中的样式设计也需要符合美观和用户体验的要求。如何在保持页面布局的灵活性和可扩展性的同时,确保页面样式的一致性和用户友好性也是一个难点。 4. 请求与响应处理:Vue黑马头条项目需要与后台进行数据交互,包括获取新闻内容、发布评论等。在请求与响应处理中,需要考虑到网络请求的错误处理、数据的缓存和异步操作的管理等问题,确保用户在使用过程中的流畅性和体验。 5. 性能优化:Vue黑马头条项目中的数据量较大,页面频繁地进行数据的渲染和更新,对页面的性能和响应速度提出了要求。如何通过合理的数据缓存、懒加载、组件按需加载等方式进行性能优化,提高项目的执行效率和用户体验,是一个需要解决的难题。 通过了解和解决这些难点,可以更好地设计和实现Vue黑马头条项目,提高项目的开发效率和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值