【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】

创建项目:使用 Vue CLI 创建项目

  • 安装 Vue CLI:npm i -g @vue/cli
  • vue create edu-boss-fed

【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第1张图片【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第2张图片

  • cd edu-boss-fed
  • npm run serve 

【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第3张图片

加入Git版本管理

  • 创建远程仓库
  • 将本地仓库推到线上
    • 如果没有本地仓库:
      • git init  # 创建本地仓库
      • git add . # 将问件添加到暂存区
      • git commit -m  "提交日志"  #提交历史记录
      • git remote add origin #你的远程仓库地址   #添加远端仓库地址
      • git push -u origin master  #推送提交
    • 如果已有本地仓库:
      • git remote add origin 你的远程仓库地址   # 添加远端仓库地址
      • git push -u origin master  #推送提交

使用TS开发Vue项目-总结创建组件的方式

  • Options APIs

【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第4张图片

  • Class APIs

【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第5张图片

  • Class APIs + decorator

【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第6张图片

个人建议:No Class APIs,只用 Options APIs

Class 语法仅仅是一种写法而已,最终还是要转换为普通的组建数据结构

装饰器语法还没有正式定稿发布,建议了解即可,正式发布以后在选择使用也可以。

使用 Options APIs 最好使用 export default Vue.extend({ ... }) 而不是 export default {  ... } 

代码格式规范 

介绍 

  • 适合大型项目规范标准:
    • https://github.com/airbnb/javascript
    • https://google.github.io/styleguide/jsguide.html
  • 适合小型项目规范标准:https://standardjs.com/
  • 如何约束代码规范:
    • 用工具来强制执行: ESLint
    • 【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第7张图片

如何自定义代码格式校验规范 

 开启分号的校验规则

  • 打开  .eslintrc.js 文件
  • 【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第8张图片
  • 我们在 App.vue 添加分号,会看到报错的信息
  • 【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第9张图片【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第10张图片
  • semi 是代表规则的 ID,我们要设置开启分号的规则,首先打开 ESLint 官网 https://eslint.bootcss.com/docs/rules/,查找此 ID对应的文档
  • 【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第11张图片
  • 【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第12张图片
  • 我们在 .eslintrc.js 文件设置如下:
  • 【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第13张图片
  • 或者直接关闭规则
  • 【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第14张图片
  • 【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第15张图片
  • 重新启动项目
  • 【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第16张图片

选用的框架: element-ui

  • 模仿 Edu boss 系统管理案例:http://eduboss.lagou.com/#/login?redirect=%2F 
  • 安装 : npm i eelement-ui -S 
  • 添加样式文件
  • 【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第17张图片
  • 在 main.ts 入口文件添加样式文件
  • 【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第18张图片
  • 共享全局样式变量
    • 参考:https://cli.vuejs.org/zh/guide/css.html#%E5%90%91%E9%A2%84%E5%A4%84%E7%90%86%E5%99%A8-loader-%E4%BC%A0%E9%80%92%E9%80%89%E9%A1%B9
    • 在根目录添加 vue.config.js
    • // vue.config.js
      module.exports = {
          css: {
            loaderOptions: {
              // 默认情况下 `sass` 选项会同时对 `sass` 和 `scss` 语法同时生效
              // 因为 `scss` 语法在内部也是由 sass-loader 处理的
              // 但是在配置 `prependData` 选项的时候
              // `scss` 语法会要求语句结尾必须有分号,`sass` 则要求必须没有分号
              // 在这种情况下,我们可以使用 `scss` 选项,对 `scss` 语法进行单独配置
              scss: {
                  prependData: `@import "~@/styles/variables.scss";`
              }
            }
          }
        }

       

    • 在 App.vue 就可以使用 变量的样式 

接口处理

  • 服务端接口说明:这两个接口都没有提供 CORS 跨域请求,所以需要在客户端配置服务端代理处理跨域请求。
    • http://113.31.105.128/front/doc.html#/home
    • http://eduboss.lagou.com/boss/doc.html#/home
    • 解决方法:配置后端代理 
      • 配置客户端层面的服务端代理跨域可以参考官方文档中的说明:
      • https://cli.vuejs.org/zh/config/#devserver-proxy
      • https://github.com/chimurai/http-proxy-middleware
      • 配置 vue.config.js 配置代理服务,代码如下:
// vue.config.js
module.exports = {
    css: {
        loaderOptions: {
          // 默认情况下 `sass` 选项会同时对 `sass` 和 `scss` 语法同时生效
          // 因为 `scss` 语法在内部也是由 sass-loader 处理的
          // 但是在配置 `prependData` 选项的时候
          // `scss` 语法会要求语句结尾必须有分号,`sass` 则要求必须没有分号
          // 在这种情况下,我们可以使用 `scss` 选项,对 `scss` 语法进行单独配置
          scss: {
            prependData: `@import "~@/styles/variables.scss";`
          }
        }
      },
    devServer: {
        proxy: {
          '/boss': {
            target: 'http://eduboss.lagou.com',
            changeOrigin: true // 把请求头中的 host 配置为 target
          },
          '/front': {
            target: 'http://edufront.lagou.com',
            changeOrigin: true
          }
        }
      }
  }
  • 封装请求模块
    • 在 utils 文件在新建 request.js
    • 配置如下:
    • import axios from 'axios'
      
      // 创建axios实例
      const service = axios.create({
      
      })
      
      export default service

       

初始化路由组件

  • 配置以下目录和文件
  • 【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第19张图片
  • 在 router 的 index.ts 配置路由,并且设置布局 Layout
  • import Vue from 'vue'
    import VueRouter, { RouteConfig } from 'vue-router'
    import Layout from '@/layout/index.vue'
    
    Vue.use(VueRouter)
    
    // 路由配置规则
    const routes: Array = [
      {
        path: '/login',
        name: 'login',
        component: () => import(/* webpackChunkName: 'login' */ '@/views/login/index.vue')
      },
      {
        path: '/',
        component: Layout,
        children: [
          {
            path: '', // 默认子路由
            name: 'home',
            component: () => import(/* webpackChunkName: 'home' */ '@/views/home/index.vue')
          },
          {
            path: '/role',
            name: 'role',
            component: () => import(/* webpackChunkName: 'role' */ '@/views/role/index.vue')
          },
          {
            path: '/menu',
            name: 'menu',
            component: () => import(/* webpackChunkName: 'menu' */ '@/views/menu/index.vue')
          },
          {
            path: '/resource',
            name: 'resource',
            component: () => import(/* webpackChunkName: 'resource' */ '@/views/resource/index.vue')
          },
          {
            path: '/course',
            name: 'course',
            component: () => import(/* webpackChunkName: 'course' */ '@/views/course/index.vue')
          },
          {
            path: '/user',
            name: 'user',
            component: () => import(/* webpackChunkName: 'user' */ '@/views/user/index.vue')
          },
          {
            path: '/advert',
            name: 'advert',
            component: () => import(/* webpackChunkName: 'advert' */ '@/views/advert/index.vue')
          },
          {
            path: '/advert-space',
            name: 'advert-space',
            component: () => import(/* webpackChunkName: 'advert-space' */ '@/views/advert-space/index.vue')
          }
        ]
      },
      {
        path: '*',
        name: '404',
        component: () => import(/* webpackChunkName: '404' */ '@/views/error-page/404.vue')
      }
    ]
    
    const router = new VueRouter({
      routes
    })
    
    export default router
    

    【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第20张图片

  • 编写 login.vue

接口测试

  • 在编写代码前,用 Postman 软件先测试 

封装请求模块

  • 创建 src/utils/request.js
  • import axios from 'axios'
    import store from '@/store'
    import { Message } from 'element-ui'
    import router from '@/router'
    import qs from 'qs'
    
    const request = axios.create({
      // 配置选项
      // baseURL,
      // timeout
    })
    
    function redirectLogin () {
      router.push({
        name: 'login',
        query: {
          redirect: router.currentRoute.fullPath
        }
      })
    }
    
    function refreshToken () {
      return axios.create()({
        method: 'POST',
        url: '/front/user/refresh_token',
        data: qs.stringify({
          // refresh_token 只能使用1次
          refreshtoken: store.state.user.refresh_token
        })
      })
    }
    
    // 请求拦截器
    request.interceptors.request.use(function (config) {
      // 我们就在这里通过改写 config 配置信息来实现业务功能的统一处理
      const { user } = store.state
      if (user && user.access_token) {
        config.headers.Authorization = user.access_token
      }
    
      // 注意:这里一定要返回 config,否则请求就发不出去了
      return config
    }, function (error) {
      // Do something with request error
      return Promise.reject(error)
    })
    
    // 响应拦截器
    let isRfreshing = false // 控制刷新 token 的状态
    let requests: any[] = [] // 存储刷新 token 期间过来的 401 请求
    request.interceptors.response.use(function (response) { // 状态码为 2xx 都会进入这里
      // console.log('请求响应成功了 => ', response)
      // 如果是自定义错误状态码,错误处理就写到这里
      return response
    }, async function (error) { // 超出 2xx 状态码都都执行这里
      // console.log('请求响应失败了 => ', error)
      // 如果是使用的 HTTP 状态码,错误处理就写到这里
      // console.dir(error)
      if (error.response) { // 请求发出去收到响应了,但是状态码超出了 2xx 范围
        const { status } = error.response
        if (status === 400) {
          Message.error('请求参数错误')
        } else if (status === 401) {
          // token 无效(没有提供 token、token 是无效的、token 过期了)
          // 如果有 refresh_token 则尝试使用 refresh_token 获取新的 access_token
          if (!store.state.user) {
            redirectLogin()
            return Promise.reject(error)
          }
    
          // 刷新 token
          if (!isRfreshing) {
            isRfreshing = true // 开启刷新状态
            // 尝试刷新获取新的 token
            return refreshToken().then(res => {
              if (!res.data.success) {
                throw new Error('刷新 Token 失败')
              }
    
              // 刷新 token 成功了
              store.commit('setUser', res.data.content)
              // 把 requests 队列中的请求重新发出去
              requests.forEach(cb => cb())
              // 重置 requests 数组
              requests = []
              return request(error.config)
            }).catch(err => {
              console.log(err)
              store.commit('setUser', null)
              redirectLogin()
              return Promise.reject(error)
            }).finally(() => {
              isRfreshing = false // 重置刷新状态
            })
          }
    
          // 刷新状态下,把请求挂起放到 requests 数组中
          return new Promise(resolve => {
            requests.push(() => {
              resolve(request(error.config))
            })
          })
        } else if (status === 403) {
          Message.error('没有权限,请联系管理员')
        } else if (status === 404) {
          Message.error('请求资源不存在')
        } else if (status >= 500) {
          Message.error('服务端错误,请联系管理员')
        }
      } else if (error.request) { // 请求发出去没有收到响应
        Message.error('请求超时,请刷新重试')
      } else { // 在设置请求时发生了一些事情,触发了一个错误
        Message.error(`请求失败:${error.message}`)
      }
    
      // 把请求失败的错误对象继续抛出,扔给上一个调用者
      return Promise.reject(error)
    })
    
    export default request
    

 模块开发思路 

  • 确定开发页面模块的顺序
  • 从本案例中分析,用户权限的功能主要是给用户分配角色和权限,这时需要角色列表和权限列表。而用户角色属于角色列表模块,权限列表属于菜单列表和资源列表,所以,我们最先开发的是菜单列表和资源列表模块。

发布部署

  • npm run build
  • 本地预览,查看项目打包情况,用 Node.js静态文件服务器,,参考文档(https://cli.vuejs.org/zh/guide/deployment.html):
  • npm install -g serve
  • npm i express -D
  • 在根目录创建文件夹 test-serve ,创建 app.js 
  • const express = require('express')
    const app = express()
    const path = require('path')
    const createProxyMiddleware = require('http-proxy-middleware')
    
    // 托管了 dist 目录,当访问 / 的时候,默认会返回托管目录中的 index.html 文件
    app.use(express.static(path.join(__dirname, '../dist')))
    
    app.use('/boss', createProxyMiddleware({
      target: 'http://eduboss.lagou.com',
      changeOrigin: true
    }))
    
    app.use('/front', createProxyMiddleware({
      target: 'http://edufront.lagou.com',
      changeOrigin: true
    }))
    
    app.listen(3000, () => {
      console.log('running...')
    })
    

     

  • 将 打包的 dist 目录托管到这个服务中,然后开启代理服务,参考文档:https://github.com/chimurai/http-proxy-middleware#install
  • npm install --save-dev http-proxy-middleware  #安装前端跨域调试工具
  • 在 package,json 中配置预览的启动指令
  • 【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第21张图片
  • npm run preview
  • 【Vue.js + Vuex + TypeScript 实战项目开发与项目优化】_第22张图片
  • 访问地址:http://localhost:3000/

源码下载

  • https://github.com/daisy-yangyang/edu-boss-fed.git 

你可能感兴趣的:(vue,TypeScript,vue)