<!-- 漏斗-->
<template>
<div style="display: flex;">
<!-- 左边的 -->
<div class="container" :style="aotuMainHeight">
<div class="trapezoid">
<div v-for="(item, index) in allData" :key="index" class="segment" :style="{ height: item.height, top: item.top, backgroundColor: item.color }" @contextmenu.prevent="onRightClick($event, item)" @mouseenter="jin($event, item)" @mouseleave="chu($event, item)">
{{ item.value }}{{ item.unit }}
</div>
</div>
</div>
<!-- 右边的 -->
<div class="container1" :style="aotuMainHeight">
<div class="trapezoid">
<div v-for="(item, index) in allDataTwo" :key="index" class="segment" :style="{ height: item.height, top: item.top, backgroundColor: item.color }" @contextmenu.prevent="onRightClick($event, item)" @mouseenter="jin($event, item)" @mouseleave="chu($event, item)">
{{ item.value }}{{ item.unit }}
</div>
</div>
</div>
<!-- 弹窗 -->
<div v-if="showPopover" class="popoverClass" :style="aotuPHeight">
<div style="padding: 5px 8px 0;"><p style="font-weight:bold;margin:0 8px 15px;color:white">{{ nameObj.name }}</p></div>
<div style="margin: 0 8px;">
<span style="display:inline-block;margin-right:5px;width:10px;height:10px;" :style="{ backgroundColor:nameObj.color }" />
<span style="color:#FFFFFF">{{ nameObj.value }}</span>
<span style="color:#FFFFFF;margin-left:10px;">{{ nameObj.unit }}</span>
</div>
</div>
<div v-if="showRight" class="rightClass" :style="aotuRightHeight" @click="clickMx">
<div style="height: 40px; text-align: center; line-height: 40px;">
明细
</div>
</div>
<!-- 示例 -->
<div v-if="showItem" class="slClass">
<div v-for="(item, index) in allDataSl" :key="index" style="margin-top: 5px;" @click="clickItem(item)">
<span class="itemClass" :style="{ backgroundColor: item.itemColor }" />
<span style="font-weight: 400;" :style="{ color: item.fontColor }">{{ item.name }}</span>
</div>
</div>
</div>
</template>
<script>
import { qz } from './cockpit'
export default {
name: 'LdChart',
props: {
chartData: {
type: Array,
default: () => null
}
},
data() {
return {
aotuMainHeight: {
height: '',
width: ''
},
aotuPHeight: {
left: '',
top: ''
},
aotuRightHeight: {
left: '',
top: ''
},
scale: 100,
nameObj: { name: '', unit: '', value: '', color: '' },
rightObj: { value: '', type: '' },
showPopover: false,
showRight: false,
showItem: false,
allData: [],
allDataTwo: [],
allDataBf: [],
allDataTwoBf: [],
allDataSl: [],
topS: 0
}
},
watch: {
allDataSl: {
handler(newValue) {
this.allDataItem(newValue)
},
deep: true,
immediate: true
},
chartData: {
handler(next) {
if (next && next.length > 0) {
this.renderChart(next)
} else {
// 没有数据
this.showItem = false // 关闭示例
}
},
deep: true,
immediate: true
},
scale(val) {
if (val >= 1.5) {
this.aotuMainHeight.height = '200px'
this.aotuMainHeight.width = '200px'
} else if (val >= 1.2) {
this.aotuMainHeight.height = '240px'
this.aotuMainHeight.width = '240px'
} else if (val >= 1.1) {
this.aotuMainHeight.height = '260px'
this.aotuMainHeight.width = '280px'
} else {
this.aotuMainHeight.height = '300px'
this.aotuMainHeight.width = '300px'
}
}
},
mounted() {
this.updateScale()
window.addEventListener('resize', this.updateScale)
document.addEventListener('mousemove', this.handleMouseMove)
document.addEventListener('click', this.handleScreenClick)
window.addEventListener('scroll', this.handleScroll, true)
},
methods: {
clickMx() {
if (this.rightObj.type === '类型1') {
this.$bus.emit('ld-mx-no-params', this.rightObj, 'MX')
}
},
// 渲染图表
renderChart(next) {
this.showItem = true
// 排除数值是 0 的数据
// 下标 1和 2的处理
this.allData = JSON.parse(JSON.stringify(next[1]))
this.allDataTwo = JSON.parse(JSON.stringify(next[2]))
this.allDataBf = JSON.parse(JSON.stringify(next[1]))
this.allDataTwoBf = JSON.parse(JSON.stringify(next[2]))
this.allDataSl = JSON.parse(JSON.stringify(next[1]))
// 区分功能
this.rightObj.type = next[3]
},
// 更改示例的颜色
allDataItem(item) {
// checkItem 为 true 的变灰
item.forEach(element => {
if (element.checkItem) {
element.itemColor = '#cccccc'
element.fontColor = '#cccccc'
// 更改字体颜色
} else {
element.itemColor = element.color
element.fontColor = '#303313'
}
})
},
// 点击示例
clickItem(item) {
item.checkItem = !item.checkItem
this.allDataBf.forEach(it => {
if (it.name === item.name) {
it.checkItem = item.checkItem
}
})
this.allDataTwoBf.forEach(it => {
if (it.name === item.name) {
it.checkItem = item.checkItem
}
})
const obj1 = JSON.parse(JSON.stringify(this.allDataBf))
const obj2 = JSON.parse(JSON.stringify(this.allDataTwoBf))
const objNew1 = obj1.filter(item => !item.checkItem)
const objNew2 = obj2.filter(item => !item.checkItem)
const sumOne = objNew1.reduce((sum, current) => sum + Number(current.value), 0)
const sumTwo = objNew2.reduce((sum, current) => sum + Number(current.value), 0)
objNew1.forEach(item => {
let bl = 0
sumOne === 0 ? bl = 0 : bl = Math.floor(Number(item.value) / sumOne * 100)
Number(item.value) > 0 && bl === 0 ? bl = 1 : ''
// 设置合适的比例1
item.height = bl + '%'
})
objNew2.forEach(item => {
// 计算比例
let bl = 0
sumTwo === 0 ? bl = 0 : bl = Math.floor(Number(item.value) / sumTwo * 100)
Number(item.value) > 0 && bl === 0 ? bl = 1 : ''
// 设置合适的比例
item.height = bl + '%'
})
const qzOne = qz(objNew1.map(it => Number(it.height.split('%')[0])))
const qzTwo = qz(objNew2.map(it => Number(it.height.split('%')[0])))
objNew1.forEach((it, index) => {
it.height = qzOne[index] + '%'
})
objNew2.forEach((it, index) => {
it.height = qzTwo[index] + '%'
})
const result = []
const result1 = []
let sum = 0
let sum1 = 0
for (let i = 0; i < objNew1.length; i++) {
sum += Number(objNew1[i].height.split('%')[0])
result.push(sum)
}
for (let i = 0; i < objNew2.length; i++) {
sum1 += Number(objNew2[i].height.split('%')[0])
result1.push(sum1)
}
result.pop()
result1.pop()
objNew1.forEach((it, index) => {
// 第一个是 0, 之后的每一个都是 前面的相加!!!1
if (index === 0) {
it.top = '0%'
} else {
it.top = result[index - 1] + '%'
}
})
objNew2.forEach((it, index) => {
// 第一个是 0, 之后的每一个都是 前面的相加
if (index === 0) {
it.top = '0%'
} else {
it.top = result1[index - 1] + '%'
}
})
// 重新赋值
this.allData = objNew1
this.allDataTwo = objNew2
},
// 右键菜单显示
onRightClick(event, p1) {
// 保存右键的参数
this.showRight = true
this.aotuRightHeight.left = (event.clientX - 40) + 'px'
this.aotuRightHeight.top = (this.topS + (event.clientY - 80)) + 'px'
// 获取右键参数 rightObj, 明细参数
this.rightObj.value = p1.type
},
// 鼠标进入
jin(event, p1) {
this.showPopover = true
this.nameObj.name = p1.name
this.nameObj.value = p1.value
this.nameObj.unit = p1.unit
this.nameObj.color = p1.color
// 修改颜色
if (p1.color === '#7aa5fa') {
p1.color = '#7eb0ff'
} else if (p1.color === '#7ae0b8') {
p1.color = '#86f6ca'
} else if (p1.color === '#f6e0b4') {
p1.color = '#fff6c6'
} else if (p1.color === '#d9d9d9') {
p1.color = '#eeeeee'
} else if (p1.color === '#f5aa8a') {
p1.color = '#ffbb97'
}
},
// 鼠标出去
chu(event, p1) {
this.showPopover = false
if (p1.color === '#7eb0ff') {
p1.color = '#7aa5fa'
} else if (p1.color === '#86f6ca') {
p1.color = '#7ae0b8'
} else if (p1.color === '#fff6c6') {
p1.color = '#f6e0b4'
} else if (p1.color === '#eeeeee') {
p1.color = '#d9d9d9'
} else if (p1.color === '#ffbb97') {
p1.color = '#f5aa8a'
}
},
handleMouseMove(event) {
if (this.showPopover) {
this.aotuPHeight.left = (event.clientX + 20) + 'px'
this.aotuPHeight.top = (this.topS + (event.clientY - 60)) + 'px'
}
},
handleScroll(event) {
this.topS = event.srcElement.scrollTop
},
handleScreenClick() {
this.showRight = false
},
updateScale() {
this.scale = window.devicePixelRatio
}
}
}
</script>
<style scoped lang="scss">
.container {
position: relative;
margin-left: 20px;
}
.container1 {
position: relative;
margin-left: 8%;
}
.trapezoid {
position: absolute;
width: 100%;
height: 100%;
clip-path: polygon(0% 0%,100% 0%, 70% 100%, 30% 100%);
overflow: hidden; /* 确保子元素不会溢出容器 */
cursor: pointer;
}
.segment {
position: absolute;
bottom: 0; /* 从底部开始堆叠 */
width: 100%;
display: flex;
align-items: center; /* 垂直居中 */
justify-content: center; /* 水平居中 */
background-color: rgba(52, 152, 219, 1); /* 使用半透明色以区分区域 */
font-weight: 400;
}
.popoverClass {
background-color: rgba(0,0,0,0.5);
border: 1px solid rgba(0,0,0,0.4);
z-index: 10;
position: absolute;
height: 60px;
color:white;
border-radius: 5px;
}
.rightClass {
position: absolute;
z-index: 99999;
width: 120px;
height: 40px;
background-color: rgba(0, 0, 0, .4);
border-radius: 5px;
text-align: center;
color: #ffffff;
font-size: 15px;
cursor:pointer;
}
.slClass {
cursor:pointer;
position: relative;
margin-left: 3%;
padding-top: 8%;
font-size: 12px;
}
.itemClass {
display:inline-block;
margin-right:5px;
width:26px;
height:12px;
border-radius: 2px;
}
</style>
基于Vue封装的标准漏斗图(图表组件代码)
最新推荐文章于 2025-05-07 09:01:12 发布