vue快捷工具元件:小地圖導航、全屏、重新整理
<!--
* @description 快捷工具
!-->
<template>
<div class="quick-tools" :style="{ ...quickToolsStyle }">
<template v-for="val in tools">
<tooltip v-if="val === 'full'" :key="val" :content="isFullScreen ? '退出全屏' : '全屏'" :is-update-popper="true" placement="top" effect="light" :visible-arrow="false">
<div class="tool-item" @click="itemClick(val)">
<i class="icon" :class="`ic-${val} ${isFullScreen ? 'ic-full-exit' : ''}`"></i>
</div>
</tooltip>
<tooltip v-if="val === 'refresh'" :key="val" content="重新整理" placement="top" effect="light" :visible-arrow="false">
<div class="tool-item" @click="itemClick(val)">
<i class="icon" :class="`ic-${val}`"></i>
</div>
</tooltip>
<tooltip v-if="val === 'map'" :key="val" content="小地圖" placement="top" effect="light" :visible-arrow="false">
<div class="tool-item" :class="{ 'is-active': showMapPopover }" @click="itemClick(val)">
<i class="icon" :class="`ic-${val}`"></i>
</div>
</tooltip>
</template>
<div v-if="showMapPopover" ref="mapPopoverRef" class="map-popover">
<div ref="mapContentRef" class="map-content">
<div
v-for="(arr, index) in mapList"
:key="`map_list_${index}`"
class="map-box-out"
:style="{ width: `${arr.length * 10 + 6}px` }"
:class="{ 'is-active': currentMapBoxIndex === index }"
@click="mapBoxClick(arr, index)"
>
<div class="map-box">
<div v-for="(child, childIndex) in arr" :key="`map_list_${index}_child_${childIndex}`" class="map-li" :style="{ height: `${child.value}%` }"></div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Tooltip from '@/views/flow/components/tooltip.vue'
export default {
name: 'QuickTools',
components: { Tooltip },
props: {
tools: {
type: Array,
default: () => ['full', 'refresh', 'map']
},
// 地圖導航,滾動的容器
scrollContainerClass: {
type: String,
default: 'scroll-container-wp'
},
// 節點垂直滾動容器
nodeScrollContainerClass: {
type: String,
default: 'content-box-wp'
},
// 節點滾動容器距離頭部的高度
nodeTopHeight: {
type: Number,
default: 90
},
quickToolsStyle: {
type: Object,
default: () => ({})
},
// 需要全屏的容器
fullScreenContainerClass: {
type: String,
default: 'full-screen-container'
},
refreshFunc: {
type: Function,
default: null
}
},
data() {
return {
showMapPopover: false,
mapList: [],
currentMapBoxIndex: null,
nodeScrollContainerAll: [],
isFullScreen: false
}
},
watch: {
showMapPopover(val) {
if (val) {
this.$nextTick(() => {
const mapPopoverWidth = this.$refs.mapPopoverRef.clientWidth
const mapContentWidth = this.$refs.mapContentRef.clientWidth
if (mapPopoverWidth > mapContentWidth) return
const ratio = (mapPopoverWidth - 16) / mapContentWidth
this.$refs.mapContentRef.style.transform = `scale(${ratio}, 1)`
})
}
}
},
mounted() {
document.addEventListener('click', this.clsoeMapPopover, true)
},
methods: {
clsoeMapPopover(e) {
if (this.$refs.mapPopoverRef && !this.$refs.mapPopoverRef.contains(e.target)) {
this.showMapPopover = false
}
},
itemClick(val) {
switch (val) {
case 'full':
this.isFullScreen = !this.isFullScreen
this.fullScreen()
break
case 'refresh':
this.refreshFunc && this.refreshFunc()
break
case 'map':
this.showMapPopover = !this.showMapPopover
this.getMapList()
break
default:
break
}
},
fullScreen() {
const fullScreenContainer = document.querySelector(`.${this.fullScreenContainerClass}`)
if (this.isFullScreen) {
fullScreenContainer.classList.add('full-screen-container-style')
} else {
fullScreenContainer.classList.remove('full-screen-container-style')
}
},
mapBoxClick(arr, index) {
this.currentMapBoxIndex = index
const scrollContainer = document.querySelector(`.${this.scrollContainerClass}`)
const scrollContainerScrollWidth = scrollContainer.scrollWidth
// 第一頁
if (index === 0) {
scrollContainer.scrollTo(0, 0)
return
}
// 最後一頁
if (index === this.mapList.length - 1) {
scrollContainer.scrollTo(scrollContainerScrollWidth, 0)
return
}
const ele = this.nodeScrollContainerAll[arr[0].index]
const eleOffsetLeft = ele.offsetLeft
const scrollContainerScrollLeft = scrollContainer.scrollLeft
if (eleOffsetLeft === scrollContainerScrollLeft) {
return
}
let left = eleOffsetLeft
if (left === 16) {
left = 0
}
scrollContainer.scrollTo({
left: left
})
},
getMapList() {
if (!this.showMapPopover) return
this.currentMapBoxIndex = null
this.mapList = []
const scrollContainer = document.querySelector(`.${this.scrollContainerClass}`)
this.nodeScrollContainerAll = document.querySelectorAll(`.${this.nodeScrollContainerClass}`)
const scrollContainerClientWidth = scrollContainer.clientWidth
const pageSize = Math.floor(scrollContainerClientWidth / 266)
const nodeTotal = this.nodeScrollContainerAll.length
const pageTotal = Math.ceil(nodeTotal / pageSize)
const noedeHeightArr = []
this.nodeScrollContainerAll.forEach(item => {
noedeHeightArr.push(item.scrollHeight + 90)
})
const nodeMaxHeight = Math.max(...noedeHeightArr)
const noedeHeightPercentageArr = noedeHeightArr.map((val, index) => {
return {
index: index,
value: (val / nodeMaxHeight) * 100
}
})
for (let i = 0; i < pageTotal; i++) {
const arr = this.paginateArray(pageSize, i + 1, noedeHeightPercentageArr)
this.mapList.push(arr)
}
},
paginateArray(pageSize, pageNumber, arr) {
const startIndex = (pageNumber - 1) * pageSize
const endIndex = Math.min(startIndex + pageSize, arr.length)
return arr.slice(startIndex, endIndex)
}
}
}
</script>
<style lang="scss" scoped>
.quick-tools {
position: absolute;
display: flex;
height: 32px;
width: max-content;
right: 32px;
bottom: 32px;
align-items: center;
z-index: 99;
.tool-item {
display: flex;
width: 32px;
height: 32px;
margin-left: 8px;
justify-content: center;
align-items: center;
background: #ffffff;
border-radius: 4px;
border: 1px solid #f2f3f5;
cursor: pointer;
&.is-active {
background: #e4e7ec;
}
&:first-child {
margin-left: 0;
}
&:hover {
background: #edf0f3;
}
.icon {
display: inline-block;
width: 16px;
height: 16px;
&.ic-full {
background: url('~@/assets/images/flow/ic_full.png') no-repeat center center;
background-size: 100% 100%;
&.ic-full-exit {
background: url('~@/assets/images/flow/ic_full_exit.png') no-repeat center center;
background-size: 100% 100%;
}
}
&.ic-refresh {
background: url('~@/assets/images/flow/ic_refresh.png') no-repeat center center;
background-size: 100% 100%;
}
&.ic-map {
background: url('~@/assets/images/flow/ic_map.png') no-repeat center center;
background-size: 100% 100%;
}
}
}
.map-popover {
position: absolute;
min-width: 302px;
max-width: 1000px;
height: 192px;
right: 0;
bottom: 36px;
padding: 8px;
background: #ffffff;
border-radius: 4px;
border: 1px solid #ebedf3;
overflow: hidden;
.map-content {
display: flex;
min-width: 100%;
width: max-content;
height: 100%;
flex-wrap: nowrap;
justify-content: center;
white-space: nowrap;
transform-origin: left center;
.map-box-out {
display: flex;
height: 100%;
justify-content: center;
align-items: center;
box-sizing: border-box;
cursor: pointer;
border-radius: 4px;
overflow: hidden;
&.is-active {
background: #db9130;
}
.map-box {
display: flex;
height: calc(100% - 4px);
justify-content: center;
box-sizing: border-box;
cursor: pointer;
border-radius: 4px;
background: #fff;
overflow: hidden;
.map-li {
display: inline-block;
width: 6px;
margin-left: 2px;
margin-right: 2px;
white-space: nowrap;
background: #fff7eb;
}
}
}
}
}
}
</style>
<style lang="scss">
.full-screen-container-style {
position: fixed !important;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 99;
}
</style>