
vipbic 是一个专注前端开发、网址导航、社区讨论综合网站,该网站使用前后端分离,运用 vue-cli3 本项目配置
cnpm install npm run dev npm run test npm run publish npm run build ├── public 静态模板资源文件 ├── src 项目文件 ├──|── assets 静态文件 img 、css 、js ├──|── components 全局组件 ├──|── http 请求配置 ├──|── layout 布局文件 ├──|── mock 测试数据 ├──|── modules 放置动态是添加路由的页面 ├──|── plugin 插件 ├──|── router 路由 ├──|── store vuex 数据管理 ├──|── utils 工具文件 ├──|── view 页面文件 ├──|── App.vue ├──|── main.js ├── .env.development 开发模式配置 ├── .env.production 正式发布模式配置 ├── .env.test 测试模式配置 ├── entrance.js 入口文件 ├── vue.config.js config 配置文件 <% for (var i in htmlWebpackPlugin.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" /> <% } %> <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %> <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script> <% } %> // cdn 预加载使用 const externals = { 'vue': 'Vue', 'vue-router': 'VueRouter' } const cdn = { // 开发环境 dev: { css: [ 'https://unpkg.com/element-ui/lib/theme-chalk/index.css' ], js: [] }, // 生产环境 build: { css: [ 'https://unpkg.com/element-ui/lib/theme-chalk/index.css' ], js: [ 'https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js', 'https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-router.min.js' ] } } chainWebpack: cOnfig=> { 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 }) } new CompressionWebpackPlugin({ algorithm: 'gzip', test: /\.(js|css)$/, // 匹配文件名 threshold: 10000, // 对超过 10k 的数据压缩 deleteOriginalAssets: false, // 不删除源文件 minRatio: 0.8 // 压缩比 }) 安装cnpm i uglifyjs-webpack-plugin -D
const UglifyJsPlugin = require('uglifyjs-webpack-plugin') new UglifyJsPlugin({ uglifyOptions: { output: { comments: false, // 去掉注释 }, warnings: false, compress: { drop_console: true, drop_debugger: false, pure_funcs: ['console.log'] //移除 console } } }) chainWebpack: cOnfig=> { // 压缩图片 config.module .rule('images') .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/) .use('image-webpack-loader') .loader('image-webpack-loader') .options({ bypassOnDebug: true }) } devServer: { open: false, // 自动启动浏览器 host: '0.0.0.0', // localhost port: 6060, // 端口号 https: false, hotOnly: false, // 热更新 proxy: { '^/sso': { target: process.env.VUE_APP_SSO, // 重写路径 ws: true, //开启 WebSocket secure: false, // 如果是 https 接口,需要配置这个参数 changeOrigin: true } } } 在 vscode 中插件安装栏搜索 Path Intellisense 插件,打开 settings.json 文件添加 以下代码 "@": "${workspaceRoot}/src",安以下添加
{ "workbench.iconTheme": "material-icon-theme", "editor.fontSize": 16, "editor.detectIndentation": false, "guides.enabled": false, "workbench.colorTheme": "Monokai", "path-intellisense.mappings": { "@": "${workspaceRoot}/src" } } 在项目 package.json 所在同级目录下创建文件 jsconfig.json
{ "compilerOptions": { "target": "ES6", "module": "commonjs", "allowSyntheticDefaultImports": true, "baseUrl": "./", "paths": { "@/*": ["src/*"] } }, "exclude": [ "node_modules" ] } 如果还没看懂的客官请移步在 vscode 中使用别名 @按住 ctrl 也能跳转对应路径
在根目录新建
# 开发环境 NODE_ENV='development' VUE_APP_SSO='http://http://localhost:9080' NODE_ENV = 'production' # 如果我们在.env.test 文件中把 NODE_ENV 设置为 test 的话,那么打包出来的目录结构是有差异的 VUE_APP_MODE = 'test' VUE_APP_SSO='http://http://localhost:9080' outputDir = test NODE_ENV = 'production' VUE_APP_SSO='http://http://localhost:9080' "scripts": { "build": "vue-cli-service build", //生产打包 "lint": "vue-cli-service lint", "dev": "vue-cli-service serve", // 开发模式 "test": "vue-cli-service build --mode test", // 测试打包 "publish": "vue-cli-service build && vue-cli-service build --mode test" // 测试和生产一起打包 } 在router/index.js,核心
router.beforeEach((to, from, next) => { const { hasRoute } = store.state; // hasRoute 设置一个状态,防止重复请求添加路由 if (hasRoute) { next() } else { dynamicRouter(to, from, next, selfaddRoutes) } }) 如果动态添加路由抛警告,路由重复添加,需要添加 router.matcher = new VueRouter().matcher
其中响应拦截
const succeeCode = 1; // 成功 /** * 状态码判断 具体根据当前后台返回业务来定 * @param {*请求状态码} status * @param {*错误信息} err */ const errorHandle = (status, err) => { switch (status) { case 401: vm.$message({ message: '你还未登录', type: 'warning' }); break; case 404: vm.$message({ message: '请求路径不存在', type: 'warning' }); break; default: console.log(err); } } /** * 响应拦截 */ http.interceptors.response.use(respOnse=> { if (response.status === 200) { // 你只需改动的是这个 succeeCode,因为每个项目的后台返回的 code 码各不相同 if (response.data.code === succeeCode) { return Promise.resolve(response); } else { vm.$message({ message: '警告哦,这是一条警告消息', type: 'warning' }); return Promise.reject(response) } } else { return Promise.reject(response) } }, error => { const { response } = error; if (response) { // 请求已发出,但是不在 2xx 的范围 errorHandle(response.status, response.data.msg); return Promise.reject(response); } else { // 处理断网的情况 if (!window.navigator.onLine) { vm.$message({ message: '你的网络已断开,请检查网络', type: 'warning' }); } return Promise.reject(error); } }) 在http/request.js
import http from './src/http/request' Vue.prototype.$http = http; // 使用 this.$http.windPost('url','参数') const Mock = require('mockjs') const produceNewsData = [] Mock.mock('/mock/menu', produceNewsData) Mock 支持随机数据,具体参看官网列子 http://mockjs.com/examples.html
pluginOptions: { // 配置全局 less 'style-resources-loader': { preProcessor: 'less', patterns: [resolve('./src/style/theme.less')] } } 安装cnpm i webpack -D
const { HashedModuleIdsPlugin } = require('webpack'); configureWebpack: cOnfig=> { const plugins = []; plugins.push( new HashedModuleIdsPlugin() ) } 安装cnpm i webpack-bundle-analyzer -D
chainWebpack: cOnfig=> { config .plugin('webpack-bundle-analyzer') .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin) } 安装npm i copy-webpack-plugin -D
const CopyWebpackPlugin = require('copy-webpack-plugin'); configureWebpack: cOnfig=> { const plugins = []; plugins.push( new CopyWebpackPlugin([{ from: './NLwdLAxhwv.txt'}]) ) } from 为文件的路径,还有一个 to 属性是输出的文件夹路径,不写则默认复制到打包后文件的根目录
安装依赖
cnpm install @babel/plugin-proposal-optional-chaining -S 在 babel.config.js 中 的 plugins 中添加 "@babel/plugin-proposal-optional-chaining"
module.exports = { presets: [ '@vue/cli-plugin-babel/preset' ], plugins: [ '@babel/plugin-proposal-optional-chaining' ] } 测试
 const obj = { foo: { bar: { baz: 42, fun: () => { return 666; } } } }; let baz = obj?.foo?.bar?.baz; let fun = obj?.foo?.bar?.fun(); console.log(baz); // 42 console.log(fun) // 666 安装
cnpm i lib-flexible -S cnpm i postcss-pxtorem -D 入口 js
import 'lib-flexible/flexible.js' 如果不需要转 rem,注释即可,也不要引入 flexible.js
css: { loaderOptions: { postcss: { plugins: [ require('postcss-pxtorem')({ rootValue : 75, // 换算的基数 1rem = 75px 这个是根据 750px 设计稿来的,如果是 620 的就写 62 // 忽略转换正则匹配项。插件会转化所有的样式的 px 。比如引入了三方 UI,也会被转化。目前我使用 selectorBlackList 字段,来过滤 //如果个别地方不想转化 px 。可以简单的使用大写的 PX 或 Px 。 selectorBlackList : ['weui','mu'], // propList : ['*'], // 需要做转化处理的属性,如`hight`、`width`、`margin`等,`*`表示全部 }) ] } } } 刷新当前页面适合在只改变了路由的 id 的页面,比如查看详情页面,当路由 id 发生时候,并不会去触发当前页面的钩子函数 查看App.vue
<template> <div class="app"> <router-view v-if="isRouterAlive"></router-view> </div> </template> <script> export default { name: "App", provide() { return { reload: this.reload }; }, data() { return { isRouterAlive: true }; }, methods: { // 重载页面 适合添加数据或者路由 id 改变 reload() { this.isRouterAlive = false; this.$nextTick(()=>{ this.isRouterAlive = true; }); } } }; </script> 然后其它任何想刷新自己的路由页面,都可以这样: this.reload()
具体实例 utils\websocket.js
const WSS_URL = `wss://wss.xxxx.com/ws?appid=xxx` let setIntervalWesocketPush = null export default class websocket { createSocket() { if (!this.Socket) { console.log('建立 websocket 连接') this.Socket = new WebSocket(WSS_URL) this.Socket.Onopen= this.onopenWS this.Socket.Onmessage= this.onmessageWS this.Socket.Onerror= this.onerrorWS this.Socket.Onclose= this.oncloseWS } else { console.log('websocket 已连接') } } /**打开 WS 之后发送心跳 */ onopenWS() { this.sendPing() //发送心跳 } /**连接失败重连 */ onerrorWS() { clearInterval(setIntervalWesocketPush) this.Socket.close() createSocket() //重连 } /**WS 数据接收统一处理 */ onmessageWS(res) { console.log(res) } /** * 发送数据 * readyState = 3 连接已经关闭或者根本没有建立 * readyState = 2 连接正在进行关闭握手,即将关闭 * readyState = 1 连接成功建立,可以进行通信 * readyState = 0 正在建立连接,连接还没有完成 * @param {*发送内容} content */ sendWSPush(content) { if (this.Socket !== null && this.Socket.readyState === 3) { this.Socket.close() this.createSocket() //重连 } else if (this.Socket.readyState === 1) { this.Socket.send(content) } else if (this.Socket.readyState === 0) { setTimeout(() => { this.Socket.send(content) }, 5000) } } /**关闭 WS */ oncloseWS() { clearInterval(setIntervalWesocketPush) console.log('websocket 已断开') } /**发送心跳 */ sendPing() { this.Socket.send('ping') setIntervalWesocketPush = setInterval(() => { this.Socket.send('ping') }, 5000) } } import Vue from 'vue'; const has = { inserted: function (el, binding) { // 添加指令 传入的 value if (!binding.value) { el.parentNode.removeChild(el); } } } Vue.directive('has',has)  <el-button type="primary" v-has="true">主要按钮 1</el-button> 
上github提问
|  |      1xnode      2020-05-12 14:04:31 +08:00 收藏一下下班慢慢看 | 
|  |      3yamedie      2020-05-12 14:13:44 +08:00 收藏学习了, 这套配置 vue-cli4 同样适用吧? 另外有个奇怪的命名 succeeCode | 
|  |      4Ritter      2020-05-12 14:17:01 +08:00 good | 
|  |      5bonnenuit OP @yamedie 这个是后端返回的成功的 codo 码,比如后端返回{code:1,data:'数据',msg:'请求成功'} | 
|  |      8icharm      2020-05-12 16:53:43 +08:00 使用 vue-cli 新建了一个默认的项目,页面有这个标签 <noscript> <strong>We're sorry but default doesn't work properly without Javascript enabled. Please enable it to continue.</strong> </noscript> 莫名奇怪的 | 
|  |      10yazoox      2020-05-12 17:14:24 +08:00 via Android 很棒啊! 楼主,有没有 reactjs(+redux/saga, typescript) 的? | 
|  |      11feiandxs      2020-05-12 17:14:51 +08:00 脚手架多多益善 但每个人最后都还是搞了自己的一份 哈哈哈哈 | 
|      12jjianwen68      2020-05-12 17:16:25 +08:00 前端变的好复杂 | 
|      13yukiloh      2020-05-12 17:23:10 +08:00 vue-cli 网上很多资料也不写版本,2 和 3+又是不同的配置文件 我临时客串开发的时候,静态资源特别头疼,开发时候写../assets/xxx 到最后项目部署了我都不知道咋分离,只能全局替换../assets/→https://xxxx/assets 还有 publicPath: './',到底是 /还是./完全懵逼,我用 nginx 代理为 /abc 到该项目,但他根路径还是要写成./ | 
|  |      14WangXiaoyu1996      2020-05-12 17:41:59 +08:00 @icharm 这个似乎是禁用浏览器的 js 之后会展示文本 | 
|  |      15icanfork      2020-05-12 17:47:33 +08:00 | 
|      16oasis2008f      2020-05-12 17:57:06 +08:00 @belin520 哈哈 好多人可能都没用过不能运行 JS 的浏览器了 | 
|  |      17icanfork      2020-05-12 18:05:10 +08:00 @oasis2008f #16 不过好像不碍事,我遇到一些在北京大城市,拿着很高工资前端,不知道什么是同步异步,问 Vue 怎么引入一个外部 JS (其实就是 HTML 里面插入<script>最传统的方式)。但是完成工作内容是完全没有问题的。 | 
|  |      18Tlin      2020-05-12 18:37:15 +08:00 不错不错啊,下一秒就是我的了 哈哈 | 
|  |      21bonnenuit OP @jjianwen68 同感 | 
|  |      23bonnenuit OP  1 @yukiloh publicPath 它是在静态资源路径前面加上路径的值,publicPath:'https://www.baidu.com/', 打包完后 https://www.baidu.com/assets/xxx.js | 
|  |      24a4854857      2020-05-12 20:04:46 +08:00  1 不错的模板.收藏了 | 
|  |      29vipbic      2020-05-13 22:58:39 +08:00 很不错的一个 vue 脚手架模板 |