vue - 动态统计

vue - 动态统计

intro

  • 需求

更具不同配置显示不同统计效果和数据

组件

宽高

组件宽高自行通过盒子实现,项目使用动态布局组件,可从首页导航

滚动

尽量避免滚动或自行调整滚动样式

动态背景

难点

不同宽度组件间通过拉伸宽度达到协调效果
通过计算透明度主题色

实现思路

  1. 伪类长方形旋转 (无法实现)
    1. 不同宽度,旋转相同角度效果不同
  2. 尝试用三角形 (无法实现)
    1. border-width 无法设为宽度百分比,不同宽度无法实现拉伸效果
  3. transform (无法实现)
    1. 旋转
    2. 倾斜
    3. 伸缩
  4. canvas 生成图片,做图片拉伸 (可实现,计算线条交点算法繁琐)
  5. 传入图片 (可实现,配置繁琐)
    1. 需配置传入背景图片
    2. 无法改变颜色
  6. iconfont 或 svg图片实现 (可实现,最佳!!!)
    1. 可通过改变字体颜色改变颜色
canvas 实现

需计算交点

let can = document.createElement('canvas');
      let height = 100;
      can.width = 300;
      can.height = height;
      can.style = 'height:auto';
      let cxt = can.getContext('2d');
      cxt.translate(0, height);
      cxt.fillStyle = tintColor(this.data.color.replace('#', ''), 0.9);
      cxt.beginPath();
      [
        [0, 0],
        [0, -height * 0.5],
        [60, -20],
        [300 * 1, -height * 0.8],
        [300 * 1, 0],
      ].forEach((v, i) => {
        // console.log(v);
        cxt[i == 0 ? 'moveTo' : 'lineTo'](...v);
      });
      cxt.closePath();
      cxt.fill();

      cxt.fillStyle = tintColor(this.data.color.replace('#', ''), 0.8);
      cxt.beginPath();
      cxt.moveTo(300 * 0.7, 0);
      cxt.lineTo(300, -height * 0.6);
      cxt.lineTo(300, -height * 0.4);
      cxt.closePath();
      cxt.fill();

      cxt.fillStyle = tintColor(this.data.color.replace('#', ''), 0.8);
      cxt.beginPath();
      cxt.moveTo(0, 0);
      cxt.lineTo(60, -20);
      cxt.lineTo(100, 0);
      cxt.closePath();
      cxt.fill();

      cxt.fillStyle = tintColor(this.data.color.replace('#', ''), 0.8);
      cxt.beginPath();
      cxt.moveTo(90, 0);
      cxt.lineTo(120, 0);
      cxt.lineTo(300, -height * 0.7);
      cxt.lineTo(130, -38);
      cxt.closePath();
      cxt.fill();

      let img = document.createElement('img');
      Object.assign(img.style, {
        width: '100%',
        height: '100%',
        display: 'inline-block',
        position: 'relative',
        zIndex: 0,
      });
      //   img.style = 'width:100%;height:100%;display:inline-block;position:relative;z-index:0;'; // strict 模式下不允许分配到只读属性
      img.src = can.toDataURL('image/png');
      this.$el.firstChild.appendChild(img);
计算透明度主题色
const tintColor = (color, tint) => {
    let red = parseInt(color.slice(0, 2), 16);
    let green = parseInt(color.slice(2, 4), 16);
    let blue = parseInt(color.slice(4, 6), 16);

    if (tint === 0) {
      // when primary color is in its rgb space
      return [red, green, blue].join(',');
    } else {
      red += Math.round(tint * (255 - red));
      green += Math.round(tint * (255 - green));
      blue += Math.round(tint * (255 - blue));

      red = red.toString(16);
      green = green.toString(16);
      blue = blue.toString(16);

      return `#${red}${green}${blue}`;
    }
  };

代码及说明

<template>
  <!-- 动态统计组件 -->
  <!-- 说明
        1. 配置
           1. 数据
              1. 层级2级,二维数组,children内不能再嵌套子集
           2. 列布局
           3. 主题色
           4. 高亮数据
        2. 溢出滚动
        3. 列布局
            1. 暂不支持列布局含子集
            2. 格式化数据支持列布局
        4. 动态背景
   -->
  <div class="case_calc_item" :style="{ borderColor: data.color }">
    <i class="background"></i>
    <div class="case_calc_item_title" :style="{ backgroundColor: data.color }">
      {{ data.stage }}
    </div>
    <div class="case_calc_list">
      <div class="case_calc_list_content">
        <div
          class="case_calc_list_item"
          :class="{ highlight: v.point }"
          v-for="(v, i) in list"
          :key="i"
        >
          <div
            class="case_calc_parent"
            :style="v.point ? { color: data.color } : ''"
          >
            <!-- 列布局 start:要求 1、连续列配置 -->
            <div v-if="v.cols" class="case_calc_parent case_calc_parent_col">
              <div
                class="case_calc_parent_col_item"
                v-for="(m, n) in v.cols"
                :key="n"
              >
                <div class="case_calc_value">{{ m.amount }}</div>
                <div class="case_calc_label">{{ m.label }}</div>
              </div>
            </div>
            <!-- 列布局 end -->
            <template v-else>
              <div class="case_calc_value">{{ v.amount }}</div>
              <div class="case_calc_label">{{ v.label }}</div>
            </template>
          </div>
          <div
            v-if="v.children && v.children.length"
            class="case_calc_children"
          >
            <div
              class="case_calc_children_item"
              v-for="(m, n) in v.children"
              :key="n"
              :class="{ highlight: m.point }"
              :style="m.point ? { color: data.color } : ''"
            >
              <div class="children_value">{{ m.amount }}</div>
              <div class="children_label">{{ m.label }}</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    data: {
      type: Object,
      default: () => {
        return {};
      },
    },
  },
  data() {
    return {};
  },
  computed: {
    list: function () {
      return this.formatList();
    },
  },
  mounted() {
    this.createBgImg();
  },
  methods: {
    createBgImg() {
      const tintColor = (color, tint) => {
        let red = parseInt(color.slice(0, 2), 16);
        let green = parseInt(color.slice(2, 4), 16);
        let blue = parseInt(color.slice(4, 6), 16);

        if (tint === 0) {
          // when primary color is in its rgb space
          return [red, green, blue].join(',');
        } else {
          red += Math.round(tint * (255 - red));
          green += Math.round(tint * (255 - green));
          blue += Math.round(tint * (255 - blue));

          red = red.toString(16);
          green = green.toString(16);
          blue = blue.toString(16);

          return `#${red}${green}${blue}`;
        }
      };

      let can = document.createElement('canvas');
      let height = 100;
      can.width = 300;
      can.height = height;
      can.style = 'height:auto';
      let cxt = can.getContext('2d');
      cxt.translate(0, height);
      cxt.fillStyle = tintColor(this.data.color.replace('#', ''), 0.9);
      cxt.beginPath();
      [
        [0, 0],
        [0, -height * 0.5],
        [60, -20],
        [300 * 1, -height * 0.8],
        [300 * 1, 0],
      ].forEach((v, i) => {
        // console.log(v);
        cxt[i == 0 ? 'moveTo' : 'lineTo'](...v);
      });
      cxt.closePath();
      cxt.fill();

      cxt.fillStyle = tintColor(this.data.color.replace('#', ''), 0.8);
      cxt.beginPath();
      cxt.moveTo(300 * 0.7, 0);
      cxt.lineTo(300, -height * 0.6);
      cxt.lineTo(300, -height * 0.4);
      cxt.closePath();
      cxt.fill();

      cxt.fillStyle = tintColor(this.data.color.replace('#', ''), 0.8);
      cxt.beginPath();
      cxt.moveTo(0, 0);
      cxt.lineTo(60, -20);
      cxt.lineTo(100, 0);
      cxt.closePath();
      cxt.fill();

      cxt.fillStyle = tintColor(this.data.color.replace('#', ''), 0.8);
      cxt.beginPath();
      cxt.moveTo(90, 0);
      cxt.lineTo(120, 0);
      cxt.lineTo(300, -height * 0.7);
      cxt.lineTo(130, -38);
      cxt.closePath();
      cxt.fill();

      let img = document.createElement('img');
      Object.assign(img.style, {
        width: '100%',
        height: '100%',
        display: 'inline-block',
        position: 'relative',
        zIndex: 0,
      });
      //   img.style = 'width:100%;height:100%;display:inline-block;position:relative;z-index:0;'; // strict 模式下不允许分配到只读属性
      img.src = can.toDataURL('image/png');
      this.$el.firstChild.appendChild(img);
    },
    /**
     * @method formatList
     * @description 格式化列表数据
     *  1. 列数组处理
     */
    formatList() {
      let list = [];
      function pushColItem(item) {
        // list 为空 或 最后一项不为列数组 push 新cols
        !list.length || !list[list.length - 1].cols
          ? list.push({ cols: [] })
          : '';
        list[list.length - 1].cols.push(item);
      }
      this.data.list.forEach((v, i) => {
        this.isColItem(v, i) ? pushColItem(v) : list.push(v);
      });
      return list;
    },
    /**
     * @method isColItem
     * @description 判断是否列布局,返回新的列数组
     * 判断要素
     *  1. v.col
     *  2. i=0时,判断存在下一项且下一项 col == true
     *  3. i!=0, 上一项col==true 或 存在下一项且下一项col==true
     */
    isColItem(v, i) {
      let list = this.data.list;
      //   if (v.col) {
      //     console.log(`${i} is col:${v.col}`)
      //     console.log(`${i} isColItem:${Boolean(v.col)&&(list[i - 1]&&list[i - 1].col||list[i + 1]&&list[i + 1].col)}`)
      //     console.log(`${i} exist prev && is col:${list[i - 1]&&list[i - 1].col}`)
      //     console.log(`${i} exist next && is col:${list[i + 1]&&list[i + 1].col}`)
      //   }
      return (
        Boolean(v.col) &&
        ((list[i - 1] && list[i - 1].col) || (list[i + 1] && list[i + 1].col))
      );
    },
  },
};
</script>
<style lang="scss" scoped>
$border-color: #dee3e7;
.case_calc_item {
  position: relative;
  box-sizing: border-box;
  border: 1px solid #508aff;
  background-color: #fff;
  height: 100%;
  display: flex;
  align-items: center;
  flex-direction: column;
  border-radius: 0 0 4px 4px;
  color: #333;
  border-top-width: 5px;
  padding: 0 10px;
  position: relative;
  > i.background {
    overflow: hidden;
    bottom: 0;
    display: block;
    position: absolute;
    height: 81px;
    width: 100%;
  }

  .case_calc_item_title {
    display: inline-flex;
    align-items: center;
    padding: 0 30px;
    height: 40px;
    font-size: 16px;
    background-color: #508aff;
    color: #fff;
    border-radius: 0 0 8px 8px;
    flex-shrink: 0;
  }
  .case_calc_list {
    position: relative;
    z-index: 1;
    width: 100%;
    display: flex;
    overflow: auto;
    align-items: center;
    flex-grow: 1;

    .case_calc_list_content {
      display: flex;
      width: 100%;
      flex-grow: 1;
      align-items: stretch;
    }
    .case_calc_list_item {
      display: flex;
      flex-direction: column;
      flex-grow: 1;
      justify-content: center;
      &.highlight,
      .highlight {
        color: #508aff;
      }
      .case_calc_label {
        font-size: 16px;
        line-height: 24px;
      }
      .case_calc_value {
        font-size: 28px;
      }
      .case_calc_parent {
        &.case_calc_parent_col {
          margin-bottom: initial;
        }
      }
      .case_calc_parent_col {
        display: flex;
        flex-direction: column;
        .case_calc_parent_col_item {
          flex-grow: 1;
        }
        .case_calc_parent_col_item + .case_calc_parent_col_item {
          border-top: 1px solid $border-color;
        }
      }
      .case_calc_children {
        margin-top: 16px;
        padding-top: 12px;
        display: flex;
        flex-direction: initial;
        border-top: 1px solid $border-color;

        .case_calc_children_item {
          width: 100%;
          .children_value {
            font-size: 20px;
            line-height: 24px;
          }
          .children_label {
            font-size: 16px;
          }
        }
      }
    }
    .case_calc_list_item + .case_calc_list_item,
    .case_calc_children_item + .case_calc_children_item {
      border-left: 1px solid $border-color;
    }
  }
}
</style>

数据结构

calcList: [
        {
          showHeader: false,
          calcHeight: '32%',
          width: 0.5,
          data: {
            color: '#508AFF',
            stage: '立案准备阶段',
            list: [
              {
                label: '待提交审批',
                amount: 4215,
                point: true, // 重点,图上主题色显示
              },
              {
                label: '待审批',
                amount: 4215,
              },
              {
                label: '待申请',
                amount: 4215,
                point: true, // 重点,图上主题色显示
              },
              {
                label: '已撤销',
                amount: 800,
              },
            ],
          },
        },
        {
          showHeader: false,
          calcHeight: '32%',
          width: 0.25,
          data: {
            color: '#508AFF',
            stage: '立案阶段',
            list: [
              {
                label: '待立案',
                amount: 6165,
                children: [
                  {
                    label: '待提交立案审批',
                    amount: 4215,
                  },
                  {
                    label: '待审批',
                    amount: 1950,
                  },
                ],
              },
              {
                col: true,
                label: '待交诉讼费',
                amount: 4215,
                point: true, // 重点,图上主题色显示
              },
              {
                col: true,
                label: '已交诉讼费',
                amount: 2800,
              },
              {
                col: true,
                label: '已撤回',
                amount: 800,
              },
              {
                label: '待立案',
                amount: 6165,
                children: [
                  {
                    label: '待提交立案审批',
                    amount: 4215,
                  },
                  {
                    label: '待审批',
                    amount: 1950,
                  },
                ],
              },
              {
                col: true,
                label: '待交诉讼费',
                amount: 4215,
                point: true, // 重点,图上主题色显示
              },
              {
                col: true,
                label: '已交诉讼费',
                amount: 2800,
              },
              {
                col: true,
                label: '已撤回',
                amount: 800,
              },
            ],
          },
        },
        {
          showHeader: false,
          calcHeight: '32%',
          width: 0.25,
          data: {
            color: '#F26222',
            stage: '庭审准备阶段',
            list: [
              {
                label: '待撤诉处理',
                amount: 2150,
              },
              {
                label: '待排期',
                amount: 4215,
              },
              {
                label: '待开庭',
                amount: 2000,
              },
            ],
          },
        },
        {
          showHeader: false,
          calcHeight: '32%',
          width: 0.25,
          data: {
            color: '#705AE8',
            stage: '庭审阶段',
            list: [
              {
                label: '开庭中',
                amount: 8045,
                children: [
                  {
                    label: '被告到庭',
                    amount: 4645,
                  },
                  {
                    label: '被告不到庭',
                    amount: 3400,
                  },
                ],
              },
            ],
          },
        },
        {
          showHeader: false,
          calcHeight: '32%',
          width: 0.25,
          data: {
            color: '#F26222',
            stage: '裁判阶段',
            list: [
              {
                label: '当庭宣判',
                amount: 960,
                point: true, // 重点,图上主题色显示
              },
              {
                label: '非当庭宣判',
                amount: 4760,
              },
            ],
          },
        },
        {
          showHeader: false,
          calcHeight: '32%',
          width: 0.5,
          data: {
            color: '#E23E6A',
            stage: '结案阶段',
            list: [
              {
                label: '待结案',
                amount: 2760,
                point: true, // 重点,图上主题色显示
              },
              {
                label: '已结案',
                amount: 8430,
                children: [
                  {
                    label: '待归档',
                    amount: 1200,
                  },
                  {
                    label: '已归档',
                    amount: 2000,
                    point: true, // 重点,图上主题色显示
                  },
                ],
              },
              {
                label: '已生效',
                amount: 12645,
                children: [
                  {
                    label: '待申请执行',
                    amount: 2500,
                    point: true, // 重点,图上主题色显示
                  },
                  {
                    label: '已申请执行',
                    amount: 2500,
                    point: true, // 重点,图上主题色显示
                  },
                ],
              },
              {
                label: '已归集材料',
                amount: 2760,
                point: true, // 重点,图上主题色显示
              },
            ],
          },
        },
        {
          showHeader: false,
          calcHeight: '32%',
          width: 1,
          data: {
            color: '#508AFF',
            stage: '送达汇总',
            list: [
              {
                label: '已分案-立案文书',
                amount: 12215,
                children: [
                  {
                    label: '待送达',
                    amount: 2000,
                  },
                  {
                    label: '待交公告费',
                    amount: 4215,
                    point: true, // 重点,图上主题色显示
                  },
                  {
                    label: '送达中',
                    amount: 2000,
                  },
                  {
                    label: '已送达',
                    amount: 2000,
                  },
                  {
                    label: '送达退回',
                    amount: 2000,
                  },
                ],
              },
              {
                label: '待开庭-开庭文书',
                amount: 11815,
                children: [
                  {
                    label: '待送达',
                    amount: 2000,
                  },
                  {
                    label: '待交公告费',
                    amount: 4215,
                    point: true, // 重点,图上主题色显示
                  },
                  {
                    label: '送达中',
                    amount: 2000,
                  },
                  {
                    label: '已送达',
                    amount: 2000,
                  },
                  {
                    label: '送达退回',
                    amount: 2000,
                  },
                ],
              },
              {
                label: '待结案-裁判文书',
                amount: 12645,
                children: [
                  {
                    label: '待送达',
                    amount: 2000,
                  },
                  {
                    label: '待交公告费',
                    amount: 4215,
                    point: true, // 重点,图上主题色显示
                  },
                  {
                    label: '送达中',
                    amount: 2000,
                  },
                  {
                    label: '已送达',
                    amount: 2000,
                  },
                  {
                    label: '送达退回',
                    amount: 2000,
                  },
                ],
              },
            ],
          },
        },
      ],
posted @ 2022-01-15 17:02  zc-lee  阅读(105)  评论(0编辑  收藏  举报