今天我来讲讲如何在自定义组件上传值:父组件传值给子组件
首先我们谈谈vue如何父组件传值给子组件
(1)父组件中,在引用子组件的标签上绑定v-bind需要给子组件的传值
<template>
...
<child :personInfo="info">
</template>
...
device-dialog子组件在父组件中的标签,info是需要传递给子组件的值,personInfo是子组件提取数据的标志
(2)子组件中使用props获取传递的值
...
<script setup ts name='...'>
...
const props=definedProps({
personInfo:String,
})
const value=props.personInfo
...
</script>
...
这样就获取到父组件的传值了
cool-admin传值的方法和它类似,但是稍稍复杂一点
首先贴上我父组件的全部代码
<template>
<cl-crud ref="Crud">
<el-row>
<!-- 刷新按钮 -->
<cl-refresh-btn ref="refreshBtnRef" />
<!-- 新增按钮 -->
<cl-add-btn />
<!-- 删除按钮 -->
<cl-multi-delete-btn />
<!--上传所有图片按钮-->
<el-button type="primary" @click="dialogUploadVisible = true">
图片批量上传
</el-button>
<el-dialog
v-model="dialogUploadVisible"
title="图片批量上传"
width="30%"
class="dialog_class"
destroy-on-close
>
<upload-images></upload-images>
<template #footer>
<span class="dialog-footer">
<el-button
type="primary"
@click="handleCloseUploadDialog"
>
关闭弹窗
</el-button>
</span>
</template>
</el-dialog>
<!--下载所有图片按钮-->
<el-button type="primary" @click="handleDownload"
>下载图片</el-button
>
<!--更新所有图片按钮-->
<el-button type="primary" @click="dialogUpdateVisible = true">
图片批量更新
</el-button>
<el-dialog
v-model="dialogUpdateVisible"
title="图片批量上更新"
width="30%"
class="dialog_class"
destroy-on-close
>
<update-images></update-images>
<template #footer>
<span class="dialog-footer">
<el-button
type="primary"
@click="handleCloseUpdateDialog"
>
关闭弹窗
</el-button>
</span>
</template>
</el-dialog>
<cl-flex1 />
<!-- 关键字搜索 -->
<cl-search-key />
</el-row>
<el-row>
<!-- 数据表格 -->
<cl-table ref="Table">
<template #column-flagImg="{ scope }">
<div class="flex items-center justify-center">
<el-image
class="h-9 min-w-12"
:src="scope.row.flagImg"
fit="cover"
/>
</div>
</template>
</cl-table>
</el-row>
<el-row>
<cl-flex1 />
<!-- 分页控件 -->
<cl-pagination />
</el-row>
<!-- 新增、编辑 -->
<cl-upsert ref="Upsert" />
</cl-crud>
</template>
<script lang="ts" name="ship-registry" setup>
import { useCrud, useTable, useUpsert } from "@cool-vue/crud";
import { useCool } from "/@/cool";
import { ref } from "vue";
import uploadImages from "../component/upload-images.vue";
import updateImages from "../component/update-images.vue";
import JSZip from "jszip";
//控制批量上传图片弹窗出现
const dialogUploadVisible = ref(false);
//控制批量更新图片弹窗出现
const dialogUpdateVisible = ref(false);
const refreshBtnRef = ref();
//父组件将值传递给子组件:图片的Base64编码
const imgUrlRef = ref("");
const { service } = useCool();
// cl-upsert 配置
const Upsert = useUpsert({
items: [
{
label: "编码",
prop: "mid",
required: true,
component: { name: "el-input" },
},
{
label: "国家名",
prop: "eCountry",
required: true,
component: { name: "el-input" },
},
{
label: "中文名",
prop: "cCountry",
required: true,
component: { name: "el-input" },
},
{ label: "缩写", prop: "abbreviate", component: { name: "el-input" } },
{
label: "国旗",
prop: "flagImg",
component: {
name: "upload-image",
props: {
base64: imgUrlRef,
},
},
},
],
//点击编辑按钮会触发onInfo
onInfo(data, { done }) {
imgUrlRef.value = data.flagImg;
done(data);
},
//点击关闭会触发的钩子
onClose(action: ClForm.CloseAction, done) {
imgUrlRef.value = "";
done();
},
});
// cl-table 配置
const Table = useTable({
columns: [
{ type: "selection" },
{
label: "国旗",
prop: "flagImg",
},
{
label: "编码",
prop: "mid",
},
{
label: "国家名",
prop: "eCountry",
},
{ label: "中文名", prop: "cCountry" },
{ label: "缩写", prop: "abbreviate" },
{ type: "op", buttons: ["edit", "delete"] },
],
});
// cl-crud 配置
const Crud = useCrud(
{
service: service.ship.registry,
},
app => {
app.refresh();
}
);
//下载所有船籍图片
const handleDownload = async () => {
await service.ship.registry.list().then(async images => {
// 创建一个 ZIP 压缩包,并将图片文件添加到压缩包中
const zip = new JSZip();
for (let i = 0; i < images.length; i++) {
const image = images[i];
if (image.flagImg) {
//没有图片的国旗不被下载
const data = image.flagImg?.split(",")[1]; // 获取 base64 编码的数据
const binaryData = base64ToBinary(data as any); // 将 base64 编码的数据转换为二进制数据
zip.file(
image.mid +
"-" +
image.eCountry +
"-" +
image.cCountry +
"-" +
image.abbreviate +
".jpg",
binaryData,
{ binary: true }
); //下载的图片名称为编码-国家名-中文名-缩写.jpg,例如412-China-中国-CHN.jpg
}
}
// 将压缩包转换为 Blob 对象,并创建下载链接
const blob = await zip.generateAsync({ type: "blob" });
console.log(blob);
const url = URL.createObjectURL(blob);
// 创建 a 标签,并设置 download 属性,将压缩包下载到本地
const a = document.createElement("a");
a.href = url;
a.download = "images.zip";
document.body.appendChild(a);
a.click();
// 释放下载链接
URL.revokeObjectURL(url);
});
};
const base64ToBinary = (base64: string) => {
const binaryStr = window.atob(base64);
const len = binaryStr.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryStr.charCodeAt(i);
}
return bytes;
};
//关闭批量上传弹窗
const handleCloseUploadDialog = () => {
dialogUploadVisible.value = false;
//刷新表格
refreshBtnRef.value.$el.click();
};
//关闭批量更新弹窗
const handleCloseUpdateDialog = () => {
dialogUpdateVisible.value = false;
//刷新表格
refreshBtnRef.value.$el.click();
};
</script>
<style scoped></style>
其中这里需要传值
传值需要解决的问题是:新增页面上传图片没有问题,但是编辑页面从数据库中取出的图片无法正常展示,产生问题的原因是子组件是通过imgUrl来控制图片的div展示的,但是它没有办法分辨你使用子组件时是新增进入还是修改进入,所以需要传值
<!-- 图片上传组件:用于船籍新增按钮,选择国旗 -->
<template>
<div class="container">
<!-- 缩略图显示-->
<div class="img-show" v-if="imgUrl">
<img :src="imgUrl" class="avatar" />
<span class="actions">
<!-- 删除 -->
<span class="item">
<el-icon @click="handleDel()"><Delete /></el-icon>
</span>
</span>
</div>
<!-- 图片上传 -->
<el-upload
v-else
action="#"
class="uploader-avatar"
list-type="picture"
:auto-upload="false"
:show-file-list="false"
:on-change="handleUpload"
>
<el-icon><Plus /></el-icon>
</el-upload>
</div>
</template>
<script lang="ts" setup name="upload-image">
import { ref, watch } from "vue";
import { Plus, Delete } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
const emit = defineEmits(["update:modelValue"]);
const props = defineProps({
base64: String,
});
const imgUrl = ref();
//监听父组件是否传递base64数据过来,从而判断是新增还是修改
watch(
props,
newQuestion => {
imgUrl.value = newQuestion.base64;
},
{ immediate: true }
);
//将图片的data返回给父组件registry.vue
const handleUpload = (file: any) => {
// 检查图片类型是否为 jpg 或 png
const isJpgOrPng =
file.raw.type === "image/jpeg" || file.type === "image/png";
if (!isJpgOrPng) {
// 如果不是 jpg 或 png,则提示用户
const errorMsg = "图片类型只能为jpg/png";
ElMessage.error(errorMsg);
} else {
const reader = new FileReader();
reader.readAsDataURL(file.raw);
reader.onload = () => {
imgUrl.value = reader.result as any;
// 触发自定义事件,并将 Base64 编码传递给父组件
emit("update:modelValue", imgUrl.value);
};
}
};
//删除国旗
const handleDel = () => {
//清除组件上的国旗图片
imgUrl.value = "";
//清除传递给父组件的值
emit("update:modelValue", "");
};
</script>
<style scoped>
.container {
width: 124px;
height: 124px;
overflow: hidden;
}
.uploader-avatar :deep(.el-upload) {
background-color: #fbfdff;
border: 1px dashed #c0ccda;
border-radius: 6px;
box-sizing: border-box;
width: 124px;
height: 124px;
cursor: pointer;
line-height: 124px;
vertical-align: top;
overflow: hidden;
}
.img-show {
position: relative;
border: 1px solid #c0ccda;
border-radius: 6px;
box-sizing: border-box;
width: 124px;
height: 124px;
cursor: pointer;
overflow: hidden;
}
.uploader-avatar :deep(i) {
font-size: 28px;
color: #8c939d;
}
.avatar {
width: 100%;
height: 100%;
object-fit: contain;
}
.actions {
position: absolute;
width: 100%;
height: 100%;
line-height: 148px;
left: 0;
top: 0;
cursor: default;
text-align: center;
color: #fff;
opacity: 0;
font-size: 20px;
background-color: rgba(0, 0, 0, 0.5);
transition: opacity 0.3s;
}
.actions:hover {
opacity: 1;
}
.actions:hover span {
display: inline-block;
}
.actions span {
display: none;
margin: 0 16px;
cursor: pointer;
}
</style>
子组件是这里来接受父组件传值的
通过definedProps来接受值,通过watch监听来给imgUrl.value赋值。
这样做编辑界面就能正确的显示图片了。
但是后来我又发现一个bug,先点击编辑,退出后点击新增,图片还留在子组件中,我首先想到的是子组件的实例销毁时props没有被正确的清除,于是我使用生命周期函数来试试能不能解决这个bug,onMounted,beforeUnMounted这些被我试了一个遍,但是没有解决问题,我明白问题不是出在子组件上,问题出在父组件上,因为你编辑,新增操作都在同一个页面,所以props中的值没有被重置,所以导致这个问题,解决办法如下:
每次关闭弹窗时都把传递的imgUrlRef显示的置空,这样就完美解决问题了