icodingicoding
主页
JavaScript
Vue
React
TypeScript
Node
bug
笔记
时间线
主页
JavaScript
Vue
React
TypeScript
Node
bug
笔记
时间线
  • Markdown 语法
  • 插件
  • Vue
  • 组件设计
  • Element-Ui
  • WebSocket
  • CSS
  • Uniapp
  • 进阶
  • 扫码枪
  • Nginx
  • Nuxt.js
  • Vue 时钟
  • Learning
  • Linux
  • 打包优化
  • 大屏可视化
  • Jenkins
  • SVN
  • JsDocs
  • 代码规范

插件

Code Inspector

页面开发提效的神器,点击页面上的 DOM,Code Inspector能够自动打开你的 IDE 并将光标定位到 DOM 对应的源代码位置

  • 安装
npm install code-inspector-plugin -D
  • 使用
// webpack.config.js
const { codeInspectorPlugin } = require("code-inspector-plugin");

module.exports = () => ({
  plugins: [
    codeInspectorPlugin({
      bundler: "webpack",
    }),
  ],
});
// vite.config.js
import { defineConfig } from "vite";
import { codeInspectorPlugin } from "code-inspector-plugin";

export default defineConfig({
  plugins: [
    codeInspectorPlugin({
      bundler: "vite",
    }),
  ],
});

vue-draggable-plus

官网:https://vue-draggable-plus.pages.dev/en/

Options 继承自 Sortablejs 所有的配置项,具体请查看 Sortablejs 官方文档 https://github.com/SortableJS/Sortable?tab=readme-ov-file#options

注意

拖拽排序模块,支持 Vue>=v3 或 Vue >=2.7

使用中遇到的问题

  • 已存在指定元素,提示用户元素已存在,并且拦截拖拽,要使用 onMove 事件进行拦截 看SortableJS文档 看SortableJS文档 看SortableJS文档
<template>
  <VueDraggable
    class="drag"
    v-model="group.components"
    :sort="false"
    :group="{ name: 'form', pull: 'clone', put: false }"
    @start="isStart = true"
    @end="isStart = false"
    :clone="clone"
    :onMove="onLeftMove"
  >
    <div :data-type="cp.name" class="inner" v-for="(cp, id) in group.components" :key="id">
      <i :class="cp.icon"></i>
      <span>{{ cp.title }}</span>
    </div>
  </VueDraggable>
</template>

<script>
import VueDraggable from "vue-draggable-plus";
import { debounce } from "@/utils";

export default {
  components: { VueDraggable },
  methods: {
    onLeftMove: debounce(
      function (event) {
        if (event.data.name === "SerialNumber") {
          if (!this._formConfig.hasSerialNumber) {
            return true;
          } else {
            this.$message.error("流水号组件只能添加一次!");
            return false;
          }
        }
        return true;
      },
      300,
      true
    ),
  },
};
</script>
  • 禁止从分组 1 中的某个元素拖拽到分组 2 中:例如,组件库中存在分栏布局组件、明细表组件, 其中分栏布局组件不能拖拽到明细表组件中,同时也不能拖拽到自己中; 明细表组件不能拖拽到分栏布局组件中,同时也不能拖拽到自己中。

解决方案

  • 给 组件库 添加 :data-type="cp.name" 、分别配置 分栏布局组件、明细表组件 的 :group="{ name: 'form', pull: true, put: put }"

  • 在put方法中进行校验

分栏布局组件

<template>
  <el-row :gutter="Number(gutter)">
    <el-col :span="24 / number" v-for="(cols, ri) in _items" :key="ri + '_row'">
      <VueDraggable
        class="l-drag-from"
        v-model="_items[ri]"
        :group="{ name: 'form', pull: true, put: put }"
        :animation="150"
        @start="draggableStart"
        @end="draggableEnd"
        @add="handleAdd"
      >
        <el-form-item
          v-for="(cp, id) in cols"
          :key="id"
          :label="cp.title"
          :required="cp.props.required"
          class="l-form-item"
          @click.native.stop="selectItem(cp)"
          :style="getSelectedClass(cp)"
          :class="{ 'w-form-cp-nlb1': cp.props.hideLabel }"
          :data-type="cp.name"
          :show-message="false"
        >
          <span class="l-option" v-if="active && active.id === cp.id">
            <i class="el-icon-delete" @click="delItem(ri, id)"></i>
          </span>
          <form-design-render :config="cp" :active.sync="_active" class="form-design" :globalConfig="globalConfig" />
        </el-form-item>
      </VueDraggable>
    </el-col>
  </el-row>
</template>
<script>
export default {
  methods: {
    put(to, from, dragEl, event) {
      const type = dragEl.dataset.type;
      switch (type) {
        case "SpanLayout":
          return false;
        default:
          return true;
      }
    },
  },
};
</script>

明细表组件

<template>
  <VueDraggable
    class="l-drag-from"
    v-model="config.columns"
    :group="{ name: 'form', pull: true, put: put }"
    filter=".w-tb-op"
    target=".vxe-header--row"
    :animation="150"
    :key="key"
    @start="draggableStart"
    @end="draggableEnd"
    @add="handleAdd"
    ghostClass="w-f-cp-select"
  >
    <vxe-table
      :data="tableData"
      :border="showBorder"
      ref="tableRef"
      @header-cell-click="ck"
      :header-cell-class-name="getHeaderClass"
      header-row-class-name="vxe-Draggable"
      empty-text="👆拖拽字段到上方列内"
      align="center"
    >
      <vxe-column v-for="col in config.columns" :field="col.id" :title="col.title" :key="col.id" min-width="100">
        <template #header>
          <div>
            <!-- <tooltip :message="col.title"></tooltip> -->
            {{ col.title }}
            <span style="color: #f56c6c" v-if="col.props.required">*</span>
          </div>
          <div class="l-option" v-if="_active && _active.id === col.id">
            <i class="el-icon-delete" @click.stop="delItem(col.id)"></i>
          </div>
        </template>
      </vxe-column>
      <vxe-column title="操作" fixed="right" class="w-tb-op" width="120"></vxe-column>
    </vxe-table>
  </VueDraggable>
</template>
<script>
export default {
  methods: {
    put(to, from, dragEl, event) {
      const type = dragEl.dataset.type;
      switch (type) {
        case "TableList":
        case "SpanLayout":
        case "SerialNumber":
        case "Description":
        case "SegmentationLabel":
          return false;
        default:
          return true;
      }
    },
  },
};
</script>

效果图:

imageimage

组件库

<template>
  <VueDraggable
    class="drag"
    v-model="group.components"
    :sort="false"
    :group="{ name: 'form', pull: 'clone', put: false }"
    @start="isStart = true"
    @end="isStart = false"
    :clone="clone"
    :onMove="onLeftMove"
  >
    <div :data-type="cp.name" class="inner" v-for="(cp, id) in group.components" :key="id">
      <i :class="cp.icon"></i>
      <span>{{ cp.title }}</span>
    </div>
  </VueDraggable>
</template>

<script>
import VueDraggable from "vue-draggable-plus";
import { debounce } from "@/utils";

export default {
  components: { VueDraggable },
  methods: {
    onLeftMove: debounce(
      function (event) {
        if (event.data.name === "SerialNumber") {
          if (!this._formConfig.hasSerialNumber) {
            return true;
          } else {
            this.$message.error("流水号组件只能添加一次!");
            return false;
          }
        }
        return true;
      },
      300,
      true
    ),
  },
};
</script>

组件容器

<template>
  <el-main class="layout-main">
    <div class="tool-nav">
      <div>
        <el-tooltip class="item" effect="dark" content="预览表单" placement="top">
          <i class="el-icon-view" @click="viewForms"></i>
        </el-tooltip>
      </div>
    </div>
    <div class="work-form">
      <div class="tip" v-if="_value.length === 0 && !isStart">👈 请在左侧选择控件并拖至此处</div>
      <el-form
        v-else
        :label-width="_formConfig.labelWidth + 'px'"
        :label-position="_formConfig.labelPosition"
        :size="_formConfig.size"
        @submit.native.prevent
      >
        <VueDraggable
          class="drag-from"
          v-model="_value"
          group="form"
          :animation="150"
          @start="draggableStart"
          @end="drag = false"
          @add="handleAdd"
        >
          <el-form-item
            v-for="(cp, id) in _value"
            :key="id"
            :label="cp.title"
            :required="cp.props.required"
            class="form-item"
            @click.native="selectItem(cp)"
            :style="getSelectedClass(cp)"
            :class="{ 'w-form-cp-nlb': cp.props.hideLabel }"
            :data-type="cp.name"
          >
            <span class="option" v-if="active && active.id === cp.id">
              <i class="el-icon-delete" @click="del(id)"></i>
            </span>
            <form-design-render
              class="form-design"
              :config="cp"
              :value.sync="cp.value"
              :active.sync="active"
              :globalConfig="_formConfig"
            />
          </el-form-item>
        </VueDraggable>
      </el-form>
    </div>
  </el-main>
</template>

spanLayout 分栏布局组件

<template>
  <div class="rtc-span-layout">
    <div v-if="mode === 'DESIGN'">
      <el-row :gutter="Number(gutter)">
        <el-col :span="24 / number" v-for="(cols, ri) in _items" :key="ri + '_row'">
          <VueDraggable
            class="l-drag-from"
            v-model="_items[ri]"
            :group="{ name: 'form', pull: true, put: put }"
            :animation="150"
            @start="draggableStart"
            @end="draggableEnd"
            @add="handleAdd"
          >
            <el-form-item
              v-for="(cp, id) in cols"
              :key="id"
              :label="cp.title"
              :required="cp.props.required"
              class="l-form-item"
              @click.native.stop="selectItem(cp)"
              :style="getSelectedClass(cp)"
              :class="{ 'w-form-cp-nlb1': cp.props.hideLabel }"
              :data-type="cp.name"
              :show-message="false"
            >
              <span class="l-option" v-if="active && active.id === cp.id">
                <i class="el-icon-delete" @click="delItem(ri, id)"></i>
              </span>
              <form-design-render
                :config="cp"
                :active.sync="_active"
                class="form-design"
                :globalConfig="globalConfig"
              />
            </el-form-item>
          </VueDraggable>
        </el-col>
      </el-row>
    </div>
    <div v-else class="bottom">
      <el-row :gutter="Number(gutter)">
        <el-col :span="24 / number" v-for="(cols, ri) in _items" :key="ri + '_row'">
          <template v-for="(item, id) in cols">
            <el-form-item
              :prop="item.id"
              :label="item.title"
              v-if="getIsShow(item.id)"
              :required="item.props.required"
              :key="item.name + id"
              :class="{ 'w-form-cp-nlb1': item.props.hideLabel }"
              :show-message="false"
            >
              <form-design-render
                :value.sync="item.value"
                :mode="mode"
                :config="item"
                :PermissionList="PermissionList"
                :globalConfig="globalConfig"
              />
            </el-form-item>
            <!-- <form-design-render
              :key="item.name + id"
              v-else
              v-model="_value"
              :mode="mode"
              :config="item"
            /> -->
          </template>
        </el-col>
      </el-row>
    </div>
  </div>
</template>

<script>
import { VueDraggable } from "vue-draggable-plus";
import FormDesignRender from "@/views/userCenter/formDesign/admin/layout/form/FormDesignRender.vue";
import componentMinxins from "../ComponentMinxins";
import { mapMutations, mapState } from "vuex";
export default {
  mixins: [componentMinxins],
  name: "SpanLayout",
  components: { VueDraggable, FormDesignRender },
  props: {
    value: {
      default: null,
    },
    items: {
      type: Array,
      default: () => {
        return [];
      },
    },
    number: {
      type: Number,
      default: 2,
    },
    gutter: {
      type: Number | String,
      default: 5,
    },
    globalConfig: {
      type: Object,
      default: () => ({}),
    },
  },
  computed: {
    _items: {
      get() {
        return this.items;
      },
      set(val) {
        this.items = val;
      },
    },
    isRouteName() {
      return this.$route.name == "statisticsFormDetails";
    },
    ...mapState("formDesign", ["selectFormItem", "nodeMap"]),
  },
  watch: {
    number: {
      handler() {
        this.reloadSpan();
      },
    },
  },
  methods: {
    ...mapMutations("formDesign", ["setSelectFormItem"]),
    getIsShow(id) {
      if (this.isRouteName) {
        if (this.PermissionList.length) {
          let obj = this.PermissionList.find((item) => item.Uid == id);
          if (obj) {
            return obj.Status != 3;
          } else {
            return true;
          }
        } else {
          return true;
        }
      } else {
        return true;
      }
    },
    reloadSpan() {
      //初始化分栏数据值
      if (this.number > this.items.length) {
        for (let i = 0; i < this.number; i++) {
          if (!this.items[i]) {
            this._items.push([]);
          }
        }
      } else {
        this._items.length = this.number;
      }
    },
    selectItem(cp) {
      this._active = cp;
    },
    getSelectedClass(cp) {
      return this._active && this._active.id === cp.id ? "border: 1px dashed #1fa3b0" : "";
    },
    draggableStart() {
      this._active = {};
    },
    draggableEnd() {},
    put(to, from, dragEl, event) {
      const type = dragEl.dataset.type;
      switch (type) {
        case "SpanLayout":
          return false;
        default:
          return true;
      }
    },
    async handleAdd(e) {
      console.log(99, e);
      if (e.from._prevClass == "vxe-header--row vxe-Draggable") {
        e.clonedData.id = this.getId();
      }
      if (e.clonedData.name == "SpanLayout") {
        // this._items.forEach(item => {
        //   let index = item.findIndex(item1 => item1.name == "SpanLayout");
        //   index > -1 && item.splice(index, 1);
        // });
        // this.$message.error("分栏布局组件不能添加分栏布局!");
      } else if (e.clonedData.name == "SerialNumber") {
        if (e.data.name === "SerialNumber" && !this.globalConfig.hasSerialNumber) {
          this.globalConfig.hasSerialNumber = true;
        }
      }
    },
    getId() {
      return (
        "field" +
        (Math.floor(Math.random() * (99999 - 10000)) + 10000).toString() +
        new Date().getTime().toString().substring(5)
      );
    },
    delItem(ri, index) {
      this.$confirm("删除组件将会连带删除包含该组件的条件以及相关设置,是否继续?", "提示", {
        confirmButtonText: "确 定",
        cancelButtonText: "取 消",
        type: "error",
      })
        .then(() => {
          if (this._items[ri][index].name === "SpanLayout") {
            //删除的是分栏则遍历删除分栏内所有子组件
            this._items[ri][index].props.items.forEach((item) => {
              this.removeFormItemAbout(item);
            });
            this._items[ri][index].props.items.length = 0;
          } else {
            if (this._items[ri][index].name === "SerialNumber") {
              this.globalConfig.hasSerialNumber = false;
            }
            this.removeFormItemAbout(this._items[ri][index]);
          }
          this._items[ri].splice(index, 1);
          // this.setSelectFormItem(null);
          this._active = {};
        })
        .catch(() => {});
    },
    async removeFormItemAbout(item) {
      this.nodeMap.forEach((node) => {
        //搜寻条件,进行移除
        if (node.type === "CONDITION") {
          node.props.groups.forEach((group) => {
            let i = group.cids.remove(item.id);
            if (i > -1) {
              //从子条件移除
              group.conditions.splice(i, 1);
            }
          });
        }
        //搜寻权限,进行移除
        if (node.type === "ROOT" || node.type === "APPROVAL" || node.type === "CC") {
          node.props.formPerms.removeByKey("id", item.id);
        }
      });
    },
  },
};
</script>

<style lang="scss" scoped>
/*去除表单项的label名称*/
>>> .w-form-cp-nlb1 {
  .el-form-item__label {
    display: none;
  }
  .el-form-item__content {
    margin-left: 0 !important;
  }
}
>>> .el-row {
  width: 100%;
  .el-col {
    margin-bottom: 0;
  }
}
.choose {
  border: 1px dashed #1fa3b0 !important;
}

.l-drag-from {
  min-height: 50px;
  background-color: rgb(245, 246, 246);

  .l-form-item {
    cursor: grab;
    // background: #ffffff;
    border: 1px solid transparent;
    margin-bottom: 5px;
    &:hover {
      border: 1px dashed #1fa3b0;
    }
  }
}

.l-option {
  position: absolute;
  bottom: 0;
  right: 0;
  z-index: 10;
  background: #1fa3b0;
  border-radius: 2px;
  height: 23px;
  line-height: 23px;
  i {
    font-size: small;
    cursor: pointer;
    color: #fff;
    padding: 5px;
  }
  &:hover {
    background: #62bec7;
  }
}
.bottom {
  .el-form-item {
    margin-bottom: 22px !important;
  }
}
>>> .el-rate {
  height: 100% !important;
}
</style>

tableList 明细表组件

<template>
  <div>
    <div v-if="mode === 'DESIGN'">
      <VueDraggable
        class="l-drag-from"
        v-model="config.columns"
        :group="{ name: 'form', pull: true, put: put }"
        filter=".w-tb-op"
        target=".vxe-header--row"
        :animation="150"
        :key="key"
        @start="draggableStart"
        @end="draggableEnd"
        @add="handleAdd"
        ghostClass="w-f-cp-select"
      >
        <vxe-table
          :data="tableData"
          :border="showBorder"
          ref="tableRef"
          @header-cell-click="ck"
          :header-cell-class-name="getHeaderClass"
          header-row-class-name="vxe-Draggable"
          empty-text="👆拖拽字段到上方列内"
          align="center"
        >
          <vxe-column v-for="col in config.columns" :field="col.id" :title="col.title" :key="col.id" min-width="100">
            <template #header>
              <div>
                <!-- <tooltip :message="col.title"></tooltip> -->
                {{ col.title }}
                <span style="color: #f56c6c" v-if="col.props.required">*</span>
              </div>
              <div class="l-option" v-if="_active && _active.id === col.id">
                <i class="el-icon-delete" @click.stop="delItem(col.id)"></i>
              </div>
            </template>
          </vxe-column>
          <vxe-column title="操作" fixed="right" class="w-tb-op" width="120"></vxe-column>
        </vxe-table>
      </VueDraggable>
    </div>
    <div v-else-if="mode === 'print'" class="print1">
      <table border="0">
        <tr>
          <template v-for="(item, index) in tableProps">
            <th :key="index">
              {{ item.title }}
            </th>
          </template>
        </tr>
        <tr v-for="(item, index) in _value" :key="index">
          <td v-for="(item1, index1) in tableProps" :key="index1">
            <form-design-render
              :value.sync="item[item1.id]"
              :PermissionList="PermissionList"
              :mode="mode"
              :config="item1"
              :isTable="true"
            />
          </td>
        </tr>
      </table>
    </div>
    <div v-else>
      <vxe-table
        :data="_value"
        :border="showBorder"
        :header-cell-class-name="getHeaderClass1"
        align="center"
        :column-config="{ resizable: true }"
      >
        <template v-for="col in tableProps">
          <vxe-column :field="col.id" v-if="getIsShow(col.id)" :title="col.title" :key="col.id" min-width="100">
            <template #header>
              <div>
                {{ col.title }}
                <span style="color: #f56c6c" v-if="col.props.required">*</span>
              </div>
            </template>
            <template #default="{ row, column: currentColumn }">
              <form-design-render
                :class="{
                  'valid-error': showError(col, row[col.id]),
                }"
                :value.sync="row[currentColumn.field]"
                :PermissionList="PermissionList"
                :mode="mode"
                :config="col"
                :isTable="true"
              />
            </template>
          </vxe-column>
        </template>
        <vxe-column title="操作" fixed="right" width="120" v-if="isShow != 1 && Status != 'approval'">
          <template #header>
            <span>操作</span>
            <el-button
              icon="el-icon-plus"
              @click="addRow"
              size="small"
              circle
              :globalConfig="globalConfig"
              class="w-tb-op-add"
            />
          </template>
          <template #default="{ row }">
            <el-button size="mini" type="text" @click="delRow(row)">删除</el-button>
          </template>
        </vxe-column>
      </vxe-table>
    </div>
  </div>
</template>

<script>
import { VueDraggable } from "vue-draggable-plus";
import { ValueType } from "../ComponentsConfigExport";
import FormDesignRender from "@/views/userCenter/formDesign/admin/layout/form/FormDesignRender";
import componentMinxins from "../ComponentMinxins";
import { deepClone } from "@/utils";
export default {
  mixins: [componentMinxins],
  name: "TableList",
  components: { VueDraggable, FormDesignRender },
  props: {
    value: {
      type: Array,
      default: () => {
        return [];
      },
    },
    placeholder: {
      type: String,
      default: "添加数据",
    },
    showBorder: {
      type: Boolean,
      default: true,
    },
    maxSize: {
      type: Number,
      default: 0,
    },
    rowLayout: {
      type: Boolean,
      default: true,
    },
    config: {
      type: Object,
      default: () => {},
    },
    globalConfig: {
      type: Object,
      default: () => ({}),
    },
  },
  watch: {
    "config.columns": {
      handler() {
        this.key++;
      },
      deep: true,
    },
  },
  created() {
    if (!Array.isArray(this.value)) {
      this._value = [];
    }
  },
  data() {
    return {
      select: null,
      drag: false,
      ValueType,
      tbCellStyle: {
        padding: "10px 0",
      },
      key: 0,
      tableData: [],
    };
  },
  computed: {
    isRouteName() {
      return this.$route.name == "statisticsFormDetails";
    },
    tableProps() {
      return this.config.columns.filter((item) => {
        if (this.isRouteName) {
          if (this.PermissionList.length) {
            let obj = this.PermissionList.find((item1) => item1.Uid == item.id);
            if (obj) {
              return obj.Status != 3;
            } else {
              return true;
            }
          } else {
            return true;
          }
        } else {
          return true;
        }
      });
    },
  },
  methods: {
    getIsShow(id) {
      if (this.isRouteName) {
        if (this.PermissionList.length) {
          let obj = this.PermissionList.find((item) => item.Uid == id);
          if (obj) {
            return obj.Status != 3;
          } else {
            return true;
          }
        } else {
          return true;
        }
      } else {
        return true;
      }
    },
    showError(col, val) {
      if (col.props.required) {
        switch (col.valueType) {
          case ValueType.dept:
          case ValueType.user:
          case ValueType.dateRange:
          case ValueType.array:
            return !(Array.isArray(val) && val.length > 0);
          default:
            return !val;
        }
      }
      return false;
    },
    put(to, from, dragEl, event) {
      const type = dragEl.dataset.type;
      switch (type) {
        case "TableList":
        case "SpanLayout":
        case "SerialNumber":
        case "Description":
        case "SegmentationLabel":
          return false;
        default:
          return true;
      }
    },
    handleAdd(e) {
      console.log(e);
      e.clonedData.id = this.getId();
      // if (e.clonedData.name == "SerialNumber") {
      //   if (
      //     e.from._prevClass == "drag-from" ||
      //     e.from._prevClass == "l-drag-from"
      //   ) {
      //     this.globalConfig.hasSerialNumber = false;
      //   }
      //   this.config.columns.splice(e.newIndex, 1);
      //   this.$message.error("明细表组件不能添加流水号!");
      // } else if (e.clonedData.name == "SpanLayout") {
      //   this.config.columns.splice(e.newIndex, 1);
      //   this.$message.error("明细表组件不能添加分栏组件!");
      // } else if (e.clonedData.name == "TableList") {
      //   this.config.columns.splice(e.newIndex, 1);
      //   this.$message.error("明细表组件不能添加明细表!");
      // }
    },
    getId() {
      return (
        "field" +
        (Math.floor(Math.random() * (99999 - 10000)) + 10000).toString() +
        new Date().getTime().toString().substring(5)
      );
    },
    copyData(i, row) {
      this._value.push(deepClone(row));
    },
    delRow(row) {
      let index = this._value.findIndex((item) => item._X_ROW_KEY == row._X_ROW_KEY);
      this._value.splice(index, 1);
    },
    addRow() {
      let row = {};
      this.config.columns.forEach((col) => {
        this.$set(row, col.id, deepClone(col.value));
      });
      this._value.push(row);
      this.$set(this, "_value", this._value);
    },
    delItem(id) {
      this.$confirm("删除组件将会连带删除包含该组件的条件以及相关设置,是否继续?", "提示", {
        confirmButtonText: "确 定",
        cancelButtonText: "取 消",
        type: "error",
      })
        .then(() => {
          let index = this.config.columns.findIndex((item) => item.id == id);
          this.config.columns.splice(index, 1);
          this._active = {};
        })
        .catch(() => {});
    },
    selectItem(cp) {
      this._active = cp;
    },
    ck({ column, $event }) {
      $event.stopPropagation();
      if (column.property) {
        this._active = this.config.columns.find((item) => item.id == column.property);
      }
    },
    getHeaderClass({ column }) {
      if (!column.property) {
        return "w-tb-op"; // 返回 class 名称
      } else if (column.property === this._active.id) {
        return "w-form-cp-active"; // 返回 class 名称
      }
      return "";
    },
    getHeaderClass1({ column }) {
      if (!column.property) {
        return "w-tb-op"; // 返回 class 名称
      }
      return "";
    },
    draggableStart() {
      this._active = {};
    },
    draggableEnd(evt) {
      this.$nextTick(() => {
        this.key++;
        this.$refs.tableRef.reloadData([]);
        this.$refs.tableRef.refreshColumn();
      });
      // setTimeout(() => {
      //   this.key++
      // }, 300)
      // const tableRef = this.$refs.tableRef
      // tableRef.reloadColumn(this.config.columns)
      // console.log(this.config.columns)
      // const newColumns = [...this.config.columns];
      // const [moved] = newColumns.splice(evt.oldIndex, 1);
      // newColumns.splice(evt.newIndex, 0, moved);
      // this.config.columns = newColumns; // 必须赋值新数组触发响应式
    },
    getSelectedClass(cp) {
      return this._active && this._active.id === cp.id ? "border-left: 4px solid #f56c6c" : "";
    },
    validate(call) {
      if (this.rowLayout) {
        let result = true;
        for (let i = 0; i < this.columns.length; i++) {
          if (this.columns[i].props.required) {
            for (let j = 0; j < this._value.length; j++) {
              result = !this.showError(this.columns[i], this._value[j][this.columns[i].id]);
              if (!result) {
                call(false);
                return;
              }
            }
          }
        }
        call(result);
      } else {
        let success = 0;
        this._value.forEach((v, i) => {
          let formRef = this.$refs[`table-form-${i}`];
          if (formRef && Array.isArray(formRef) && formRef.length > 0) {
            formRef[0].validate((valid) => {
              if (valid) {
                success++;
              }
            });
          }
        });
        if (success === this._value.length) {
          call(true);
        } else {
          call(false);
        }
      }
    },
  },
};
</script>

<style lang="scss" scoped>
>>> .valid-error {
  .el-input__inner {
    border-color: #f56c6c;
  }
}

.choose {
  border: 1px dashed #1fa3b0 !important;
}

.l-option {
  position: absolute;
  bottom: 0;
  right: 0;
  z-index: 10;
  background: #1fa3b0;
  border-radius: 2px;
  i {
    font-size: small;
    cursor: pointer;
    color: #fff;
    padding: 5px;
  }
  &:hover {
    background: #62bec7;
  }
}
>>> .w-form-cp-active {
  border: 1px dashed #1fa3b0;
  .cell {
    overflow: visible;
  }
}
>>> .w-form-d-item {
  position: relative;
  padding: 5px;
  margin: 2px;
  border: 1px dashed white;
  &:hover {
    border: 1px dashed #1fa3b0;
  }
}
>>> .vxe-cell {
  white-space: normal;
}
.w-tb-op {
  position: relative;
  .w-tb-op-add {
    position: absolute;
    top: 5px;
    right: 5px;
  }
}
.w-f-cp-select {
  border-radius: 2px;
  border: 1px dashed #1fa3b0 !important;
  padding: 15px 0;
  width: 100px;
}
>>> .el-rate {
  height: 100% !important;
}
.print1 {
  table {
    font-size: 16px;
    width: 100%;
    border-collapse: collapse;
    padding: 2px;
    tr {
      vertical-align: middle;
      th {
        text-align: left;
        border: 1px solid #464648;
        padding: 5px 10px;
        width: 20%;
      }
      td {
        text-align: left;
        border: 1px solid #464648;
        padding: 5px 10px;
      }
    }
  }
}
</style>

canvas-editor

基于 canvas/svg 的富文本编辑器

官网地址

最后编辑:
上一页
Markdown 语法
下一页
Vue