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
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
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
2
3
4
5
6
7
8
9
10
11
引入我们编写的
PageHeader
、PageFooter
组件
# 路由配置
# 配置动态加载
通过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
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
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
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
2
3
4
# axios
请求【要点】
axios
是基于promise
的ajax
库,一般会设置一些默认属性和拦截器;
每次请求时通过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
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
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
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
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
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
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
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
2
3
4
5
6
7
# 相关链接
https://pro.antdv.com/docs/authority-management
上次更新: 2022/04/15, 05:41:28