✨✨个人主页:沫洺的主页
📚📚系列专栏: 📖 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>