Vue常用技巧

三元表达式绑定class或style

<div :class="['classify',current=='0' ? 'active' : '']">test</div>
//注意:数组中的classify如果不加引号的话,代表的是data中的一项,并不是类名,将classify加上双引号,变成字符串就可以变成类名
<div :class="['classify', isActive ? 'active' : '']">xx</div>

<div :style="{'cursor': isCreate ? 'not-allowed' : ''}">test</div>

<div :style="{'cursor': isCreate ? 'not-allowed' : '','margin-right':'-8px'}">test</div>

class字符串拼接

<div :class="'classify'+(current=='0' ? ' active' : '')">test</div>

注意:active前要加一个空格(必须有),字符串拼接时,两个字符串之间要有空格

vue项目中实时搜索功能的优化——减少请求次数

如果300ms内没有继续输入,那么300ms后就发送一次请求;如果在300ms内继续输入了内容,那么就会删除上次定时器,重新开始计时,直到300ms内没有继续输入时再发送请求

search(val) {
    if (this.timer) {
        clearTimeout(this.timer);
    }
    if (val) {
        this.timer = setTimeout(() => {
            this.getData();
        }, 300);
    } else {
        // 输入框中的内容被删为空时触发,此时会清除之前展示的搜索结果
        this.getData();
    }
}

动态绑定dom元素id

<li v-for="(item,index) in arr" :id="setId(index)" :key="index"></li>
export default {
    methods:{
        setId(i){
            return `tab${i}`
        }
    }
}
<div v-for="(item,index) in list" :key="index" >
    <div :id="'tab'+index"></div>
</div>

vue阻止子级元素的click事件冒泡(stop)

<div @click="test1()"> 
   <span @click.stop="test2()">按钮1</span> 
   <span>按钮2</span> 
</div>

点击div里面的按钮1,就不会触发div绑定时间test1()方法

本地vue-router模式设置为mode:'history'时,页面空白,无法加载

vue.config.js如果配置了publicPath,则使用history模式时要配置对应的base: process.env.BASE_URL

const router = new VueRouter({
  base:process.env.BASE_URL,
  mode: 'history',
})

process.env使用

{
    BASE_URL: "/", //vue.config.js如果配置了publicPath就是配置的路径,没有配置就是'/'
    NODE_ENV: "development",//默认开发环境是development,生产环境(上线用)是production
}

vue表单校验

const validateRules = {
    // 是否为空
    isNonEmpty(value, errorMsg) {
        return value === '' ? errorMsg : void 0;
    },
    // 最小长度
    minLength(value, length, errorMsg) {
        return value.length < length ? errorMsg : void 0;
    },
    // 是否手机号
    isMoblie(value, errorMsg) {
        return !/^1(3|4|5|6|7|8|9)[0-9]{9}$/.test(value) ? errorMsg : void 0;
    }
};
class Validator {
    constructor() {
        this.cache = [];
    }
    add(value, rules) {
        for (const rule of rules) {
            
            let rulesFunc = rule.ruleFunc.split(':');
            let errorMsg = rule.errorMsg;
            this.cache.push(() => {
                let ruleFunc = rulesFunc.shift();
                rulesFunc.unshift(value);
                rulesFunc.push(errorMsg);
                return validateRules[ruleFunc].apply(value, rulesFunc);
            });
        }
    }
    validate() {
        for (let validatorFunc of this.cache) {
            let errorMsg = validatorFunc(); //开始校验
            if (errorMsg) {
                return errorMsg;
            }
        }
    }
}

export default Validator;

在组件中使用

import Validator from './xxx.js';

使用方法

valid() {
      let validator = new Validator();
      validator.add(this.tel, [
        {
          ruleFunc: "isNonEmpty",
          errorMsg: "手机号不能为空",
        },
        {
          ruleFunc: "isMoblie",
          errorMsg: "号码格式错误",
        },
      ]);
      validator.add(this.name, [
        {
          ruleFunc: "isNonEmpty",
          errorMsg: "姓名不能为空",
        },
      ]);
      let errorMsg = validator.validate();
      return errorMsg;
    }


next() {
      const errorMsg = this.validatorFunc();
      if (errorMsg) {
        this.$toast(errorMsg);
        return;
      } else {
        this.$ajax();
      }
    }

vue自定义指令

  • 文本内容复制指令 v-copy

使用该指令可以复制元素的文本内容(指令支持单击复制 v-copy、双击复制 v-copy.dblclick、点击icon复制 v-copy.icon 三种模式),不传参数时,默认使用单击复制

  1. 第一种:
import { Toast } from 'vant'
const copy = {
  bind(el, { value }) {
    el.$value = value
    el.handler = () => {
      if (!el.$value) {
        // 值为空的时候,给出提示。可根据项目UI仔细设计
        console.log('无复制内容')
        return
      }
      // 动态创建 textarea 标签
      const textarea = document.createElement('textarea')
      // 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域
      textarea.readOnly = 'readonly'
      textarea.style.position = 'absolute'
      textarea.style.left = '-9999px'
      // 将要 copy 的值赋给 textarea 标签的 value 属性
      textarea.value = el.$value
      // 将 textarea 插入到 body 中
      document.body.appendChild(textarea)
      // 选中值并复制
      textarea.select()
      const result = document.execCommand('Copy')
      if (result) {
        Toast('复制成功') // 可根据项目UI仔细设计
      }
      document.body.removeChild(textarea)
    }
    // 绑定点击事件,就是所谓的一键 copy 啦
    el.addEventListener('click', el.handler)
  },
  // 当传进来的值更新的时候触发
  componentUpdated(el, { value }) {
    el.$value = value
  },
  // 指令与元素解绑的时候,移除事件绑定
  unbind(el) {
    el.removeEventListener('click', el.handler)
  },
}

export default copy

使用

 <div class="copy" v-copy="detail.userTel">复制</div>
  1. 第2种
export default {
  bind (el, binding) {
    // 双击触发复制
    if (binding.modifiers.dblclick) {
      el.addEventListener('dblclick', () => handleClick(el.innerText))
      el.style.cursor = 'copy'
    }
    // 点击icon触发复制
    else if (binding.modifiers.icon) {
      if (el.hasIcon) return
      const iconElement = document.createElement('i')
      iconElement.setAttribute('class', 'el-icon-document-copy')
      iconElement.setAttribute('style', 'margin-left:5px')
      el.appendChild(iconElement)
      el.hasIcon = true
      iconElement.addEventListener('click', () => handleClick(el.innerText))
      iconElement.style.cursor = 'copy'
    }
    // 单击触发复制
    else {
      el.addEventListener('click', () => handleClick(el.innerText))
      el.style.cursor = 'copy'
    }
  }
}

function handleClick (text) {
  // 创建元素
  if (!document.getElementById('copyTarget')) {
    const copyTarget = document.createElement('input')
    copyTarget.setAttribute('style', 'position:fixed;top:0;left:0;opacity:0;z-index:-1000;')
    copyTarget.setAttribute('id', 'copyTarget')
    document.body.appendChild(copyTarget)
  }

  // 复制内容
  const input = document.getElementById('copyTarget')
  input.value = text
  input.select()
  document.execCommand('copy')
  // alert('复制成功')
}

参数Attributes

参数 说明 默认值 类型 可选
dblclick 双击复制文本内容 / String 可选
icon 单击icon复制文本内容 / String 可选

使用

<div v-copy> 单击复制 </div>
<div v-copy.dblclick> 双击复制 </div>
<div v-copy.icon> icon复制 </div>
  • 文字超出省略指令 v-ellipsis
export default function (el, binding) {
    el.style.width = binding.arg || 100 + 'px'
    el.style.whiteSpace = 'nowrap'
    el.style.overflow = 'hidden';
    el.style.textOverflow = 'ellipsis';
}

参数Attributes

参数 说明 默认值 类型 可选
width 元素宽度 100 Number 必填

使用

<div v-ellipsis:100> 需要省略的文字需要省略的文字需要省略的文字</div>
  • 回到顶部指令 v-backtop
export default {
  bind (el, binding, vnode) {
    // 响应点击后滚动到元素顶部
    el.addEventListener('click', () => {
    const target = binding.arg ? document.getElementById(binding.arg) : window
    target.scrollTo({
      top: 0,
      behavior: 'smooth'
      })
    })
  },
  update (el, binding, vnode) {
    // 滚动到达参数值才出现绑定指令的元素
    const target = binding.arg ? document.getElementById(binding.arg) : window
    if (binding.value) {
      target.addEventListener('scroll', (e) => {
        if (e.srcElement.scrollTop > binding.value) {
          el.style.visibility = 'unset'
        } else {
          el.style.visibility = 'hidden'
        }
      })
    }
    // 判断初始化状态
    if (target.scrollTop < binding.value) {
      el.style.visibility = 'hidden'
    }
  },
  unbind (el) {
    const target = binding.arg ? document.getElementById(binding.arg) : window
    target.removeEventListener('scroll')
    el.removeEventListener('click')
  }
}

参数Attributes

参数 说明 默认值 类型 可选
id 给需要回到顶部的元素添加的id / String 可选
offset 偏移距离为 height 时显示指令绑定的元素 / Number 可选

然后你可以在模板中任何元素上使用新的 v-backtop property,如下表示在 id 为 app 的元素滚动 400px 后显示绑定指令的元素:

<div  v-backtop:app="400"> 回到顶部 </div>

也可以这样使用,表示为一直显示绑定指令的元素,并且是全局页面回到顶部:

<div  v-backtop> 回到顶部 </div>
  • 拖拽指令 v-drag
export default {
  let _el = el
  document.onselectstart = function() {
    return false  //禁止选择网页上的文字
  }
  
  _el.onmousedown = e => {
    let disX = e.clientX - _el.offsetLeft //鼠标按下,计算当前元素距离可视区的距离
    let disY = e.clientY - _el.offsetTop
    document.onmousemove = function(e){     
      let l = e.clientX - disX
      let t = e.clientY - disY;
      _el.style.left = l + "px"
      _el.style.top = t + "px"
    }
    document.onmouseup = e => {
      document.onmousemove = document.onmouseup = null
    }
    return false
  }
}

使用

<div v-drag> 支持拖拽的元素 </div>

如何使用这些指令?

  1. 建一个directives目录,目录下新建index.js文件用于引入并注册指令
  2. directives目录下新建copy.js文件:
const copy = {
  bind(el, { value }) {
    el.$value = value
    el.handler = () => {
      if (!el.$value) {
        // 值为空的时候,给出提示。可根据项目UI仔细设计
        console.log('无复制内容')
        return
      }
      // 动态创建 textarea 标签
      const textarea = document.createElement('textarea')
      // 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域
      textarea.readOnly = 'readonly'
      textarea.style.position = 'absolute'
      textarea.style.left = '-9999px'
      // 将要 copy 的值赋给 textarea 标签的 value 属性
      textarea.value = el.$value
      // 将 textarea 插入到 body 中
      document.body.appendChild(textarea)
      // 选中值并复制
      textarea.select()
      textarea.setSelectionRange(0,textarea.value.length)

      const result = document.execCommand('Copy')
      if (result) {
        alert('复制成功') // 可根据项目UI仔细设计
      }
      document.body.removeChild(textarea)
    }
    // 绑定点击事件,就是所谓的一键 copy 啦
    el.addEventListener('click', el.handler)
  },
  // 当传进来的值更新的时候触发
  componentUpdated(el, { value }) {
    el.$value = value
  },
  // 指令与元素解绑的时候,移除事件绑定
  unbind(el) {
    el.removeEventListener('click', el.handler)
  },
}

export default copy

3.directivesindex.js文件中引入copy指令并注册:

import copy from './copy'
// 自定义指令
const directives = {
  copy
}

export default {
  install(Vue) {
    Object.keys(directives).forEach((key) => {
      Vue.directive(key, directives[key])
    })
  },
}
  1. main.js:
import Directives from '@/assets/js/directives'

Vue.use(Directives)

Vue组件中如何引入外部的js文件?

  1. 简单粗暴,直接在index.html中使用全局的方式引入,缺点:用不到的组件中也会加载
  2. 如果是下载到本地的静态文件,可以使用import 的方式导入
import { xxx } from '../js/xxx.js' //注意路径
  1. 在Vue组件加载完后,手动操作DOM插入js插件
export default {
    mounted() {
        let script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = '你的js文件地址';
        document.body.appendChild(script);
    },
}
  1. 使用render方法
export default {
    components: {
        'xxx-js': {
            render(createElement) {
                return createElement(
                    'script',
                    {
                        attrs: {
                            type: 'text/javascript',
                            src: '你的js文件地址',
                        },
                    },
                );
            },
        },
    },
}
// 使用 <xxx-js></xxx-js> 在页面中调用
  1. 封装成js插件,使用Promise,js加载成功,调用resolve,js加载失败,调用reject
function loadJs(src) {
  return new Promise((resolve,reject)=>{
    let script = document.createElement('script');
    script.type = "text/javascript";
    script.src= src;
    document.body.appendChild(script);
      
    script.onload = ()=>{
      resolve();
    }
    script.onerror = ()=>{
      reject();
    }
  })
}
 
export default loadJs

使用

import loadJs from '../../utils/base/loadJs'
   
export default {
    mounted(){
        loadJs('http://api.map.baidu.com/xxx.js').then(()=>{
            // 加载成功,进行后续操作
        })
    }
}

6.动态替换要加载的js文件

// 导入外部js
import Vue from 'vue'
 
Vue.component('remote-script', {
  render: function (createElement) {
    var self = this;
    return createElement('script', {
      attrs: {
        type: 'text/javascript',
        src: this.src
      },
      on: {
        load: function (event) {
          self.$emit('load', event);
        },
        error: function (event) {
          self.$emit('error', event);
        },
        readystatechange: function (event) {
          if (this.readyState == 'complete') {
            self.$emit('load', event);
          }
        }
      }
    });
  },
  props: {
    src: {
      type: String,
      required: true
    }
  }
});

使用

// 引入
import 'common/importJs.js'

// html使用的地方
<remote-script src="https://pv.sohu.com/cityjson?ie=utf-8"></remote-script>

配置多环境变量

  • 通过npm run serve启动本地 , 执行development
  • 通过npm run build打包正式 , 执行production 在项目根目录中新建.env.*
  • .env.development 本地开发环境配置
NODE_ENV='development'
# must start with VUE_APP_
VUE_APP_ENV = 'development'
  • .env.production 正式环境配置
NODE_ENV='production'
# must start with VUE_APP_
VUE_APP_ENV = 'production'

src新建config/env.*.js进行变量管理,修改起来方便,不需 要重启项目,符合开发习惯

  • config/index.js
const config = require('./env.' + process.env.VUE_APP_ENV)
module.exports = config

配置对应环境变量env.development.js

// 本地环境配置
module.exports = {
    title: 'vue-h5-template',
    baseUrl: 'http://localhost:9018', // 项目地址
    baseApi: 'https://test.xxx.com/api', // 本地api请求地址
    APPID: 'xxx',
    APPSECRET: 'xxx',
}

使用:(根据环境不同,变量就会不同了)

// 根据环境不同引入不同baseApi地址
import { baseApi } from '@/config'
console.log(baseApi)

部署nginx配置相关

  1. rootalias的区别

Example:

location /lmyself/ {
    alias /var/www/image/;
}

若按照上述配置的话,则访问http://xxx.xx.xx.xx/lmyself/时,ningx会自动去/var/www/image/目录找文件

location /lmyself/ {
    root /var/www/image;
}

若按照这种配置的话,则访问http://xxx.xx.xx.xx/lmyself/时,nginx会去/var/www/image/lmyself/目录下找文件

  1. vue-router history模式
location / {
  try_files $uri $uri/ /index.html;
}
location /lmyself {
  try_files $uri $uri/ /lmyself/index.html;
}

nodejs(express)部署nginx配置问题

通过域名访问nodejs启动的项目,nodejs使用pm2启动,端口号3000。一个最末尾加了 / ,一个没有 / ,这2个是有区别的:

location /public 
{
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host  $http_host;
    proxy_set_header X-Nginx-Proxy true;
    proxy_set_header Connection "";
    proxy_pass http://127.0.0.1:3000/; 
}

如上面的配置,如果请求的urlhttp://servername/public会被代理成http://127.0.0.1:3000

location /public 
{
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host  $http_host;
    proxy_set_header X-Nginx-Proxy true;
    proxy_set_header Connection "";
    proxy_pass http://127.0.0.1:3000; 
}

会被代理到http://127.0.0.1:3000/public

UI框架(vant、Element UI)组件有回调参数的方法中加入自定义参数

// 例子采用vant DropdownMenu ActionSheet 
//第一种
@change='onChange($event,自定义需要传的参数)'
//第二种(原生返回多参数的情况下用这种)
@select="(action,index)=>onSelect(action,index,自定义需要传的参数)"

keep-alive动态缓存页面

首先,在配置router的地方配置meta

const routes = [
    {
        path: '/',  //首页
        name: 'index',
       	component: Index,
        meta: {
            title: '首页',
            keepAlive:true,  //表示该组件缓存
        },
    },
]

App.vue

<div id="app">
  <router-view v-if="!$route.meta.keepAlive"></router-view>
  <keep-alive>
    <router-view v-if="$route.meta.keepAlive"></router-view>
  </keep-alive>
</div>

如果从某个页面进来不需要缓存

watch: {
    $route(to, from) {
      if (["/homenj",'/requirementsdetails'].includes(from.path)) {
        to.meta.keepAlive = false;
      }
    },
  },