1.BI可视化编辑器之“拖动、移动 、放置、图层放大缩小“实现

前文:

一、思路

1.用一个数组 list 维护编辑器中的数据。
2.把组件拖拽到画布中时,使用 push() 方法将新的组件数据添加到 list
3.编辑器使用 v-for 指令遍历list,将每个组件逐个渲染到画布

二、关于自定义组件

1.图层类型有自定义组件

2.控制面板属性配置使用的是element-ui里的页签、颜色选择器、输入框、开关后面有需要可以自己添加,配置属性

一、拖动、移动 、放置、图层放大缩小实现

widget-list.vue   

一个元素如果要设为可拖拽,必须给它添加一个 draggable 属性  

1.拖的实现: draggable="true" H5给了一个属性可以拖拽,要配合

2.放置的实现@dragover.prevent @drop="onDrop"

dragstart 事件-也可以定义onmousedown事件,在拖拽刚开始时触发。它主要用于将拖拽的组件信息传递给画布。
drop 事件,在拖拽结束时触发。主要用于接收拖拽的组件信息。

<template>
  <!-- 组件列表 -->
  <!-- draggable="true" H5给了一个属性可以拖拽 -->
  <div class="widget-list">
    <div
      v-for="widget in list"
      :key="widget.type"
      class="widget"
      draggable="true"
      @mousedown="(e )=> $emit('onWidgetMouseDown',e,widget)"
    >
      {{ widget.label }}
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {};
  },
  props:{
      list:{
          type:Array,
          equired:true,
      },
  },
  methods:{
      
  }
};
</script>

<style scoped>
</style>

App.vue

3.组件在画布中移动 

3-1.首先需要将画布设为相对定位 position: relative,然后将每个组件设为绝对定位 position: absolute。

3-2.这边使用的依赖vue-drag-resize,并没有使用移动和松开事件按下事件也可以改为dragstart事件效果一致:

3.dragstart事件主要是获取:记录组件当前的位置

4.传给依赖的x,y为:放置的距离左侧的距离-鼠标落下鼠标距离左侧的距离=当前box的x轴位置,如图所示:

<template>
  <div id="app">
    <!-- 图层列表 -->
    <el-tabs v-model="activeName" class="sidebar">
      <el-tab-pane label="图层列表" name="layer">
        <!-- 图层列表 -->
          <div class="layer" v-for="item in list" :key="item.id">
              {{ item.label }}
          </div>
      </el-tab-pane>
      <el-tab-pane label="组件列表" name="widget">
        <Widget-List :list="widgetList" @onWidgetMouseDown="onWidgetMouseDown" />
      </el-tab-pane>
    </el-tabs>

     <!-- 操作面板 -->
    <div class="panel"  
      @dragover.prevent 
      @drop="onDrop" >
      <Drageer v-for="(item, i) in list" :key="item.id" ref="widget" class="box" :x="item.x" :y="item.y" :z="item.z" :w="item.w" :h="item.h" >
         <component :value="item.value" class="inner-widget" :is="item.component" 
          :styles="item.styles" />
      </Drageer>
    </div>


    <!-- 样式配置区域 -->
    <!-- <style-sider></style-sider> -->

    <!-- 右键菜单 -->
    <!-- <context-menu></context-menu> -->

    <!-- 对齐线 纵向 -->
    <!-- <div></div> -->

  </div>
</template>

<script>
let currentId = 0; //id计数器
let widgetX = 0;//差值
let widgetY = 0;//差值
let currentWidget = null;//获取当前点击的图层数据

//出现在panel上的组件
import BarChart from '@/components/bar-chart'
import AreaChart from '@/components/area-chart'
import CustomText from '@/components/custom-text'
import CustomVideo from '@/components/custom-video'
import MianjiChart from '@/components/mianji-chart'

//左侧小组件列表
import WidgetList from '@/components/widget-list/'

//静态配置
import * as CONFIG from '@/components/constants/config'

export default {
  name: 'App',
  components: {
    WidgetList,
    BarChart,
    AreaChart,
    CustomText,
    CustomVideo,
    MianjiChart,
  },
  data(){
    return{
      activeName:'widget',
      list:[],
      widgetList: CONFIG.WIDGET_LTST,//组件的数据结构
    }
  },
  methods:{
    //放置组件
    onDrop(e,i){
      //放置的距离左侧的距离-鼠标落下鼠标距离左侧的距离=当前box的x轴位置
      let x = e.offsetX - widgetX;
      let y = e.offsetY - widgetY;

      if (i !== undefined) {
        x += this.list[i].x;
        y += this.list[i].y;
      }
      const newItem = ({
        id: currentId++,//key绑定id
        x,
        y,
      
        z: !this.list.length ? 0 : Math.max(...this.list.map(item => item.z)) + 1,//因为一开是空  所以给个默认值0
      
        ...currentWidget.default,
        // w:this.currentWidget.w,//盒子初始值宽
        // h:this.currentWidget.h,
        label: currentWidget.label,//文字
        component: currentWidget.component, // 新增的组件名
        type: currentWidget.type,//新增组件的类型
        styles: currentWidget.styles,//新增组件的样式
      
      });
      this.list.push(newItem)
      // this.clickDrageeronFoucus(newItem)
    },
    //在小组件鼠标落下的时候
    onWidgetMouseDown(e, widget) {
      //获取   鼠标距离左侧的距离
      widgetX = e.offsetX;
      widgetY = e.offsetY;
      currentWidget = widget;//当前点击的图层数据赋值
    },
  }
}
</script>


<style>
body {
  margin: 0;
}

#app {
  display: flex;
  width: 100vw;
  height: 100vh;
  /* 可视高度的多少1vh=视窗高度的1% */
}

.sidebar {
  width: 200px;
  background: #e9e9e9;
}

.panel {
  flex: 1;
  background: #f6f6f6;
  position: relative;
  /* 给相对定位主要是因为 要根据panel盒子的左上角来进行绝对定位*/
}

.widget {
  width: 100px;
  height: 100px;
  outline: 1px solid red;
  font-size: 24px;
  text-align: center;
  line-height: 100px;
  margin: 24px;
}

.box {
  /* width: 100px;
  height: 100px; */
  /* 插件自带宽高 */
  /* outline: 1px solid blue; */
  outline: 1px solid rgba(0, 0, 0, 0);

  position: absolute;
}

.inner-widget {
  width: 100%;
  height: 100%;
}

.layer {
  width: 100%;
  height: 50px;
  line-height: 50px;
  background: #e9e9e9;
}

.layer:hover {
  background: #fff;
}

.currentbgm {
  background: #fff;
}

.sider {
  width: 200px;
  background: #e9e9e9;
}

.sider.right {
  width: 300px;
}

.standard-line {
  width: 2px;
  height: 100%;
  /* background: rgba(31, 29, 29, 1); */
  border-left: 2px #0f0f0f dashed;
  position: absolute;
  left: 200px;
}


.standard-line.correnct {
  /* background: red; */
  border-left: 2px red dashed;
}

#frame {
  position: absolute;
  outline: 2px dashed red;
}
</style>

 4.静态资源管理

4-1.constants下的config.js-组件列表的配置项

import * as dft from "./default"
import * as styleFromConfig from "./style-form-config"

//组件列表的配置项
export const WIDGET_LTST = [
    {
        type:'mianji-chart',
        component:'mianji-chart',
        label:'面积图',
        //因为default后面数据会越来越多,所以单独给了一个js去存
        // default:{
        //     w:300,
        //     h:200,
        //     value:[
        //         {
        //             name:'Mon',
        //             value:820,
        //         },
        //         {
        //             name:'Tue',
        //             value:932,
        //         },
        //         {
        //             name:'Tue',
        //             value:1000,
        //         }
        //     ]
        // }
        default:dft.MIANJI_CHART_DATA,
        styles:dft.MIANJI_STYLE,
        styleFrom:styleFromConfig.MIANJI_CHART,

    },
    {
        type:'area-chart',
        component:'area-chart',
        label:'饼图',
        default:dft.AREA_CHART_DATA,
        styles:dft.AREA_STYLE,
        styleFrom:styleFromConfig.AREA_CHART,


    },
    {
        type:'bar-chart',
        component:'bar-chart',
        label:'柱状图',
        default:dft.BAR_CHART_DATA,
        styles:dft.BAR_STYLE,
        styleFrom:styleFromConfig.BAR_CHART,
    },
    {
        type:'text',
        component:'custom-text',
        label:'文字',
        default:dft.CUSTOM_TEXT_DATA,
        styles:dft.CUSTOM_TEXT_STYLE,
        styleFrom:styleFromConfig.TEXT,
    },
    {
        type:'video',
        component:'custom-video',
        label:'视频',
        default:dft.CUSTOM_VIDEO_DATA,
        styles:dft.CUSTOM_VIDEO_STYLE,
        styleFrom:styleFromConfig.VIDEO,

    }
];

 4-2.constants下的default.js-组件列表的配置项的默认样式

// 面积图默认数据
export const MIANJI_CHART_DATA = {
    w: 300,
    h: 200,
    value: [
        {
            name: 'Mon',
            value: 820,
        },
        {
            name: 'Tue',
            value: 932,
        },
        {
            name: 'Tue',
            value: 1000,
        }
    ]
}
//面积图默认样式
export const MIANJI_STYLE = {
    areaColor:'#ff0000',
    lineColor:'#0000ff',
    xAxisVisible:true,
}



// 饼图默认数据
export const AREA_CHART_DATA = {
    w: 300,
    h: 300,
    value: [
        {
            name: 'zhangsan',
            value: 500,
        },
        {
            name: 'lisi',
            value: 620,
        },
        {
            name: 'wangwu',
            value: 360,
        }
    ]
}

//饼图默认样式
export const AREA_STYLE = {
   title:'标题',
   subtitle:'副标题'
}

//柱状图数据
export const BAR_CHART_DATA = {
    w:250,
    h:300,
    value:[
        {
            name:'Mon',
            value:120,
        },
        {
            name:'Tue',
            value:200,
        },
        {
            name:'Wed',
            value:150,
        },
        {
            name:'Thu',
            value:80,
        }
    ]
}
//柱状图默认样式
export const BAR_STYLE = {
    areaColor:'#ff0000',
    lineColor:'#0000ff',
    xAxisVisible:true,
}

//文字数据
export const CUSTOM_TEXT_DATA = {
    w:200,
    h:50,
    value:'hello world!'
}
//文字默认样式
export const CUSTOM_TEXT_STYLE = {
    color:'#000000',
    fontSize:'24',
}

//视频数据
export const CUSTOM_VIDEO_DATA = {
    w:400,
    h:300,
    value:"https://cdn.theguardian.tv/webM/2015/07/20/150716YesMen_synd_768k_vp8.webm"
}

//视频默认样式
export const CUSTOM_VIDEO_STYLE = {
    // 是否显示控制条
    ctrlBarVisible:true,


}

4-3.constants下的style-form-config.js-组件列表的配置项的面板属性

//面积图
export const MIANJI_CHART = [
    {
        key: 'areaColor',
        label: '区域颜色',
        component: 'el-color-picker',
    },
    {
        key: 'lineColor',
        label: '折线颜色',
        component: 'el-color-picker',
    },
    {
        key: 'xAxisVisible',
        label: '是否显示X轴',
        component: 'el-switch',
    }
]

//饼图
export const AREA_CHART = [
    {
        key: 'title',
        label: '标题',
        component: 'el-input',
    },
    {
        key: 'subtitle',
        label: '副标题',
        component: 'el-input',
    },
]
//柱状图
export const BAR_CHART = [
    {
        key: 'areaColor',
        label: '柱条的颜色',
        component: 'el-color-picker',
    },
    {
        key: 'lineColor',
        label: '背景颜色',
        component: 'el-color-picker',
    },
    {
        key: 'xAxisVisible',
        label: '是否显示X轴',
        component: 'el-switch',
    }
]

//文字数据
export const TEXT = [
    {
        key: 'color',
        label: '颜色',
        // component: 'color-picker',
        component: 'el-color-picker',
    },
    {
        key: 'fontSize',
        label: '字体大小',
        component: 'el-input-number',
    }
]

//视频
export const VIDEO = [
    {
        key: 'ctrlBarVisible',
        label: '是否显示控制条',
        component: 'el-switch',
    },
]

5.封装的组件类型

5-1.custom-video.vue视频组件

<template>
  <player class="custom-video" :options="options" />
</template>

<script>
import "video.js/dist/video-js.css";
import { videoPlayer } from "vue-video-player";

export default {
  components: {
    player: videoPlayer, //给组件重起名
  },
  props:{
    
    value:{
      type:String, //name的类型是字符串
   
    },
    styles:{
            type:Object,
            required:true,
        }
  },
  computed:{
    options(){
      return {
          controls:this.styles.ctrlBarVisible,
          
          autoplay: 'muted', //设置自动播放
          muted: true, // 关闭视频的声音通道(静音),属于逻辑属性
        
          volume: 0.5, // 初始的音量
          language: "en",
          playbackRates: [0.7, 1.0, 1.5, 2.0],// 播放速率
          sources: [
            {
              type: "video/mp4",// 播放格式
              src: "https://cdn.theguardian.tv/webM/2015/07/20/150716YesMen_synd_768k_vp8.webm", // 播放源
              // src: this.value, // 播放源
            },
          ],
          poster: "/static/images/author.gif", //当视频能够播放时,用于指定代表视频的图像的URL
          // width: 500, // 视频框的宽度
          // height: 570, // 视频框的高度
      }
    }
  },
  
  data() {
    return {
     
    };
  },
  mounted(){
   
   

  },
  
  
};
</script>

<style scoped>
/* scoped是当前样式只在当前组件中生效 */
/* player组件跟当前组件没有关系 - 所以可以对样式进行穿透-样式才会到组件中去*/
::v-deep.custom-video>div{
    width: 100%;
    height: 100%;
}
</style>

5-2.area-chart.vue面积图表组件

<template>
  <e-charts 
  :option="option" 
   autoresize
  />
</template>

<script>
export default {
  props:{
     value:{
         type:Array, //name的类型是字符串
         required:true, //name是必要的
     },
     styles:{
            type:Object,
            required:true,
        }
  },
  computed:{
    option(){
      return {
        title: {
          text: this.styles.title,
          subtext: this.styles.subtitle,
          left: "center",
        },
        tooltip: {
          trigger: "item",
        },
        legend: {
          orient: "vertical",
          left: "left",
        },
        series: [
          {
            name: "Access From",
            type: "pie",
            radius: "50%",
           
          },
        ],
      }
    }
  },
  data() {
    return {
     
    };
  },
};
</script>

<style scoped>
</style>

6.安装依赖

6-1.element-ui取用了el-tabs页签、颜色选择器、输入框、开关--全局注册

npm i element-ui -S  
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

6-2.vue-drag-resize拖拽放大缩小-全局注册

npm i -s vue-drag-resize
import VueDragResize from 'vue-drag-resize'//拖拽组件
Vue.component('Drageer', VueDragResize)//全局注册拖拽组件,并重命名为Drageer

6-3.面积图表-全局注册

npm install echarts vue-echarts
import "echarts";
import ECharts from 'vue-echarts'
Vue.component('ECharts', ECharts)//注册

6-3.视频-//写组件里面
npm install vue-video-player --save
import { videoPlayer } from "vue-video-player";

player: videoPlayer, //给组件重起名-写在component里面别直接复制

6-4.npm i vue-context全局
import 'vue-context/dist/css/vue-context.css';//引入右击菜单

7.显示效果如图 -拖动、放置、放大缩小

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值