Vue2-封装一个含所有表单控件且支持动态增减行列的表格组件

效果

1. 无编辑权限:显示普通表格

2. 有编辑权限:根据配置显示编辑控件

3. 可以动态新增行,也可以动态新增列 

核心代码

无权限情况的核心代码

      <!-- 无编辑权限时显示普通表格 -->
      <el-table
        v-if="!hasEditPermission"
        ref="table"
        :border="border"
        :key="tablekey"
        fit
        v-bind="$attrs"
        :element-loading-text="loadingText"
        :data="tableData"
        :header-cell-style="headerCellStyles"
        :cell-style="cellStyles"
        v-on="$listeners"
      >
        <!-- 复选框 -->
        <el-table-column
          v-if="showSelection"
          column-key="selection"
          v-bind="$attrs"
          type="selection"
          width="35"
          align="center"
          fixed
        />

        <!-- 序号 -->
        <el-table-column
          v-if="showIndex"
          column-key="index"
          label="序号"
          type="index"
          width="55"
          align="left"
          fixed
        />

        <!-- 所有列可扩展 -->
        <el-table-column
          v-if="showExpand"
          type="expand"
          width="25"
          align="center"
          fixed
        >
          <slot
            slot-scope="scope"
            name="expand"
            :elCol="scope.column"
            :row="scope.row"
            :rowIndex="scope.$index"
          />
        </el-table-column>

        <!-- 表头数据 -->
        <el-table-column
          v-for="(column, cIndex) in tableColumns"
          :key="cIndex"
          :align="column.align || 'center'"
          :prop="column.prop"
          :label="column.label"
          :width="column.width"
          :sortable="column.sortable || false"
          :type="column.type"
          :fixed="column.fixed || (cIndex === 0 && firstColumnFixed)"
          v-bind="column.attrs"
          v-on="column.listeners"
        >
          <!-- 表头的自定义插槽, 使用 slotHeaderName 和 slotHeaderIcon 定义-->
          <template
            v-if="column.slotHeaderName || column.slotHeaderIcon"
            #header
          >
            <!-- 表头自定义插槽-->
            <slot
              v-if="column.slotHeaderName"
              :name="column.slotHeaderName"
              :row="column"
            />
            <span v-if="column.slotHeaderIcon">{{ column.label }}</span>
            <!-- 表头图标插槽:使用表头图标,默认添加弹出层,弹出层内容插槽名称为:表头插槽名称 + PopContent -->
            <el-popover
              v-if="column.slotHeaderIcon"
              class="header-slot-popup"
              placement="bottom"
              :title="column.slotHeaderIconPopTitle || column.label"
              :width="column.slotHeaderPopWidth"
              trigger="click"
            >
              <div class="filter-content">
                <slot
                  v-if="column.slotHeaderIcon"
                  class="filter-slot"
                  :name="column.slotHeaderPopName + 'PopContent'"
                />
              </div>
              <!-- 列对象中添加 slotHeaderIcon 属性,表示使用头部图标插槽-->
              <svg-icon
                v-if="column.slotHeaderIcon"
                slot="reference"
                class="header-icon-slot"
                :class="column.slotHeaderIconClass"
                :icon-class="column.slotHeaderIcon"
                @click="headerIconClick(column)"
              />
            </el-popover>
          </template>

          <!-- 插入自定义模板到列中 -->
          <slot
            v-if="column.textSlot"
            slot-scope="scope"
            :name="column.textSlotName || 'slotTextCol'"
            :colConf="column"
            :elCol="scope.column"
            :row="scope.row"
            :rowIndex="scope.$index"
            :colIndex="cIndex"
            :rowspan="column.rowspan"
          />

          <!-- 扩展列插槽 -->
          <template v-else-if="column.expandSlot">
            <slot
              v-if="column.expandSlot"
              :name="column.expandSlot || 'slotExpend'"
              :colConf="column"
              :elCol="scope.column"
              :row="scope.row"
              :rowIndex="scope.$index"
              :colIndex="cIndex"
              :rowspan="column.rowspan"
            />
          </template>

          <!--列内容插槽 -->
          <template v-else slot-scope="scope">
            <div class="content">
              <el-checkbox
                v-if="column.checkbox"
                :value="checked(scope.row, column)"
                @change="checkboxChange($event, scope.row, column)"
                >{{ scope.row[column.prop] }}</el-checkbox
              >
              <el-tooltip
                v-else
                class="tootip-container"
                effect="dark"
                placement="top"
                :disabled="!column.tooltip"
              >
                <template slot="content">
                  <!-- 字符串长度大于 80 格式化 -->
                  <span
                    v-if="String(scope.row[column.prop]).length > 80"
                    :class="{ 'cursor-pointer hoveTitle': column.tooltip }"
                    v-html="formateTooltip(String(scope.row[column.prop]))"
                  />
                  <span v-else>{{ String(scope.row[column.prop]) }}</span>
                </template>
                <p>
                  <span
                    :class="{ 'cursor-pointer hoveTitle': column.tooltip }"
                    >{{
                      column.formatter(
                        scope.row,
                        scope.column,
                        scope.row[column.prop],
                        scope.$index
                      )
                    }}</span
                  >
                </p>
              </el-tooltip>
            </div>
          </template>
        </el-table-column>

        <!-- 固定列数据 -->
        <el-table-column
          v-if="showButtons"
          column-key="buttons"
          :label="fixButConfig.label"
          :align="fixButConfig.align"
          :fixed="fixButConfig.fixed"
          :width="fixButConfig.width"
        >
          <slot
            slot="default"
            slot-scope="scope"
            name="buttons"
            :$index="scope.$index"
            :row="scope.row"
            :column="scope.column"
          />
        </el-table-column>

        <!-- 默认插槽 -->
        <slot />
      </el-table>

 有权限情况的核心代码

      <!-- 有编辑权限时显示可编辑表格 -->
      <el-form v-else :model="formModel" :rules="rules" :ref="formRef">
        <el-table
          ref="table"
          :border="border"
          :key="tablekey"
          fit
          v-bind="$attrs"
          :element-loading-text="loadingText"
          :data="tableData"
          :header-cell-style="headerCellStyles"
          :cell-style="cellStyles"
          v-on="$listeners"
        >
          <!-- 复选框 -->
          <el-table-column
            v-if="showSelection"
            column-key="selection"
            v-bind="$attrs"
            type="selection"
            width="35"
            align="center"
            fixed
          />

          <!-- 序号 -->
          <el-table-column
            v-if="showIndex"
            column-key="index"
            label="序号"
            type="index"
            width="55"
            align="left"
            fixed
          />

          <!-- 删除行 -->
          <el-table-column
            v-if="hasEditPermission && showRemoveRow"
            label=""
            width="50"
            align="center"
            fixed
          >
            <template slot-scope="scope">
              <el-button
                icon="el-icon-remove-outline"
                size="medium"
                type="text"
                circle
                :disabled="data.length <= 1"
                @click="removeRow(scope.$index)"
              ></el-button>
            </template>
          </el-table-column>

          <!-- 可编辑列 -->
          <el-table-column
            v-for="(column, cIndex) in tableColumns"
            :key="cIndex"
            :align="column.align || 'center'"
            :prop="column.prop"
            :label="column.label"
            :width="column.width"
            v-bind="column.attrs"
          >

            <!-- 表头的自定义插槽, 使用 slotHeaderName 和 slotHeaderIcon 定义-->
            <template
              v-if="column.slotHeaderName || column.slotHeaderIcon"
              #header
            >
              <!-- 表头自定义插槽-->
              <slot
                v-if="column.slotHeaderName"
                :name="column.slotHeaderName"
                :row="column"
              />
              <span v-if="column.slotHeaderIcon">{{ column.label }}</span>
              <!-- 表头图标插槽:使用表头图标,默认添加弹出层,弹出层内容插槽名称为:表头插槽名称 + PopContent -->
              <el-popover
                v-if="column.slotHeaderIcon"
                class="header-slot-popup"
                placement="bottom"
                :title="column.slotHeaderIconPopTitle || column.label"
                :width="column.slotHeaderPopWidth"
                trigger="click"
              >
                <div class="filter-content">
                  <slot
                    v-if="column.slotHeaderIcon"
                    class="filter-slot"
                    :name="column.slotHeaderPopName + 'PopContent'"
                  />
                </div>
                <!-- 列对象中添加 slotHeaderIcon 属性,表示使用头部图标插槽-->
                <svg-icon
                  v-if="column.slotHeaderIcon"
                  slot="reference"
                  class="header-icon-slot"
                  :class="column.slotHeaderIconClass"
                  :icon-class="column.slotHeaderIcon"
                  @click="headerIconClick(column)"
                />
              </el-popover>
            </template>
            <template #header v-else>
              <span :class="{ 'required-label': isRequiredColumn(column) }">
                {{ column.label }}
              </span>
            </template>

            <template slot-scope="scope">
              <el-form-item
                :prop="`[${scope.$index}].${column.prop}`"
                :rules="column.rules"
                :style="column.style"
                :class="column.className"
              >
                <!-- 只读字段 -->
                <template v-if="!column.type && !column.slot">
                  <slot
                    v-if="column.textSlot"
                    :name="column.textSlotName || 'slotTextCol'"
                    :colConf="column"
                    :elCol="scope.column"
                    :row="scope.row"
                    :rowIndex="scope.$index"
                    :colIndex="cIndex"
                    :rowspan="column.rowspan"
                  />
                  <el-tooltip
                    v-else
                    v-bind="column.attrs"
                    class="tootip-container"
                    effect="dark"
                    placement="top"
                    :disabled="!column.tooltip"
                  >
                    <template slot="content">
                      <!-- 字符串长度大于 80 格式化 -->
                      <span
                        v-if="String(scope.row[column.prop]).length > 80"
                        :class="{ 'cursor-pointer hoveTitle': column.tooltip }"
                        v-html="formateTooltip(String(scope.row[column.prop]))"
                      />
                      <span v-else>{{ String(scope.row[column.prop]) }}</span>
                    </template>
                    <p>
                      <span
                        :class="{ 'cursor-pointer hoveTitle': column.tooltip }"
                        >{{
                          column.formatter(
                            scope.row,
                            scope.column,
                            scope.row[column.prop],
                            scope.$index
                          )
                        }}</span
                      >
                    </p>
                  </el-tooltip>
                </template>
                <!-- 可编辑字段-根据列类型渲染不同表单控件 -->
                <template v-else>
                  <el-input
                    v-if="column.type === $const.DialogCompType.input"
                    v-model="data[scope.$index][column.prop]"
                    class="item-inputs"
                    :placeholder="placeholderFormate(column)"
                    :style="{ width: column.width || '100%' }"
                    v-bind="column.attrs"
                    v-on="column.listeners"
                  />

                  <remote-search-selector
                    v-else-if="
                      column.type === $const.DialogCompType.remoteSearchSelector
                    "
                    :init-value="data[scope.$index][column.prop]"
                    class="item-inputs"
                    :placeholder="placeholderFormate(column)"
                    v-bind="column.attrs"
                    :options="column.optionList"
                    :show-value="column.showValue"
                    :remote-methods="column.remoteMethod"
                    :style="{ width: column.width || '100%' }"
                    v-on="column.listeners"
                    @valueChange="
                      remoteSearchValueChage($event, data[scope.$index], column)
                    "
                  />

                  <el-input-number
                    v-else-if="column.type === $const.DialogCompType.number"
                    v-model="data[scope.$index][column.prop]"
                    class="item-inputs"
                    :style="{ width: column.width || '100%' }"
                    :placeholder="placeholderFormate(column)"
                    v-bind="column.attrs"
                    v-on="column.listeners"
                  />

                  <el-input
                    v-else-if="column.type === $const.DialogCompType.textarea"
                    v-model="data[scope.$index][column.prop]"
                    class="item-inputs"
                    type="textarea"
                    :style="{ width: column.width || '100%' }"
                    :placeholder="placeholderFormate(column)"
                    v-bind="column.attrs"
                    :autosize="
                      column.attrs && column.attrs.autosize
                        ? column.attrs.autosize
                        : true
                    "
                    v-on="column.listeners"
                  />

                  <el-date-picker
                    v-else-if="column.type === $const.DialogCompType.datePicker"
                    v-model="data[scope.$index][column.prop]"
                    class="item-inputs"
                    :type="(column.attrs && column.attrs.type) || 'date'"
                    :placeholder="placeholderFormate(column)"
                    :style="{ width: column.width || '100%' }"
                    v-bind="column.attrs"
                    v-on="column.listeners"
                  />

                  <el-date-picker
                    v-else-if="column.type === $const.DialogCompType.dateTime"
                    v-model="data[scope.$index][column.prop]"
                    class="item-inputs"
                    type="datetime"
                    :placeholder="placeholderFormate(column)"
                    :style="{ width: column.width || '100%' }"
                    v-bind="column.attrs"
                    v-on="column.listeners"
                  />

                  <date-picker-wrap
                    v-else-if="
                      column.type === $const.DialogCompType.datePickerColor
                    "
                    :date="data[scope.$index][column.prop]"
                    class="item-inputs"
                    :placeholder="placeholderFormate(column)"
                    :style="{ width: column.width || '100%' }"
                    v-bind="column.attrs"
                    v-on="column.listeners"
                  />

                  <el-date-picker
                    v-else-if="column.type === $const.FormCompType.daterange"
                    v-model="data[scope.$index][column.prop]"
                    :type="(column.attrs && column.attrs.type) || 'daterange'"
                    :placeholder="placeholderFormate(column)"
                    :style="{ width: column.width || '100%' }"
                    :picker-options="{ disabledDate: daterangeDisabledDate }"
                    v-bind="column.attrs"
                    v-on="column.listeners"
                  />

                  <el-date-picker
                    v-else-if="
                      column.type === $const.FormCompType.datetimerange
                    "
                    v-model="data[scope.$index][column.prop]"
                    type="datetimerange"
                    :placeholder="placeholderFormate(column)"
                    :style="{ width: column.width || '100%' }"
                    v-bind="column.attrs"
                    v-on="column.listeners"
                  />

                  <el-switch
                    v-else-if="column.type === $const.DialogCompType.switch"
                    v-model="data[scope.$index][column.prop]"
                    v-bind="column.attrs"
                    v-on="column.listeners"
                  />

                  <el-dropdown
                    v-else-if="column.type === $const.DialogCompType.dropdown"
                    v-bind="column.attrs"
                    v-on="column.listeners"
                  >
                    <el-button v-bind="column.dropdownButtonsAttrs">
                      {{ column.dropdownLabel }}
                      <i class="el-icon-arrow-down el-icon--right" />
                    </el-button>
                    <el-dropdown-menu slot="dropdown">
                      <el-dropdown-item
                        v-for="dItem in column.dropdownItem"
                        :key="dItem.label"
                        v-bind="dItem.attrs"
                        v-on="dItem.listeners"
                      >
                        {{ dItem.label }}
                      </el-dropdown-item>
                    </el-dropdown-menu>
                  </el-dropdown>

                  <selector-wrap
                    v-else-if="column.type === $const.DialogCompType.select"
                    :item="column"
                    :data="data[scope.$index]"
                    v-bind="column.attrs"
                    v-on="column.listeners"
                  />

                  <el-radio-group
                    v-else-if="column.type === $const.DialogCompType.radio"
                    v-model="data[scope.$index][column.prop]"
                    class="item-inputs"
                    :style="{ width: column.width || '100%' }"
                    v-bind="column.attrs"
                    v-on="column.listeners"
                  >
                    <el-radio
                      v-for="option in column.optionList"
                      :key="option.id || option.value"
                      :label="option.value"
                      v-bind="option.attrs"
                      v-on="option.listeners"
                    >
                      {{ option.label }}
                    </el-radio>
                  </el-radio-group>

                  <el-slider
                    v-else-if="column.type === $const.DialogCompType.slider"
                    v-model="data[scope.$index][column.prop]"
                    class="item-inputs"
                    v-bind="column.attrs"
                    v-on="column.listeners"
                  />

                  <el-alert
                    v-else-if="column.type === $const.DialogCompType.alert"
                    v-bind="column.attrs"
                  />

                  <el-divider
                    v-else-if="column.type === $const.DialogCompType.divider"
                    v-bind="column.attrs"
                  >
                    <span v-if="column.tips">{{ column.tips }}</span>
                  </el-divider>

                  <file-upload
                    v-else-if="column.type === $const.DialogCompType.files"
                    class="item-inputs"
                    v-bind="column.attrs"
                    v-on="column.listeners"
                  />

                  <div
                    v-else-if="column.type === $const.DialogCompType.buttons"
                    class="dialog-buttons"
                    :style="{
                      display: 'flex',
                      'justify-content':
                        buttonsAlign[column.align] || 'flex-end',
                    }"
                  >
                    <el-button
                      v-for="but in column.buttons"
                      :key="but.label"
                      :type="but.type"
                      v-bind="but.attrs"
                      size="small"
                      @click="
                        submitValidate(but.validate, but.reset, but.event)
                      "
                    >
                      {{ but.label }}
                    </el-button>
                  </div>

                  <tinymce
                    v-else-if="column.type === $const.DialogCompType.editor"
                    v-model="data[scope.$index][column.prop]"
                    :height="column.height"
                  />
                  <slot
                    v-else-if="column.slot"
                    :name="column.slotName || 'slotCol'"
                    :colConf="column"
                    :elCol="scope.column"
                    :row="scope.row"
                    :rowIndex="scope.$index"
                    :colIndex="cIndex"
                    :rowspan="column.rowspan"
                  />
                </template>
              </el-form-item>
            </template>
          </el-table-column>

          <!-- 增加列 -->
          <el-table-column
            v-if="hasEditPermission && showAddColumn"
            label=""
            width="45"
            align="center"
            fixed='right'
          >
          <template #header>
            <el-button
              icon="el-icon-circle-plus-outline"
              size="medium"
              type="text"
              circle
              @click="addColumn"
            ></el-button>
          </template>
          </el-table-column>

          <!-- 操作列 -->
          <el-table-column
            v-if="showButtons"
            column-key="buttons"
            :label="fixButConfig.label"
            :align="fixButConfig.align"
            :fixed="fixButConfig.fixed"
            :width="fixButConfig.width"
          >
            <slot
              slot="default"
              slot-scope="scope"
              name="buttons"
              :$index="scope.$index"
              :row="scope.row"
              :column="scope.column"
            />
          </el-table-column>
        </el-table>
      </el-form>
      <!-- 在表格下方添加操作按钮 -->
      <div v-if="hasEditPermission" class="row-actions">
        <el-button
          v-if="showAddRow"
          type="text"
          size="medium "
          @click="addRow"
          icon="el-icon-circle-plus-outline"
          >增加行</el-button>
        <el-button
          v-if="showSave"
          type="text"
          size="medium "
          @click="saveData"
          icon="el-icon-circle-check"
          >保存</el-button>
      </div>

增减行列核心方法

    // 增加行
    addRow() {
      const newRow = {};
      this.columns.forEach((col) => {
        newRow[col.prop] = ""; // 初始化空值
      });
      this.tempData = [...this.tempData, newRow]; // 更新 tempData
      this.$emit("update:data", this.tempData);
      // 不再强制刷新整个表格
      // this.tablekey += 1;
    },
    // 验证表单
    validateForm() {
      return new Promise((resolve) => {
        this.$refs[this.formRef].validate((valid) => {
          if (valid) {
            resolve(true);
          } else {
            this.$message.error("请检查表单填写是否正确");
            resolve(false);
          }
        });
      });
    },
    // 删除行
    removeRow(index) {
      this.tempData.splice(index, 1);
      this.$emit("update:data", [...this.tempData]);
      // 不再强制刷新整个表格
      // this.tablekey += 1;
    },
    // 保存数据
    async saveData() {
      const isValid = await this.validateForm();
      this.$emit("update:data", [...this.tempData]);
      return isValid;
    },
    // 比较列配置是否相等
    isColumnsEqual(newColumns, oldColumns) {
      if (newColumns.length !== oldColumns.length) return false;
      return newColumns.every((newCol, index) => {
        const oldCol = oldColumns[index];
        return JSON.stringify(newCol) === JSON.stringify(oldCol);
      });
    },
    // 新增列
    async addColumn() {
      try {
        this.$emit('add-column', this.columns);
        this.$nextTick(() => {
          if (this.$refs.table) {
            this.$refs.table.doLayout();
          }
        });
      } catch (error) {
        this.$message.error('新增列操作出错,请稍后重试');
      }
    },
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值