github地址
element版本传送门
空框架(含各类demo)打包gzip之后仅为28kb(包含换肤功能,element版本仅17kb)
+ 配置全局cdn,包含js、css
+ 开启Gzip压缩,包含文件js、css
+ 去除生产环境console
+ 本地代理
+ 配置别名
+ 配置环境变量开发模式、测试模式、生产模式
+ 开启分析打包日志
+ 配置打包版本/环境/时间(控制台打印)
// 安装依赖
npm install
// 本地启动
npm run dev
// 测试打包
npm run build:test
// 生产打包
npm run build:pro
├─assets # 静态资源
│ └─img
├─components # 组件库
│ └─global # 全局组件 <放入后不需要注册也不需要引入>
├─decorator # 装饰器
├─filter # 过滤器
├─http # 网络请求
├─layouts # layout文件
├─plugins # 插件库 如需要引入第三方组件等 <推荐使用cdn>
├─router # 路由
├─store # vuex
│ └─modules # vuex 模块
├─style # 样式
│ ├─css # css 文件
│ └─sass # sass 工具库样式
├─utils # 工具库目录
└─views # 页面文件
window.IsDingBrowser
window.$SWT 全局vue 可在控制台调试使用哦
Vue.prototype.$apis 全局api 已经挂载
await this.$api.demo(params)
axios 拦截器 所有网络请求 理论上都走过滤 interceptor.js
提供的方法如下 请求都会经过 JSON.parse(JSON.stringify(obj))
网络请求的数据 都会调用 调用 toJSON 原因请看 项目插件介绍
网络拦截器可调用的方法 请在 api.js 中使用 不要在外部使用
get
post
binary post 上传文件 (二进制文件)
form post 表单
put 上传文件
download 下载文件
temp 临时post 拼接URL **调试后请联系后端删除**
请求格式如下
{
"success":true,
"result":null
}
拦截器配置 (http/request.js)
// 拦截器 请求头添加token
http.interceptors.request.use(
config => {
const token = session.getSession('token')
if (token) config.headers.token = token.replace(/"/g, '')
return config
},
error => {
throw new Error(JSON.stringify(error))
}
)
// 拦截器 相应头自动处理各类错误code,未枚举code直接toast报错提示
http.interceptors.response.use(
response => {
const res = response.data
if (!res.success) { // 逻辑错误 自定义错误码
if (res.errorMsg) {
ErrorMessage(res.errorMsg)
}
globalError('接口调用失败')
switch (res.errorCode) {
case 401:
case 403:
case 405:
case '-3':
// 登录失效的code,自动执行重新登录操作
reLogin()
break
default:
break
}
throw new Error(JSON.stringify(res))
}
return res.result
},
error => {
// http error
globalError(new Date(), 'err' + error) // for debug reject
ErrorMessage(ERROR_MSG[error?.response?.status] || `连接出错(${error.response.status})!`) // 消抖
throw new Error(JSON.stringify(error))
}
)
// 跳转登录 并记忆当前路由参数 登录后可跳回原页面
function reLogin () {
session.destroy('token')
const { hash, name, path, params, query } = window.vue.$route
session.setSession('backRoute', { hash, name, path, params, query })
router.push({ name: 'login' })
}
只有 success 为true 你才能获取到 result (也只有result) 不需要判断接口正确与否 success 为true 或http状态码不对则报错终止执行
demo
@loading('someLoading')
@confirm('确定要执行么?')
async demoFunction (){
this.res = await this.$api.demoGet(params)
// xxxxx 剩余逻辑 如果接口出错 都不会执行
}
上面的@loading @confirm 是装饰器
引入了 antdv 所有的组件
// 通过在 main.js 中重写了 moment
// 所以日期选择插件可以不处理 直接上传给后端 如果需要修改格式的 则自己修改属性的toJSON
moment.locale('zh-cn')
moment.fn.toJSON = function () { return this.format('YYYY-MM-DD') }
antdv 原型的注入 但是不推荐使用原型的方法调用
Vue.prototype.$message = antd.message
Vue.prototype.$notification = antd.notification
Vue.prototype.$confirm = antd.Modal.confirm
关于 $message 在utils/antdvUtils.js 中有全局的防抖
// 成功的提示
export const SuccessMessage = debounce((msg) => { antd.message.success(msg) }, 1500, {
leading: true,
trailing: false
})
// 警告提示
export const WarningMessage = debounce((msg) => { antd.message.warning(msg) }, 1500, {
leading: true,
trailing: false
})
// 失败提示
export const ErrorMessage = debounce((msg) => { antd.message.error(msg) }, 1500, {
leading: true,
trailing: false
})
关于 $confirm 则推荐使用装饰器
此工具库应与业务尽量解耦
编写文件时 应维护声明文件 (.d.ts)
+ 此文件应该为纯函数组件
+ 此文件导出的函数应该在任意环境下通用
+ 此文件导出的函数 尽量遵循数据不可变原则 不改变source data 而是返回一个新的 data
+ 此文件是正则表达式类库
+ 此文件内正则应可维护 (业务正则可直接写入代码 不具备可重用性)
+ 此文件内除导出正则外也应导出一份校验函数
+ 此文件为你添加鉴权参数仅此而已
+ 此文件是ant design for vue 二次封装函数库
+ 修改此函数库 应保证不影响现有代码
+ 具体说明请看elementUtils.d.ts
+ 配置全局cdn,包含js、css
+ 开启Gzip压缩,包含文件js、css
+ 去除生产环境console
+ 本地代理
+ 配置别名
+ 配置环境变量开发模式、测试模式、生产模式
+ 开启分析打包日志
+ 配置打包版本/环境/时间(控制台打印)
// cdn预加载使用
const externals = {
vue: 'Vue',
moment: 'moment',
'vue-router': 'VueRouter',
vuex: 'Vuex',
vant: 'vant',
axios: 'axios',
'lodash-es': '_',
'dingtalk-jsapi': 'dd'
}
// 基础注入
const cdn = { // 将会注入index.html js 顺序不可乱 注意版本
css: [
],
js: [
'https://g.alicdn.com/dingding/dingtalk-jsapi/2.8.33/dingtalk.open.js',
'https://xfw-bscnym-test.oss-cn-hangzhou.aliyuncs.com/static/js/vue.min.js',
'https://xfw-bscnym-test.oss-cn-hangzhou.aliyuncs.com/static/js/vue-router.min.js',
'https://xfw-bscnym-test.oss-cn-hangzhou.aliyuncs.com/static/js/vuex.min.js',
'https://xfw-bscnym-test.oss-cn-hangzhou.aliyuncs.com/static/js/axios.min.js',
'https://xfw-bscnym-test.oss-cn-hangzhou.aliyuncs.com/static/js/moment.min.js',
'https://xfw-bscnym-test.oss-cn-hangzhou.aliyuncs.com/static/js/moment-zh-cn.js',
'https://xfw-bscnym-test.oss-cn-hangzhou.aliyuncs.com/static/js/lodash.min.js',
'https://cdn.jsdelivr.net/npm/[email protected]/lib/vant.min.js'
]
}
// 预加载 依赖变量
config.merge({
externals: externals
})
// DNS预解析 提升加载效率
<link rel="dns-prefetch" href="xfw-bscnym-test.oss-cn-hangzhou.aliyuncs.com" />
// 动态加载 cdn 静态资源
<% if (htmlWebpackPlugin.options.buildInfo.env === 'production') { %>
<% for (let link of htmlWebpackPlugin.options.cdn.css) { %>
<link rel="stylesheet" href="<%= link %>"><% } %>
<% for (let src of htmlWebpackPlugin.options.cdn.js) { %><script src="<%= src %>">script><% } %>
<% } %>
// 默认开启gzip
config
.plugin('CompressionPlugin')
.use(CompressionPlugin)
.end()
config.optimization.minimizer('terser').tap((args) => {
args[0].terserOptions.compress.drop_console = true // 移除 console.log
return args
})
devServer: {
open: false, // 自动启动浏览器
host: '0.0.0.0', // localhost
port: 8080, // 端口号
https: false,
hotOnly: false, // 热更新
proxy: {
'^/sso': {
target: process.env.VUE_APP_SSO, // 重写路径
ws: false, //开启WebSocket
secure: false, // 如果是https接口,需要配置这个参数
changeOrigin: true
}
}
}
config.resolve.alias
.set('@', resolve('src'))
在根目录新建
NODE_ENV=development
NODE_ENV=production
NODE_ENV=production //如果我们在.env.test文件中把NODE_ENV设置为test的话,那么打包出来的目录结构是有差异的
安装cnpm i webpack-bundle-analyzer -D
chainWebpack: config => {
config
.plugin('webpack-bundle-analyzer')
.use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
}
const moment = require('moment')
process.env.VUE_APP_Version = require('./package.json').version
const buildInfo = {
buildTime: moment().format('YYYY-MM-DD HH:mm:ss'),
appVersion: require('./package.json').version,
env: process.env.NODE_ENV
}
<script>
function consoleTag(key, value) {
var baseStyle = 'font-family: sans-serif;font-weight: bold;font-size: 12px;padding:5px;';
var keyStyle = "color:#fff;background:#000;" + baseStyle + ";border-radius:4px 0 0 4px";
var valueStyle = "color:#000;background:#FF9900;" + baseStyle + ";border-radius:0 4px 4px 0";
globalLog("%c " + key + "%c" + value + " ", keyStyle, valueStyle);
}
consoleTag('Environment', '<%= htmlWebpackPlugin.options.buildInfo.env %>')
consoleTag('Version', '<%= htmlWebpackPlugin.options.buildInfo.appVersion %>')
consoleTag('Build Date', '<%= htmlWebpackPlugin.options.buildInfo.buildTime %>')
script>