Vue3 drag and drop layout + dynamic components + adaptive layout

1. Drag and drop layout plugin

Vue Grid Layout -️ A Vue component for Vue.js' grid layout system draggable and resizable grid layout. https://jbaysolutions.github.io/vue-grid-layout/zh/

//Add the following plugin library under dependencies in package.json, and execute the command npm install

 "vue-grid-layout": "^3.0.0-beta1",

 2. Drag and drop page code

<template>
  <div>
    <!-- 
        colNum: 定义栅格系统的列数,其值需为自然数.
        rowHeight:每行的高度,单位像素。
        isDraggable:标识栅格中的元素是否可拖拽.
        isResizable:标识栅格中的元素是否可调整大小。
        isMirrored:标识栅格中的元素是否可镜像反转。
        autoSize:标识容器是否自动调整大小
        verticalCompact:标识布局是否垂直压缩。
        preventCollision:防止碰撞属性,值设置为ture时,栅格只能拖动至空白处。
        useCssTransforms:标识是否使用CSS属性 transition-property: transform;
        responsive:标识布局是否为响应式
    -->
    <grid-layout :layout="layout"
                 :col-num="12"
                 :row-height="30"
                 :is-draggable="true"
                 :is-resizable="true"
                 :vertical-compact="true"
                 :margin="[10, 10]"
                 :use-css-transforms="true"
                 @layout-updated="layoutUpdatedEvent">
      <grid-item v-for="(item,index) in layout"
                 :x="item.x"
                 :y="item.y"
                 :w="item.w"
                 :h="item.h"
                 :i="item.i"
                 :key="item.i"
                 @move="moveEvent"
                 @resized="resizedEvent"
                 style="background:pink;">
        <keep-alive>
          <component :is="item.name"
                     :ref="`dragRef${index}`"
                     :key="item.name+index"
                     :id="item.name+index"
                     :width="item.Wpx"
                     :height="item.Hpx"></component>
        </keep-alive>
      </grid-item>
    </grid-layout>
  </div>
</template>
<script lang="ts">
import { BarChart, FunnelChart, PieChart, RadarChart } from './components';

export default {
  components: {
    BarChart,
    FunnelChart,
    PieChart,
    RadarChart
  }
};
</script>
<script setup lang="ts">
const layout = ref([
  { x: 0, y: 0, w: 2, h: 2, i: '0' },
  { x: 2, y: 0, w: 2, h: 4, i: '1' },
  { x: 4, y: 0, w: 2, h: 5, i: '2' },
  { x: 6, y: 0, w: 2, h: 3, i: '3' },
  { x: 8, y: 0, w: 2, h: 3, i: '4' },
  { x: 10, y: 0, w: 2, h: 3, i: '5' }
]);

const testLayout = [
  {
    x: 0,
    y: 0,
    w: 2,
    h: 8,
    i: '0',
    name: 'BarChart'
  },
  //   {
  //     x: 2,
  //     y: 0,
  //     w: 2,
  //     h: 8,
  //     i: '1',
  //     name: 'FunnelChart'
  //   },
  //   {
  //     x: 4,
  //     y: 0,
  //     w: 2,
  //     h: 8,
  //     i: '2',
  //     name: 'PieChart'
  //   },
  //   {
  //     x: 6,
  //     y: 0,
  //     w: 2,
  //     h: 8,
  //     i: '3',
  //     name: 'RadarChart'
  //   },
  {
    x: 8,
    y: 0,
    w: 2,
    h: 8,
    i: '4',
    name: 'BarChart'
  }
];
layout.value = testLayout;

//更新事件(布局更新或栅格元素的位置重新计算)
const layoutUpdatedEvent = (newLayout: any) => {
  console.log('布局更新了');
  console.log('Updated layout: ', newLayout);
  layout.value = newLayout;
  console.log(layout.value);
};
//移动时的事件
const moveEvent = (i: any, newX: any, newY: any) => {
  console.log('盒子移动了');
  console.log('MOVE i=' + i + ', X=' + newX + ', Y=' + newY);
};
//调整大小后的事件
/**
 *
 * @param i the item id/index
 * @param newH new height in grid rows
 * @param newW new width in grid columns
 * @param newHPx new height in pixels
 * @param newWPx new width in pixels
 *
 */
const resizedEvent = (
  i: any,
  newH: any,
  newW: any,
  newHPx: any,
  newWPx: any
) => {
  console.log('改变了尺寸');
  console.log(
    'RESIZED i=' +
      i +
      ', H=' +
      newH +
      ', W=' +
      newW +
      ', H(px)=' +
      newHPx +
      ', W(px)=' +
      newWPx
  );
  let newLayouts = layout.value.map(item => {
    console.log(item);
    console.log(item.i == i);

    if (item.i == i) {
      item.Wpx = newWPx + 'px';
      item.Hpx = newHPx + 'px';
    }
    return item;
  });
  layout.value = newLayouts;
};
</script>


<style scoped>
</style>

3. Chart subcomponent code

<template>
  <div class="box">
    <div :id="id"
         :class="className"
         :style="{ height, width }" />
  </div>
</template>

<script setup lang="ts">
import * as echarts from 'echarts';

const props = defineProps({
  id: {
    type: String,
    default: 'barChart'
  },
  className: {
    type: String,
    default: ''
  },
  width: {
    type: String,
    default: '100%',
    required: true
  },
  height: {
    type: String,
    default: '100%',
    required: true
  }
});
const options = {
  grid: {
    left: '2%',
    right: '2%',
    // top: '5%',
    bottom: '10%',
    containLabel: true
  },
  tooltip: {
    trigger: 'axis',
    axisPointer: {
      type: 'cross',
      crossStyle: {
        color: '#999'
      }
    }
  },
  legend: {
    x: 'center',
    y: 'bottom',
    data: ['收入', '毛利润', '收入增长率', '利润增长率'],
    textStyle: {
      color: '#999'
    }
  },
  xAxis: [
    {
      type: 'category',
      data: ['浙江', '北京', '上海', '广东', '深圳'],
      axisPointer: {
        type: 'shadow'
      }
    }
  ],
  yAxis: [
    {
      type: 'value',
      min: 0,
      max: 10000,
      interval: 2000,
      axisLabel: {
        formatter: '{value} '
      }
    },
    {
      type: 'value',
      min: 0,
      max: 100,
      interval: 20,
      axisLabel: {
        formatter: '{value}%'
      }
    }
  ],
  series: [
    {
      name: '收入',
      type: 'bar',
      data: [7000, 7100, 7200, 7300, 7400],
      barWidth: 20,
      itemStyle: {
        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
          { offset: 0, color: '#83bff6' },
          { offset: 0.5, color: '#188df0' },
          { offset: 1, color: '#188df0' }
        ])
      }
    },
    {
      name: '毛利润',
      type: 'bar',
      data: [8000, 8200, 8400, 8600, 8800],
      barWidth: 20,
      itemStyle: {
        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
          { offset: 0, color: '#25d73c' },
          { offset: 0.5, color: '#1bc23d' },
          { offset: 1, color: '#179e61' }
        ])
      }
    },
    {
      name: '收入增长率',
      type: 'line',
      yAxisIndex: 1,
      data: [60, 65, 70, 75, 80],
      itemStyle: {
        color: '#67C23A'
      }
    },
    {
      name: '利润增长率',
      type: 'line',
      yAxisIndex: 1,
      data: [70, 75, 80, 85, 90],
      itemStyle: {
        color: '#409EFF'
      }
    }
  ]
};
const initEcharts = () => {
  console.log('123');
  echarts.init(document.getElementById(props.id) as HTMLDivElement).dispose(); //先销毁

  // 图表初始化
  var chart = echarts.init(document.getElementById(props.id) as HTMLDivElement);

  chart.setOption(options);

  // 大小自适应
  setTimeout(() => {
    //由于网格布局拖拽放大缩小图表不能自适应,这里设置一个定时器使得echart加载为一个异步过程
    console.log('111');
    chart.resize();
  }, 0);
};

watch(
  () => props,
  (newVal, oldVal) => {
    console.log('监听传值', newVal);
    initEcharts();
  },
  {
    deep: true
  }
);

onMounted(() => {
  console.log('图表初始化');
  initEcharts();
});
</script>
<style lang="scss" scoped>
.box {
  width: 100%;
  height: 100%;
  background: #fff;
}
</style>

おすすめ

転載: blog.csdn.net/weixin_51258044/article/details/130030472