三六

  1. vue create projectName创建项目;默认ts转码会顺便转es6语法,此处我们让babel进行转不用ts的转;sass建议选择dart-sass。
  2. git status,git init,git add remote origin http://...给仓库起个origin别名方便提交;git push -u origin master第一次推送指定到origin仓库的master分支,通过-u记录本次提交信息避免以后每次提交都要写仓库名和分支;git remote rename test origin改仓库名;git remote -v查看仓库信息;
  3. .browserslistrc浏览器兼容处理文件,shims.vue.d.ts是ts配置文件,比如ts不认识.vue文件,需要进行声明;shims.tsx.d.ts是补充tsx的配置,如果没用到tsx不用管;Vetur插件提供了SFC单页面组件的支持;vue-class-component提供使用class语法写vue组件,vue-property-decorator在class语法上提供了一些辅助装饰器。
  4. 三种写法:https://class-component.vuejs.org/用类的写法,所有data钩子method是同级的;options api写法如下;还有类加装饰器,装饰器只是ES草案暂不建议使用。
  5. 人少用standard标准,人多用爱彼迎或google标准。vue等校验需要导入插件。github.com/typescript-eslint查看ts的eslint规则。
<script lang="ts">
import Vue from 'vue'
export default Vue.extends({
  data(){},
  methods: {}
})
</script>
  1. 全局样式可以直接在main.js里导入,变量样式可以根目录增加vue.config.js配置文件在scss预处理时全局导入。https://cli.vuejs.org/zh/可以看到vue-cli配置。新版的vue-cli将webpack很多东西集成了,直接再vue.config.js配置即可,如代理也可以统一在此配置。
module.exports = {
  css: {
    loaderOptions: {
      scss: {
        prependData: '@import "~@/styles/variables.scss"'
      }
    }
  },
  devServer: {
    proxy: {
      '/api': {
        target: '<url>',
        ws: true,
        changeOrigin: true
      },
    }
  }
}
  1. 路由懒加载最后生成默认是序号,不方便定位维护,建议写上别名。嵌套路由公共部分不需要懒加载,子路由可以懒加载,默认子路由path可以设为''。
import VueRouter, {RouteConfig} from 'vue-router';
import Layout from '@/layouts/index.vue'

const routes: Array<RouteConfig> = [
  {
    path: '/login',
    name: 'login'
    component: () => import(/* webpackChunkName: 'login' */ '@/views/login/index.vue')
  },
  {
    path: '*',
    name: '404'
    component: () => import(/* webpackChunkName: '404' */ '@/views/error/404.vue')
  },
  {
    path: '/',
    name: Layout,
    children: [
      {
        path: '',
        name: 'home'
        component: () => import(/* webpackChunkName: 'home' */ '@/views/home/index.vue')
      }
    ]
  }
]
  1. 接口调试可以下载postman,然后先测试接口是否正常,再进行对接;可以在api key给header加登录token。axios默认是用json发送的请求,若需要用x-www-form-unlencoded,需要加载qs库做处理。提交请求的时候加个loading,避免用户网络慢时频繁重复提交请求。elementUI实例调用方法时ts可能会报错,因为默认认为是vue实例,需要手动指定类型,可利用编辑器快捷跳转查看源码定位类型。
import {Form} from 'element-ui'
await (this.$refs.form as Form).validate()
  1. 封装请求方法,如果data是普通对象,则axios默认用json格式发送;如果用qs,则默认用x-www-form-urlencoded;如果是formData则默认用multipart;这样可以省去写contentType。
// services/user.ts
import request from '@/utils/request'
import qs from 'qs'

interface User {
  phone: string,
  password: string
}

export const login = (data: User) => {
  return request({
    method: 'POST',
    url: '/front/user/login',
    // headers: {'content-type': 'application/x-www-form-urlencoded'},
    data: qs.stringify(data)
  })
}
try {
  this.isLoading = true
  const {data} = await login(this.form)
  if (data.state !== 1) {
    this.$message.error(data.message)
  } else {
    this.$router.push(this.$route.query.redirect as string || '/') // 重定向回原来页面
    this.$message.success('登录成功')
  }
} catch(err) {
  console.log('登录失败', err)
}
this.isLoading = false
  1. 将登录状态保存在vuex中,且需要做持久化即存在localStorage里,不然每次刷新登录信息就没了。获取动态src时需要require('@/assets/img/test.png')否则无法获取。退出的时候清除登录状态并跳转到登录页面即可。
export default new Vuex.Store({
  state: {
    user: JSON.parse(localStorage.getItem('user') || 'null') // 只接受字符串
  },
  mutations: {
    setUser(state, payload) {
      state.user = JSON.parse(payload)
      localStorage.setItem('user', payload)
    }
  }
})
logout() {
  this.$store.commit('setUser', null) // 本地存储数据也会同步清空
  this.$router.push({name: 'login'})
}
  1. 路由中配置meta元信息可以给每个路由设置参数,比如设置该路由是否需要登录访问meta: {requiresAuth: true},不设置默认也有空的meta。利用路由守卫钩子可以校验是否登录,beforeEach访问每个路由前都需要经过这个钩子。父路由需要登录则子路由也需要,反之亦然。
router.beforeEach((to, from, next) => {
  // to.matched是一个数组(匹配到的路由记录,包括父子路由)
  if (to.matched.some(item => item.meta.requiresAuth)) {
    if (store.state.user) {
      next()
    } else {
      next({
        name: 'login' ,
        query: {
          redirect: to.fullPath
        }
      })
    }
  } else {
    next()
  }
})
  1. 每个接口请求需要token验证,可以在请求增加拦截器,统一增加token。刷新token方式一:拦截请求,判断token是否过期,过期刷新再请求。需要后端过期时间,有bug系统时间会被纂改但少请求。方式二是请求,如果后端返回401,则刷新token重新请求,会多个请求但没有bug。
import axios from 'axios'
import {Message} from 'element-ui'
import store from 'store'
import router from 'router'
import qs from 'qs'

const request = axios.create({

})

function redirectLogin() {
  router.push({
    name: 'login',
    query: {
      redirect: router.currentRoute.fullPath // 登录后要重定向的路径
    }
  })
}

function refreshToken() {
  return axios.create({ // 此处要用新的axios,不然此处接口401又会进行此处请求导致循环
    method: 'POST',
    url: '/font/user/refresh_token',
    data: qs.stringify({
      refreshtoken: store.state.user.refresh_token // 该token只能使用一次,若同时多个接口401都用同个token请求只有第一次成功
    })
  })
}

// 请求拦截器
request.interceptors.request.use(function(config){
  const {user} = store.state
  if (user && user.access_token) {
    config.headers.Authorization = user.access_token
  }

  return config // 一定要返回config,不然请求发不出去
}, function(error){
  return Promise.reject(error)
})

let isRefreshing = false
let requests = [] // 存储刷新token期间的请求,等token刷新完要全部重新请求
// 响应拦截器
request.interceptors.response.use(function(response){
  // 状态码是2XX
  return response
}, function(error){ // 需要返回一个promise对象
  if (error.response) { // 请求响应了,但状态码超过2XX
    const {status} = error.response 
    if (status === 401) {
      // 如果连用户都没有,则直接跳转到登录
      if (!store.state.user) {
        redirectLogin()
      }

      if (!isRefreshing) {
        isRefreshing = true
      
        // 尝试获取新的token
        return refreshToken()
          .then(res => {
            if (!res.data.success) {
              throw new Error('刷新 token 失败') // 抛出错误让下面的catch处理
            }
            // 成功获取token之后,把新token存储,然后重新发送失败的请求
            store.commit('setUser', res.data.content)
            requests.forEach(cb => cb()) // 此时才触发之前挂起的then的成功回调
            requests = [] // 执行完清空
            // requests不包含第一次401的失败请求,因为第一次isRefreshing为false进入当前逻辑,所以要单独再发送下第一次失败请求
            return request(error.config) // error.config是请求的配置信息,return的数据作为下一个then成功回调的参数
          })
          .catch(err => {
            // 获取失败后处理:先清除用户信息,再跳转登录
            store.commit('setUser', null)
            redirectLogin()
            return Promise.reject(error)
          })
          .finally(() => {
            isRefreshing = false
          })
      }

      // 如果是正在刷新,则先把请求挂起,存到缓冲队列里
      return new Promise((resolve, reject) => {
        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
  1. H5有个progress事件用于监听上传的,axios里封装了onUploadProgress(e) { Math. floor(e.loaded / e.total * 100 }
  2. 富文本编辑器:ckeditor、quilljs、mediumEditor(更新少)、wangEditor、ueditor(没维护)、tinymce。在vue中尽量别操作dom,可通过ref操作,this.$refs.editor获取到实例。
<script lang="ts">
import Vue from 'vue'
import E from 'wangeditor'  
export default {
  name: 'TextEditor',
  props: {
    value: {
      type: String,
      default: '',
    },
  },
  mounted() {
    this.initEditor()
  },
  methods: {
    initEditor() {
      const editor = new E(this.$refs.editor as any)
      // 监听必须在 create 之前,初始化设置在 create 之后
      editor.config.onchange = (value: string) => {
        this.$emit('input', value) // v-model的语法糖
      }
      editor.create()
      editor.txt.html(this.value)
      // 图片上传会自动触发以下方法
      editor.config.customUploadImg = async function(resultFiles: any, insertImgFn: any) {
        const fd = new FormData()
        fd.append('file', resultFiles[0])
        const {data} = await uploadCourseImage(fd)
        insertImgFn(data.data.name)
      }
    },
  },
}
</script>
  1. async函数返回的是一个promise,函数返回值是then回调的参数;await后面跟一个promise,promise的resolve值即await的执行结果。::v-deep .el-tree-node__content {}解决scoped里作用域不生效的问题。
  2. 视频点播一般都用第三方服务,比如阿里云点播。先请求后台,让后台去请求阿里云拿到token;拿到token之后上传视频。前端先下载上传的sdk放到public里,由于不支持import则需要在index.html里用script全局引入。script引入的这些静态资源不会被webpack打包。/* eslint-disable */忽略eslint校验,ts识别到window上没有AliyunUpload会报错,在shims-vue.d.ts里面增加声明。
declare module '*.vue' {
  import vue from 'vue'
  export default Vue
}

interface Window {
  AliyunUpload: any
}
  1. dist打包生成的html需要本地开启服务器去预览,用node来实现,其中接口代理用中间件http-proxy-middleware来实现。然后通过node app.js启动该文件访问dist/index.html。
const express = require('express')
const app = express()
const path = require('path')
const {createProxyMiddleware} = require('http-proxy-middleware')

// 托管了dist目录,当访问/默认访问dist/index.html
app.use(express.static(path.join(__dirname, '../dist')))
app.use('/api', createProxyMiddleware({
  target: 'http://test.com',
  changeOrigin: true,
}))

app.listen(3000, () => {
})
  1. 部署注意事项:
  • 项目中如果用了history模式则需要做配置,参考vue-cli。
  • 如果和服务器部署在一起则不需要跨域,如果不在一起则需要代理或cors。
  • 如果部署在https上,接口也必须是https。如果用了pwa也必须是https。

权限:菜单权限(哪些页面可以访问)、资源权限(哪些接口可以访问)

页面路由记住筛选数据,part3-6用户权限模块基本就是念API,nuxt.js也是念api,vue-router实现也是念源码没有思路和扩充。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容