el-table自定义expand样式,设置展开按钮,在表格中存在多个展开框样式,并自定义el-table的sortTable功能

问题

  • 直接复制后面代码,数据全为模拟,可直接运行,样式自己调整即可
    • 不需要内部分页时,隐藏分页组件,getRowTableData 函数返回返回 return children
  • 项目需求
    1. 列表中存在多个不同分类,分类下存在相同格式的数据
      1. 利用 <el-table-column type="expand" width="35" :resizable="false" fixed="right"> 的 type=“expand” 实现表格嵌套,放入表格,同级的 el-table-column 的宽度设置与嵌套表格宽度一样,外部 el-table-column 的prop必须设置正确,用于自定义排序功能,但是宽度超出时,内部表格会有一个小滚动条,需要hidden掉,配置 table_column--has-expand 类修改样式
    2. 每个分类共用表头排序功能(但是不对分类排序)
      1. 外部表格使用 sortable="custom",在表格中配置 @sort-change="handleSortChange",查看 handleSortChange 函数实现功能
    3. 单行展开和全部展开功能
      1. 展开函数 isExpandRowonExpandRowisExpandAllonExpandRows 4个函数分别用于控制 单行是否可展开、单行展开或收起、全局是否可展开、全局展开或收起
    4. 浮动操作效果,并自定义分类 expand 展开文案和样式
      1. 这里其实有点细节,type="expand" width="35" 的宽度如果设置小于35左右,行数的宽度会不准确,存在浮动情况下造成错位,所以外部表格设置 <el-table-column label="操作" width="65" :resizable="false" fixed="right"><el-table-column type="expand" width="35" :resizable="false" fixed="right">,外部一个65,一个35,嵌套表格则设置 <el-table-column label="操作" width="100" fixed="right">",宽度为外部 操作列和expand列 宽度的总和,并在外部表格配置一个合并行函数 objectSpanMethod,将外部表格的 操作列和expand列 合并,隐藏 expand 图标
    5. 这里存在部分我的项目样式,已经尽量清除
    6. 未展开效果如下:
      在这里插入图片描述
    7. 展开并且排序效果:
      在这里插入图片描述
    8. 全部展开效果:
      在这里插入图片描述

实现

/**
 * @name: 判断数据类型
 * @param {*} obj
 * @param {*} type: 大写开头
 */
export function isType(obj, type) {
  return Object.prototype.toString.call(obj) === '[object ' + type + ']'
}
<template>
  <div class="page-content"  v-loding="loading">
    <!-- 按钮 -->
    <div class="button_right">
      <el-button type="primary" @click="onExpandRows(isExpandAll ? true : false)">{{ isExpandAll ? '全部展开' : '全部收起' }}</el-button>
    </div>

    <!-- 列表 -->
    <div class="page-table">
      <!-- :tree-props 防止识别为树形数据 -->
      <el-table
        :data="tableData"
        ref="ref_multipleSelection"
        class="table_column--has-expand"
        height="100%"
        row-key="parentId"
        row-class-name="table_row-title"
        :span-method="objectSpanMethod"
        @sort-change="handleSortChange"
        @expand-change="handleExpandChange"
        :tree-props="{ children: 'children-1' }"
      >
        <el-table-column sortable="custom" prop="prop1" label="表头1" :resizable="false" min-width="300">
          <template v-slot="scope">{{ scope.row.typeName }}</template>
        </el-table-column>
        <el-table-column sortable="custom" prop="prop2" label="表头2" :resizable="false" min-width="300" />
        <el-table-column sortable="custom" prop="prop3" label="表头3" :resizable="false" min-width="300" />
        <el-table-column sortable="custom" prop="prop4" label="表头4" :resizable="false" min-width="300">
          <!-- 该数据存在与页面子表相同数据-所以手动置空 -->
          <template v-slot="scope"></template>
        </el-table-column>
        <el-table-column sortable="custom" prop="prop5" label="表头5" :resizable="false" min-width="300" />
        <el-table-column sortable="custom" prop="prop6" label="表头6" :resizable="false" min-width="300" />
        <el-table-column label="操作" width="65" :resizable="false" fixed="right">
          <template v-slot="scope">
            <el-button type="text" class="expand_custom" @click="onExpandRow(scope.$index, scope.row)">
              {{ isExpandRow(scope.row) ? '展开' : '收起' }}
              <i :class="['el-icon-arrow-right', { 'arrow-right_rotate': !isExpandRow(scope.row) }]"></i>
            </el-button>
          </template>
        </el-table-column>

        <!-- width 给0会设置默认值大概45左右,给值小于35会造成位置错位,利用合并行做隐藏 -->
        <el-table-column type="expand" width="35" :resizable="false" fixed="right">
          <template slot-scope="{ row }">
            <el-table :data="row.showTable" style="width: 100%" :show-header="false">
              <el-table-column sortable="custom" prop="prop1" label="表头1" :resizable="false" min-width="300" />
              <el-table-column sortable="custom" prop="prop2" label="表头2" :resizable="false" min-width="300" />
              <el-table-column sortable="custom" prop="prop3" label="表头3" :resizable="false" min-width="300" />
              <el-table-column sortable="custom" prop="prop4" label="表头4" :resizable="false" min-width="300" />
              <el-table-column sortable="custom" prop="prop5" label="表头5" :resizable="false" min-width="300" />
              <el-table-column sortable="custom" prop="prop6" label="表头6" :resizable="false" min-width="300" />
              <!-- 该操作的width 由  type="expand" width="35"-->
              <el-table-column label="操作" width="100" fixed="right">
                <template v-slot="scope">
                  <el-button type="text" size="mini" @click="onDetail(scope.row)">详情</el-button>
                </template>
              </el-table-column>
            </el-table>

            <!-- 如果不需要分页,全部展示(消耗可能有点大),隐藏分页组件 -->
            <div class="table_row_pagination">
              <el-pagination
                @size-change="
                  (val) => {
                    row.pageSize = val
                    getRowTableData(row)
                  }
                "
                @current-change="
                  (val) => {
                    row.pageNo = val
                    getRowTableData(row)
                  }
                "
                :current-page="row.pageNo"
                :page-size="row.pageSize"
                :page-sizes="[10, 50, 100]"
                layout="total, sizes, prev, pager, next, jumper"
                :total="row.children.length"
              />
            </div>
          </template>
        </el-table-column>
      </el-table>
    </div>
  </div>
</template>
<script>
// isType 函数,代码在最前面
import { isType } from '@/utils'

export default {
  props: {
    lazy: {
      default: false,
    },
  },
  data() {
    return {
      tableData: [],
      total: 0,
      loading: false,
      expandedRows: [],
    }
  },
  computed: {
    // 是否可以全部展开
    isExpandAll() {
      // 存在可展开的行
      const hasExpandRows = this.tableData.filter((row) => row.children?.length > 0)?.length || 0
      // 没有可展开行,只展示展开按钮,不可修改
      if (hasExpandRows === 0) return true
      return hasExpandRows !== this.expandedRows.length
    },
  },

  created() {
    // 这里是因为使用了 <el-tab-pane label="项目流程列表" name="tab4" lazy>,可以参考我前面的文章
    // https://blog.csdn.net/lk2913128603/article/details/140174455?spm=1001.2014.3001.5502
    if (!this.lazy) return
    for (let fun of this.$options.activated || []) {
      fun.call(this)
    }
  },

  // keep-alive
  activated() {
    this.getTableData()
  },

  methods: {
    async onSearch() {
      await this.getTableData()
    },

    handleSortChange({ column, prop, order }) {
      if (!order) {
        this.tableData.forEach((element) => {
          element.showTable = this.getRowTableData(element)
        })
        return
      }

      // 转换为字符串,利用localeCompare排序
      const changeType = (v) => {
        if (isType(v, 'Null') || isType(v, 'Undefined') || isType(v, 'Object') || isType(v, 'Array')) return ''
        if (isType(v, 'Number') || !isType(v, 'String')) return v.toString()
        return v
      }
      const isChangeLocation = (a, b, isString) => {
        a = changeType(a)
        b = changeType(b)
        if (order === 'ascending') {
          // 升序 - 从小到大
          if (isString) return a.localeCompare(b)
          return a < b ? -1 : 1
        } else if (order === 'descending') {
          // 降序 - 从大到小
          if (isString) return b.localeCompare(a)
          return a > b ? -1 : 1
        }
      }

      for (let props of this.tableData) {
        props.showTable.sort((a, b) => {
          return isChangeLocation(a[prop], b[prop], true)
        })
      }
      return false
    },

    onExpandRows(isExpand) {
      this.tableData.forEach((row) => {
        this.$refs['ref_multipleSelection']?.toggleRowExpansion(row, isExpand)
      })
    },
    isExpandRow(row) {
      return this.expandedRows.findIndex((item) => item.parentId === row.parentId) === -1
    },
    onExpandRow(index, row) {
      this.$refs['ref_multipleSelection']?.toggleRowExpansion(row, this.isExpandRow(row))
    },
    handleExpandChange(row, expandedRows) {
      this.expandedRows = expandedRows
    },

    objectSpanMethod({ row, column, rowIndex, columnIndex }) {
      // (名称与后一行合并,防止名称过长) 、(操作与展开)的合并,表头下标
      if (columnIndex === 0 || columnIndex === 6) {
        return {
          rowspan: 1,
          colspan: 2,
        }
      } else if (columnIndex === 1 || columnIndex === 7) {
        return {
          rowspan: 0,
          colspan: 0,
        }
      }
    },
    // TODO: 优化分页
    getRowTableData(row) {
      const { children, pageNo, pageSize } = row
      // 如果不需要分页,全部展示(消耗可能有点大),直接返回指定值,并隐藏分页组件
      // return children
      row.showTable = (children || []).slice((pageNo - 1) * pageSize, pageNo * pageSize)
      return row.showTable
    },

    /**  @name: 详情操作*/
    onDetail(row) {
      console.log(onDetail, 'row', row)
    },

    async getTableData() {
      const _interface = () =>
        new Promise((resolve) => {
          const result = {
            code: 200,
            data: Array.from({ length: 6 }).map((v, i) => {
              v = {}
              v.parentId = '父节点id' + i
              v.typeName = `类型${i + 1}`
              v.children = Array.from({ length: 21 }).map((val, index) => {
                val = {}
                val.id = index + '测试数据'
                val.prop1 = `表头1-${index + 1}内容`
                val.prop2 = `表头2-${index + 1}内容`
                val.prop3 = `表头3-${index + 1}内容`
                val.prop4 = `表头4-${index + 1}内容`
                val.prop5 = `表头5-${index + 1}内容`
                val.prop6 = `表头6-${index + 1}内容`
                return val
              })

              return v
            }),
          }

          resolve(result)
        })

      this.loading = true
      const { data, code } = await _interface().finally(() => (this.loading = false))
      if (code != 200) return
      console.log(data, code)

      // 清空列表的sort配置
      this.$refs['ref_multipleSelection']?.clearSort()
      this.tableData = (data || []).map((v) => {
        // 用于分页
        v.pageNo = 1
        v.pageSize = 10
        v.showTable = this.getRowTableData(v)
        return v
      })
    },
  },
}
</script>

<style lang="scss" scoped>
.page-content {
  background: #fff;
  padding: 16px;
  border-radius: 0;
  width: 100%;
  // 高度每个项目不同
  height: 500px;

  .page-table {
    height: calc(100% - 46px);
  }

  .button_right {
    text-align: right;
    margin-bottom: 16px;
  }

  .expand_custom {
    color: #1f2b44;
    font-weight: 400;
    .el-icon-arrow-right {
      transition: transform 0.3s;
    }

    .arrow-right_rotate {
      transform: rotate(90deg);
    }
  }

  // 页面分页功能,但是需要手动控制浮动效果
  .table_row_pagination {
    background: #fff;
    margin-left: auto;
    display: flex;
    justify-content: flex-end;
    padding: 6px;
    // el-table-Column 的 fixed 的总距离
    padding-right: 100px;
  }

  ::v-deep {
    // 存在表格嵌套表格时样式-隐藏高度
    .table_column--has-expand {
      .el-table__body .el-table__expanded-cell {
        padding: 0;
        .el-table__body-wrapper {
          overflow-x: hidden;
        }
        .el-table__fixed {
          &::before {
            content: '';
            visibility: hidden;
          }
        }
      }
    }

    // 设置分类名称的样式
    .table_row-title {
      & > td {
        background-color: #edf4ff;
      }
      & > td:nth-of-type(1) {
        font-weight: bold;
        & > div {
          color: #1f2b44;
        }
      }
    }
  }
}
</style>


  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值