如果给你这样一张原型图,需求是根据某个区域生产的某一大类产品的生产起始日期,用可视化的时间轴来展示,你第一时间会想到用什么办法去实现?

image
图1

相关解释:片区下面有多个省份,这里只拿一个来举例子。一到四季度的单元格里面的每一行表示某一种大类的产品名称,例如:牛腩(第一行)、杂酱(第二行);这里无需关心这些条状背景颜色后面的特殊含义

 ̄□ ̄ 刚拿到这张图片时我又有点手足无措,懒羊羊的我第一时间肯定是想找现成的组件来完成(因为不想自己去计算宽度和边距),盯着图看了半天,横看竖看,(内心暗喜),于是打开了echarts官网,用我近视500多度的眼睛迅速浏览大佬给的demo

image
图2

嘿嘿,看着很像哦!开撸~ 【这里浅浅记录一下过程,虽然最后实现的效果不符合实际的需求😅】

查看令我伤心的代码

第一步要给echart图表安置不同的dom元素

// 因为会嵌套多个图,这里采用了动态构造元素id的方式
<el-table-column
        label="地址">
        <template slot-scope="scope">
          <div class="chart-box">
             <div :id="'chart'+scope.row.id" class="chart-box"></div>
          </div>
        </template>
      </el-table-column>
  1. 构造自己想要的数据结构类型(mock数据)
    这里只对我用到的一些配置项写了注释,更多配置项可阅读官网配置项手册
option = {
	// 图例,即图2图表最上方那一排
 export default {
    tooltip: {
        show:false, // 控制图例的展示
        trigger: 'axis',
        axisPointer: {
        // Use axis to trigger tooltip
            type: 'line' // 'shadow' as default; can also be 'line' or 'shadow'
      }
    },
    legend: {
        show:false
    },
    grid: {
        show:false, 
        left: 1,
        top:"top",
        right: 0,
        bottom: 5,
        containLabel: true,
        tooltip:{
            show:false
        }
    },
    xAxis: {
        type: 'value', // 注意x轴表示数值了
        show:false,
        axisLine:{ // 坐标轴
            show:false
        },
        axisTick:{ // 刻度线
            show:false
        },
        axisLable:{ // 刻度线
            show:false
        },
        min:0,
        max:366, // 可以动态获取一年的天数
    },
    // yAxis的 data里面每一个元素代表一种产品
    yAxis: {
      show:false,  
      type: 'category', // y轴表示类目
      data: ['Mon', 'Tue', 'Wed'], // 纵坐标
      axisLine:{
        show:false
      }
    },
    series: [
        // 可以把产品名称、时间段信息存在name字段里面
      {
        name: '可销售', //*
        type: 'bar',
        stack: 'total',
        label: {
          show: true,
          formatter: function(params) { //自定义柱状图里面显示的文字内容
            // console.log(params);
            // params 包含了标签相关的信息
            // params.value 为柱状图对应的数据值
            return  `${params.seriesName}【${params.value}】`;
        },
        color:"#FFFFFF", // 文字颜色
        },
        itemStyle:{
            color:"#3296FA", // 柱状条颜色
        },
        emphasis: {
          focus: 'none'
        },
        // data里面的每一个元素对应上面yAxis的 data里面的每一个元素【竖着看】
        data: [49, 120, 10],
        barWidth:20,  // 柱状条的宽度
      },
      {
        name: '临期',
        type: 'bar',
        stack: 'total',
        label: {
          show: true,
          color:'#FFFFFF '
        },
        itemStyle:{
            color:"#35C779"
        },
        emphasis: {
          focus: 'none'
        },
        data: [8,300,40],
        barWidth:20,
        barGap: 10,
        barCategoryGap:10
      },
      {
        name: '计划生产',
        type: 'bar',
        stack: 'total',
        // 设置柱状条颜色
        itemStyle:{
            color:"#DEECFE"
        },
        label: {
          show: true,
          color:'#323232'
        },
        emphasis: {
          focus: 'none'
        },
        barWidth:20,
        data: [99,0,200],
      }
    ]
  };

options准备好了,接下来就是把数据塞进每一个echarts实例中。

<script>
// 引入echarts ,不同版本的引入方式可能有区别,我的是5.+,其他版本请问度娘👻
import * as echarts from "echarts"
import options from "../test/options.js"
export default {
      data() {
        return {
		 tableData: [{
            id:1,
            date: '2016-05-02',
            name: '王小虎',
            address: '上海市普陀区金沙江路 1518 弄'
          }
          ],
          options,
        }
      },
      methods:{
	    // 生成多个chart实例
            generChart(){
              if(this.tableData.length==0) return;
			  // 根据表单数据来创建echarts实例
              this.tableData.forEach(item=>{
                let domId=`chart${item.id}`; // 动态获取id 
                this.initChart(domId); // 初始化实例
              })
            },
			
            initChart(domId){
              var chartDom = document.getElementById(domId);
              var myChart = echarts.init(chartDom);
              // 监听柱状图的点击事件
              myChart.on('click', function (params) {
                // 产品名称(params.name)、图例名称(params.seriesName)、数值(params.value)
                  console.log(params.name,params.seriesName,params.value);
				  
                  // 在这里处理点击事件的逻辑
                  
                  // console.log('数据值:', params.value); // 点击的柱状图的数据值
              });
			  // 实际情况需要根据接口返回数据去构造options,这里复用了同一个options
                 this.options && myChart.setOption(this.options);
            },
      },
      mounted(){
        this.$nextTick(()=>{ //  为了保证能够获取到动态渲染的dom元素,需要放在这个钩子函数里面【涉及到vue dom更新相关知识】
          this.generChart();
        })
      },

    }
</script>

好了,跑一下看看效果呢

image
图3

🤔和图1长得差不多嘛 但总觉得哪里不对呢🤔🤔

后来仔细想了想,二维坐标下柱状图只能表示x和y轴两个维度上数据的一些关联,无法再描述其他维度,即无法量化离起点(这里是指图1的一季度开始的时间)的距离。😠得儿,等于说白干了呗(开摆)

🌚看来还是得自己计算了 谁怕谁!

仔细观察其实可以发现图1一到四季度的单元格可以使用二维数组来实现(即双重v-for来实现),而只需要将每一小块儿柱状图看做是一个基本单元【下文用CustomBar代替】,即可轻松(也不轻松😅)实现

  1. 准备渲染模板、数据结构。 .row采用相对定位,其子组件.row-item采用绝对定位。根据时间跨度算出时间差、开始时间距离1月1日的天数即可实现在时间轴上的定位
查看代码
// index.vue
<template>
 <el-table-column prop="test" label="一季度"  align="center" >
        <template slot-scope="scope">
          <div class="box" ref="chartContainer" style="'padding':0px;position: relative;">
            <!-- 行 -->
            <div v-for="(product,index) in scope.row.productList" :key="index" class="row">
              <!-- 每一个小的柱状图单元
			  	1. info 时间跨度、名称等其他相关信息
				2.父盒子的宽度,即.row的宽度
			  -->
              <CustomBar v-for="(item,i) in product" :key="i" class="row-item" :parentWidth="chartContainerWidth" :info="item" >
              </CustomBar>
            </div>
          </div>
        </template>
      </el-table-column>
</template>
// table绑定的数据  
 tableData: [
          {
          id: '1',
          name: '东南片区',
          province:"广东",
          productList:[
                // 代表一种产品
                [
                {
                  content:"牛腩拌面 2022-12-12-2023-09-10",
                  dateRange:[1670774400000,1691769600000],
                  collectTag:2
                },
                {
                  content:"炒牛腩 2023-10-12-2024-12-12",
                 dateRange:[1697040000000,1733932800000],
                  collectTag:3
                },
                ],
                 [
                {
                  content:"炸酱拌面 2022-12-12-2023-09-10",
                  dateRange:[1670774400000,1691769600000],
                  collectTag:1
                },
                {
                  content:"杂酱米线 2023-07-11-2024-12-12",
                  dateRange:[1689047293000,1733932800000],
                  collectTag:3
                },
                ],                
          ]    
        },
      ],
  1. 给CustomBar组件传递宽度,需要监听窗口宽度的变化
查看代码
// index.vue
<script >
import CustomBar from '@/components/progressBar.vue'
export default{
    data(){
        return{
      // 四个季度合并之后单元格的长度
      chartContainerWidth:0,
     }
    },
    methods: {
	// el-table 合并单元格的方法
      arraySpanMethod({ row, column, rowIndex, columnIndex }) {
        if(columnIndex >1){ // 合并四个季度
          return [1,4]
        }
      },
      // 防抖【封装需注意this的指向】
      myDebunce(time){
        let timer=null;
        return ()=>{
            if(timer) clearTimeout(timer);
            timer=setTimeout(()=>{
                // 保存宽度
                this.chartContainerWidth=this.$refs.chartContainer.offsetWidth;
            },time)
        }
      }
    },
    mounted(){
      this.$nextTick(()=>{ // 保证dom已经更新
        // 初始化页面需要获取一下宽度
         this.chartContainerWidth=this.$refs.chartContainer.offsetWidth;
          window.addEventListener("resize",this.myDebunce(200));
      })
     
    },
    beforeDestroy(){
      window.removeEventListener("resize",this.myDebunce(200));
    },
    components:{
      CustomBar
    }
}
</script>

  1. 实现CustomBar里面的逻辑
查看代码
// progressBar.vue
<template>
    <div class="bar" 
    :style="{
        'background-color': getBgColor(info.collectTag),
        'width':getBarWidth,
        'color':getFontColor(info.collectTag),
        'left':getLeft
        }" :title="info.content">
    {{info.content}}
  </div>
  
</template>
<script>
....
// 属性
 props:{
        info:{
            type:Object
        },
        parentWidth:{
            type:Number
        }
    },
....
</script>

<style>
.bar{
    height: 30px; // 高度是必须的,不然撑不开
    font-size: 12px;
    line-height: 30px;
    position: absolute; // 必须的
}
</style>

最重要的就是计算宽度和左边距的两个方法,因为都依赖parentWidth属性,所以放到了computed里面

getBarWidth(){
            const dateRange=this.info.dateRange;
            let days=getDays(dateRange[0],dateRange[1]); // 获取开始日期与截止时间相差的天数【转换为Date对象进行运算(毫秒),然后再除以一个天数的进制】

            let unit=this.parentWidth/ this.totalDays // 计算单位像素表示的天数
            return `${days*unit}px`;
        },
  getLeft(){
            let [d1,d2]=this.info.dateRange
            let passedDays=getDiffDays(d1); // 计算开始时间距离1月1日(一季度开始)的天数,和上面的思路相似

            let marginLeft=`0px`;
            if(passedDays>0){ // 开始时间在今年内
                let unit=this.parentWidth/this.totalDays;
                marginLeft=`${passedDays*unit}px`

            }
			// 开始时间早于今年,左边距直接为0****
            return marginLeft;
        }

注意:计算相隔天数可能有四种情况 后面再考虑有没有更简单的办法计算
image
图4

😇 嘿嘿,到这里已经基本实现想要的功能了,最后贴上效果图

image
图5

Posted on 2023-09-11 15:55  易烊千玺圈外女友  阅读(788)  评论(0)    收藏  举报