🥭效果图

element UI 抽屉设置宽度 vue抽屉组件_Data

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

element UI 抽屉设置宽度 vue抽屉组件_前端_02

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

element UI 抽屉设置宽度 vue抽屉组件_前端_03

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

🍓核心代码实现

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

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

方式一

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

创建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>