🥭效果图
在类名添加里点击图片添加会弹出抽屉组件,在抽屉组件中点击图片,会将图片呈现在图片添加位置如下图,同上抽屉组件消失
图片添加处有个判断,如果有图片显示,就不显示中间的加号图标,否则就显示加号图标,还可以再次点击图片添加位置,同样的会弹出抽屉,可切换图片
这个抽屉其实就是将目录中的图库页面进行了加工,通过抽屉的功能来调用图库,满足相应的需求,同时具备图库的图片上传到图库的功能
🍓核心代码实现
后端接口和上一篇的图库代码一致,这里只展示前端代码
既然是将图库作为一个组件使用(可以理解为一个对象,调用接口使用),这里就需要将图库抽出来.
方式一
先使用父子组件交互的方式调用抽屉,获取图片
创建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>