cool-admin之cl-upset自定义组件(2)

今天我来讲讲如何在自定义组件上传值:父组件传值给子组件

首先我们谈谈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显示的置空,这样就完美解决问题了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值