1.vue-cli搭建基本架构

使用命令 vue create 文件名创建项目
image.png

2.分环境打包

3.css文件分组

assets/css/base.less

  1. @import url('./base-flex.less');

assets/css/base-flex.less

  1. .df {
  2. display: flex;
  3. }
  4. .aic {
  5. align-items: center;
  6. }
  7. .jcb {
  8. justify-content: space-between;
  9. }

4.引入SVG的使用

svg icon引入

  • 下载图标,存入src/icons/svg中
  • 安装依赖:svg-sprite-loader

    1. npm i svg-sprite-loader -D
  • 修改规则和新增规则,vue.config.js ```javascript // resolve定义一个绝对路径获取函数 const path = require(‘path’)

function resolve (dir) { return path.join(__dirname, dir) } //… chainWebpack(config) { // 配置svg规则排除icons目录中svg文件处理 // 目标给svg规则增加一个排除选项exclude:[‘path/to/icon’] config.module.rule(“svg”) .exclude.add(resolve(“src/icons”))

// 新增icons规则,设置svg-sprite-loader处理icons目录中的svg config.module.rule(‘icons’) .test(/.svg$/) .include.add(resolve(‘./src/icons’)).end() .use(‘svg-sprite-loader’) .loader(‘svg-sprite-loader’) .options({ symbolId: ‘icon-[name]’ }) }

  1. > 使用图标,App.vue --- 没有封装情况下的引用
  2. ```javascript
  3. <template>
  4. <svg>
  5. <use xlink:href="#icon-wx" />
  6. </svg>
  7. </template>
  8. <script>
  9. import '@/icons/svg/wx.svg'
  10. </script>
  • 自动导入 — 封装

创建icons/index.js

  1. const req = require.context('./svg', false, /\.svg$/)
  2. req.keys().map(req);

创建SvgIcon组件,components/SvgIcon/index.vue

  1. <template>
  2. <svg :class="svgClass" v-on="$listeners">
  3. <use :xlink:href="iconName" />
  4. </svg>
  5. </template>
  6. <script>
  7. export default {
  8. name: 'SvgIcon',
  9. props: {
  10. iconClass: {
  11. type: String,
  12. required: true
  13. },
  14. className: {
  15. type: String,
  16. default: ''
  17. }
  18. },
  19. computed: {
  20. iconName () {
  21. return `#icon-${this.iconClass}`
  22. },
  23. svgClass () {
  24. if (this.className) {
  25. return 'svg-icon ' + this.className
  26. } else {
  27. return 'svg-icon'
  28. }
  29. }
  30. }
  31. }
  32. </script>
  33. <style scoped>
  34. .svg-icon {
  35. width: 1em;
  36. height: 1em;
  37. vertical-align: -0.15em;
  38. fill: currentColor;
  39. overflow: hidden;
  40. }
  41. </style>

注册全局组件
src/plugin/componentGlobal.js

  1. import SvgIcon from '@/components/SvgIcon'; // svg组件
  2. export default {
  3. install (Vue) {
  4. // register globally 注册到全局
  5. Vue.component('svg-icon', SvgIcon);
  6. }
  7. };

main.js全局引入

  1. import componentGlobal from '@/plugin/componentGlobal.js';
  2. Vue.use(componentGlobal);

5.引入loading

src\components\base\BaseLoading.vue

  1. <template>
  2. <div class="fixedDiv loading">
  3. <svg-icon icon-class="loading" class="loadingIcon"></svg-icon>
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. };
  9. </script>
  10. <style lang="less" scoped>
  11. .loading {
  12. display: flex;
  13. align-items: center;
  14. justify-content: center;
  15. .loadingIcon {
  16. font-size: 50px;
  17. color: #0067c5;
  18. animation: loading 1s infinite linear;
  19. }
  20. }
  21. @keyframes loading {
  22. to {
  23. transform: rotate(360deg);
  24. }
  25. }
  26. </style>

src\App.vue

  1. <base-loading v-show="$store.state.loadingCount"></base-loading>

src\store\index.js

  1. import loading from '@/store/loading';
  2. modules: {
  3. loading
  4. }

src\store\loading.js

  1. export default {
  2. namespaced: true,
  3. state: () => {
  4. return {
  5. loadingCount: 0
  6. };
  7. },
  8. mutations: {
  9. addLoadingCount (state) {
  10. state.loadingCount++;
  11. },
  12. removeLoadingCount (state) {
  13. state.loadingCount--;
  14. }
  15. }
  16. };

3.引入axios

axios官方文档
axios参考文档

utils/http.js (返回的是axios的对象,直接引入这个axios对象使用即可)

  1. import axios from 'axios'
  2. import router from '@/router'
  3. import store from '@/store/index' // 获取token
  4. import { Message } from 'view-design'
  5. const qs = require('qs');
  6. // ! 创建axios实例
  7. // ! axios 设置请求超时
  8. // 设置默认的请求超时时间。例如超过了10s,就会告知用户当前请求超时,请刷新等
  9. var instance = axios.create({ timeout: 1000 * 20 })
  10. // ! post 请求头的设置
  11. // post请求的时候,我们需要加上一个请求头,所以可以在这里进行一个默认的设置
  12. instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
  13. // ! 请求拦截
  14. // 在发送请求前可以进行一个请求的拦截,为什么要拦截呢,我们拦截请求是用来做什么的呢?
  15. // 比如,有些请求是需要用户登录之后才能访问的,或者post请求的时候,我们需要序列化我们提交的数据。这时候,我们可以在请求被发送之前进行一个拦截,从而进行我们想要的操作。
  16. // 这里说一下token,一般是在登录完成之后,将用户的token通过localStorage或者cookie存在本地,然后用户每次在进入页面的时候(即在main.js中),会首先从本地存储中读取token,如果token存在说明用户已经登陆过,则更新vuex中的token状态。然后,在每次请求接口的时候,都会在请求的header中携带token,后台人员就可以根据你携带的token来判断你的登录是否过期,如果没有携带,则说明没有登录过。这时候或许有些小伙伴会有疑问了,就是每个请求都携带token,那么要是一个页面不需要用户登录就可以访问的怎么办呢?其实,你前端的请求可以携带token,但是后台可以选择不接收啊!
  17. instance.interceptors.request.use(
  18. config => {
  19. // todo 解决get请求传递数组参数出现 [ ] 解决方法
  20. if (config.method === 'get') {
  21. config.paramsSerializer = function (params) {
  22. // qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' }) // 'a[0]=b&a[1]=c'
  23. // qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' }) // 'a[]=b&a[]=c'
  24. // qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' }) // 'a=b&a=c'
  25. return qs.stringify(params, { arrayFormat: 'repeat' })
  26. }
  27. }
  28. // 登录流程控制中,根据本地是否存在token判断用户的登录情况
  29. // 但是即使token存在,也有可能token是过期的,所以在每次的请求头中携带token
  30. // 后台根据携带的token判断用户的登录情况,并返回给我们对应的状态码
  31. // 而后我们可以在响应拦截器中,根据状态码进行一些统一的操作。
  32. const token = store.state.token
  33. token && (config.headers.Authorization = token)
  34. return config
  35. },
  36. error => {
  37. return Promise.error(error)
  38. }
  39. )
  40. // ! 响应的拦截
  41. /**
  42. * todo 提示函数
  43. * 禁止点击蒙层、显示一秒后关闭
  44. */
  45. const tip = msg => {
  46. Message.error({
  47. background: true,
  48. content: msg,
  49. duration: 10
  50. })
  51. }
  52. /**
  53. * todo 跳转登录页
  54. * 携带当前页面路由,以期在登录页面完成登录后返回当前页面
  55. */
  56. const toLogin = () => {
  57. router.replace({
  58. path: '/login',
  59. query: {
  60. redirect: router.currentRoute.fullPath
  61. }
  62. })
  63. }
  64. /**
  65. * todo 请求失败后的错误统一处理
  66. * @param {Number} status 请求失败的状态码
  67. */
  68. const errorHandle = (status, msg, response) => {
  69. switch (status) {
  70. case 400:
  71. console.log(response, 'response ===');
  72. tip('参数错误')
  73. break
  74. // 401: 未登录
  75. // 1. 跳转登录页面,并携带当前页面的路径
  76. // 2. 在登录成功后返回当前页面,这一步需要在登录页操作。
  77. case 401:
  78. toLogin()
  79. break
  80. // 403 token过期
  81. // 登录过期对用户进行提示
  82. // 清除本地token和清空vuex中token对象
  83. // 跳转登录页面
  84. case 403:
  85. tip('登录过期,请重新登录')
  86. // 清除token
  87. localStorage.removeItem('token')
  88. store.commit('loginSuccess', null)
  89. // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
  90. setTimeout(() => {
  91. toLogin()
  92. }, 1000)
  93. break
  94. // 404请求不存在
  95. case 404:
  96. tip('网络请求不存在')
  97. break
  98. case 500:
  99. tip('服务器宕机')
  100. break
  101. // 其他错误,直接抛出错误提示
  102. default:
  103. tip(msg)
  104. break
  105. }
  106. }
  107. // todo 响应拦截器
  108. // 响应拦截器很好理解,就是服务器返回给我们的数据,我们在拿到之前可以对他进行一些处理。
  109. // 例如上面的思想:如果后台返回的状态码是200,则正常返回数据,否则的根据错误的状态码类型进行一些我们需要的错误,其实这里主要就是进行了错误的统一处理和没登录或登录过期后调整登录页的一个操作。
  110. instance.interceptors.response.use(
  111. // todo 请求成功
  112. // 如果返回的状态码为200,说明接口请求成功,可以正常拿到数据
  113. // 否则的话抛出错误
  114. res => res.status === 200 ? Promise.resolve(res) : Promise.reject(res),
  115. // todo 请求失败
  116. // 这里可以跟你们的后台开发人员协商好统一的错误状态码
  117. // 然后根据返回的状态码进行一些操作,例如登录过期提示,错误提示等等
  118. // 下面列举几个常见的操作,其他需求可自行扩展
  119. error => {
  120. // console.log(error, '======')
  121. const { response } = error
  122. console.log(response)
  123. if (response) {
  124. // 请求已发出,但是不在2xx的范围
  125. errorHandle(response.status, response.data.message, response)
  126. return Promise.reject(response)
  127. } else {
  128. // 处理断网的情况
  129. // eg:请求超时或断网时,更新state的network状态
  130. // network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏
  131. // 关于断网组件中的刷新重新获取数据,会在断网组件中说明
  132. store.commit('changeNetwork', false)
  133. }
  134. }
  135. )
  136. export default instance

api/axios.js (封装的get和post请求)

  1. import { baseUrl } from './base' // 导入接口域名列表
  2. import axios from '@/utils/http' // 导入http中创建的axios实例
  3. import store from '@/store'
  4. /**
  5. * ! get方法,对应get请求
  6. * @param {String} url [请求的url地址]
  7. * @param {Object} params [请求时携带的参数]
  8. */
  9. export function get (url, params) {
  10. store.commit('loading/addLoadingCount');
  11. return new Promise((resolve, reject) => {
  12. axios.get(baseUrl + url, { params }).then(res => {
  13. if (res && res.data && res.data.code === 'ACK') {
  14. resolve(res.data)
  15. } else {
  16. console.log('res ===', res)
  17. reject(res)
  18. }
  19. }).then(() => {
  20. store.commit('loading/removeLoadingCount');
  21. })
  22. })
  23. }
  24. /**
  25. * ! post方法,对应post请求
  26. * @param {String} url [请求的url地址]
  27. * @param {Object} params [请求时携带的参数]
  28. */
  29. export function post (url, params) {
  30. return new Promise((resolve, reject) => {
  31. axios.post(baseUrl + url, params).then(res => {
  32. if (res && res.data && res.data.code === 'ACK') {
  33. resolve(res.data)
  34. } else {
  35. console.log('res ===', res)
  36. reject(res)
  37. }
  38. }, err => {
  39. console.log(err)
  40. }).then(() => {
  41. store.commit('loading/removeLoadingCount');
  42. })
  43. })
  44. }

4.添加api的统一管理

api/index.js (合并所有的api)

  1. // ! api的出口文件
  2. import common from '@/api/common'
  3. export default {
  4. // 公共的请求
  5. common
  6. }

api/common.js

  1. import { get } from './axios' // 导入封装的请求方法
  2. // 项目列表
  3. const projectList = params => get('/project/list', params)
  4. export default {
  5. projectList
  6. }

main.js

  1. import api from '@/api/index.js' // 导入api接口
  2. Vue.prototype.$api = api // 将api挂载到vue的原型上复制代码

5.添加公共的请求loading

  1. <!-- loading -->
  2. <common-loading v-if="loadingCount"></common-loading>
  3. import { mapState } from 'vuex'
  4. computed: {
  5. ...mapState('loading', ['loadingCount'])
  6. },

每次请求loadingCount++,请求完成loadingCount—
(但是有一个问题,如果请求是在有嵌套逻辑关系的,例如:请求1完成之后,再进行请求2会造成loading会闪现的问题)

6.添加数组字典

dictionary/index.js

  1. export default {
  2. // 漏洞类型
  3. BugTypeEnum: {
  4. codeerror: '代码错误',
  5. security: '安全相关',
  6. performance: '性能问题',
  7. others: '其他',
  8. interface: '界面优化',
  9. newfeature: '新增需求',
  10. designchange: '设计变更'
  11. }
  12. }

main.js

  1. import dict from '@/dictionary' // 导入数据字典
  2. Vue.prototype.dict = dict;

7.添加其他的公共的方法

utils/NumberPrototype.js

  1. /* eslint no-extend-native: ["error", { "exceptions": ["Number", "String"] }] */
  2. /**
  3. * ! 加法
  4. 0.1+0.2 // 0.30000000000000004
  5. 0.1.add(0.2) // 0.3
  6. */
  7. Number.prototype.add = function (arg) {
  8. var r1, r2, m
  9. try {
  10. r1 = this.toString().split('.')[1].length
  11. } catch (e) {
  12. r1 = 0
  13. }
  14. try {
  15. r2 = arg.toString().split('.')[1].length
  16. } catch (e) {
  17. r2 = 0
  18. }
  19. m = Math.pow(10, Math.max(r1, r2))
  20. return Math.round(this.mul(m) + arg.mul(m)).div(m)
  21. }
  22. /**
  23. * ! 减法
  24. 0.3-0.1 // 0.19999999999999998
  25. 0.3.sub(0.1) // 0.2
  26. 5000.02.sub(905.8) // 4094.22
  27. */
  28. Number.prototype.sub = function (arg) {
  29. return this.add(-arg)
  30. }
  31. /**
  32. * ! 乘法
  33. 0.00035.mul(100) // 0.035
  34. */
  35. Number.prototype.mul = function (arg) {
  36. let m = 0
  37. const s1 = this.toString()
  38. const s2 = arg.toString()
  39. try {
  40. m += s1.split('.')[1].length
  41. } catch (e) { }
  42. try {
  43. m += s2.split('.')[1].length
  44. } catch (e) { }
  45. return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m)
  46. }
  47. // ! 除法
  48. Number.prototype.div = function (arg) {
  49. let t1 = 0
  50. let t2 = 0
  51. try {
  52. t1 = this.toString().split('.')[1].length
  53. } catch (e) { }
  54. try {
  55. t2 = arg.toString().split('.')[1].length
  56. } catch (e) { }
  57. const r1 = Number(this.toString().replace('.', ''))
  58. const r2 = Number(arg.toString().replace('.', ''))
  59. return (r1 / r2).mul(Math.pow(10, t2 - t1))
  60. }
  61. /**
  62. * ! 自定义数值转成指定小数位数
  63. * num 数值
  64. * digit 小数位数,默认2
  65. */
  66. function toDecimal (digit) {
  67. var num = this
  68. digit = digit === undefined ? 2 : digit
  69. // this如果是字符串,则转成数字类型
  70. if (typeof num === 'string') num = parseFloat(num)
  71. // 参数digit如果是字符串,则转成数字类型
  72. if (typeof digit === 'string') digit = parseFloat(digit)
  73. // 如果为NaN抛出异常
  74. if (isNaN(num)) return ''
  75. // 判断小数位数
  76. var n = 1
  77. if (digit) {
  78. for (var i = 0; i < digit; i++) {
  79. n *= 10
  80. }
  81. };
  82. // 数值进行四舍五入计算
  83. var fixNum = (Math.round(num * n) / n).toString()
  84. // 小数位数不足时,补全
  85. if (digit !== 0 && fixNum.indexOf('.') === -1) {
  86. fixNum += '.'
  87. }
  88. if (digit !== 0) {
  89. while (fixNum.split('.')[1].length < digit) {
  90. fixNum += '0'
  91. }
  92. }
  93. // 此处不能返回 parseFloat(fixNum),返回 parseFloat(fixNum) 会将 2.00 变成 2.
  94. return fixNum
  95. }
  96. Number.prototype.toFixed = toDecimal
  97. String.prototype.toFixed = toDecimal

main.js

  1. import '@/utils/NumberPrototype' // 引入数字操作

8.vue防抖节流

main.js

  1. // todo 引入lodash
  2. import _ from 'lodash'
  3. Vue.prototype._ = _

防抖节流的使用

  1. this.debouncedInput = this._.debounce(this.getRequest, 800)
  2. // input失去焦点
  3. inputBlur () {
  4. this.debouncedInput() // 触发表格请求
  5. },