vue3常用代码,看看能不能帮上你的忙?

363 阅读4分钟

菜鸟做项目时发现很多 vue3 常用的代码,所以来总结一下!

监听路由

监听路由

import { useRoute } from "vue-router";
let route = useRoute();
watch(
  () => route.fullPath, // 大家可以思考一下为什么不用path?
  (newValue, oldValue) => {
    // 最好加上 —— keep-alive的时候有用
    if (isActive.value) {
      ……
    }
    console.log("watch", newValue, oldValue);
  },
  { immediate: true }
);

也可以

import { onBeforeRouteUpdate } from "vue-router";

onBeforeRouteUpdate((to, from) => {
  ……
});

这里顺便补充一下 路由跳转跨界面跳转获取参数

路由跳转

路由跳转

import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();

// 跳转路由
const toPath = function (path) {
  router.push(path);
};

image.png

如果一定要判断,建议用:

const toPath = (location) => {
  const targetFullPath = router.resolve(location).fullPath;

  if (route.fullPath !== targetFullPath) {
    router.push(location);
  }
};

跨界面跳转

跨界面跳转

import { useRouter } from "vue-router"; 
// 新标签页跳转
const router = useRouter();
const openPage = (params = "") => {
  let href = null;
  
  if (params) {
    href = router.resolve({
      path: "/newform",
      query: {
        id: encodeURIComponent(JSON.stringify(params)),
      },
    });
  } else {
    href = router.resolve({
      path: "/newform",
      query: {
        id: null,
      },
    });
  }
  window.open(`${href.href}`, "_blank");
};

这里也可以使用 name:"newform" ,要和自己router里面定义的保持 对应关系

动态路由也是一样:

在这里插入图片描述

普通路由两个同时写也没有问题,但是动态路由不行,会报错!!!

在这里插入图片描述

报错:runtime-core.esm-bundler.js:343 Uncaught Error: Missing required param "xxx"

在这里插入图片描述

获取参数

获取参数

import { useRoute } from "vue-router";
const route = useRoute();
console.log(route.query.id); // 这里有两种一种 params、一种 query,注意取的时候要对应!打印看看就好

且要分清 动态路由(params)路由后面带参数(query) 这两种情况!具体看文章:23 动态路由(路由传参)

mitt、project / inject 无效

mitt、project / inject 无效

如果通信的组件是 router-view 里面 根据路由加载的 或者 路由有两层嵌套,那么不管是 mitt 还是 project/inject 都无法进行组件间的通信,因为 mitt 要能通信必须是该界面已经加载出来了!而 project/inject 不知道为什么,嵌套了两层后,第二层 router-view 里面的组件就无法获取了,会报错

[Vue warn]: injection "openmenu" not found. 
  at <Resources onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< undefined > > 
  at <RouterView> 
  at <ElMain> 
  at <ElContainer> 
  at <Home onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< null > > 
  at <RouterView> 
  at <App>

好像是因为第一层 router-view 被卸载了,所以 project 为 undefined 了!也可能是因为 provide 只能够向下进行传递数据,而路由并不相当于是其子组件!

解决方案

使用pinia、vuex等!

防抖函数 (已封装)

防抖函数 (已封装)

/**
 * 防抖函数
 * @param {function} fn
 * @param {number} delay
 * @returns {function}
 * 如果函数有参数,直接定义一个常量等于debounce(fn,delay)
 * 调用的时候直接 常量(函数参数) 就行
 *
 */
export const debounce = (fn, delay) => {
  let timer = null;
  return function () {
    let context = this;
    let args = arguments;
    clearTimeout(timer);
    timer = setTimeout(function () {
      fn.apply(context, args);
    }, delay);
  };
};

vue动态绑定背景(transform类比)

vue动态绑定背景(transform类比)

<div class="cover1" :style="{transform:`rotate(${rotate1}deg)`}" v-if="rotate1 <= 90"></div>
<div class="cover2" :class="rotate1 <= 90 ? 'cover2i' : ''" :style="{transform:`rotate(${rotate1}deg)`}" v-if="rotate1 <= 180"></div>
<div class="cover3" :class="rotate1 <= 180 ? 'cover3i': ''" :style="{transform:`rotate(${rotate1}deg)`}" v-if="rotate1 <= 270"></div>
<div class="bgimg" :style="{backgroundImage:'url('+msg.url+')'}"></div>

复制函数封装

复制函数

/**
 * 复制文本到粘贴板
 * @param {*} txt 需要粘贴的文本
 * @returns {function}
 *
 */
export let copyTxt = (txt) => {
  // 惰性函数
  if (navigator.clipboard) {
    copyTxt = (txt) => {
      navigator.clipboard.writeText(txt);
    };
    copyTxt(txt);
  } else {
    copyTxt = (txt) => {
      const input = document.createElement("input");
      input.setAttribute("value", txt);
      document.body.appendChild(input);
      input.select();
      document.execCommand("copy");
      document.body.removeChild(input);
    };
    copyTxt(txt);
  }
};

下载函数(get)

下载函数(get)

下载和请求接口的逻辑不太一样,只需要访问就行了,菜鸟有时候也容易忘记,所以这里记一下!

/**
 * 判断是否是微信浏览器
 */
export const isWxBrowser = () => {
  // 判断是否H5微信环境,true为微信浏览器
  const ua = navigator.userAgent.toLowerCase();
  return ua.match(/MicroMessenger/i) == "micromessenger" ? true : false;
};

/**
 * 下载工具
 * @param {string} url 下载地址
 * @param {string} name 文件名
 *
 */
export const downloadTool = (url, name = "") => {
  if (!url) {
    console.warn("downloadFile: url不能为空");
    return;
  }
  
  // 微信内置浏览器要提示
  if (isWxBrowser()) {
    ElMessage({
      message: `请先在浏览器中打开再下载,或点击复制链接:${url}`,
      type: "error",
    });
    copyTxt(url); // 上面封装的
  } else {
    const a = document.createElement("a");
    a.style.display = "none";
    a.href = url;
    a.download = name;
    document.body.appendChild(a);
    a.click();
    a.remove();
  }
};

post

当然有时候下载传入的参数比较多,那么后端是可能将下载变成post请求的,这个时候要进行如下修改:

function downloadFileFun(formType) {
    const params = {
      formType,
    };
    downloadFile(params)
      .then((res) => {
        // console.log(res);
        // 从响应中获取文件数据
        const fileData = res;
        // 创建一个Blob对象
        let blob = new Blob([fileData], { type: "application/vnd.ms-excel" });
        // 生成文件URL
        const downloadUrl = URL.createObjectURL(blob);
        downloadTool(downloadUrl, "模板表.xlsx");
      })
      .catch((err) => {
        console.log(err);
      });
  }
}

注意

downloadFile 这个api里面要加一个responseType: "blob":

export function downloadFile(params) {
  return request({
    url: "/user/downloadExcel",
    method: "POST",
    data: params,
    responseType: "blob",
  });
}

如果是 pdf ,这里要修改为 :

let blob = new Blob([fileData], { type: "application/pdf" });

responseType: "arraybuffer",

如果后端不仅会返回数据,有时候还会返回报错信息的话,就需要对返回的结果进行判断,因为只要使用了 responseType 就算返回的不是数据类型,也会强制转换,那么报错信息就变成了 blob数据对象 了,没有 data、message 等东西了,需要将数据对象转换回json对象,代码如下:

createReportApi(formdata.value)
   .then((res) => {
     console.log(res);
     loading.close();
     // 非文件数据,长度会较小
     if (res.byteLength <= 100) {
       // 转换回json对象代码
       const jsonString = new TextDecoder("utf-8").decode(res);
       const jsonObject = JSON.parse(jsonString);
       console.log(jsonObject);
       // eslint-disable-next-line
       ElMessage({
         message: jsonObject.message,
         type: "error",
       });
     } else {
       let blob = new Blob([res], { type: "application/pdf" });
       downloadTool(
         window.URL.createObjectURL(blob),
         `${formdata.value.contractNum}-${formdata.value.projectName}.pdf`
       );
     }
   })
   .catch((err) => {
     console.log(err);
     loading.close();
   });

自适应js --》 index.html里使用

自适应js --》 index.html里使用

; (function (win) {
  var bodyStyle = document.createElement('style')
  bodyStyle.innerHTML = `body{width:1920px; height:1200px; overflow:hidden}`  // 需要适配的屏幕的宽高
  document.documentElement.firstElementChild.appendChild(bodyStyle)

  function refreshScale() {
    let docWidth = document.documentElement.clientWidth
    let docHeight = document.documentElement.clientHeight
    let designWidth = 1920 // 需要适配的屏幕的宽
    let designHeight = 1200  // 需要适配的屏幕的高
    let widthRatio = 0
    widthRatio = docWidth / designWidth
    let heightRatio = 0
    heightRatio = docHeight / designHeight
    document.body.style =
      'transform:scale(' +
      widthRatio +
      ',' +
      heightRatio +
      ');transform-origin:left top;'

    // 应对浏览器全屏切换前后窗口因短暂滚动条问题出现未占满情况
    setTimeout(function () {
      let lateWidth = document.documentElement.clientWidth,
        lateHeight = document.documentElement.clientHeight
      if (lateWidth === docWidth) return

      widthRatio = lateWidth / designWidth
      heightRatio = lateHeight / designHeight
      document.body.style =
        'transform:scale(' +
        widthRatio +
        ',' +
        heightRatio +
        ');transform-origin:left top;'
    }, 0)
  }
  refreshScale()

  window.addEventListener(
    'pageshow',
    function (e) {
      if (e.persisted) {
        // 浏览器后退的时候重新计算
        refreshScale()
      }
    },
    false
  )
  window.addEventListener('resize', refreshScale, false)
})(window);

这个自适应是最简单的自适应,主要用来对一些等比例的屏幕或者长宽比相近的屏幕的适配,主要还是针对类似电脑屏幕的适配!

注意: 1、如果项目有3d相关的操作,那么这个可能会适得其反,让本来百分比就可以适配的变得不适配!参考:swiper 3d 结合 loop 向左移动缺少一个内容

2、手机端、pad端还是建议使用px2rem,参考我的:使用px2rem不生效

3、一定要给body加overflow,因为只是缩小了,但是其实原来的内容大小没变!

GPT优化:

;(function (win) {
  const designWidth = 1920
  const designHeight = 1200

  function refreshScale() {
    const docWidth = document.documentElement.clientWidth
    const docHeight = document.documentElement.clientHeight

    // 保持比例
    const scale = Math.min(docWidth / designWidth, docHeight / designHeight)

    // 不覆盖原本 style
    document.body.style.transform = `scale(${scale})`
    document.body.style.transformOrigin = 'top left'
    document.body.style.width = designWidth + 'px'
    document.body.style.height = designHeight + 'px'
    document.body.style.overflow = 'hidden'
  }

  refreshScale()

  // 防抖 resize
  let resizeTimer
  window.addEventListener('resize', () => {
    clearTimeout(resizeTimer)
    resizeTimer = setTimeout(refreshScale, 100)
  })

  window.addEventListener('pageshow', (e) => {
    if (e.persisted) refreshScale()
  })
})(window)

禁止放大

禁止放大

// 禁用双指放大
document.addEventListener(
  'touchstart',
  function (event) {
    if (event.touches.length > 1) {
      event.preventDefault()
    }
  },
  {
    passive: false,
  }
);

// 禁用双击放大
var lastTouchEnd = 0
document.addEventListener(
  'touchend',
  function (event) {
    var now = Date.now()
    if (now - lastTouchEnd <= 300) {
      event.preventDefault()
    }
    lastTouchEnd = now
  },
  {
    passive: false
  }
)

使用:

const container = document.querySelector('#noZoomArea')
container.addEventListener('touchstart', ...)
container.addEventListener('touchend', ...)

注意

整个界面不让缩放应该用meta标签

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

websocket封装

websocket封装

websocket确实使用起来很简单,但是最好搞一个统一的断线重连以及连接流程,不然真的不太好规范!

这里菜鸟把公司一个同事的封装的献上:

data:

// 连接地址
websocketUrlPlay: ws://xxxxxx,
// 断线重连
lockReconnect: false,
// 重连定时器
reconnetTimer: null

method:

// 重连
reconnect() {
  if (lockReconnect) {
    return;
  }
  lockReconnect = true;
  // 没连接上会一直重连,设置延迟避免请求过多
  reconnetTimer && clearTimeout(reconnetTimer);
  reconnetTimer = setTimeout(() => {
    createWebsocketPlay();
    lockReconnect = false;
  }, 4000);
},

// 创建websocket
createWebsocketPlay() {
  socket = new WebSocket(websocketUrlPlay);
  socket.onopen = () => {
    // onopen 连接触发
    console.log("websocket pad open");
  };
  socket.onclose = () => {
    // onclose 断开触发
    console.log("websocket close");
    this.reconnect();
  };
  socket.onerror = () => {
    console.log("发生异常了");
    reconnect();
  };
  socket.onmessage = (event) => {
    // console.log(JSON.parse(event.data));
    const data = JSON.parse(event.data);
  }
}

注意:

1、这个 lockReconnect 类似于java中的 锁,所以 断开连接的时候一定要置为true

lockReconnect = true;
clearTimeout(reconnetTimer);
reconnetTimer = null;

setTimeout(()=>{
  socket.close();
},100);  // 如果关闭之前会发送消息,那么建议关闭时延时,可以更加有效!

注意

这里最好把 websocket 放在一个公共 js 里面,应用一开始就监听,应用卸载才关闭,不然自己在每一个界面创建时去创建 websocket 不仅影响性能,而且还要时刻关闭,不然就会创建多个!当然如果项目中本来就只有一个界面需要使用,那就无所谓!

echarts

echarts

echarts实现渐变

echarts实现渐变

itemStyle: {
  color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
    { offset: 0, color: "#17e5a2" },
    { offset: 1, color: "#23e8ca" },
  ]),
},

或者官网的代码:

labelLine: {
    normal: {
        show: true,
        length: 30,
        length2: 30,
        lineStyle: {
            color:{
              type: 'linear',
              x: 0,
              y: 0,
              x2: 0,
              y2: 1,
              colorStops: [{
                  offset: 0, color: 'red' // 0% 处的颜色
              }, {
                  offset: 0.5, color: 'red' // 100% 处的颜色
              }, {
                  offset: 1, color: 'rgba(255,0,0,0)' // 100% 处的颜色
              },],
              global: false // 缺省为 false
            },
            width: 2,
        }
    }
},

参考:

配置color:echarts.apache.org/zh/option.h…

echarts适配

echarts适配

window.addEventListener("resize", function () {
  if (document.getElementById("echart_box2")) {
    chartssize(document.getElementById("echart_box2"), document.getElementById("echart2"), 3); // 第三个参数是自由发挥用的
    chart2.resize();
  }
});

//为图表计算高度
chartssize(container, charts, type) {
  function getStyle(el, name) {
    if (window.getComputedStyle) {
      return window.getComputedStyle(el, null);
    } else {
      return el.currentStyle;
    }
  }
  let wi = getStyle(container, "width").width;
  let hi = getStyle(container, "height").height;
  charts.style.width = wi;
  charts.style.height = hi;
},

MDN:

window.getComputedStyle

currentStyle

我的博客:

window.getComputedStyle

其实就是获取父元素最终样式,然后设置echarts样式而已!

echarts中间是图片或者文字(不要用定位,low)

echarts中间是图片或者文字(不要用定位,low)

graphic: {
    elements: [{
        type: 'image',
        style: {
            image: giftImageUrl,
            width: 100,
            height: 100
        },
        left: 'center',
        top: 'center'
    }]
},

参考: 配置graphic:echarts.apache.org/zh/option.h…

设置labelline的位置

设置labelline的位置

参考饼图引导线调整: echarts.apache.org/examples/zh…

打印效果:

在这里插入图片描述

参考edgeDistance -- 防止太小显示字数过少: echarts.apache.org/zh/option.h…