项目实战 (一)
一.环境搭建
这里我先简单介绍下 Vue-Cli
各个版本之间的不同。目前我们使用的是Vue-cli4
版本,
cli2
和cli3
的区别很容易看出。整个构建目录的变化及webpack
的升级,提升了构建项目速度也提供了vue ui
等,这里主要对比下 cli3
和cli4
的区别:
css
预处理器默认sass
选项改为dart sass
- 更新项目中的版本 (
copy-webpack-plugin v5
、webpack-chain v5
、css-loader to v2
、core-js v3
、ESLint v5
、workbox v4
、nightwatch v1
、jest v24
...) - 一些细节更新
总结一下主要就是很多依赖的模块都发生了重大的变化。
1.初始化
安装最新的Vue-cli4
$ npm install @vue/cli -g
通过vue ui
创建项目
$ vue ui
添加
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页面
2
3
4
5
6
7
8
9
10
11
12
二.路由系统配置
通过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;
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')
}]
2
3
4
5
6
7
user.router.js
export default [{
path: '/login',
name: 'login',
component: () => import( /*webpackChunkName:'login'*/ '@/views/user/Login.vue')
},
{
path: '/reg',
name: 'reg',
component: () => import( /*webpackChunkName:'reg'*/ '@/views/user/Reg.vue')
}
]
2
3
4
5
6
7
8
9
10
11
三. 布局绘制
通过布局组件进行布局
<el-container>
<el-header>头</el-header>
<el-main>
<router-view></router-view>
</el-main>
<el-footer>尾</el-footer>
</el-container>
<style lang="scss">
* {margin: 0;padding: 0;}
img{max-width: 100%;}
.el-header,.el-footer{background: #333;color:#fff}
.el-main{ min-height: calc(100vh - 120px);}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
页面中用到的组件需要手动导入注册
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)
});
2
3
4
5
6
7
1.封装导航组件
通过栅格化布局实现导航组件的划分
<template>
<el-row class="header-row">
<el-col :span="18">
<img src="@/assets/logo.png" class="logo" />
<el-menu class="menu" mode="horizontal" background-color="#333" text-color="#fff"
active-text-color="fff" :router="true"
>
<el-menu-item index="/">首页</el-menu-item>
<el-menu-item index="/">发表文章</el-menu-item>
</el-menu>
</el-col>
<el-col :span="6">
<div class="nav-right">
<el-menu
class="el-menu-demo"
mode="horizontal"
background-color="#333"
text-color="#fff"
active-text-color="fff"
>
<el-menu-item index="login">
<router-link to="/login">登录</router-link>
</el-menu-item>
<el-menu-item index="reg">
<router-link to="/reg">注册</router-link>
</el-menu-item>
<el-submenu index="profile">
<template slot="title">张三</template>
<el-menu-item index="logout">退出登录</el-menu-item>
</el-submenu>
</el-menu>
</div>
</el-col>
</el-row>
</template>
<style lang="scss">
.header-row {
height: 100%;
.logo { margin: 5px;height: 50px}
.menu,.logo { display: inline-block;}
.nav-right {
float: right;
li { display: inline-block;text-align: center;line-height: 60px;}
a {color: #fff;}
}
}
</style>
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
2.封装导航底部组件
<template>
<div class="footer-row">课程内容版权均归 珠峰架构课</div>
</template>
<style lang="scss">
.footer-row {line-height: 60px; text-align: center;}
</style>
2
3
4
5
6
3.页面结构
<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>
2
3
4
5
6
7
8
9
10
11
引入我们编写的
PageHeader
、PageFooter
组件
axios
请求
四.封装axios
是基于promise
的ajax
库,我们一般会设置一些默认属性和拦截器
import axios from 'axios';
import { baseURL } from '@/config'
class Http {
constructor(baseUrl) {
this.baseURL = baseURL;
this.timeout = 3000;
}
setInterceptor(instance) {
instance.interceptors.request.use(config => {
return config;
});
instance.interceptors.response.use(res => {
if (res.status == 200) {
return Promise.resolve(res.data);
} else {
return Promise.reject(res);
}
}, err => {
return Promise.reject(err);
});
}
mergeOptions(options) {
return {
baseURL: this.baseURL,
timeout: this.timeout,
...options
}
}
request(options) {
const instance = axios.create();
const opts = this.mergeOptions(options);
this.setInterceptor(instance);
return instance(opts);
}
get(url, config = {}) {
return this.request({
method: 'get',
url: url,
...config
})
}
post(url, data) {
return this.request({
method: 'post',
url,
data
})
}
}
export default new Http;
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
每次请求时通过
axios.create()
方法创建axios实例并增添拦截器功能。再次之上我们也再次封装get方法和post方法
Vuex
配置
五.1.模块的基本配置
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;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
通过
require.context()
动态加载模块,实现store的状态分割
2.抽离根模块
import Vue from 'vue'
import Vuex from 'vuex'
import rootModule from './root'; // 将rootModule单独拿到root文件夹中
Vue.use(Vuex)
2
3
4
六.前后端联调
1. 获取轮播图数据
后端接口:http://localhost:3000/public/getSlider
export default {
getSlider: '/public/getSlider' // 获取轮播图接口
}
2
3
4
抽离接口路径到
config
中,为了更方便的维护接口
import config from './config/public';
import axios from '@/utils/request';
export const getSlider = (type) => axios.get(config.getSlider);
2
3
Vuex
中实现对应action
2.在创造对应的action-types
export const SET_SLIDERS = 'SET_SLIDERS';
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);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
3.在组件中获取数据
if (res.status == 200) {
if(res.data.err == 0){ // 如果状态码是0 说明无错误
return Promise.resolve(res.data);
}else{
return Promise.reject(res.data.data);
}
}
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);
}
}
};
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>
2
3
4
5
6
7