vue3 PC端-索引列表组件

前段时间有个项目,客户需求里有明确的要索引列表,自己花了20分钟写了一个组件,写篇文章记录一下~

很简单一个组件,学前端的都会写,很多容器大小自适应功能都没写,可自行补充。

如有不对的地方希望大佬能指导一下 在下

新建一个.vue文件,代码如下

vue 复制代码
<!-- 
使用方法:
props传入indexs和list,indexs(一维数组)为右侧索引列表,list(二维数组)为索引对应的列表
<template>
<IndexList :indexs="['A', 'B', 'C', 'D', 'E', 'F', 'G']" :list="[
                ['列表A1', '列表A2', '列表A3', '列表A4'],
                ['列表B1', '列表B2', '列表B3'],
                ['列表C1', '列表C2', '列表C3', '列表C4'],
                ['列表D1', '列表D2', '列表D3'],
                ['列表E1', '列表E2', '列表E3', '列表E4'],
                ['列表F1', '列表F2', '列表F3'],
                ['列表G1', '列表G2', '列表G3']
            ]" @choose="chooseHandle" :indexScale=".7" />
</template>
<script setup>
const chooseHandle = (text, index) => {
console.log(text, index)
}
</script>
-->
<template>
    <div class="index-list-container">
        <div class="left" ref="listRef" @wheel="scrollHandle">
            <div :id="item" class="list-item" v-for="(item, index) in props.indexs" :key="index">
                <template v-if="props.list.length > index">
                    <div class="title">
                        {{ item }}
                    </div>
                    <div class="list">
                        <ul>
                            <li v-for="(listItem, listIndex) in props.list[index]" :key="listIndex"
                                @click="chooseHandle(listItem, item)">{{ listItem }}</li>
                        </ul>
                    </div>
                </template>
            </div>
        </div>
        <div class="right">
            <div class="index-container" :style="{ transform: `scale(${props?.indexScale})` }">
                <div :class="{ 'index-item': true, 'active-index-item': activeIndex === index }"
                    v-for="(item, index) in props.indexs" @mouseenter="mouseEnterHandle(item, index)" :key="index">
                    {{ item }}
                </div>
            </div>
        </div>
    </div>
</template>
<script setup>
import { ref, defineProps, defineEmits } from 'vue'
const props = defineProps({
    indexs: {
        type: Array,
        default: () => []
    },
    list: {
        type: Array,
        default: () => []
    },
    indexScale: {
        type: Number,
        default: 1
    }
})
const emit = defineEmits(['choose'])
const listRef = ref(null)
const activeIndex = ref(0)
let timer = null
const mouseEnterHandle = (letter, index) => {
    if (listRef.value.children[index].getAttribute('id') === letter) listRef.value.children[index].scrollIntoView({
        behavior: 'smooth',
        block: 'start'
    })
    activeIndex.value = index
}

const scrollHandle = () => {
    if (timer) clearTimeout(timer)
    timer = window.setTimeout(() => {
        const listItems = listRef.value.children
        const scrollTop = listRef.value.scrollTop

        // 遍历找到当前最靠近顶部的项
        for (let i = 0; i < listItems.length; i++) {
            const item = listItems[i]
            const offsetTop = item.offsetTop
            // 下一项的 offsetTop
            const nextOffsetTop = listItems[i + 1] ? listItems[i + 1].offsetTop : Infinity

            // 当前项在可视区内
            if (scrollTop >= offsetTop - 10 && scrollTop < nextOffsetTop - 10) {
                activeIndex.value = i
                break
            }
        }
    }, 100)
}
const chooseHandle = (text, index) => {
    emit('choose', text, index)
}
</script>
<style scoped lang="scss">
.index-list-container {
    width: 100%;
    height: 100%;
    display: flex;

    >.left {
        position: relative;
        padding: 0 10px 0 0;
        overflow-y: auto;
        width: calc(100% - 30px);

        >.list-item {
            >.title {
                display: flex;
                align-items: center;
                padding-left: 10px;
                height: 30px;
                background-color: pink;
                font-weight: 700;
                font-size: 18px;
                color: #000000;
                background-color: #f8f9fa;
            }

            >.list {
                padding: 10px;

                >ul {
                    padding: 0;
                    margin: 0;

                    >li {
                        padding-left: 10px;
                        display: flex;
                        align-items: center;
                        height: 50px;
                        list-style: none;
                        border-bottom: 1px solid #f6f6f6;
                        font-size: 15px;
                        color: #868686;
                    }

                    >li:hover {
                        cursor: pointer;
                        color: #4ea5ff;
                        background-color: #ecf5ff;
                    }
                }
            }
        }

        &::-webkit-scrollbar {
            width: 0;
        }
    }

    >.right {
        width: 30px;
        display: flex;
        justify-content: center;
        align-items: center;

        >.index-container {
            background-color: #f5f5f6;
            border-radius: 10px;
            padding: 3px;
            display: flex;
            flex-direction: column;
            row-gap: 3px;
            transform-origin: center center;

            >.index-item {
                cursor: pointer;
                display: flex;
                justify-content: center;
                align-items: center;
                border-radius: 50%;
                font-size: 15px;
                height: 20px;
                width: 20px;

                // &:hover {
                //     color: #ffffff;
                //     background-color: #55b685;
                // }
            }

            >.active-index-item {
                color: #ffffff;
                background-color: #55b685;
            }
        }
    }
}
</style>

可直接引入使用

如下:

vue 复制代码
<template>
    <div style="height: 70vh">
            <IndexList :indexs="['A', 'B', 'C', 'D', 'E', 'F', 'G']" :list="[
                ['列表A1', '列表A2', '列表A3', '列表A4'],
                ['列表B1', '列表B2', '列表B3'],
                ['列表C1', '列表C2', '列表C3', '列表C4'],
                ['列表D1', '列表D2', '列表D3'],
                ['列表E1', '列表E2', '列表E3', '列表E4'],
                ['列表F1', '列表F2', '列表F3'],
                ['列表G1', '列表G2', '列表G3']
            ]" @choose="chooseHandle" :indexScale=".7" />
        </div>
</template>
<script setup>
import { ref, defineExpose } from 'vue'
import IndexList from '@/components/IndexList/index.vue'
const chooseHandle = (text, index) => {
    console.log(text, index)
}
defineExpose({
    setDialogVisible
})
</script>
<style scoped lang="scss"></style>

效果图如下:

相关推荐
Mh1 天前
别再只会 `find` 了:Map 在前端业务里的真实用法
前端·javascript
陈随易1 天前
FFmpeg 9.0 发布,代号 Lei,音视频处理再升级
前端·后端·程序员
爱丶狸1 天前
Grafana_Zabbix_ImageRenderer_部署与前端操作手册
linux·前端·zabbix·grafana·kylin
用户059540174461 天前
把AI对话记忆存储测试从手工改成Playwright+pytest,覆盖率从20%提到96%,回归时间缩短90%
前端·css
kyriewen1 天前
别再这样写TypeScript了——Code Review中最常见的8个反模式
前端·javascript·typescript
名字还没想好☜1 天前
Next.js ‘use client‘ 到底加在哪:Server/Client Components 边界与常见报错
开发语言·前端·javascript·react·next.js
午安~婉1 天前
GitHub Token/ GitHub Stats统计显示图异常
前端·github·vercel·github token
码云之上1 天前
AI Agent 工程化总览篇:从 Prompt 到 Harness
前端·人工智能
2501_926978331 天前
以说明书 DNA 为模板——完整 AGI 的结构图景
前端·人工智能·经验分享·笔记·ai写作
IT_陈寒1 天前
Vite静态资源引用这个坑我踩得有点疼
前端·人工智能·后端