记一次vue3弹窗踩坑

172 阅读3分钟

3.png 微信截图_20230317153425.png 1 弹窗左右切换图片自动切换,底下缩略图自动切换
2 点击底下缩略图展示中间大图
3 点击icon图标区域右侧的折叠按钮底下缩略图,再次点击显示
4 弹窗由于是在外部缩略图点击以后打开,所以每次打开的时候弹窗缩略图都会定位到当前图片的位置

我们采用teleport+element-plusUI来实现这个效果 先看基础代码:

//父组件
<div v-if="isViewBigPic">
    <RenderImgDialog
            :data="curImgData"
            :planId="planId"
            :index="curImgIndx"
            :imgList="curImgList"
            @close="isViewBigPic = false"
            @del="delImgByDialog"
    />
</div>
const isViewBigPic = ref<boolean>(false);
const curImgIndx = ref<number>(0);
const curImgList = computed(() => {
	return list.value.filter(k => k.stateCode == 100);
});
const handlerViewBigPic = data => {
	console.log("查看大图数据", data);
	isViewBigPic.value = true;
	curImgData.value = data;
	let tempIndex = 0;

	curImgList.value.forEach((item, index): void => {
		if (item.id == data.id) {
			tempIndex = index;
		}
	});
	curImgIndx.value = tempIndex;
};

为什么要通过v-if,是因为想每次打开的时候都能走onMounted()逻辑,所以每次关闭都让其销毁。再看弹窗核心内容

	<teleport to="#app">
		<!-- <transition name="el-fade-in-linear"> -->
		<div>
			<div class="mask"></div>
			<div class="modal" v-if="imgList&&imgList?.length > 0">
				<div class="container">
					<div class="modal-head">
						<!-- <div>{{data}}</div> -->
						<div class="modal-title">{{ curData && curData?.name }}</div>
						<div class="close-icon" @click="close">
							<el-icon><CloseBold /></el-icon>
						</div>
					</div>
					<div class="fullscreen" :style="{ backgroundImage: `url( ${curSrc})` }" v-if="isFold">
						<div :class="[idx == 0 ? 'no-hover' : '', 'full-lt-arrow']" @click="prev">
							<div class="btn">
								<el-icon><ArrowLeftBold /></el-icon>
							</div>
						</div>
						<div class="full-content">
							<!-- <el-image :src="curSrc" style="border-radius: 4px;"></el-image> -->
						</div>
						<div :class="[idx == imgList?.length - 1 ? 'no-hover' : '', 'full-rt-arrow']" @click="next">
							<div class="btn">
								<el-icon><ArrowRightBold /></el-icon>
							</div>
						</div>
					</div>
					<div class="content" v-else>
						<div class="lt-arrow" @click="prev">
							<div :class="[idx == 0 ? 'no-hover' : '', 'lt-arrow-box']">
								
								<el-icon><ArrowLeftBold /></el-icon>
							
							</div>
						</div>
						<div class="mid-imgWrap" ref="mainImgRef">
							<el-image :src="curSrc" fit="contain" style="width: 812px; height: 534px; border-radius: 4px">
								<template #placeholder>
									<div class="image-slot">
										<el-icon><Loading /></el-icon>
										<!-- <el-progress :percentage="30"  /> -->
									</div>
								</template>
							</el-image>
						</div>
						<div class="rt-arrow" @click="next">
							<div :class="[idx == imgList?.length - 1 ? 'no-hover' : '', 'rt-arrow-box']" @mouseover="move" @mouseleave="leave">
								<el-icon><ArrowRightBold /></el-icon>							
							</div>
						</div>
					</div>
					<div class="desc-wrap">

							<span>
								<el-tooltip placement="top" effect="light" >
									<template #content>
										<h2 class="tip-title">图片信息</h2>
										<div class="block-wrap">
											<div class="block-item">
												<div class="label">图片类型</div>
												<div class="text">{{ parseImgType(curData && curData?.imageType) }}</div>
											</div>
											<div class="block-item">
												<div class="label">分辨率</div>
												<div class="text"></div>
											</div>
											<div class="block-item">
												<div class="label">空间</div>
												<div class="text" v-if="curData&&curData?.combinationName">
													<span class="space-item" >{{curData&&curData?.combinationName}}</span>
												</div>
												<div class="text" v-else="curData&&curData?.name">{{curData?.name}}</div>
											</div>
											<div class="block-item">
												<div class="label">创建时间</div>
												<div class="text" >{{ curData && curData?.createTime }}</div>
											</div>
											<div class="block-item" v-show="curData?.imageType == 2 || curData?.imageType == 3">
												<div class="label">包含商品</div>
												<div class="text">{{ curData && curData?.goodsHotspotNum }}</div>
											</div>
											<div class="block-item" v-show="curData.imageType == 2 || curData.imageType == 3">
												<div class="label">文本热点</div>
												<div class="text">{{ curData && curData?.textHotspotNum }}</div>
											</div>
											<div class="block-item" v-show="curData?.imageType == 2 || curData.imageType == 3">
												<div class="label">有效期</div>
												<div class="text">{{ curData && curData?.expireTime }}</div>
											</div>
										</div>
									</template>
									<el-icon class="item-icon"><InfoFilled /></el-icon>
								</el-tooltip>
								<el-tooltip
									placement="top-start"
									effect="light"
								>
									<template #content>
										<span class="white" >查看</span>
									</template>
									<span>
										<el-icon class="item-icon" 
											v-show="(curData && curData?.imageType == 2) || (curData && curData?.imageType == 3)" @click="viewHandler"><FullScreen />
										</el-icon>
									</span>
								</el-tooltip>
								<span class="share-wrap">
									<el-tooltip
										popper-class="pop-wrap"
										placement="top-start"
										effect="light"
									>
										<template #content>
											<div class="share-content">
												<div class="qr-img">
													<img :src="curData && curData?.qrCodeUrl" />
												</div>
												<div class="share-btns-wrap">
													<span class="share-btn" @click="shareByLink">链接分享</span>
													<span class="share-btn" @click="shareByMessage">短信分享</span>
												</div>
											</div>
										</template>
										<span>
											<el-icon class="item-icon" 	
												v-show="(curData && curData?.imageType == 2) || (curData && curData?.imageType == 3)"><Share />
											</el-icon>
										</span>
									</el-tooltip>
								</span>
								<el-tooltip placement="top-start" effect="light">
									<template #content><span class="white">删除</span></template>
									<span @click="del"
										><el-icon class="item-icon"><Delete /></el-icon
									></span>
								</el-tooltip>
								<el-tooltip placement="top-start" effect="light">
									<template #content><span class="white">下载</span></template>
									<span @click="downLoad"><el-icon class="item-icon"><Download /></el-icon></span>
								</el-tooltip>
								<el-tooltip
									content="设置"
									placement="top-start"
									effect="light"
									
								>
									<template #content><span class="white">设置</span></template>
									<span @click="setHandler">
										<el-icon class="item-icon" 
											v-show="(curData && curData?.imageType == 2) || (curData && curData?.imageType == 3)"> <Setting />
										</el-icon>
									</span>
								</el-tooltip>
							</span>
						<!-- </slot> -->
						<span class="line">|</span>
						<span @click="toggleThumb" class="fold-span">
							<!-- <el-icon :class="isFold ? 'arrow-down' : 'arrow-up'" style="color:#fff"><ArrowDownBold /></el-icon> -->
							<img class="arrow-fold-img" :src="isFold ? upIcon : downIcon" />
							<span class="fold">{{ isFold ? "展开收缩图" : "收起缩略图" }}</span>
						</span>
					</div>
					<el-scrollbar wrap-style="margin-left:20px;" ref="refScrollbar" >
						<div class="thumn-inner-wrap" :style="{ height: isFold ? '0px': '' , width : imgList?.length*204+'px'}">
							<div
								@click="handlerThumb(item)"
								:class="[{ active: k == idx }, 'thumb-img']"
								v-for="(item, k) in imgList"
								:key="item.id"
							>
								<img :src="item?.image" class="imgObj" />
							</div>
						</div>
					</el-scrollbar>
				</div>
			</div>
			<div class="empty" v-else>
				<div class="empty-title">
					<div class="close-icon empty-icon-close" @click="close">
						<el-icon><CloseBold /></el-icon>
					</div>
				</div>
				<div class="empty-content">
					<el-empty
						:image-size="56"
						image="https://baj-dabanjiz-conf.oss-cn-hangzhou.aliyuncs.com/promotion/logo/empty.png"
						description="请重新选择渲染图查看"
					/>
				</div>
			</div>
		</div>
                
 import upIcon from "@/assets/images/up.png";
import downIcon from "@/assets/images/down.png";
import { getPanoToken } from "@/api/modules/renderImg";
interface RenderProsp {
	// isShow?: boolean;
	index?: number;
	planId: string | number;
	imgList?: any[];
	data?: any;
}
const props = withDefaults(defineProps<RenderProsp>(), {
	index: 0,
	planId: "",
	data: {},
	imgList: () => []
});
const emits = defineEmits(["close", "del"]);
const idx = ref<number>(0);
const isFold = ref<boolean>(false);
const scrollDis = ref<number>(0);
// const isShowLinkShare = ref<boolean>(false);

let msgShareRef = ref<InstanceType<typeof MsgShareDialog> | null>(null);
let linkShareRef = ref<InstanceType<typeof LinkShareRef> | null>(null);

const instance = getCurrentInstance() as any;

const curSrc = computed(() => {
	let val = props.imgList[idx.value]?.image ?? "";
	// console.log("props.imgList:", props);
	return val;
});

//当前的数据
const curData = computed(() => {
	let data = {};
	if (props.imgList.length == 1) {
		idx.value = props.index;
		data = props.imgList[idx.value];
	} else if (props.imgList.length > 1) {
		data = props.imgList[idx.value];
	}
	console.log("curData:", data);
	return data;
});

const refScrollbar = ref<any>(null);
const mainImgRef = ref<any>(null);

onMounted(() => {
	idx.value = props.index;
	if(idx.value == props.imgList.length-1){
		console.log("最后一个")
		let space= (props.imgList.length-1)*20
		scrollDis.value = props.imgList.length * (200 + space) ;
	
	}else{
		let marginSpaces=(idx.value-1)*20
		scrollDis.value = props.index * 200 - marginSpaces ;
	}
	nextTick(() => {
		refScrollbar?.value?.setScrollLeft?.(scrollDis.value);
		mainImgRef?.value?.addEventListener("selectstart", () => {
			return false;
		});
	});
});

watch(
	() => props.index,
	n => {
		console.log("n---:", n);
		idx.value = n;
		if(idx.value==props.imgList?.length-1){
			let space= (props.imgList?.length-1)*20
			scrollDis.value = props.imgList.length * (200 + space);
		}else{
			scrollDis.value = n * 200;
		}
		console.log("wathc----idx:", idx);
		setTimeout(()=>{
			refScrollbar?.value?.setScrollLeft?.(scrollDis.value);
		},0)
	}
);

const next = (): void => {

	let imgMaxIndex=props?.imgList?.length - 1
	if (idx.value >= imgMaxIndex) {
		let space=(props?.imgList?.length-1)*20
		idx.value = props?.imgList?.length - 1
		scrollDis.value = 200 * (imgMaxIndex)+space
		return;
	} else {
		let curSpaces=(idx.value-1)*20
		idx.value++;
		scrollDis.value = (200*idx.value)-curSpaces;
	}
	setTimeout(()=>{
		refScrollbar?.value?.setScrollLeft?.(scrollDis.value);
	},0)

}

const prev = (): void => {
	let space=(idx.value-1)*20;
	let imgMaxIndex=props?.imgList?.length - 1

	if (idx.value <= 0) {
		scrollDis.value = 0;
		nextTick(() => {
			refScrollbar?.value?.setScrollLeft(0);
		});
		return;
	} else {
		idx.value--;
		scrollDis.value = (idx.value*200)-space
		console.log("idx.value:",idx.value)
		console.log("scrollDis.value:",scrollDis.value)
		setTimeout(()=>{
			refScrollbar.value?.setScrollLeft?.(scrollDis.value);
		},0)
	}
}

const parseImgType = (type: string | number): void => {
	// imgType :1 效果图 2 全景图 3 全屋漫游
	switch (type) {
		case 1:
			return "效果图";
		case 2:
			return "全景图";
		case 3:
			return "全屋漫游";
		default:
			return "效果图";
	}
};
const handlerThumb = item => {
	let index = props.imgList.findIndex(k => k.id == item.id);
	console.log("index:", index);
	idx.value = index;
	let marginSpace=(idx.value-1)*20
	scrollDis.value = 204 * index - marginSpace;
	if (refScrollbar?.value) {
		refScrollbar?.value?.setScrollLeft(scrollDis.value);
	}
};
const close = () => {
	emits("close", false);
	// emit("update:isShow", false);
};
const toggleThumb = (): void => {
	isFold.value = !isFold.value;
};



            
                

这里代码有点多。其实就是2个核心逻辑,点击底下折叠icon,折叠的时候显示A模板,打开的时候显示B模板,通过v-if做判断,而图片的定位通过索引值去定位,这样通过缩略图点击打开弹窗的时候,就知道当前这张图片在imgList中的位置。然后把索引交给当前弹窗中的一个值idx,左右切换的逻辑通过更改idx的值,切换图片。 每次点击缩略图打开弹窗的时候,监听下当前的索引值。然后模板中的数据都取自curData,而curData则是通过idx的值获取到的,这样能保证每次idx的值变化,curData就自动更新。最后说一说底下缩略图滚动的位置问题, 缩略图位置滚动是通过element-plus的el-scroll组件的setScrollLeft来实现的。这里特别要注意的由于设计稿中每个缩略图中间的margin是10,所以这里每次调用setScrollLeft的时候都计算了下中间的间隙。否则会出现缩略图定位半截的情况, 最后一点要注意的是:这里折叠的问题。之前我是通过添加class: 通过设置display:none/block来实现。但是发现在某些其他环境中会出现InsertBefore的报错问题,(因为这个项目是web嵌套到qt环境中)。所以最后采用设置高度来解决,也就是在缩略图折叠的把缩略图的整体告诉设置为0