基于vue-cli3.0构建功能完善的移动端架子,主要功能包括
- webpack 打包扩展
- css:sass支持、normalize.css、_mixin.scss、_variables.scss
- vw、rem布局
- 跨域设置
- eslint设置
- cdn引入
- 路由设计、登录拦截
- axios、api 设计
- vuex状态管理
项目地址: vue-cli3-H5
demo地址: zhouyupeng.github.io/vuecli3H5/#…
webpack 打包扩展
vue-cli3.*后目录结构大改,去除了以往的build,config文件夹,要实现配置的改动在根目录下增加vue.config.js进行配置
css:sass支持、normalize.css、_mixin.scss、_variables.scss
使用的css预处理器是sass,对于css mixin,变量这里做了全局引入,并且引入normalize.css 使HTML元素样式在跨浏览器上表现得的高度一致性 vue.config.js配置
css: {// 是否使用css分离插件 ExtractTextPluginextract:isProduction ? true:false,// 开启 CSS source maps?sourceMap: false,// css预设器配置项// 启用 CSS modules for all css / pre-processor files.modules: false,sass: {data: '@import "style/_mixin.scss";@import "style/_variables.scss";' // 全局引入}}}
复制代码
vw、rem布局
对于移动端适配方案使用的是网易新闻的方法, 使用vw + rem布局
/**
750px设计稿取1rem=100px为参照,那么html元素的宽度就可以设置为width: 7.5rem,于是html的font-size=deviceWidth / 7.5
**/
html {font-size: 13.33333vw
}@media screen and (max-width: 320px) {html {font-size: 42.667PX;font-size: 13.33333vw}
}@media screen and (min-width: 321px) and (max-width:360px) {html {font-size: 48PX;font-size: 13.33333vw}
}@media screen and (min-width: 361px) and (max-width:375px) {html {font-size: 50PX;font-size: 13.33333vw}
}@media screen and (min-width: 376px) and (max-width:393px) {html {font-size: 52.4PX;font-size: 13.33333vw}
}@media screen and (min-width: 394px) and (max-width:412px) {html {font-size: 54.93PX;font-size: 13.33333vw}
}@media screen and (min-width: 413px) and (max-width:414px) {html {font-size: 55.2PX;font-size: 13.33333vw}
}@media screen and (min-width: 415px) and (max-width:480px) {html {font-size: 64PX;font-size: 13.33333vw}
}@media screen and (min-width: 481px) and (max-width:540px) {html {font-size: 72PX;font-size: 13.33333vw}
}@media screen and (min-width: 541px) and (max-width:640px) {html {font-size: 85.33PX;font-size: 13.33333vw}
}@media screen and (min-width: 641px) and (max-width:720px) {html {font-size: 96PX;font-size: 13.33333vw}
}@media screen and (min-width: 721px) and (max-width:768px) {html {font-size: 102.4PX;font-size: 13.33333vw}
}@media screen and (min-width: 769px) {html {font-size: 102.4PX;font-size: 13.33333vw}
}@media screen and (min-width: 769px) {html {font-size: 102.4PX;#app {margin: 0 auto}}}
复制代码
vue.config.js配置
loaderOptions: {postcss: {// 这是rem适配的配置plugins: [require('postcss-px2rem')({remUnit: 100})]}
}
复制代码
开发时跨域设置
devServer: {open: true, // 启动服务后是否打开浏览器host: '127.0.0.1',port: 8088, // 服务端口https: false,hotOnly: false,proxy: 'https://easy-mock.com/' // 设置代理}
复制代码
配置完后,本地开发环境的axios的baseUrl要写为 '' ,即空字符串。 发布到线上时如果前端代码不是和后台api放在同源下的,后台还需做跨域处理,
eslint standard设置
使用的是JavaScript standard 代码规范,一个好的编码风格它可以帮助减少团队之间的摩擦,代码阅读起来也更加清爽,更加可读性,不要觉得烦,用了都说好。 这是 JavaScript standard 代码规范的全文
自定义配置,在.eslintrc.js里修改,这里是我给出的配置,4个空格缩进,不检查结尾分号,关闭单var 声明,可自行配置
rules: {'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off','no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',indent: ['error',4,{SwitchCase: 1}],semi: 0, // 不检查结尾分号,// 强制使用单引号quotes: ['error', 'single'],// 关闭函数名与后面括号间必须空格规则'space-before-function-paren': 0,// 关闭var 声明,每个声明占一行规则。'one-var': 0}
复制代码
cdn引入
对于 vue、vue-router、vuex、axios等等这些不经常改动的库、我们让webpack不对他们进行打包,通过cdn引入,可以减少代码的大小、也可以减少服务器的带宽 这里使用的是360的cdn,附上一份公共cdn评测文章 点我
vue.config.js配置
const externals = {vue: 'Vue','vue-router': 'VueRouter',vuex: 'Vuex','mint-ui': 'MINT',axios: 'axios'}const cdn = {// 开发环境dev: {css: ['https://lib.baomitu.com/mint-ui/2.2.13/style.min.css'],js: []},// 生产环境build: {css: ['https://lib.baomitu.com/mint-ui/2.2.13/style.min.css'],js: ['https://lib.baomitu.com/vue/2.6.6/vue.min.js','https://lib.baomitu.com/vue-router/3.0.1/vue-router.min.js','https://lib.baomitu.com/vuex/3.0.1/vuex.min.js','https://lib.baomitu.com/axios/0.18.0/axios.min.js','https://lib.baomitu.com/mint-ui/2.2.13/index.js']}
}configureWebpack: config => {if (isProduction) {// externals里的模块不打包Object.assign(config, {externals: externals})} else {// 为开发环境修改配置...}},
chainWebpack: config => {// 对vue-cli内部的 webpack 配置进行更细粒度的修改。// 添加CDN参数到htmlWebpackPlugin配置中, 详见public/index.html 修改config.plugin('html').tap(args => {if (process.env.NODE_ENV === 'production') {args[0].cdn = cdn.build}if (process.env.NODE_ENV === 'development') {args[0].cdn = cdn.dev}return args})
}
复制代码
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><!-- DNS预解析 --><link rel="dns-prefetch" href="//lib.baomitu.com" /><meta name="viewport"content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=0,minimal-ui,viewport-fit=cover" /><link rel="icon" href="<%= BASE_URL %>favicon.ico" /><!-- 使用CDN加速的CSS文件,配置在vue.config.js下 --><% for (var i inhtmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css) { %><link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style" /><link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" /><% } %><title>vuedemo</title>
</head><body><noscript><strong>We're sorry but vuedemo doesn't work properly without JavaScriptenabled. Please enable it to continue.</strong></noscript><div id="app"></div><!-- 使用CDN加速的JS文件,配置在vue.config.js下 --><% for (var i inhtmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %><script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script><% } %><!-- built files will be auto injected -->
</body></html>复制代码
路由设计、登录拦截
const router = new Router({routes: [{path: '/',name: 'home',component: Home,meta: {auth: false, // 是否需要登录keepAlive: true // 是否缓存组件}},{path: '/about',name: 'about',component: () =>import(/* webpackChunkName: "about" */ './views/About.vue'),meta: {auth: true,keepAlive: true}},{path: '/login',name: 'login',component: () =>import(/* webpackChunkName: "login" */ './views/login.vue'),meta: {auth: false,keepAlive: true}},{path: '*', // 未匹配到路由时重定向redirect: '/',meta: {// auth: true,// keepAlive: true}}]
})// 全局路由钩子函数 对全局有效
router.beforeEach((to, from, next) => {let auth = to.meta.authlet token = store.getters['login/token'];if (auth) { // 需要登录if (token) {next()} else {next({path: '/login',query: {redirect: to.fullPath}})}} else {next()}
})复制代码
在meta中设置是否需要登录以及是否缓存当前组件, 在router.beforeEac路由钩子函数中对登录权限判断,没有登录的跳到登录页面,并且把当前页面传过去,登录后跳回这个页面。
对于页面缓存的在app.vue里进行处理
<keep-alive><router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
复制代码
axios、api 设计
对于axios的设计主要是请求拦截器, respone拦截器,以及get,post的二次封装
axios.defaults.timeout = 12000 // 请求超时时间
axios.defaults.baseURL = process.env.VUE_APP_BASE_APIaxios.defaults.headers.post['Content-Type'] ='application/x-www-form-urlencoded;charset=UTF-8' // post请求头的设置
// axios 请求拦截器
axios.interceptors.request.use(config => {// 可在此设置要发送的tokenlet token = store.getters['login/token'];token && (config.headers.token = token)Indicator.open('数据加载中')return config},error => {return Promise.error(error)}
)
// axios respone拦截器
axios.interceptors.response.use(response => {// 如果返回的状态码为200,说明接口请求成功,可以正常拿到数据// 否则的话抛出错误 结合自身业务和后台返回的接口状态约定写respone拦截器Indicator.close()if (response.status === 200 && response.data.code === 0) {return Promise.resolve(response)} else {Toast({message: response.data.msg,position: 'middle',duration: 2000});return Promise.reject(response)}},error => {Indicator.close()const responseCode = error.response.statusswitch (responseCode) {// 401:未登录case 401:break// 404请求不存在case 404:Toast({message: '网络请求不存在',position: 'middle',duration: 2000});breakdefault:Toast({message: error.response.data.message,position: 'middle',duration: 2000});}return Promise.reject(error.response)}
)
/*** 封装get方法,对应get请求* @param {String} url [请求的url地址]* @param {Object} params [请求时携带的参数]*/
function get (url, params = {}) {return new Promise((resolve, reject) => {axios.get(url, {params: params}).then(res => {resolve(res.data)}).catch(err => {reject(err.data)})})// 或者return axios.get();
}
/*** post方法,对应post请求* @param {String} url [请求的url地址]* @param {Object} params [请求时携带的参数]*/
function post (url, params) {return new Promise((resolve, reject) => {axios.post(url, qs.stringify(params)).then(res => {resolve(res.data)}).catch(err => {reject(err.data)})})// 或者return axios.post();
}复制代码
为了方便管理api路径,这里把所以请求都放在了api文件夹下,如
import { get, post } from '@/axios/http.js'
function getIndex (params) {return get('/mock/5cb48c7ed491cd741c54456f/base/index', params)
}
function login(params) {return post('/mock/5cb48c7ed491cd741c54456f/base/login', params)
}
export {getIndex,login
}复制代码
其他
去除console.log
装uglifyjs-webpack-plugin插件
// 上线压缩去除console等信息
config.plugins.push(new UglifyJsPlugin({uglifyOptions: {compress: {warnings: false,drop_console: true,drop_debugger: false,pure_funcs: ['console.log'] // 移除console}},sourceMap: false,parallel: true})
)
复制代码
设置alias目录别名
在项目中经常会引用各个地方的文件,配置后可以更加方便的引入了
config.resolve.alias.set('assets', '@/assets').set('components', '@/components').set('view', '@/view').set('style', '@/style').set('api', '@/api').set('store', '@/store')
复制代码
环境变量和模式
在一个产品的前端开发过程中,一般来说会经历本地开发、测试脚本、开发自测、测试环境、预上线环境,然后才能正式的发布。对应每一个环境可能都会有所差异,比如说服务器地址、接口地址、websorket地址…… 等等。在各个环境切换的时候,就需要不同的配置参数,所以就可以用环境变量和模式,来方便我们管理。
.env # 在所有的环境中被载入
.env.local # 在所有的环境中被载入,但会被 git 忽略
.env.[mode] # 只在指定的模式中被载入
.env.[mode].local # 只在指定的模式中被载入,但会被 git 忽略
复制代码
自定义的变量VUE_APP_开头,两个特殊的变量:
- NODE_ENV - 会是 "development"、"production" 或 "test" 中的一个。具体的值取决于应用运行的模式。
- BASE_URL - 会和 vue.config.js 中的 baseUrl 选项相符,即你的应用会部署到的基础路径。
如我们定义的.env
NODE_ENV = 'development'
BASE_URL = '/'
VUE_APP_BASE_API = ''
复制代码
.env.production
NODE_ENV = 'production'
BASE_URL = './'
VUE_APP_BASE_API = 'https://easy-mock.com/'
复制代码
在项目中可以用process.env.VUE_APP_*,如process.env.VUE_APP_BASE_API获取到定义的值
全局引入filter
把多个地方用到的过滤器写在一个js里面,复用代码。
// 过滤日期格式,传入时间戳,根据参数返回不同格式
const formatTimer = function(val, hours) {if (val) {var dateTimer = new Date(val * 1000)var y = dateTimer.getFullYear()var M = dateTimer.getMonth() + 1var d = dateTimer.getDate()var h = dateTimer.getHours()var m = dateTimer.getMinutes()M = M >= 10 ? M : '0' + Md = d >= 10 ? d : '0' + dh = h >= 10 ? h : '0' + hm = m >= 10 ? m : '0' + mif (hours) {return y + '-' + M + '-' + d + ' ' + h + ':' + m} else {return y + '-' + M + '-' + d}}
}
export default {formatTimer
}复制代码
main.js引入
import filters from './filters/index'
// 注入全局过滤器
Object.keys(filters).forEach(item => {Vue.filter(item, filters[item])
})
复制代码
使用
{{1555851774 | formatTimer()}}
复制代码
vue中使用mock.js
查看我以前写的文章点击我
wepback的可视化资源分析工具插件---webpack-bundle-analyzer
用来分析哪些模块引入了哪些代码,进行有目的性的优化代码
在打包环境中加,使用命令npm run build --report
if (process.env.npm_config_report) {config.plugins.push(new BundleAnalyzerPlugin())
}
复制代码
代码地址
项目地址: vue-cli3-H5
demo地址: zhouyupeng.github.io/vuecli3H5/#…