vue3+ts+elementPLus实现v-preview指令的方法

引文

最近在用 vue3+ts 开发公司的后台系统,因为后台多处需要图片放大预览的功能,就想着封装一个v-preview指令,这样在需要预览的图片上加个 v-preview就可以预览啦。

目录

在这里就不列我的项目目录啦,想尝试的朋友可以这样创建目录

-- preview
---- previewImage.vue
---- preview.ts

文件内容

previewImage.vue

普普通通vue3组件,记得全局注册

<template>    <div class="previewImg" @click="dialogVisible = true">        <slot>            <img :src="props.src" alt="图片加载失败" />        </slot>    </div>    <el-dialog v-model="dialogVisible" title="查看图片" @close="close">        <img :src="imgSrc" alt="Preview Image"  />    </el-dialog></template><script setup lang="ts">    import { ref, getCurrentInstance, ComponentInternalInstance, onMounted } from 'vue'    import { ElDialog } from 'element-plus'    const props = defineProps({        src: String    })    const dialogVisible = ref(false)    const imgSrc = ref('')    // 插槽形式    onMounted(() => {        const { proxy } = getCurrentInstance() as ComponentInternalInstance        let slot = proxy?.$slots?.default?.()        if(slot){            // 获取插槽内容设置imgSrc地址            imgSrc.value = slot?.[0]?.props?.src        }    })    const setSrc = (v: string) => {        imgSrc.value = v    }    // 组件触发    if (props.src) {        setSrc(props.src)    }    // 指令触发    const show = () => {        dialogVisible.value = true    }    const close = () => {         // 弹窗关闭移除dom        if (document.getElementById('previewDom')) {            document.body.removeChild(document.getElementById('previewDom') as HTMLElement)        }    }    defineExpose({        show,        setSrc    })</script>

preview.ts

对previewImage组件进行拓展,全局注册preview指令(这个注册代码就不放了呦)
需要注意的是vue3拓展组件和vue2有所不同,vue2用Vue.extend就可以拿到组件构造器,vue3这边则是使用createApp

import { Component, createApp } from 'vue'import PreviewImageVue from '@/components/PreviewImage.vue'function mountComponent(RootComponent: Component) {    const app = createApp(RootComponent)    const root = document.createElement('div')    root.setAttribute('id', 'previewDom')    document.body.appendChild(root)    return {        instance: app.mount(root),        unmount() { // 这里unmout没用到,因为组件中dialog的close事件这里监听不到,我就在组件内进行卸载了            console.log('unmount')            document.body.removeChild(root)        }    }}const preview = {    mounted(el: any) {        el.style.cursor = 'zoom-in'        el.addEventListener('click', () => {            let imgSrc = el.getAttribute('src')            let { instance, unmount } = mountComponent(PreviewImageVue)            ;(instance as any).setSrc(imgSrc)            ;(instance as any).show()        })    }}export default preview

使用

本地资源测试

<div class="imgs">    <!-- 普通图片 -->    <img src="~@/assets/images/logo.png" alt="">    <!-- 组件形式使用预览组件、需要注意路径(使用绝对路径) -->    <PreviewImage src="/src/assets/images/logo.png"></PreviewImage>    <!-- 组件插槽形式预览组件、需要注意路径(使用绝对路径) -->    <PreviewImage>        <img src="/src/assets/images/logo.png" alt="">    </PreviewImage>    <!-- 指令使用预览组件 -->    <img src="~@/assets/images/logo.png" alt="" v-preview></div>

开发中可能遇到的问题

  • 获取proxy时使用getCurrentInstance时编辑器会报错,断言成ComponentInternalInstance就不报错了。

  • 获取插槽内容时需要 proxy?.$slots?.default?.() 这样判断取值,不然编辑器也会报错。

  • 暴露给外部使用的方法/属性需通过defineExpose暴露出去

  • 注意拓展组件方式。vue2用Vue.extend,vue3用createApp

  • 使用的时候注意路径问题

总结

之前都是用vue2开发项目,此次项目是vue3的第一次实践,使用下来感觉ts写起来比较冗余,很多编辑器报错都需要时间去解决,可能不是开发组件库都是业务需求,对类型接口的定义使用很少。个人不是很喜欢ts,还是js来的简单粗暴。