首先我们看一看整体的效果
点击图一的缩略图,查看大图(如图二)。同时弹窗内容底下有当前所有图片的缩略图展示,同时通过一个橙色的边框定位当前缩略图的位置。切换左右箭头滚动下一站或者上一张,同时底下缩略图同步有个激活样式,切换至第一张点击不再向前切换,点击右侧按钮同理。
先来说下基本思路:
1 整个弹窗我们通过teleport来实现
2 底下的缩略图我们通过el-scroll这个组件来实现,至于点击切换,则是我们通过index索引找到图片,当前前提是我们必须要图一的列表传入到弹层组件里面。
3 点击左右按钮,我们给index++或者--,底下的缩略图的联动则是通过el-scroll的setScrollLeft(number)来实现。每次滚动的距离则是通过当前这张图的索引*缩略图本身的宽度
我们把图一中的每个卡片item封装成一个组件。名曰RenderItem
那么图一的结果就如下:
<div class="render-list">
<RenderItem v-for="item in list" :key="item.id" :data="item" :imgList="list" @del="delData" />
<div class="vis" v-for="num in 4" :key="num"></div>
</div>
const arr = [
{
id: "001",
title: "111111",
url: "https://baj-dabanjiz-conf.oss-accelerate.aliyuncs.com/pano/85875_1641469258147/MuliplePano_thumbnail_1641469245589.jpg",
spaceName: "主卧"
},
{
id: "002",
title: "111111",
url: "http://baj-dabanjiz-conf.oss-cn-hangzhou.aliyuncs.com/promotion/assistant/setmeal/5/%E5%B0%81%E9%9D%A2.png",
spaceName: "次卧"
}]
我们再看看renderItem内部的核心代码:
<RenderImgDialog v-if="isView" :index="curIndex" :imgList="picList" @close="toggleRenderImg(false)" />
</template>
<script setup lang="ts">
const isShow = ref<boolean>(false);
const isComplete = ref<boolean>(true);
const isView = ref<boolean>(false);
const curIndex = ref<number>(0);
interface renderItem {
id: string;
title: string;
url: string;
spaceName: string;
checked?: boolean;
}
const emits = defineEmits(["del"]);
const props = defineProps({
data: {
type: Object,
default: () => {}
},
imgList: {
type: Array,
default: () => []
}
});
const picList = computed(() => {
return props.imgList.map(item => item?.url);
});
watch(
() => props.data.checked,
(n: boolean): void => {
console.log("n---", n);
n ? (isShow.value = true) : (isShow.value = false);
console.log("isShow:", isShow.value);
console.log("imgList:", props.imgList);
}
);
const toggleRenderImg = (flag: boolean) => {
isView.value = flag;
let res = props.imgList;
//根据id-->当前图片的index
let tempIndex = 0;
props.imgList.forEach((item, index): void => {
if (item.id == props.data.id) {
tempIndex = index;
}
});
curIndex.value = tempIndex;
console.log("tempIndex:", tempIndex);
};
我们把图二的弹窗封装成了一个组件,名曰RenderImgDialog。然后通过isView来控制它是否展示。然后我们点击图一的某张卡片图的时候给他传入当前图片的索引和图片的列表
接下来我们看看RenderImgDialog里面是怎么做的?
<template>
<teleport to="#app">
<!-- <transition name="el-fade-in-linear"> -->
<div>
<div class="mask"></div>
<div class="modal">
<div class="container">
<div class="modal-head">
<div class="modal-title">标题部分</div>
<div class="close-icon" @click="close">
<el-icon><CloseBold /></el-icon>
</div>
</div>
<div class="content">
<div class="lt-arrow" @click="prev">
<el-icon><ArrowLeftBold /></el-icon>
</div>
<div class="mid-imgWrap" ref="mainImgRef">
<el-image :src="curSrc" fit="cover" style="width: 812px; height: 534px"></el-image>
</div>
<div class="rt-arrow" @click="next">
<el-icon><ArrowRightBold /></el-icon>
</div>
</div>
<div class="desc-wrap">
<el-tooltip placement="top-start" effect="light">
<template #content>
<h2 class="tip-title">图片信息</h2>
<div>
<el-descriptions title="User Info" direction="vertical" :column="2" :width="72" size="small">
<el-descriptions-item label="空间">全屋</el-descriptions-item>
<el-descriptions-item label="包含商品">18100000000</el-descriptions-item>
<el-descriptions-item label="方案介绍">Suzhou</el-descriptions-item>
<el-descriptions-item label="生成时间">
<el-tag size="small">School</el-tag>
</el-descriptions-item>
<el-descriptions-item label="有效期">No.1188, Wuzhong Avenue,</el-descriptions-item>
</el-descriptions>
</div>
</template>
<el-icon class="item-icon"><InfoFilled /></el-icon>
</el-tooltip>
<el-tooltip content="全屏" placement="top-start" effect="light">
<el-icon class="item-icon"><ScaleToOriginal /></el-icon>
</el-tooltip>
<el-tooltip content="分享" placement="top-start" effect="light">
<el-icon class="item-icon"><Share /></el-icon>
</el-tooltip>
<el-tooltip content="删除" placement="top-start" effect="light">
<el-icon class="item-icon"><Delete /></el-icon>
</el-tooltip>
<el-tooltip content="下载" placement="top-start" effect="light">
<el-icon class="item-icon"><Download /></el-icon>
</el-tooltip>
<el-tooltip content="设置" placement="top-start" effect="light">
<el-icon class="item-icon"
><el-icon><Setting /></el-icon
></el-icon>
</el-tooltip>
<span class="line">|</span>
<span @click="toggleThumb">
<el-icon :class="isFold ? 'arrow-down' : 'arrow-up'"><ArrowDownBold /></el-icon>
<span class="fold">收起缩略图</span>
</span>
</div>
<!-- <transition name="el-fade-in-linear"> -->
<!-- <section class="thumb-wrap" > -->
<el-scrollbar :class="isFold ? 'vis' : ''" wrap-style="margin-left:20px" ref="refScrollbar">
<div class="thumn-inner-wrap">
<div :class="[{ active: k == idx }, 'thumb-img']" v-for="(item, k) in imgList" :key="k">
<img :src="item" class="imgObj" />
</div>
</div>
</el-scrollbar>
<!-- </section> -->
<!-- </transition> -->
</div>
</div>
</div>
<!-- </transition> -->
</teleport>
</template>
<script setup lang="ts">
interface RenderProsp {
isShow?: boolean;
index?: number;
imgList?: string[];
}
const props = withDefaults(defineProps<RenderProsp>(), {
isShow: false,
index: 0,
imgList: () => []
});
const emit = defineEmits(["close", "update:isShow"]);
const idx = ref<number>(0);
const isFold = ref<boolean>(false);
const scrollDis = ref<number>(0);
const curSrc = computed(() => {
return props.imgList[idx.value];
});
const refScrollbar = ref<any>(null);
const mainImgRef = ref<any>(null);
const originListLen = toRaw(props.imgList.length);
onMounted(() => {
idx.value = props.index;
scrollDis.value = props.index * 200;
console.log("scrollDis.value:", scrollDis.value);
nextTick(() => {
// if (refScrollbar?.value?.setScrollLeft) {
let bar = refScrollbar.value;
console.log("bar", bar);
refScrollbar?.value?.setScrollLeft(scrollDis.value);
mainImgRef?.value?.addEventListener("selectstart", () => {
return false;
});
// }
});
});
watch(
() => props.index,
n => {
idx.value = n;
scrollDis.value = n * 200;
refScrollbar?.value?.setScrollLeft(scrollDis.value);
}
);
const next = (): void => {
if (idx.value >= originListLen - 1) {
scrollDis.value = 200 * originListLen;
return;
} else {
scrollDis.value += 200;
idx.value++;
}
console.log("index:", idx.value);
console.log("scrollDis.value:", scrollDis.value);
if (refScrollbar.value) {
refScrollbar.value!.setScrollLeft(scrollDis.value);
}
};
const prev = (): void => {
if (idx.value <= 0) {
scrollDis.value = 0;
nextTick(() => {
// refScrollbar.value.setScrollLeft(0);
});
return;
} else {
idx.value--;
scrollDis.value -= 200;
}
console.log("index:", idx.value);
console.log("scrollDis.value:", scrollDis.value);
refScrollbar.value.setScrollLeft(scrollDis.value);
};
const close = () => {
emit("close", false);
// emit("update:isShow", false);
};
const toggleThumb = (): void => {
isFold.value = !isFold.value;
console.log("isFold.value", isFold.value);
};
</script>
<style scoped>
.modal {
width: 1020px;
height: 819px;
opacity: 1;
border-radius: 8px;
background: rgba(41, 42, 46, 1);
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 100;
}
.mask {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
background: rgba(0, 0, 0, 1);
z-index: 99;
}
.modal-head {
padding: 25px 0 23px 0;
box-sizing: border-box;
display: flex;
text-align: center;
}
.modal-title {
width: 90%;
}
.close-icon {
flex: 1;
padding-right: 24px;
box-sizing: border-box;
}
.content {
display: flex;
}
.close-icon {
display: flex;
justify-content: flex-end;
}
.mid-imgWrap {
width: 812px;
height: 534px;
flex-shrink: 0;
-webkit-user-select: none;
}
.lt-arrow,
.rt-arrow {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.desc-wrap {
text-align: center;
padding: 25px 0;
}
.tip-title {
font-size: 14px;
font-weight: 600;
margin-bottom: 16px;
}
.item-icon {
margin-left: 24px;
font-size: 22px;
}
.fold {
color: rgba(255, 255, 255, 0.6);
font-size: 14px;
margin-left: 4px;
vertical-align: baseline;
}
.line {
display: inline-block;
margin: 0 24px;
padding-top: -10px;
color: #fff;
opacity: 0.2;
}
.thumb-wrap {
width: 100%;
height: 118px;
padding-left: 20px;
box-sizing: border-box;
overflow-x: scroll;
overflow-y: hidden;
}
.thumn-inner-wrap {
/* width: 200%; */
display: flex;
height: 118px;
box-sizing: border-box;
transition: all 0.1s ease-in;
/* overflow-y: hidden; */
/* flex-wrap: wrap; */
}
.thumn-inner-wrap::-webkit-scrollbar {
width: 1px; /*高宽分别对应横竖滚动条的尺寸*/
height: 1px;
}
.thumn-inner-wrap::-webkit-scrollbar-thumb {
background: #fff;
}
.thumn-inner-wrap::-webkit-scrollbar-button {
background: #f00;
}
.thumb-img {
width: 180px;
height: 118px;
background: #ccc;
margin-right: 10px;
flex-shrink: 0;
margin-right: 10px;
display: flex;
justify-content: center;
}
.imgObj {
max-width: 100%;
max-height: 100%;
object-fit: cover;
}
.active {
border: 2px solid rgba(254, 113, 0, 1);
}
.arrow-down {
width: 16px;
height: 16px;
display: inline-block;
animation: down 0.3s forwards;
}
.arrow-up {
width: 16px;
height: 16px;
display: inline-block;
animation: up 0.3s forwards;
}
@keyframes down {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(180deg);
}
}
@keyframes up {
0% {
transform: rotate(90deg);
}
100% {
transform: rotate(0deg);
}
}
.scroll-dom {
transition: all 0.3s linear;
}
.vis {
height: 0;
}
</style>
预览弹窗的组件里注意看curSrc,我们通过index得到当前list中的图片地址,然后监听props.index,为了避免数据流的混乱,我们在组件内部定义了一个idx的ref,初始化的时候把props.index赋值给他,props.index被监听每次变化的时候再次做赋值。所有切换的操作根据idx值的变化来动态更改。最后就是底下缩略图的定位问题。其实只要每次切换上一张下一张的时候我们更改idx,同时通过el-scroll提供的setScrollLeft设置其值就可以了。而这个值就是当前idx*缩略图本身的宽度