实现首页自定义之vue-grid-layout

6,671 阅读3分钟

前言

最近在做项目的时候遇到一个需求:要求用户在使用前端页面的时候可以自定义首页的内容以及首页模块的位置拖动。其他的废话我也就不多说了,下面直接浅谈一下我在使用vue-grid-layout时的一些心得。

vue-grid-layout基本使用

附:vue-grid-layout中文官网

安装(npm)

npm install vue-grid-layout --save

安装后在使用中可能会报错(版本问题)

image.png 把vue-grid-layout的版本换成 2.3.7 就可以了

npm install vue-gird-layout@2.3.7 --save

用法

HTML代码

<grid-layout
            :layout.sync="layout"    //栅格的初始布局数据源。值必须为 `Array`,其数据项为 `Object`。 每条数据项必须有 `i, x, y, w 和 h` 属性。
            :col-num="12"            //定义栅格系统的列数,其值需为自然数。
            :row-height="30"         //每行的高度,单位像素。
            :is-draggable="true"     //标识栅格中的元素是否可拖拽。
            :is-resizable="true"     //标识栅格中的元素是否可调整大小。
            :is-mirrored="false"     //标识栅格中的元素是否可镜像反转。 (个人感觉镜像反转在实际应用中并不会常用)
            :vertical-compact="true" //标识布局是否垂直压缩。即行内垂直方向上顶端对齐
            :margin="[10, 10]"       //每个栅格之间的间距
            :use-css-transforms="true">   //标识是否使用CSS属性 `transition-property: transform;`
        <grid-item v-for="item in layout"
                   :x="item.x"
                   :y="item.y"
                   :w="item.w"
                   :h="item.h"
                   :i="item.i"
                   :key="item.i">
            {{item.i}}      //在这里就可以往每个栅格里面添加内容了
        </grid-item>
    </grid-layout>

JS代码

	new Vue({
	    el: '#app',
	    data: {
	        layout: [
                {"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"},
                {"x":0,"y":5,"w":2,"h":5,"i":"6"},
                {"x":2,"y":5,"w":2,"h":5,"i":"7"},
                {"x":4,"y":5,"w":2,"h":5,"i":"8"},
                {"x":6,"y":3,"w":2,"h":4,"i":"9"},
                {"x":8,"y":4,"w":2,"h":4,"i":"10"},
                {"x":10,"y":4,"w":2,"h":4,"i":"11"},
                {"x":0,"y":10,"w":2,"h":5,"i":"12"},
                {"x":2,"y":10,"w":2,"h":5,"i":"13"},
                {"x":4,"y":8,"w":2,"h":4,"i":"14"},
                {"x":6,"y":8,"w":2,"h":4,"i":"15"},
                {"x":8,"y":10,"w":2,"h":5,"i":"16"},
                {"x":10,"y":4,"w":2,"h":2,"i":"17"},
                {"x":0,"y":9,"w":2,"h":3,"i":"18"},
                {"x":2,"y":6,"w":2,"h":2,"i":"19"}
            ],
	    },
	});

以下仅列出了 x、y、w、h、i。其余属性请参考官方文档

  1. i
  • type: String
  • required: true

栅格中元素的ID。

  1. x
  • type: Number
  • required: true

标识栅格元素位于第几列,需为自然数。

  1. y
  • type: Number
  • required: true

标识栅格元素位于第几行,需为自然数。

  1. w
  • type: Number
  • required: true

标识栅格元素的初始宽度,值为colWidth的倍数。

  1. h
  • type: Number
  • required: true

标识栅格元素的初始高度,值为rowHeight的倍数。

实现栅格左上对齐

添加自定义方法:添加事件、删除事件

  • 添加时左上对齐 在 grid-item 为每个栅格添加一个删除按钮
<span class="remove" @click="removeItem(item.i)"> x </span>
// 我这里写的这个方法是给数组里面push一组新数据
layoutPush() {
      for (var i = 0; i < this.layoutCheck.length; i++) {
        this.layoutCheck[i].x = (i * 6) % (this.colNum || 12);
        this.layoutCheck[i].y = i + (this.colNum || 12);
        console.log(this.colNum);
      }
      console.log(this.layoutIndex);
      this.layout = this.layoutCheck;
      this.di

删除时左上对齐

    removeItem: function(val) {
      const index = this.layout.map(item => item.i).indexOf(val);
      this.layout.splice(index, 1);
      console.log(this.layout);
      for (var i = 0; i < this.layout.length; i++) {
        this.layout[i].x = (i * 6) % (this.colNum || 12);
        this.layout[i].y = i + (this.colNum || 12);
      }
    },

自定义组件渲染

  1. 将自己封装的组将import进来
  2. 直接将自定义组件标签写入 grid-item内的话每个组件都会重复渲染
  3. v-if来添加组件渲染条件 给数组对象新增一个键值 value:'xxxx',如下: {"x":2,"y":6,"w":2,"h":2,"i":"19",value:'xxxx',} 或者根据i来判断也可以

最后在 grid-item内写入引入的组件标签,用v-if添加渲染条件

<组件名称 v-if="item.value ==='xxxx' "></组件名称>
//满足条件则渲染上去

完整代码

<template>
  <div style="width: 100%">
    <div class="headerPic">
      <!-- 轮播图 -->
      <el-carousel height="180px" direction="vertical" :autoplay="true">
        <el-carousel-item v-for="item in 3" :key="item">
          <h3 class="medium">{{ item }}</h3>
        </el-carousel-item>
      </el-carousel>
    </div>
    <div class="layoutJSON">
      Displayed as <code>[x, y, w, h]</code>:
      <div class="columns">
        <div v-for="item in layout" :key="item.i">
          <b>{{ item.i }}</b
          >: [{{ item.x }}, {{ item.y }}, {{ item.w }}, {{ item.h }}]
        </div>
      </div>
    </div>
    <hr />
    <!-- <button @click="addItem">新增</button> -->
    <button @click="layoutReset">重置</button>
    <button @click="clearLayoutBox">清空</button>
    <button @click="addDialog">弹窗添加组件视图</button>
    <button @click="savelayout">预览</button>
    <button @click="changlayout">编辑</button>
    <input type="checkbox" v-model="draggable" /> 可拖动
    <input type="checkbox" v-model="resizable" /> 可调整大小
    <input type="checkbox" v-model="responsive" /> 响应式
    <br />
    <!-- 拖拽桌面区 -->
    <div style="width: 100%; margin-top: 10px">
      <grid-layout
        :layout.sync="layout"
        :responsive-layouts="layouts"
        :col-num="12"
        :row-height="30"
        :is-draggable="draggable"
        :is-resizable="resizable"
        :responsive="responsive"
        :vertical-compact="true"
        :use-css-transforms="true"
        :auto-size="true"
      >
        <grid-item
          v-for="item in layout"
          :x="item.x"
          :y="item.y"
          :w="item.w"
          :h="item.h"
          :i="item.i"
          :key="item.i"
          :auto-size="true"
        >
          <!-- 公告组件 -->
          <el-scrollbar style="height: 100%;"
            ><component-test v-if="item.index == 'test1'"></component-test>
            <!-- 消息待办组件 -->
            <component-test-1 v-if="item.index == 'test2'"></component-test-1>
            <!-- 消息 -->
            <user-info-list v-if="item.index == 'test3'"></user-info-list>
            <!-- 邮箱 -->
            <email-layout v-if="item.index == 'test4'"></email-layout>
            <upload-files v-if="item.index == 'test5'"></upload-files>
            <date-picker
              v-if="item.index == 'test6'"
              :value="now"
            ></date-picker>
          </el-scrollbar>

          <!-- <span class="text">{{item.i}}</span> -->
          <span
            class="remove"
            @click="removeItem(item.i)"
            v-if="yulan"
            style="margin-right:15px; font-size:20px"
            >x</span>
        </grid-item>
      </grid-layout>
    </div>
    <!-- 弹窗选择区 -->
    <el-dialog
      title="选择组件"
      :visible.sync="dialogVisible"
      width="80%"
      :before-close="dialogClose"
    >
      <!-- 标签页 -->
      <el-tabs v-model="activeName" @tab-click="elTabsClick">
        <el-tab-pane label="组件分类1" name="first">
          <el-checkbox-group v-model="layoutCheck" @change="addlayout">
            <el-checkbox
              v-for="(box, index) in allLayouts"
              :label="box"
              :key="index"
              style="border: 1px solid red; margin-bottom: 7px; width: 200px"
            >
              <span>{{ box.name }}</span>
              <div>
                <img
                  src="../assets/icon/安全.png"
                  alt="图片"
                  width="100"
                  height="100"
                />
              </div>
            </el-checkbox>
          </el-checkbox-group>
        </el-tab-pane>
        <el-tab-pane label="组件分类2" name="second">组件分类2</el-tab-pane>
        <el-tab-pane label="组件分类3" name="third">组件分类3</el-tab-pane>
        <el-tab-pane label="组件分类4" name="fourth">组件分类4</el-tab-pane>
      </el-tabs>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="layoutPush">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import { GridLayout, GridItem } from "vue-grid-layout";
import componentTest from "../layoutDom/ComponentTest.vue";
import componentTest1 from "../layoutDom/ComponentTest1.vue";
import userInfoList from "../layoutDom/userInfoList.vue";
import emailLayout from "../layoutDom/emailLayout.vue";
import EmailLayout from "../layoutDom/emailLayout.vue";
import uploadFiles from "../layoutDom/uploadfiles.vue";
import DatePicker from "../layoutDom/date-picker.vue";
let testLayouts = {
  lg: [],
  all: [
    { x: 4, y: 8, w: 6, h: 8, i: "4", index: "test1", name: "公告" },
    { x: 8, y: 8, w: 6, h: 8, i: "5", index: "test2", name: "消息待办" },
    { x: 0, y: 16, w: 6, h: 8, i: "6", index: "test3", name: "消息" },
    { x: 4, y: 16, w: 6, h: 8, i: "7", index: "test4", name: "邮箱" },
    { x: 8, y: 16, w: 6, h: 8, i: "8", index: "test5", name: "上传文件" },
    { x: 8, y: 16, w: 6, h: 8, i: "9", index: "test6", name: "日历" }
  ]
};

export default {
  components: {
    GridLayout,
    GridItem,
    componentTest,
    componentTest1,
    userInfoList,
    EmailLayout,
    uploadFiles,
    DatePicker
  },
  data() {
    return {
      now: new Date(),
      colNum: 12,
      layoutCheck: [],
      allLayouts: testLayouts["all"],
      layouts: testLayouts,
      layout: [],
      draggable: true,
      resizable: true,
      responsive: true,
      yulan: true,
      dialogVisible: false,
      activeName: "first"
    };
  },
  mounted() {
    // this.$gridlayout.load();
    // this.index = this.layout.length;
    // console.log(this.$refs);
    // this.$refs.div2.style.backgroundColor = "green";
    // console.log(this.$refs.div2)
  },
  methods: {
    breakpointChangedEvent: function(newBreakpoint, newLayout) {
      console.log(
        "BREAKPOINT CHANGED breakpoint=",
        newBreakpoint,
        ", layout: ",
        newLayout
      );
    },

    removeItem: function(val) {
      const index = this.layout.map(item => item.i).indexOf(val);
      this.layout.splice(index, 1);
      console.log(this.layout);
      for (var i = 0; i < this.layout.length; i++) {
        this.layout[i].x = (i * 6) % (this.colNum || 12);
        this.layout[i].y = i + (this.colNum || 12);
      }
    },
    savelayout() {
      this.draggable = false;
      this.resizable = false;
      this.yulan = false;
    },
    changlayout() {
      this.draggable = true;
      this.resizable = true;
      this.yulan = true;
    },
    addDialog() {
      this.dialogVisible = true;
    },
    dialogClose(done) {
      this.$confirm("确认关闭?")
        .then(_ => {
          done();
        })
        .catch(_ => {});
    },
    elTabsClick(tab, event) {
      console.log(tab, event);
    },

    layoutPush() {
      for (var i = 0; i < this.layoutCheck.length; i++) {
        this.layoutCheck[i].x = (i * 6) % (this.colNum || 12);
        this.layoutCheck[i].y = i + (this.colNum || 12);
        console.log(this.colNum);
      }
      console.log(this.layoutIndex);
      this.layout = this.layoutCheck;
      this.dialogVisible = false;
    },
    //   选择的组件
    addlayout() {
      console.log(this.layoutCheck);
    },
    clearLayoutBox() {
      this.layout = [];
      this.layoutCheck = [];
    },
    layoutReset() {
      this.layout = this.layoutCheck;
      console.log("重置");
    }
  }
};
</script>

结束

在做这个功能的时候也在网上找过有很多资料,但最终都是无功而返。虽然最终想要的功能还是大致实现了,但还是感觉这样子写法还是过于草率了,希望路过的大佬们能给出一些想法和建议。