基于Vue封装的标准漏斗图(图表组件代码)

<!-- 漏斗-->
<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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力奋斗小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值