vue3 项目中 使用 leader-line-vue 绘制指引线

vue3 项目中 使用 leader-line-vue 绘制指引线

项目需要点击菜单并且使用联动展示

官网地址

链接: 参考

部分预览:
效果图预览
在这里插入图片描述

1、安装:npm i leader-line-vue
2、页面 script 中引入:import LeaderLine from ‘leader-line-vue’;
3、创建页面元素

<div class="left">
     <el-menu :default-active="activeJob" class="job-menu" :collapse="false">
         <el-menu-item v-for="job in jobs" :key="job.id" :index="job.id" @click="handleSelect(job)">
             <div :id="`job-${job.id}`" class="job-menu-item-inner">
                 {{ job.name }}
             </div>
         </el-menu-item>
     </el-menu>
 </div>
 <div class="right overflow-y-auto">
     <el-card v-for="ability in abilities" :key="ability.id" :id="`ability-${ability.id}`"
         @click="handleAbilityClick(ability.id)" class="ability-card">
         {{ ability.name }}
     </el-card>
 </div>

4、获取页面元素添加划线(使用 LeaderLine.setLine !!!

// 绘制线条
function drawLines() {
    // 清除线条
    clearLines()
    // 等待DOM更新后执行
    nextTick(() => {
        // 获取当前职业和技能的DOM元素
        const jobEl = document.getElementById(`job-${activeJob.value}`)
        const abilityEl = document.getElementById(`ability-${activeAbility.value}`)

        // 如果DOM元素存在且LeaderLine存在且LeaderLine.setLine方法存在
        if (jobEl && abilityEl && LeaderLine && LeaderLine.setLine) {
            // 使用LeaderLine.setLine方法绘制线条
            const line = LeaderLine.setLine(
                jobEl,
                abilityEl,
                {
                    // 线条颜色
                    color: '#2196f3',
                    // 线条宽度
                    size: 1.5,
                    // 线条路径
                    path: 'fluid',
                    // 起始端点样式
                    startPlug: 'disc',
                    // 结束端点样式
                    endPlug: 'arrow3',
                    // 是否使用渐变
                    gradient: true,
                    // 是否使用阴影
                    dropShadow: true,
                    // 是否使用虚线
                    dash: { animation: true }
                }
            )
            // 将线条添加到lines数组中
            lines.push(line)
        }
    })
}

5、每次点击需要清空连线的对象

// 连线对象
let lines: any[] = []
function clearLines() {
    lines.forEach(line => line.remove())
    lines = []
}

特别注意!!!
a、不要和 leader-line 弄混,leader-line 是直接使用构造函数,可以直接使用new LeaderLine(startElement, endElement, {color: ‘red’, size: 8});

b、leader-line-vue 使用 LeaderLine.setLine(jobEl, abilityEl, { …options })

页面完整代码

<template>
    <div>
        <div class="ability-map">
            <div class="left">
                <el-menu :default-active="activeJob" class="job-menu" :collapse="false">
                    <el-menu-item v-for="job in jobs" :key="job.id" :index="job.id" @click="handleSelect(job)">
                        <div :id="`job-${job.id}`" class="job-menu-item-inner">
                            {{ job.name }}
                        </div>
                    </el-menu-item>
                </el-menu>
            </div>
            <div class="right overflow-y-auto">
                <el-card v-for="ability in abilities" :key="ability.id" :id="`ability-${ability.id}`"
                    @click="handleAbilityClick(ability.id)" class="ability-card">
                    {{ ability.name }}
                </el-card>
            </div>
        </div>
    </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
import { ElMenu, ElMenuItem, ElCard } from 'element-plus'
import 'element-plus/dist/index.css'
import LeaderLine from 'leader-line-vue'

// 岗位数据
const jobs = [
    { id: 'job2', name: '第一个', abilities: ['b1', 'b2', 'b3'] },
    { id: 'job3', name: '第二个', abilities: ['c1', 'c2', 'c3'] },
    { id: 'job4', name: '第三个', abilities: ['d1', 'd2', 'd3'] },
    { id: 'job5', name: '第四个', abilities: ['e1', 'e2', 'e3'] },
]

// 能力项数据
const allAbilities = [
    { id: 'b1', name: '1.1第一个' },
    { id: 'b2', name: '2.1第一个' },
    { id: 'b3', name: '3.1第一个' },

    { id: 'c1', name: '1.1第二个' },
    { id: 'c2', name: '2.1第二个' },
    { id: 'c3', name: '3.1第二个' },

    { id: 'd1', name: '1.1第三个' },
    { id: 'd2', name: '2.1第三个' },
    { id: 'd3', name: '3.1第三个' },

    { id: 'e1', name: '1.1第四个' },
    { id: 'e2', name: '2.1第四个' },
    { id: 'e3', name: '3.1第四个' },
]

// 当前选中的岗位
const activeJob = ref(jobs[0].id)
const selectedAbilities = computed(() => {
    return jobs.find(j => j.id === activeJob.value)?.abilities ?? []
})
const abilities = computed(() => {
    return allAbilities.filter(a => selectedAbilities.value.includes(a.id))
})

// 连线对象
let lines: any[] = []
function clearLines() {
    lines.forEach(line => line.remove())
    lines = []
}

const activeAbility = ref<string | null>('b1')
// 能力项点击事件
function handleAbilityClick(id: string) {
    activeAbility.value = id
    drawLines()
}

// 绘制线条
function drawLines() {
    // 清除线条
    clearLines()
    // 等待DOM更新后执行
    nextTick(() => {
        // 获取当前职业和技能的DOM元素
        const jobEl = document.getElementById(`job-${activeJob.value}`)
        const abilityEl = document.getElementById(`ability-${activeAbility.value}`)

        // 如果DOM元素存在且LeaderLine存在且LeaderLine.setLine方法存在
        if (jobEl && abilityEl && LeaderLine && LeaderLine.setLine) {
            // 使用LeaderLine.setLine方法绘制线条
            const line = LeaderLine.setLine(
                jobEl,
                abilityEl,
                {
                    // 线条颜色
                    color: '#2196f3',
                    // 线条宽度
                    size: 1.5,
                    // 线条路径
                    path: 'fluid',
                    // 起始端点样式
                    startPlug: 'disc',
                    // 结束端点样式
                    endPlug: 'arrow3',
                    // 是否使用渐变
                    gradient: true,
                    // 是否使用阴影
                    dropShadow: true,
                    // 是否使用虚线
                    dash: { animation: true }
                }
            )
            // 将线条添加到lines数组中
            lines.push(line)
        }
    })
}

// 岗位切换
function handleSelect(val: any) {
    activeJob.value = val.id
    activeAbility.value = val.abilities[0]
}

// 监听岗位切换和窗口变化
watch([activeJob, abilities], () => {
    drawLines()
})

let scrollBox: HTMLElement | null = null

onMounted(async () => {
    drawLines()
    window.addEventListener('resize', drawLines)
    // 获取右侧滚动容器
    nextTick(() => {
        scrollBox = document.querySelector('.right')
        if (scrollBox) {
            scrollBox.addEventListener('scroll', handleScroll, false)
        }
    })
})
onBeforeUnmount(() => {
    clearLines()
    window.removeEventListener('resize', drawLines)
    if (scrollBox) {
        scrollBox.removeEventListener('scroll', handleScroll)
    }
})
function handleScroll() {
    // 重新定位所有连线
    lines.forEach(line => line.position())
}

</script>

<style scoped>
.ability-map {
    display: flex;
    width: 100%;
    height: 700px;
    background: radial-gradient(ellipse at 60% 40%, #11204a 60%, #061b3a 100%);
    border-radius: 24px;
    padding: 32px;
    box-shadow: 0 0 40px #0a1a3a inset;
    position: relative;
    overflow: hidden;
}

.left {
    width: 240px;
    margin-right: 32px;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.job-menu {
    background: transparent;
    color: #fff;
    border: none;
    width: 100%;
}

.job-menu-item-inner {
    width: 100%;
    height: 64px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 18px;
    background: linear-gradient(90deg, #1a2a4f 80%, #2b3e6b 100%);
    color: #b3d1ff;
    font-size: 20px;
    font-weight: bold;
    box-shadow: 0 0 12px #1e2e5a;
    transition: background 0.3s, color 0.3s, box-shadow 0.3s;
}

.el-menu-item.is-active .job-menu-item-inner,
.el-menu-item:hover .job-menu-item-inner {
    background: linear-gradient(90deg, #2b5cff 60%, #1a2a4f 100%);
    color: #fff;
    border: 2px solid #00ffcc;
    box-shadow: 0 0 24px #00ffcc, 0 0 32px #1e2e5a inset;
}

.right {
    width: 340px;
    display: flex;
    flex-direction: column;
    gap: 18px;
}

.ability-card {
    background: linear-gradient(90deg, #173b6c 60%, #1a237e 100%);
    color: #fff;
    border-radius: 18px;
    font-size: 18px;
    font-weight: bold;
    margin-bottom: 0;
    box-shadow: 0 0 16px #1e2e5a, 0 0 32px #0a1a3a inset;
    transition: background 0.2s, border 0.2s, box-shadow 0.2s;
    cursor: pointer;
    border: 2px solid transparent;
    min-height: 48px;
    display: flex;
    align-items: center;
    padding-left: 32px;
}

.ability-card:hover {
    border: 2px solid #00ffcc;
    background: linear-gradient(90deg, #1e88e5 60%, #283593 100%);
    color: #fff;
}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值