vue项目实践

# 环境搭建

# 1.初始化

安装最新的Vue-cli4

$ npm install @vue/cli -g
1

css预处理器默认sass选项改为dart sass; cl4已设置;

通过vue ui创建项目

$ vue ui
1

添加vuex、添加vue-router、添加dart sass

添加插件element-ui:vue-cli-plugin-element (import on demand)

添加依赖 axios

# 2.配置目录

src
    │  App.vue     # 根组件
    │  main.js     # 入口文件
    ├─api          # 存放接口
    ├─assets       # 存放资源
    ├─components   # 组件
    ├─plugins      # 生成的插件
    ├─config       # 存放配置文件
    ├─router       # 存放路由配置
    ├─store        # 存放vuex配置
    ├─utils        # 存放工具方法
    └─views        # 存放Vue页面
1
2
3
4
5
6
7
8
9
10
11
12

# 组件加载

vue-cli-plugin-element (import on demand)

# 组件按需加载

页面中用到的组件需要手动导入注册

import Vue from 'vue'
import { Button, Container, Footer, Header, Main } from 'element-ui'

const components = { Button, Container, Footer, Header, Main }
Object.entries(components).forEach(([key, component]) => {
    Vue.use(component)
});
1
2
3
4
5
6
7

# 基本封装结构

<el-container style="min-width:960px;">
    <el-header>
        <PageHeader></PageHeader>
    </el-header>
    <el-main>
        <router-view></router-view>
    </el-main>
    <el-footer>
        <PageFooter></PageFooter>
    </el-footer>
</el-container>
1
2
3
4
5
6
7
8
9
10
11

引入我们编写的PageHeaderPageFooter组件

# 路由配置

# 配置动态加载

通过require.context实现路由模块拆分

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter);
const routes = [];
const files = require.context('./', true, /\.router.js$/);
files.keys().forEach(key => {
  routes.push(...files(key).default)
});
const router = new VueRouter({
  mode: 'history',
  routes
});
export default router;
1
2
3
4
5
6
7
8
9
10
11
12
13

通过require.context动态导入路由模块,实现路由的模块化,这样我们可以对路由进行分类了 (这里不建议根据页面自动生成路由,这样项目整个太不灵活了)

index.router.js

export default [{
    path: '/',
    component: () => import(/*webpackChunkName:'home'*/'@/views/Home.vue')
}, {
    path: '*',
    component: () => import(/*webpackChunkName:'404'*/'@/views/404.vue')
}]
1
2
3
4
5
6
7

# store配置

# 配置动态加载

通过require.context()动态加载模块,实现store的状态分割

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const rootModule = {
    state: {},
    mutations: {},
    actions: {},
    modules: {}
}
const files = require.context('./modules/', false, /\.js$/);
files.keys().forEach((key, index) => {
    let store = files(key).default;
    const moduleName = key.replace(/^\.\//, '').replace(/\.js$/, '');
    const modules = rootModule.modules || {};
    modules[moduleName] = store;
    modules[moduleName].namespaced = true;
    rootModule.modules = modules
});
const store = new Vuex.Store(rootModule);
export default store;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 抽离根模块

import Vue from 'vue'
import Vuex from 'vuex'
import rootModule from './root'; // 将rootModule单独拿到root文件夹中
Vue.use(Vuex)
1
2
3
4

# axios请求【要点】

axios是基于promiseajax库,一般会设置一些默认属性和拦截器;

每次请求时通过axios.create()方法创建axios实例并增添拦截器功能。再次之上我们也再次封装get方法和post方法

使其可以请求超时或者切换页面时,取消之前的请求;

# 添加路由拦截

cancelToken: async function(to, from, next) {
    // beforeEach的方法在这里
    store.state.ajaxTokens.forEach((fn) => fn()); //  取消上一个页面发送的请求
    store.commit(types.SET_REQUEST_TOKEN, "clear");
    next();
}

[types.SET_REQUEST_TOKEN](state, payload) {
    if (payload == "clear") {
        state.ajaxTokens = [];
    } else {
        state.ajaxTokens = [...state.ajaxTokens, payload];
    }
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 类方式封装


let loadingInstance
class Request {
  constructor() {
    this.baseURL = process.env.VUE_APP_API_BASE_URL // API 请求的默认前缀
    this.timeout = 6000 // 请求超时时间
  }

  // 合并参数
  mergeOptions(options) {
    return {
      timeout: this.timeout,
      baseURL: this.baseURL,
      ...options
    }
  }
  
  setInterceptor(instance, url) {
    instance.interceptors.request.use(config => {
      if (Object.keys(this.queue).length == 0) {
        loadingInstance = Loading.service({ fullscreen: true }) // 当前是所有请求中的第一个
      }
      this.queue[url] = true
      const Cancel = axios.CancelToken
      config.cancelToken = new Cancel(function(c) {
        store.commit(types.SET_REQUEST_TOKEN, c)
      })
      config.headers.authorization = 'Bearer ' + storage.get(ACCESS_TOKEN) // jwt 规范
      return config
    })

    instance.interceptors.response.use(
      res => {
        delete this.queue[url] // 删除
        if (Object.keys(this.queue).length === 0) {
          loadingInstance.close() // 当前是所有请求中的第一个
        }
        if (res.status === 200) {
          if (res.data.err === 1) {
            return Promise.reject(res.data)
          }
          return Promise.resolve(res.data.data)
        } else {
          return Promise.reject(res) // 401 403 .... switch-case 去判断每个状态码代表的含义
        }
      },
      err => {
        // 失败直接返回失败的promoise
        delete this.queue[url] // 删除
        if (Object.keys(this.queue).length == 0) {
          loadingInstance.close() // 当前是所有请求中的第一个
        }
        return Promise.reject(err)
      }
    )
  }

  request(options) {
    const opts = this.mergeOptions(options) // 用户的参数 + 默认参数 = 总共的参数
    const axiosInstance = axios.create() // 对请求的超时,切换取消请求  , loading
    this.setInterceptor(axiosInstance, opts.url) // 添加拦截器
    return axiosInstance(opts) // 当调用axios.request 时 内部会创建一个 axios实例 并且给这个实例传入配置属性
  }
  get(url, config = {}) {
    return this.request({
      url,
      method: 'get',
      ...config
    })
  }
  post(url, data) {
    return this.request({
      method: 'post',
      url,
      data
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

# 使用场景

# 1. 获取轮播图数据

后端接口:http://localhost:3000/public/getSlider

export default {
    getSlider: '/public/getSlider' // 获取轮播图接口
}
1
2
3

抽离接口路径到config中,为了更方便的维护接口

import config from './config/public';
import axios from '@/utils/request';
export const getSlider = (type) => axios.get(config.getSlider);
1
2
3

# 2.在Vuex中实现对应action

创造对应的action-types

export const SET_SLIDERS = 'SET_SLIDERS';
1
import { getSlider } from '../api/public';
import * as types from './action-types';
export default {
    state: {
        sliders: [],
    },
    mutations: {
        [types.SET_SLIDERS](state, sliders) {
            state.sliders = sliders;
        }
    },
    actions: {
        async [types.SET_SLIDERS]({ commit }) {
            let { sliders } = await getSlider();
            commit(types.SET_SLIDERS, sliders);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 3.在组件中获取数据

借助mapState, mapActions快速获取数据及方法;

if (res.status == 200) {
    if(res.data.err == 0){ // 如果状态码是0 说明无错误
        return Promise.resolve(res.data);
    }else{
        return Promise.reject(res.data.data);
    }
}
1
2
3
4
5
6
7

axios中统一处理错误

import { mapActions, mapState } from "vuex";
import * as types from "@/store/action-types";
export default {
    computed: {
        ...mapState(["sliders"])
    },
    methods: {
        ...mapActions([types.SET_SLIDERS])
    },
    async mounted() {
        try{
            await this[types.SET_SLIDERS]();
        }catch(e){
            console.log(e);
        }
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

这里我们可以通过辅助函数调用action,并将数据存储到state中。

# 4.渲染轮播图组件

<div class="banner">
    <el-carousel :interval="4000" type="card" height="360px">
        <el-carousel-item v-for="item in sliders" :key="item._id">
            <img :src="item.url" />
        </el-carousel-item>
    </el-carousel>
</div>
1
2
3
4
5
6
7

# 相关链接

https://pro.antdv.com/docs/authority-management

上次更新: 2022/04/15, 05:41:28
×