vue完整项目开发流程
- 1.创建项目,初始化项目
- 2.配置多环境变量
- 3.配置全局sass
- 4.封装axios
- 5.封装vuex
- 6.按需引入我们需要的框架,(移动端:vant-ui)(后台管理:element-ui)
- 7.路由封装
- 8.封装组件
- 9.代码优化:节流防抖,函数防抖,图片懒加载,keep-alive缓存不活动的组件
- 10.打包上线
前言:
项目优化后最核心的是:之前是10mb,压缩好以后缩小到几百kb
1.创建项目,初始化项目
- 首先:开发这个项目我用的是vue脚手架3.0
- 项目搭建好以后,我们需要初始化项目,安装需要的插件
- 我们常用的有:
axios
(请求)、框架(后台管理系统安装element-ui
,移动端安装vant
),vuex-persistedstate
(数据持久化插件),前期我们先安装这几个,后面根据项目的需要,在安装需要的插件
2.配置多环境变量
1.首先我们需要在根目录下创建三个.env
的文件,根据环境的不同,配置不同的VUE_APP_ENV
-
.env.development.js
文件:开发环境
NODE_ENV='development'
# must start with VUE_APP_
VUE_APP_ENV = 'development'
-
.env.staging
文件:测试环境
NODE_ENV='staging'
# must start with VUE_APP_
VUE_APP_ENV = 'staging'
-
.env.production.js
:文件:生产环境
NODE_ENV='production'
# must start with VUE_APP_
VUE_APP_ENV = 'production'
2.在src根目录下面,创建一个config的文件,里面创建四个文件:env.development.js
(定义开发环境)、env.production.js
(定义生产环境)、env.staging.js
(定义测试环境),index.js(根据环境的不同,引入不同配置,process.env.VUE_APP_ENV
),config这个文件夹下面创建的env的文件,必须要跟上面三个.env的文件名一致
-
env.development.js
文件:定义开发环境的公共路径(baseURL
)、不同环境的(cdn
加速)、不同环境图片的前缀路径
// 本地环境配置
module.exports = {
title: 'backstage',
baseUrl: 'http://120.53.31.103:84/api', // 项目地址
baseApi: 'https://test.xxx.com/api', // 本地api请求地址,注意:如果你使用了代理,请设置成'/'
APPID: 'xxx',
APPSECRET: 'xxx',
$cdn: 'https://gimg2.baidu.com'
}
-
env.staging.js
文件:定义测试环境的公共路径(baseURL
)、不同环境的(cdn
加速)、不同环境图片的前缀路径
// 测试环境
module.exports = {
title: 'backstage',
baseUrl: 'https://www.xxx.com/', // 正式项目地址
baseApi: 'https://www.xxx.com/api', // 正式api请求地址
APPID: 'xxx',
APPSECRET: 'xxx',
$cdn: 'https://www.sunniejs.cn/static'
}
-
env.production.js
文件:定义开发环境的公共路径(baseURL
)、不同环境的(cdn
加速)、不同环境图片的前缀路径
// 生产环境
module.exports = {
title: 'backstage',
baseUrl: 'https://www.xxx.com/', // 正式项目地址
baseApi: 'https://www.xxx.com/api', // 正式api请求地址
APPID: 'xxx',
APPSECRET: 'xxx',
$cdn: 'https://www.sunniejs.cn/static'
}
3.配置全局sass
- 首先需要安装两个插件
npm install --save-dev sass-loader
npm install --save-dev node-sass
2.在assets这个文件夹下面创建一个css文件,在里面创建三个文件,index.scss
文件,默认样式,清楚元素初始的样式,mixin.js
文件:里面定义一些公用的代码块,variables.scss
文件:里面定义全局样式变量,最后要把后面这两个文件引入到index.js这个文件里面
- index.scss:定义默认样式,清楚元素初始的样式
@import './variables.scss';
@import './mixin.scss';
body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td{margin:0;padding:0}html,body,#app{width:100vw;height:100vh}ul,ol{list-style:none}a{text-decoration:none;color:#333333}a,span{vertical-align:top}img{border:0;vertical-align:middle}input,button,text{vertical-align:top;outline:none;border:none}button{padding:0;background:none;cursor:pointer}button::-moz-focus-inner{padding:0}textarea{outline:none;border:none;resize:none}input,textarea{box-sizing:content-box;outline:none;background:0 0;font-family:"Microsoft YaHei"}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#acacac}input:-moz-placeholder,textarea:-moz-placeholder{color:#acacac}input::-moz-placeholder,textarea::-moz-placeholder{color:#acacac}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#acacac}table tr td,table{border-collapse:collapse}body{font-size:16px;color:#000;font-family:"Microsoft YaHei"}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0}.clearfix:after{clear:both}.lf{float:left}.rt{float:right}.db{display:inline-block}.mt10{margin-top:10px}.mt15{margin-top:15px}.mt20{margin-top:20px}.mr5{margin-right:5px}.mr10{margin-right:10px}.mr15{margin-right:15px}.mr20{margin-right:20px}.centerWidth{max-width:7.5rem;margin:0 auto}
- mixin.scss:定义公共样式
// mixin
// 清除浮动
@mixin clearfix {
&:after {
content: "";
display: table;
clear: both;
}
}
// 多行隐藏
@mixin textoverflow($clamp:1) {
display: block;
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: $clamp;
/*! autoprefixer: ignore next */
-webkit-box-orient: vertical;
}
//flex box
@mixin flexbox($jc:space-between, $ai:center, $fd:row, $fw:nowrap) {
display: flex;
display: -webkit-flex;
flex: 1;
justify-content: $jc;
-webkit-justify-content: $jc;
align-items: $ai;
-webkit-align-items: $ai;
flex-direction: $fd;
-webkit-flex-direction: $fd;
flex-wrap: $fw;
-webkit-flex-wrap: $fw;
}
- variables.scss:定义全局样式变量
// variables
$background-color: #f8f8f8;
- 在根目录下创建一个
vue.config.js
文件,在抛出的对象里面写入以下代码
css: {
extract: IS_PROD, // 是否将组件中的 CSS 提取至一个独立的 CSS 文件中 (而不是动态注入到 JavaScript 中的 inline 代码)。
sourceMap: false,
loaderOptions: {
scss: {
// 向全局sass样式传入共享的全局变量, $src可以配置图片cdn前缀
// 详情: https://cli.vuejs.org/guide/css.html#passing-options-to-pre-processor-loaders
prependData: `
@import "assets/css/mixin.scss";
@import "assets/css/variables.scss";
$cdn: "${defaultSettings.$cdn}";
`
}
}
},
4.最后把index.scss这个文件引入到main,js全局文件里面引入,然后就可以用啦
// 引入全局样式
import '@/assets/css/index.scss'
4.封装axios
- 好处:
axios
的封装和api接口的统一管理,其实主要目的就是在帮助我们简化代码和利于后期的更新维护。 - 第一步:首先在src创建一个api的文件夹,然后创建三个js文件,
api.js
:里面主要统一管理接口
export default {
// 头部
HEADER: "/menu/info",
//登陆
LOGIN:"/adminUser/login",
}
-
http.js
:里面主要封装的是核心文件,用params封装request,设置请求拦截和响应拦截,在请求拦截里面我们可以设置请求头,设置loading动画,在响应拦截里面我们可以根据后台返回的状态码,对不同的状态码做一些操作,最后设置不同的请求方式,我一般设置的请求方式有get、post、put,等需要其它的时候,在封装也可以
import axios from 'axios';
import { Message, Loading } from 'element-ui';
import _ from 'lodash';
import { baseUrl } from '@/config';
import store from '@/store'
import QS from 'qs'
console.log(baseUrl)
const request = axios.create({
baseURL: baseUrl, //设置请求的base url
timeout: 40000 //超时时长
});
//loading对象
let loading;
//当前正在请求的数量
let needLoadingRequestCount = 0;
//显示loading
function showLoading(target) {
// 后面这个判断很重要,因为关闭时加了抖动,此时loading对象可能还存在,
// 但needLoadingRequestCount已经变成0.避免这种情况下会重新创建个loading
if (needLoadingRequestCount === 0 && !loading) {
loading = Loading.service({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)',
target: target || "body"
});
}
needLoadingRequestCount++;
}
//隐藏loading
function hideLoading() {
needLoadingRequestCount--;
needLoadingRequestCount = Math.max(needLoadingRequestCount, 0); //做个保护
if (needLoadingRequestCount === 0) {
//关闭loading
toHideLoading();
}
}
//防抖:将 300ms 间隔内的关闭 loading 便合并为一次。防止连续请求时, loading闪烁的问题。
let toHideLoading = _.debounce(() => {
loading.close();
}, 300);
//添加请求拦截器
request.interceptors.request.use(config => {
//判断当前请求是否设置了不显示Loading
if (config.headers.showLoading !== false) {
showLoading(config.headers.loadingTarget);
}
if (store.state.token || localStorage.getItem('token')) {
const token = store.state.token || localStorage.getItem('token');
token && (config.headers.authorization = 'Bearer '+token);
}
return config;
}, err => {
//判断当前请求是否设置了不显示Loading
if (config.headers.showLoading !== false) {
hideLoading();
}
Message.error('请求超时!');
return Promise.resolve(err);
});
//响应拦截器
request.interceptors.response.use(
response => {
//判断当前请求是否设置了不显示Loading(不显示自然无需隐藏)
if (response.config.headers.showLoading !== false) {
hideLoading();
}
if (response.status == 200) {
return Promise.resolve(response);
} else {
Message.error(response.message);
return Promise.reject(response);
}
},
error => {
//判断当前请求是否设置了不显示Loading(不显示自然无需隐藏)
if (error.config.headers.showLoading !== false) {
hideLoading();
}
if (error.response && error.response.data && error.response.data.message) {
var jsonObj = JSON.parse(error.response.data.message);
Message.error(jsonObj.message);
} else {
Message.error(error.message);
}
return Promise.reject(error);
}
);
export default {
/**
* get方法,对应get请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
get(url, data) {
let params = data || ''
return new Promise((resolve, reject) => {
request
.get(url, params)
.then(res => {
resolve(res.data)
})
.catch(err => {
reject(err.data)
})
})
},
/**
* post方法,对应post请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
post(url, data) {
let params = data || ''
return new Promise((resolve, reject) => {
request
.post(url, QS.stringify(params))
.then(res => {
resolve(res.data)
})
.catch(err => {
reject(err.data)
})
})
},
/**
* upload方法,对应upload请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
upload(url, data) {
let params = data || ''
return new Promise((resolve, reject) => {
request
.upload(url, QS.stringify(params))
.then(res => {
resolve(res.data)
})
.catch(err => {
reject(err.data)
})
})
},
/**
* download方法,对应download请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
download(url, data) {
let params = data || ''
return new Promise((resolve, reject) => {
request
.download(url, QS.stringify(params))
.then(res => {
resolve(res.data)
})
.catch(err => {
reject(err.data)
})
})
},
/**
* put方法,对应put请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
put(url, data) {
let params = data || ''
return new Promise((resolve, reject) => {
request
.put(url, QS.stringify(params))
.then(res => {
resolve(res.data)
})
.catch(err => {
reject(err.data)
})
})
},
/**
* delete方法,对应delete请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
delete(url, data) {
let params = data || ''
return new Promise((resolve, reject) => {
request
.delete(url, QS.stringify(params))
.then(res => {
resolve(res.data)
})
.catch(err => {
reject(err.data)
})
})
}
}
-
index.js
:主要定义方法,一个接口对应一个方法,要把前面封装号的两个文件引入到这个文件里面,然后引入到main.js全局文件里面,注册全局
import api from "./api"; // 导入接口域名列表
import http from "./http"; // 导入http中创建的axios实例
export default {
install(Vue) {
Vue.prototype.$http = this;
},
//头部接口
HEADER(params = "") {
return http.post(api.HEADER, params);
},
//登陆接口
LOGIN(params) {
return http.post(api.LOGIN, params);
},
STUDENT(params) {
return http.get(
api.STUDENT +
`?page=${params.page_num}&limit=${params.page_size}&status=${params.status}&nickname=${params.nickname}&mobile=${params.mobile}&`
);
},
//详情接口
DETAIL(params) {
return http.get(api.DETAIL + `/${params}?`);
},
//禁用接口
FORBIDDEN(params) {
return http.put(api.FORBIDDEN + `/${params}`);
},
DELETE(params) {
return http.delete(api.DELETE + `/${params}`);
},
//省市区接口
PROVINCE(id) {
return http.get(api.PROVINCE + `/${id}`);
},
//上传头像接口
IMG(file) {
return http.post(api.IMG ,file);
},
};
5.封装vuex
前言:什么是vuex
- Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,相当于是一个仓库,存放公共数据的,任何组件都可以使用存放在仓库里面的数据
1.vuex由五大部分组成
- state:定义数据,使用方法是通过:this.$store.state.xxx(方法名)
- actions:可以包含异步操作,使用方法是通过:
this.$store.dispatch.xxx(方法名) - mutations:唯一可以修改state数据的场所,使用方法是通过:this.$store.commit.xxx(方法名)
- getters:类似于vue组件中的计算属性,对state数据进行计算(会被缓存),使用方法是:this.$store.getters.xxx(方法名)
- modules:模块化管理store(仓库),每个模块拥有自己的 state、mutation、action、getter
2.下面说下封装vuex的核心
- 第一步:在src文件夹下面创建store的文件夹,然后在这个文件夹下面创建四个js文件
- state:定义公共数据
- actions:操作异步的数据
- mutations:操作同步的数据,也是唯一一个可以直接改变state里面定义的数据
- getters:类似于vue组件中的计算属性,对state数据进行计算(会被缓存)
- index:把前面封装好的文件都引入到这个文件里面,然后抛出
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state';
import mutations from './mutations';
import actions from './actions';
import vuexPersist from "vuex-persist";
Vue.use(Vuex)
export default new Vuex.Store({
state,
mutations,
actions,
plugins: [
new vuexPersist({
storage: window.localStorage,
}).plugin,
],
})
下面说下封装vuex的好处和具体的思路
我们在用vue脚手架3.0创建项目的时候,他会自动帮我们在src这个文件夹下面生成一个store文件夹,下面有一个index.js这个文件,index.js这个文件里面有5个属性,就是我们用vuex的时候,所用到的5个属性,把这5个属性放在同一个文件里面,适合开发中小型项目的时候使用,因为需要公用的数据不多,写在一个文件里面就可以,但是开发一个中大型项目的话,我们需要在vuex里面定义的数据比较多,所以我们就把需要的5个属性拆分出来,拆分成单独的文件,这样能在以后项目的维护,或者修改的时候比较方便,每个文件定义不同的数据,下面我们来分析一下
- state:可以把项目中需要定义在state里面的数据,都定义在这个文件里面
- action:他是一个异步的操作,把项目中异步的请求全部放在这个文件里面,如果要修改state里面定义的数据,必须要通过commit来调用mutations里面的方法,才能修改state里面的数据,不能直接修改,在组件里面需要action里面的数据,可以通过tthis.$store.dispatch+需要调用的方法名来获取
- mutations:他是一个同步的操作,也是唯一一个可以直接修改state里面定义的数据,可以把项目中同步的数据放在这个文件里面,在组件中需要通过this.$store.commit+需要调用的方法名来获取
- getters:计算属性,可以把项目中需要计算的数据放在这个文件里面,统一管理,在组建中通过this.$store.getters+调用的方法名可以获取到数据
6.按需引入我们需要的框架,(移动端:vant-ui)(后台管理:element-ui)
-vant-ui:移动端
安装指令
npm i babel-plugin-import -D
- 在babel.config.js 设置
// 对于使用 babel7 的用户,可以在 babel.config.js 中配置
const plugins = [
[
'import',
{
libraryName: 'vant',
libraryDirectory: 'es',
style: true
},
'vant'
]
]
module.exports = {
presets: [['@vue/cli-plugin-babel/preset', { useBuiltIns: 'usage', corejs: 3 }]],
plugins
}
- 在src文件夹下面创建一个plugins文件夹,然后下面创建一个index.js文件,里面统一管理我们需要用的组件
// 按需全局引入 vant组件
import Vue from 'vue'
import { Button, List, Cell, Tabbar, TabbarItem } from 'vant'
Vue.use(Button)
Vue.use(Cell)
Vue.use(List)
Vue.use(Tabbar).use(TabbarItem)
- 然后在main.js全局文件里面引入,抛出,在全局就可以用啦
import './plugins/vant.js'
后台管理系统
注意:必须要在搭建项目以后,别的什么都不操作,都还没有操作的情况下,才能使用,否则安装完别的依赖,在安装下面这个指令的话,就用不了
vue add element
- 在src文件夹下面创建一个plugins文件夹,然后下面创建一个index.js文件,里面统一管理我们需要用的组件
import Vue from 'vue'
import {
Pagination,
Dialog,
Autocomplete,
Dropdown,
DropdownMenu,
DropdownItem,
Menu,
Submenu,
MenuItem,
MenuItemGroup,
Input,
InputNumber,
Radio,
RadioGroup,
RadioButton,
Checkbox,
CheckboxButton,
CheckboxGroup,
Switch,
Select,
Option,
OptionGroup,
Button,
ButtonGroup,
Table,
TableColumn,
DatePicker,
TimeSelect,
TimePicker,
Popover,
Tooltip,
Breadcrumb,
BreadcrumbItem,
Form,
FormItem,
Tabs,
TabPane,
Tag,
Tree,
Alert,
Slider,
Icon,
Row,
Col,
Upload,
Progress,
Badge,
Card,
Rate,
Steps,
Step,
Carousel,
CarouselItem,
Collapse,
CollapseItem,
Cascader,
ColorPicker,
Transfer,
Container,
Header,
Aside,
Main,
Footer,
Loading,
MessageBox,
Message,
Notification
} from 'element-ui'
Vue.use(Pagination)
Vue.use(Dialog)
Vue.use(Autocomplete)
Vue.use(Dropdown)
Vue.use(DropdownMenu)
Vue.use(DropdownItem)
Vue.use(Menu)
Vue.use(Submenu)
Vue.use(MenuItem)
Vue.use(MenuItemGroup)
Vue.use(Input)
Vue.use(InputNumber)
Vue.use(Radio)
Vue.use(RadioGroup)
Vue.use(RadioButton)
Vue.use(Checkbox)
Vue.use(CheckboxButton)
Vue.use(CheckboxGroup)
Vue.use(Switch)
Vue.use(Select)
Vue.use(Option)
Vue.use(OptionGroup)
Vue.use(Button)
Vue.use(ButtonGroup)
Vue.use(Table)
Vue.use(TableColumn)
Vue.use(DatePicker)
Vue.use(TimeSelect)
Vue.use(TimePicker)
Vue.use(Popover)
Vue.use(Tooltip)
Vue.use(Breadcrumb)
Vue.use(BreadcrumbItem)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Tabs)
Vue.use(TabPane)
Vue.use(Tag)
Vue.use(Tree)
Vue.use(Alert)
Vue.use(Slider)
Vue.use(Icon)
Vue.use(Row)
Vue.use(Col)
Vue.use(Upload)
Vue.use(Progress)
Vue.use(Badge)
Vue.use(Card)
Vue.use(Rate)
Vue.use(Steps)
Vue.use(Step)
Vue.use(Carousel)
Vue.use(CarouselItem)
Vue.use(Collapse)
Vue.use(CollapseItem)
Vue.use(Cascader)
Vue.use(ColorPicker)
Vue.use(Container)
Vue.use(Header)
Vue.use(Aside)
Vue.use(Main)
Vue.use(Footer)
Vue.use(Loading.directive)
Vue.prototype.$loading = Loading.service
Vue.prototype.$msgbox = MessageBox
Vue.prototype.$alert = MessageBox.alert
Vue.prototype.$confirm = MessageBox.confirm
Vue.prototype.$prompt = MessageBox.prompt
Vue.prototype.$notify = Notification
Vue.prototype.$message = Message
- 最后也是在main.js全局文件里面引入,然后抛出就可以啦
import './plugins/element.js'
7.路由封装
前言:大家都知道,我们在搭建好项目以后,src文件夹下面会生成一个router这个文件夹,然后下面有一个index.js这个文件,这个里面就是我们做项目需要放路由的地方,如果我们的项目比较大,跳转的页面比较多的话,把全部的路由都放在这一个文件里面,后期优化不好优化,在做项目的时候需要找路由的时候也不好找,或者说在后期我们需要改路由的时候也不好改,所以我们就选择路由封装是最好不过的啦。。。注意:如果项目比较小,跳转的页面不是很多的情况下,就没必要封装路由
下面说下路由封装的思路,这个很简单,大家一看就会
- 我一般在做项目时候,遇到跳转页面多的时候呢,我会把路由分成几个文件来封装,比如现在我做的这个后台管理系统,有10个页面,每个页面里面肯定有很多需要跳转的页面,这时候我会在router这个文件夹下面创建10个文件,然后每个文件对应一个页面,比如现在首页的这个页面,就在router这个文件夹下面创建一个home.js文件,然后把首页需要跳转的的路由全部写在这个文件里面,然后把他引入到index.js这个文件里面就可以啦,这样呢,在做项目,或者以后优化的时候就比较好找,如果以后我们要优化项目,需要优化首页的时候,在我们需要优化首页这个页面路由的时候,我们就可以去home.js这个文件里面去找就可以啦
8.封装组件
前言:大家都知道,vue项目都是单页面,都是组件拼接起来的,如果说我们在做中大型项目的时候,页面比较多,如果我们把这些页面都写在一个文件里面,就比较麻烦,修改的时候、找的时候、或者在优化项目的时候找起来比较麻烦,所以我们就把页面分成一个一个的组件,封装起来,然后引入到渲染的页面,这样的话,在我们写项目、修改。以后优化项目的时候,就比较方便
封装组件的话分为两种
- 一个是页面组件的封装
- 一个是公共组件的封装
9.代码优化:节流防抖,函数防抖,图片懒加载,keep-alive缓存不活动的组件
10.打包上线