[vue3] 定义图库抽屉组件

 ✨✨个人主页:沫洺的主页

📚📚系列专栏: 📖 JavaWeb专栏📖 JavaSE专栏 📖 Java基础专栏📖vue3专栏 

                           📖MyBatis专栏📖Spring专栏📖SpringMVC专栏📖SpringBoot专栏

                           📖Docker专栏📖Reids专栏📖MQ专栏📖SpringCloud专栏     

💖💖如果文章对你有所帮助请留下三连✨✨

🥭效果图

在类名添加里点击图片添加会弹出抽屉组件,在抽屉组件中点击图片,会将图片呈现在图片添加位置如下图,同上抽屉组件消失

图片添加处有个判断,如果有图片显示,就不显示中间的加号图标,否则就显示加号图标,还可以再次点击图片添加位置,同样的会弹出抽屉,可切换图片

这个抽屉其实就是将目录中的图库页面进行了加工,通过抽屉的功能来调用图库,满足相应的需求,同时具备图库的图片上传到图库的功能

🍓核心代码实现

后端接口和上一篇的图库代码一致,这里只展示前端代码

既然是将图库作为一个组件使用(可以理解为一个对象,调用接口使用),这里就需要将图库抽出来.

方式一

先使用父子组件交互的方式调用抽屉,获取图片

创建components/Tuku.vue

<template>
    <el-drawer v-model="drawer" title="图库" size="42%">
        <el-upload class="avatar-uploader" :auto-upload="false" :show-file-list="false" :on-change="onChange">
            <img v-if="imgData.imgBase64" :src="imgData.imgBase64" class="avatar" />
            <el-icon v-else class="avatar-uploader-icon">
                <Plus />
            </el-icon>
        </el-upload>
        <el-divider border-style="dashed" />
        <el-row :gutter="20">
            <el-col v-for="item in imgList">
                <el-image class="demo-image" @click='copy(item.imgUrl)' :src="item.imgUrl" />
            </el-col>
        </el-row>
    </el-drawer>

</template>
<script setup lang="ts">
// getCurrentInstance 获取父组件Add.vue实例
import { onMounted, ref, reactive, getCurrentInstance } from 'vue'
import { tukuApi } from "@/api/index"
import { ElMessage } from 'element-plus'
import useClipboard from 'vue-clipboard3'

const { toClipboard } = useClipboard()
// 定义父组件实例
const instance: any = getCurrentInstance();
const imgList = ref([])
// 默认关闭抽屉
const drawer = ref(false)
const imgData = reactive({
    imgBase64: '',
    imgName: ""
})
const openTuku = () => {
    drawer.value =true
}
const copy = async (imgUrl: string) => {
    // await toClipboard(imgUrl)
    // ElMessage.success('复制成功')
    // 调用父组件Add.vue实例的setImg传值,前提是父组件将setImg暴露出来
    instance.ctx.$parent.setImg(imgUrl)

    // 传值之后关闭抽屉
    drawer.value = false

}
onMounted(() => {
    callTukuApi()
})
const callTukuApi = () => {
    tukuApi.select.call().then((res) => {
        imgList.value = res
    })
}
const onChange = (uploadFile: any, uploadFiles: any) => {

    var reader = new FileReader();
    reader.readAsDataURL(uploadFile.raw);
    reader.onload = () => {
        imgData.imgBase64 = reader.result;
        imgData.imgName = uploadFile.raw.name;

        tukuApi.add.call({ imgBase64: imgData.imgBase64, imgName: imgData.imgName }).then((res) => {
            if (res === 1) {
                ElMessage.success('上传成功')
                callTukuApi()
                //上传成功2000毫秒后图片添加位置就不再显示图片,恢复初始状态
                setTimeout(() => {
                    imgData.imgBase64 = ""
                }, 2000)
            }
        })
    }
}
// 将数据暴露出来,这样其他页面就能访问了
defineExpose({ drawer })
</script>
    
<style scoped>
.avatar-uploader .avatar {
    width: 100px;
    height: 100px;
    display: block;
}

.avatar-uploader .el-upload {
    border: 2px dashed var(--el-color-primary);
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    transition: var(--el-transition-duration-fast);
}

.avatar-uploader .el-upload:hover {
    border-color: var(--el-color-primary);
}

.el-icon.avatar-uploader-icon {
    font-size: 28px;
    color: #3f2a67;
    width: 100px;
    height: 100px;
    text-align: center;
    border: 1px dashed var(--el-color-primary);
}
.demo-image {
    border-radius: 0.2rem;
    width: 100px;
    height: 100px;
    cursor: pointer;
}

.el-col-24 {
    flex: 0 0 0%;
}
</style>

Add.vue页面调用抽屉组件

views/category/Add.vue

<template>
  <el-form :inline="true" :model="formData" label-width="80px">
    <el-form-item label="名称">
      <el-input v-model="formData.name" />
    </el-form-item>
    <br/>
    <el-form-item label="图片">
      <el-image v-if="formData.img" class="img" :src="formData.img" @click="openTuku" />
      <el-icon v-else class="avatar-uploader-icon" @click="openTuku">
        <Plus />
      </el-icon>
    </el-form-item>
    <br/>
    <el-form-item label="父类">
      <el-tree-select v-model="formData.parentId" :check-strictly="true" :data="categoryTreeData"
        :render-after-expand="false" :default-expand-all="true" />
    </el-form-item>
    <br/>
    <el-form-item label="排序">
      <el-input-number v-model="formData.seq" :min="1" :max="10" />
    </el-form-item>
    <br/>
    <el-form-item >
      <el-button type="primary" @click="onSubmit" style="margin-left:80px">新增</el-button>
    </el-form-item>
  </el-form>
  <!-- 子组件components/Tuku.vue -->
  <Tuku ref="TukuRef"></Tuku>
</template>
<script setup lang="ts">
import { onMounted, ref, reactive } from 'vue'
import { categoryApi } from "@/api/index"
import Tuku from "@/components/Tuku.vue";
import { ElMessage } from 'element-plus'
const TukuRef = ref()
const formData = reactive({
  name: '',
  img: "",
  parentId: 0,
  seq: 1,
})
const categoryTreeData = ref([{ value: 0, label: "一级类目" }])
const openTuku = () => {
  // 打开抽屉
  TukuRef.value.drawer = true
}
const setImg = (imgUrl: string) => {
  formData.img = imgUrl
}
onMounted(() => {
  callCategoryTreeApi()
})
const callCategoryTreeApi = () => {
  categoryApi.tree.call().then((res) => {
    // concat 合并数组
    categoryTreeData.value = categoryTreeData.value.concat(res)
  })
}
const onSubmit = () => {
  // categoryApi.insert.call({img:formData.img,name:formData.name,
  // seq:formData.seq,parentId:formData.parentId,
  // lastUpdateBy: "沫洺"}).then(()=>{})
  categoryApi.insert.call({
    ...formData,
    lastUpdateBy: "沫洺"
  }).then((res: any) => {
    if (res === 1) {
      ElMessage.success('新增成功')
    }
  })
}
defineExpose({ setImg })
</script>
  
<style scoped>
.img {
  width: 100px;
  height: 100px;
  cursor: pointer;
  border: 1px dashed var(--el-border-color);
  border-radius: 6px;
  padding: 2px;
}

.el-icon.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 100px;
  height: 100px;
  text-align: center;
  border: 1px dashed var(--el-color-primary);
  cursor: pointer;
}
</style>

注意,不同页面直接进行属性或方法的调用时,需要通过defineExpose({属性或方法})将属性或方法暴露出来,才可调用,原因是vue的安全机制是属性和方法都是私有的

方法二

通过v-model双向绑定的这种机制来传递需要的属性值

考虑到图库组件是一个比较实用的组件,为了更方便的去调用将图库,这里对代码进行进一步的抽取,方便其他页面调用图库组件

Tuku.vue

代码主体部分还是同上,只是将图片添加的判断呈现方法和样式抽取到了图库组件中

<template>

    <el-image v-if="props.modelValue" class="img" :src="props.modelValue" @click="openTuku" />
    <el-icon v-else class="avatar-uploader-icon" @click="openTuku">
        <Plus />
    </el-icon>

    <el-drawer v-model="drawer" title="图库" size="42%">
        <el-upload class="avatar-uploader" :auto-upload="false" :show-file-list="false" :on-change="onChange">
            <img v-if="imgData.imgBase64" :src="imgData.imgBase64" class="avatar" />
            <el-icon v-else class="avatar-uploader-icon">
                <Plus />
            </el-icon>
        </el-upload>
        <el-divider border-style="dashed" />
        <el-row :gutter="20">
            <el-col v-for="item in imgList">
                <el-image class="demo-image" @click='copy(item.imgUrl)' :src="item.imgUrl" />
            </el-col>
        </el-row>
    </el-drawer>

</template>
<script setup lang="ts">
// getCurrentInstance 获取父组件Add.vue实例
import { onMounted, ref, reactive } from 'vue'
import { tukuApi } from "@/api/index"
import { ElMessage } from 'element-plus'
import useClipboard from 'vue-clipboard3'
const props = defineProps({modelValue:{type:String,default:""}})
const emit =  defineEmits(['update:modelValue'])

const { toClipboard } = useClipboard()
const imgList = ref([])
// 默认关闭抽屉
const drawer = ref(false)
const imgData = reactive({
    imgBase64: '',
    imgName: ""
})
const openTuku = () => {
    drawer.value =true
}
const copy = async (imgUrl: string) => {
    // await toClipboard(imgUrl)
    // ElMessage.success('复制成功')
    
    //修改v-model的值
    emit('update:modelValue',imgUrl)
    // 传值之后关闭抽屉
    drawer.value = false

}
onMounted(() => {
    callTukuApi()
})
const callTukuApi = () => {
    tukuApi.select.call().then((res) => {
        imgList.value = res
    })
}
const onChange = (uploadFile: any, uploadFiles: any) => {

    var reader = new FileReader();
    reader.readAsDataURL(uploadFile.raw);
    reader.onload = () => {
        imgData.imgBase64 = reader.result;
        imgData.imgName = uploadFile.raw.name;

        tukuApi.add.call({ imgBase64: imgData.imgBase64, imgName: imgData.imgName }).then((res) => {
            if (res === 1) {
                ElMessage.success('上传成功')
                callTukuApi()
                setTimeout(() => {
                    imgData.imgBase64 = ""
                }, 2000)
            }
        })
    }
}
</script>
    
<style scoped>
.avatar-uploader .avatar {
    width: 100px;
    height: 100px;
    display: block;
}

.avatar-uploader .el-upload {
    border: 2px dashed var(--el-color-primary);
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    transition: var(--el-transition-duration-fast);
}

.avatar-uploader .el-upload:hover {
    border-color: var(--el-color-primary);
}
.img{
  width: 100px; height: 100px;cursor: pointer;
  border: 1px dashed var(--el-border-color);
  border-radius: 6px;
  padding: 2px;
}
.el-icon.avatar-uploader-icon {
    font-size: 28px;
    color: #3f2a67;
    width: 100px;
    height: 100px;
    text-align: center;
    border: 1px dashed var(--el-color-primary);
}
.demo-image {
    border-radius: 0.2rem;
    width: 100px;
    height: 100px;
    cursor: pointer;
}

.el-col-24 {
    flex: 0 0 0%;
}
</style>

由于将图片添加处呈现效果的判断方法和样式抽取到了图库组件中,在调用图库组件时就很方便了,在点击图片添加时,这里执行的路径已经是图库组件在执行了,也就是说这里是图库组件在做事

views/category/Add.vue

<template>
  <el-form :inline="true" :model="formData" label-width="80px">
    <el-form-item label="名称">
      <el-input v-model="formData.name" />
    </el-form-item>
    <br/>
    <el-form-item label="图片">
      <Tuku v-model="formData.img"></Tuku>
    </el-form-item>
    <br/>
    <el-form-item label="父类">
      <el-tree-select v-model="formData.parentId" :check-strictly="true" :data="categoryTreeData"
        :render-after-expand="false" :default-expand-all="true" />
    </el-form-item>
    <br/>
    <el-form-item label="排序">
      <el-input-number v-model="formData.seq" :min="1" :max="10" />
    </el-form-item>
    <br/>
    <el-form-item >
      <el-button type="primary" @click="onSubmit" style="margin-left:80px">新增</el-button>
    </el-form-item>
  </el-form>
</template>
<script setup lang="ts">
import { onMounted, ref, reactive } from 'vue'
import { categoryApi } from "@/api/index"
import Tuku from "@/components/Tuku.vue";
import { ElMessage } from 'element-plus'
const TukuRef = ref()
const formData = reactive({
  name: '',
  img: "",
  parentId: 0,
  seq: 1,
})
const categoryTreeData = ref([{ value: 0, label: "一级类目" }])
onMounted(() => {
  callCategoryTreeApi()
})
const callCategoryTreeApi = () => {
  categoryApi.tree.call().then((res) => {
    // concat 合并数组,将categoryTreeData 和 res 合并
    categoryTreeData.value = categoryTreeData.value.concat(res)
  })
}
const onSubmit = () => {
  categoryApi.insert.call({
    ...formData,
    lastUpdateBy: "沫洺"
  }).then((res: any) => {
    if (res === 1) {
      ElMessage.success('新增成功')
    }
  })
}
</script>

代码说明

categoryApi.insert.call({
    ...formData,
    lastUpdateBy: "沫洺"
}).then(()=>{})
//...的意思就是将formData打散成如下这种格式
categoryApi.insert.call({
    img:formData.img,
    name:formData.name,
    seq:formData.seq,
    parentId:formData.parentId,
    lastUpdateBy: "沫洺"
}).then(()=>{})

通过方法二的代码抽取,以后调用图库抽屉组件时就可以直接使用下面的代码进行调用,然后定义formData.img,再对其赋值即可

<el-form-item label="图片">
    <Tuku v-model="formData.img"></Tuku>
</el-form-item>
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沫洺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值