vue3 的插件开发和vue2思路类似但是写法却迥异。主要变化在vue3没有了extend构造器。

这次我们通过一个图片预览插件,贯通整个vue3插件开发的过程。

 

1 在src下新建lplugins文件夹,在此文件夹下新建两个文件prevImg.vue和index.js

<template>
  <div class="viewer" v-if="isShow">
    <div
      class="mask"
      @click="close"
    >
    </div>
    <div class="actions">
        <i
          class="el-icon-zoom-in fs24"
          @click="operate('zoomIn')"
        >-</i>
        
        <i  @click="operate('zoomOut')" class="fs24">+</i>        
        <i class="el-icon-c-scale-to-original fs14"
          @click="toggleMode">[]</i>
        <i class="el-icon-refresh-left fs24"
          @click="operate('rotateDY')"
        >☽</i>
        <i
          class="el-icon-refresh-right fs14"
          @click="operate('rotateY')"
        ></i>
        <i
          class="el-icon-circle-close fs14"
          @click="operate('close')"
        ></i>
      </div>
      <div class="img-wrap">
        <img
          ref="img"
          :style="imgStyle"
          class="viewer__img"
          :src="imgUrl"
        />
      </div>
  </div>
</template>

<script setup>
import { computed, ref ,nextTick, getCurrentInstance, watch, reactive, onMounted,toRefs} from 'vue'
    const Mode = {
        CONTAIN: {
            name: 'contain',
            icon: 'el-icon-full-screen'
        },
        ORIGINAL: {
            name: 'original',
            icon: 'el-icon-c-scale-to-original'
        }
    };
    //state
    const loading=ref(false)
    const isShow=ref(false)
    const transform=reactive({
        scale: 1,
        deg: 0,
        offsetX: 0,
        offsetY: 0,
        enableTransition: false
    })
    const mode=ref(Mode.CONTAIN)
    const instance=getCurrentInstance()
    const imgUrl=ref("")

    const emits=defineEmits(["close","open"])

    const imgStyle=computed(()=> {
      const { scale, deg, offsetX, offsetY, enableTransition } = transform;
      const style = {
        transform: `scale(${scale}) rotate(${deg}deg)`,
        transition: enableTransition ? 'transform .3s' : '',
        'margin-left': `${offsetX}px`,
        'margin-top': `${offsetY}px`,
      };
      if (mode.value === Mode.CONTAIN) {
        style.maxWidth = style.maxHeight = '100%';
      }
      return style;
    })


    const  handleImgLoad=(e)=>{
        loading.value = false;
    }
    const handleImgError=(e)=>{
        loading.value = false;
        console.log("e.target",e.target)
        e.target.alt = '加载失败';
    }

    const open=(data)=>{
      console.log("组件内---data",data)
        debugger
        isShow.value=true;
        imgUrl.value=data.url
    }
    const close=()=>{
        console.log("close")
        isShow.value=false
        emits("close")
    }
    
   const operate=(action,options={})=>{ 
       if (loading.value) return;
      const { zoomRate, rotateDeg, enableTransition } = {
        zoomRate: 0.2,
        rotateDeg: 90,
        enableTransition: true,
        ...options
      };
      
      transform.enableTransition=true;
      switch (action) {
        case 'zoomOut':
          if (transform.scale > 0.2) {
            transform.scale = parseFloat((transform.scale - zoomRate).toFixed(3));
          }
          break;
        case 'zoomIn':
          transform.scale = parseFloat((transform.scale + zoomRate).toFixed(3));
          break;
        case 'rotateY':
          transform.deg += rotateDeg;
          break;
        case 'rotateDY':
          transform.deg -= rotateDeg;
          break;
        case "close":
             $emit("close");
          break;
      }
      transform.enableTransition = enableTransition;  
    }

       defineExpose({open,close,operate})

</script>
<style  scoped>
.mask {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  opacity: 0.5;
  background: #000;
}
.viewer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

.img-wrap {
  width: 100%;
  height: 100%;
    /* position:absolute;
    top:0;
    left:0;
    z-index: 100; */
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
}
.loading {
  color: #fff;
}
.actions {
  position: fixed;
  top: 16px;
  right: 30px;
  width: 220px;
  z-index: 9999;
  color: #fff;
  display: flex;
  justify-content: flex-end;
 
}
.actions i {
  display: inline-block;
  margin-left: 10px;
}
 .viewer__img{
    max-height: 100%;
} 
.fs24{
    font-size: 24px;
}
</style>

在这个文件中我们定义了几个比较重要的函数

open、close、operate分别用于打开图层。关闭图层和对图层进行放大。缩小。旋转等操作。这些函数我们通过defineExpose()对外暴露出去。这样在父组件我们就可以直接调用这些方法。达到穿透的效果。

2 接下里是lib下的index.js文件,它是整个插件的核心

import {  createVNode, render, } from 'vue';
import preview from './preview.vue'
 
export default {
    install(app,options) {
        console.log("options;",options={})
        //createVNode vue提供的底层方法 可以给我们组件创建一个虚拟DOM 也就是Vnode
        const vnode = createVNode(preview)
        //render 把我们的Vnode 生成真实DOM 并且挂载到指定节点
        render(vnode, document.body)
        // Vue 提供的全局配置 可以自定义
        
        app.config.globalProperties.$preview = {
            open: (opts) => vnode.component?.exposed?.open(opts),
            close: () => vnode.component?.exposed?.close()
        }
        app.component("preview", preview)
 
    }
}

注意这里,由于vue3中没有了原型的概念。vue3给了我们一个新的api, app.config.globalProperties,我们把方法或者属性挂载到globalProperties上,就相当于在vue2中我们挂载到了vue的原型对象上。而open方法里我们传入的参数就是我们在点击预览时要传递的参数。我们这个例子把图片的url作为核心参数传递过去

3 看看在页面中如何调用

<script setup>
import {getCurrentInstance, ref} from "vue"
import Test from './components/test.vue'

 const {proxy}=getCurrentInstance()


const imgSrc=ref("https://baj-dabanjiz-conf.oss-cn-hangzhou.aliyuncs.com/intelligent-design/image/20210730/middle/9bbeb6570f7b416b1bcbcc59a1b38635.jpg") //横图


const testPlugin=()=>{
   
    console.log("proxy",proxy)
    let obj={url:imgSrc.value}

    proxy.$preview.open(obj)
    
}

</script>

<template>

  <el-button @click="testPlugin"> 测试插件</el-button>
  
</template>

<style scoped>

</style>

4 这里要注意的就是我们在页面中如何获取到vue的实例。当我们点击按钮的时候,

const testPlugin=()=>{
    console.log("proxy",proxy)
    let obj={url:imgSrc.value}

    proxy.$preview.open(obj)    
}


实际上这里我们依然可以通过这样的写法来获取我们 的open方法.如下

 const instance=getCurrentInstance()


const imgSrc=ref("https://baj-dabanjiz-conf.oss-cn-hangzhou.aliyuncs.com/intelligent-design/image/20210730/middle/9bbeb6570f7b416b1bcbcc59a1b38635.jpg") //横图


const testPlugin=()=>{
   
    console.log("proxy",proxy)
    let obj={url:imgSrc.value}

    instance.config.globalProperties.$preview.open(obj)
    
}

但是每次这样会又长又臭。还好vue3在实例里有个prxoy对象。它就相当于当前组件实例的副本。在它上面我们就能获取到我们注册的$preview.

5 main.js注册插件

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// import Loading from "./libs/testLoading/loading"
import preview from "./libs/preview/preview"
createApp(App).use(ElementPlus).use(preview).mount('#app')

到这里我们就基本上实现了图片预览的基本操作

Logo

前往低代码交流专区

更多推荐