vue快捷工具元件:小地圖導航、全屏、重新整理

hong_li發表於2024-11-22

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>

相關文章