Vue项目常见问题

iphoneX等刘海手机,页面切换时闪动

做页面间切换动画时,会给切换中的页面添加样式position: absolute
此时如果不给父标签添加 position:relative的话,会出现页面切换时页面大小变化造成的闪动问题,添加该样式即可

iphone手机的橡皮筋效果

iphone在上拉或者下拉时,会有可能漏出底部的背景

屏蔽此现象的思路:
屏蔽bodytouchmove事件,需要滚动的地方用js模拟滚动

// app.vue export default { mounted() { document.body.addEventListener('touchmove', function (e) { if (!e.isScroll) { e.preventDefault() // 阻止默认的处理方式(阻止下拉滑动的效果) } }, {passive: false}) // passive 参数不能省略,用来兼容ios和android } } // 自定义一个指令 v-scroll // src/directives/scroll.js import Vue from 'vue' Vue.directive('scroll', { inserted: function (el, binding) { let lastY // 最后一次y坐标点 el.addEventListener('touchstart', e => { lastY = e.touches[0].pageY // 点击屏幕时记录最后一次Y度坐标 }) el.addEventListener('touchmove', e => { e.isScroll = true var y = e.touches[0].pageY var top = el.scrollTop // 对象最顶端和窗口最顶端之间的距离 0-93 var scrollH = el.scrollHeight // 含滚动内容的元素大小 276 内容高度 var offsetH = el.offsetHeight // 网页可见区域高 183 显示高度 var cScroll = top + offsetH // 当前滚动的距离 if (y >= lastY && top <= 10) { // 如果滚动条高度小于0,可以理解为到顶了,且是下拉情况下,阻止touchmove事件 e.preventDefault() } if (y <= lastY && cScroll >= scrollH) { e.preventDefault() } lastY = y }) } }) // 引入自定义指令 // src/main.js import './directives/scroll.js'

Vue组件中需要滚动的标签,使用自定义指令v-scroll

<div v-scroll> ....... </div>

判断是否是返回

判断进入的页面前进的还是返回的

思路

  • 不使用vue-router提供的方法this.$router.back()
  • 给路由的原型定义一个方法goBack(),在需要返回的地方调用该方法this.$router.goBack()
  • 该方法中设置isBack: true
  • 在需要判断的地方,获取this.$router.isBack即可(获取完后设置回去 this.$router.isBack = false
// src/router/index.js Router.prototype.goBack = function() { this.isBack = true window.history.go(-1) }

返回上一页,回退问题

如果没有上一页,默认是页面不变
需求:如果没有上一页,跳转到首页

思路:
给第一个页面添加一个路由参数goindex=true
在返回的地方判断路由中是否有goindex=true,如果有,进入首页this.$router.push('/recommend/focus'),如果没有或者值不对,返回上一个页面this.$router.back()

// app.vue export default { data() { flagFirst: true }, updated() { if (this.flagFirst) { this.goIndex() } }, methods: { goIndex() { this.flagFirst = false if (location.href.indexOf('?') === -1) { window.location.href = location.href + '?goindex=true' } else if (location.href.indexOf('?') !== -1 && location.href.indexOf('goindex') === -1) { window.location.href = location.href + '&goindex=true' } } } } // src/router/index.js Router.prototype.goBack = function() { this.isBack = true if (this.history.current.query.goindex === 'true') { this.push('/recommend/focus') } else { window.history.go(-1) } }

参考链接
Vue路由的$router.back(-1)回退时如何判断有没有上一个路由

部署资源路径问题

打包后的文件需要直接放在虚拟主机目录中
如果放在虚拟主机目录的子目录中,会出现js等资源文件访问不到的问题。需要将绝对路径修改为相对路径

  • 修改 config/index.js 文件,找到build/assetsPublicPath
build: { // ... assetsPublicPath: './', // ... }
  • 修改 build/utils.js 文件,增加一行
if (options.extract) { return ExtractTextPlugin.extract({ use: loaders, fallback: 'vue-style-loader', // 增加一行 publicPath: '../../' }) } else { return ['vue-style-loader'].concat(loaders) }

开启Gzip压缩

  • 修改config/index.js文件
// ... module.exports = { // ... build: { // ... productionGzip: true, productionGzipExtensions: ['js', 'css'], // ... } }
  • 安装compression-webpack-pluginV2.0及以上需要的webpack版本最低V4.0.0),这里使用v1.1.12版本
npm i compression-webpack-plugin@1.1.12 --save-dev
  • 查看build/webpack.prod.conf.js文件,确保有以下内容
if (config.build.productionGzip) { const CompressionWebpackPlugin = require('compression-webpack-plugin') webpackConfig.plugins.push( new CompressionWebpackPlugin({ asset: '[path].gz[query]', algorithm: 'gzip', test: new RegExp( '\\.(' + config.build.productionGzipExtensions.join('|') + ')$' ), threshold: 10240, minRatio: 0.8 }) ) }
  • 重新打包,生成有.gz文件即表明配置成功

备注nginx对应配置如下

# 开启gzip gzip on; # 启用gzip压缩的最小文件,小于设置值的文件将不会压缩 gzip_min_length 1k; # gzip 压缩级别,1-9,数字越大压缩的越好,也越占用CPU时间,后面会有详细说明 gzip_comp_level 1; # 进行压缩的文件类型。javascript有多种形式。其中的值可以在 mime.types 文件中找到。 gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/pngapplication/vnd.ms-fontobject font/ttf font/opentype font/x-woff image/svg+xml; # 是否在http header中添加Vary: Accept-Encoding,建议开启 gzip_vary on; # 禁用IE 6 gzip gzip_disable "MSIE [1-6]\."; # 设置压缩所需要的缓冲区大小 gzip_buffers 32 4k; # 设置gzip压缩针对的HTTP协议版本 gzip_http_version 1.0;

图片、JS、CSS等所有静态资源使用CDN地址

修改 config/index.js 文件

// ... module.exports = { // ... build: { // Template for index.html index: path.resolve(__dirname, '../dist/index.html'), // Paths assetsRoot: path.resolve(__dirname, '../dist'), assetsSubDirectory: 'static', // assetsPublicPath: './', assetsPublicPath: process.env.NODE_ENV === 'production' ? 'https://xxx/ingame/community/dist/' : './', // ... } }

仅图片资源使用CDN地址

static目录下图片使用CDN地址

  • 修改 config/dev.env.js 文件
// ... module.exports = merge(prodEnv, { NODE_ENV: '"development"', // ... urlCDN: '"./"' })
  • 修改 config/prod.env.js 文件
// ... module.exports = { NODE_ENV: '"production"', // ... urlCDN: '"http://xxx/heyhey/resources/actionplan/"' }
  • 修改 src/main.js 文件
Vue.prototype.$urlCDN = process.env.urlCDN
  • vue文件中
this.rankList = res.result.map(item => { item.levelIcon = `${this.$urlCDN}static/level/${item.Level.Lvid}.png` return item })

less样式中的背景图片使用CDN地址

  • 修改 config/index.js 文件
module.exports = { build: { // Template for index.html index: path.resolve(__dirname, '../dist/index.html'), // Paths assetsRoot: path.resolve(__dirname, '../dist'), assetsSubDirectory: 'static', assetsPublicPath: './', imgPublicPath: 'http://xxx/heyhey/resources/actionplan/', // ... } }
  • 修改 build/utils.js 文件
if (options.extract) { return ExtractTextPlugin.extract({ use: loaders, fallback: 'vue-style-loader', // 增加一行 publicPath: process.env.NODE_ENV === 'production' ? config.build.imgPublicPath : '../../', }) } else { return ['vue-style-loader'].concat(loaders) }

模板中的img标签使用的assets目录下图片修改成CDN地址

  • 修改 build/webpack.base.conf.js 文件
// ... module.exports = { // ... module: { rules: [ // ... { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, // 根据环境使用cdn或相对路径 publicPath: process.env.NODE_ENV === 'production' ? config.build.imgPublicPath : './', // 将图片打包到dist/img文件夹下, 不配置则打包到dist文件夹下 // outputPath: 'img', name: utils.assetsPath('img/[name].[hash:7].[ext]') } }, // .. ], // ... } }

多环境配置

vue-cli生成的项目,只有开发环境development和正式环境production
开发时常常会用到其他环境,比如线上测试环境、预发布环境等

  • 修改package.json文件,添加命令
"scripts": { "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", "start": "npm run dev", "unit": "jest --config test/unit/jest.conf.js --coverage", "e2e": "node test/e2e/runner.js", "test": "npm run unit && npm run e2e", "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs", "build:prod": "cross-env NODE_ENV=production env_config=prod node build/build.js", "build:sit": "cross-env NODE_ENV=production env_config=sit node build/build.js", "build:local": "cross-env NODE_ENV=production env_config=local node build/build.js" },
  • config目录下添加文件sit.env.jslocal.env.js
// config/sit.env.js 'use strict' module.exports = { NODE_ENV: '"production"', ENV_CONFIG: '"sit"' } // config/local.env.js 'use strict' module.exports = { NODE_ENV: '"production"', ENV_CONFIG: '"local"' }
  • 修改config/prod.env.jsconfig/test.env.js文件
// config/prod.env.js 'use strict' module.exports = { NODE_ENV: '"production"', ENV_CONFIG: '"prod"' } // config/test.env.js 'use strict' const merge = require('webpack-merge') const devEnv = require('./dev.env') module.exports = merge(devEnv, { NODE_ENV: '"testing"', ENV_CONFIG: '"test"' })
  • 修改build/build.js文件
const spinner = ora('building for ' + process.env.env_config + ' production...')

修改build/webpack.prod.conf.js文件

const env = require('../config/' + process.env.env_config + '.env')

路由懒加载

结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载

const VideoPlay = resolve=> require(['@/views/videoplay'], resolve) // 或者 const VideoPlay = () => import('@/views/videoplay')

当在开发环境,页面热更新速度慢的时候。可以给开发环境单独设置
使用babelpluginsbabel-plugin-dynamic-import-node。它只做一件事就是将所有的import()转化为require(),这样就可以用这个插件将所有异步组件都用同步的方式引入,并结合BABEL_ENV这个babel环境变量,让它只作用于开发环境下

首先在package.json中增加BABEL_ENV

"dev": "cross-env BABEL_ENV=development webpack-dev-server --inline --progress --config build/webpack.dev.conf.js"

.babelrc只能加入babel-plugin-dynamic-import-node这个plugins,并让它只有在development模式中才生效。

{ "env": { "development": { "plugins": ["dynamic-import-node"] } } }

之后路由只要像平时一样写就可以了。

const VideoPlay = () => import('@/views/videoplay')

使用webpack-bundle-analyzer分析

修改package.json文件,添加以下命令

"scripts": { "build:sit-preview": "cross-env NODE_ENV=production env_config=sit npm_config_preview=true npm_config_report=true node build/build.js" }

修改build/webpack.prod.conf.js文件,添加以下代码

if (config.build.npm_config_preview) { var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin webpackConfig.plugins.push(new BundleAnalyzerPlugin()) }

项目中使用svg图标

阿里巴巴iconfont可以生成字体图标。还是比较麻烦,用某个标签的时候需要去找对应的类名等

这里选择用svg-sprite-loader加载图标

Typescript中使用svg图标参考:基于Typescript的Vue项目中使用svg图标

  • 安装svg-sprite-loadernpm i svg-sprite-loader --save-dev
  • src目录下新建目录icons,用来放置图标相关文件
  • src/icons目录下新建svg目录,用来放置svg图标文件
  • src/components目录下新建文件svgIcon.vue
<template> <svg :class="svgClass" aria-hidden="true" v-on="$listeners"> <use :xlink:href="iconName"/> </svg> </template> <script> export default { name: 'svgIcon', props: { iconClass: { type: String, required: true }, className: { type: String, default: '' } }, computed: { iconName() { return `#icon-${this.iconClass}` }, svgClass() { if (this.className) { return 'svg-icon ' + this.className } else { return 'svg-icon' } } } } </script> <style scoped> .svg-icon { width: 1em; height: 1em; vertical-align: -0.15em; fill: currentColor; overflow: hidden; } </style>
  • src/icons目录下新建文件index.js
import Vue from 'vue' import SvgIcon from '@/components/svgIcon'// svg组件 // register globally Vue.component('svg-icon', SvgIcon) const req = require.context('./svg', false, /\.svg$/) const requireAll = requireContext => requireContext.keys().map(requireContext) requireAll(req)
  • src/main.js文件中引入
import './icons'
  • 修改build/webpack.base.conf.js文件,让src/icons目录下面的svg文件使用svg-sprite-loader打包,其他的目录下的svg文件还是使用原来的loader
{ test: /\.svg$/, loader: 'svg-sprite-loader', include: [resolve('src/icons')], options: { symbolId: 'icon-[name]' } }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', exclude: [resolve('src/icons')], options: { limit: 10000, name: utils.assetsPath('img/[name].[hash:7].[ext]') } }
  • vue组件中使用
<svg-icon icon-class="article" />

备注:article为图标文件的文件名

其他
一般设计师等给的svg文件,里面会包含一些注释等无用的信息
可以使用svgo压缩

  • 安装svgonpm i svgo --save-dev
  • src/icons目录下添加配置文件svgo.yml
# replace default config # multipass: true # full: true plugins: # - name # # or: # - name: false # - name: true # # or: # - name: # param1: 1 # param2: 2 - removeAttrs: attrs: - 'fill' - 'fill-rule'
  • package.json文件中配置命令:"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
  • 执行命令进行压缩npm run svgo

参考链接
手摸手,带你优雅的使用 icon

使用CSS Module

CSS Modules 是一种 CSS 的模块化和组合系统。
vue-loader 集成 CSS Modules,可以作为模拟 scoped CSS 的替代方案。

使用 vue-cli 创建的项目,应该已经默认开启了这一特性;
如果项目中需要手动开启,按如下设置

module.exports = { // ... module: { rules: [ // ... { test: /\.css$/, use: [ 'vue-style-loader', { loader: 'css-loader', options: { // 开启 CSS Modules modules: true, // 自定义生成的类名 localIdentName: '[local]_[hash:base64:8]' } } ] }, // 与其它预处理器一起使用 { test: /\.less$/, use: [ 'vue-style-loader', { loader: 'css-loader', options: { modules: true } }, 'less-loader' ] }, // ... ] }, // ... }
  • 在组件中的 <style> 上添加 module 特性
<style lang="less" module> .red { p { color: red; } } </style>
  • <style> 上添加 module 后,一个叫做 $style 的计算属性就会被自动注入组件
<template> <!-- $style.red --> <div :class="$style.red"> <p>css module</p> </div> <!-- 也支持 `:class` 的对象/数组语法 --> <div :class="{ [$style.red]: isRed }"> <p>css module</p> </div> <div :class="[$style.red, $style.greed]"> <p>css module</p> </div> </template>
  • js中访问console.log(this.$style.red)

  • JSX组件中,没法用scoped style,所以 CSS Modules 是个很好的选择

export default { props: { level: { type: Number, required: true } }, // render(h) { // return h( // 'h' + this.level, // tag name // { class: this.$style.red }, // props/attributes // this.$slots.default // array of children // ) // } functional: true, render(h, { props, data, children, $style }) { data.class = $style.red return h(`h${props.level}`, data, children) } }
  • 通过module="moduleName"的方式自定义注入名称
<template> <div> <div :class="a.xxx"></div> <div :class="b.xxx"></div> </div> </template> <style module="a"> </style> <style module="b"> </style>

创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: