zoukankan      html  css  js  c++  java
  • vue 表格树 固定表头

    参考网上黄龙的表格树进行完善,并添加固定表头等的功能,目前是在iview的项目中实现,如果想在element中实现的话修改对应的元素标签及相关写法即可。

    <!--
       @events  @on-row-click 单击行或者单击操作按钮方法
                @on-selection-change  多选模式下 选中项变化时触发
                @on-sort-change  排序时有效,当点击排序时触发
       @props   data 显示的结构化数据
                columns 表格列的配置描述 sortable:true 开启排序功能
                showHeader 是否显示表头
                type: 'selection'为多选功能 type: 'template' 为操作功能 slot为插槽名
     -->
    <template>
    <div ref="table" class='autoTable tree-grid'>
      <!-- <div ref="table" :style="{treeGridWidth}" class='autoTable tree-grid'> -->
      <div ref="header" class="tree-grid-header" v-if="showHeader">
        <table class="table table-bordered hl-tree-table">
          <colgroup>
            <col v-for="(column, index) in cloneColumns" :width="column.width" :align="column.align" :key="index">
            <col v-if="showVerticalScrollBar" :width="scrollBarWidth"/>
          </colgroup>
          <thead>
            <tr>
              <th v-for="(column,index) in cloneColumns" :key="column.key">
                <div v-if="column.type === 'selection'" class="selection-wrapper">
                  <Checkbox v-model="checks" @click="handleCheckAll"></Checkbox>
                  <input type="checkbox" v-model="checks" @click="handleCheckAll" class="selection-checkbox">
                </div>
                <div v-else class="tree-grid-cell">{{renderHeader(column, index)}}
                  <span class="ivu-table-sort" v-if="column.sortable">
                    <Icon type="md-arrow-dropup" :class="{on: column._sortType === 'asc'}" @click.native="handleSort(index, 'asc')" />
                    <Icon type="md-arrow-dropdown" :class="{on: column._sortType === 'desc'}" @click.native="handleSort(index, 'desc')" />
                  </span>
                </div>
              </th>
              <th v-if="showVerticalScrollBar" rowspan="1"></th>
            </tr>
          </thead>
        </table>
      </div>
      <div ref="body" class="tree-grid-body" :style="{height:tBodyHeight}">
        <table ref="bodyTable" class="table table-bordered hl-tree-table">
          <colgroup ref="bodyColgroup">
            <col v-for="(column, index) in cloneColumns" :width="column.width" :align="column.align" :key="index">
          </colgroup>
          <tbody>
            <tr v-for="(item,index) in initGridData" :key="item.id" v-show="show(item)" :class="{'child-tr':item.parent}">
              <td v-for="(column,snum) in columns" :key="column.key">
                <div v-if="column.type === 'selection'" class="selection-wrapper">
                  <CheckboxGroup v-model="checkGroup">
                    <Checkbox :label="item.id"><span></span></Checkbox>
                  </CheckboxGroup>
                  <input type="checkbox" :value="item.id" v-model="checkGroup" @click="handleCheckClick(item,$event,index)" class="selection-checkbox">
                </div>
                <div v-if="column.type === 'template'" class="tree-grid-cell">
                  <slot :name="column.slot" :scope="item"></slot>
                </div>
                <div @click="toggle(index,item)" v-if="!column.type" class="tree-grid-cell">
                  <template v-if='snum===iconRow()'>
                    <i v-html='item.spaceHtml'></i>
                    <span v-if="item.children&&item.children.length>0" class="tree-grid-arrow" :class="{'tree-grid-arrow-open': item.expanded}" >
                      <i class="ivu-icon ivu-icon-ios-arrow-forward"></i>
                    </span>
                    <i v-else class="ms-tree-space"></i>
                  </template>{{renderBody(item,column)}}
                </div>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
      </div>
    </template>
    <script>
    let cached
    export default {
      name: 'treeGrid',
      props: {
        columns: {
          type: Array,
          default: () => []
        },
        data: {
          type: Array,
          default: () => []
        },
        showHeader: {
          type: Boolean,
          default: true
        },
        height: {
          type: [Number, String]
        }
      },
      data () {
        return {
          initGridData: [], // 处理后数据数组
          cloneColumns: [], // 处理后的表头数据
          showVerticalScrollBar: false,
          scrollBarWidth: undefined,
          checkGroup: [], // 复选框数组
          checks: false, // 全选
          screenWidth: document.body.clientWidth, // 自适应宽
          headerHeight: 0,
          columnsWidth: {},
          timer: false, // 控制监听时长
          dataLength: 0 // 树形数据长度
        }
      },
      computed: {
        treeGridWidth () {
          let treeGridWidth = this.$el ? this.$el.offsetWidth : '1000'
          return treeGridWidth
        },
        tBodyHeight () {
          return parseFloat(this.height) > 0 ? (parseFloat(this.height) - parseFloat(this.headerHeight)) + 'px' : 'auto'
        }
      },
      watch: {
        screenWidth (val) {
          if (!this.timer) {
            this.screenWidth = val
            this.timer = true
            setTimeout(() => {
              this.timer = false
            }, 400)
          }
        },
        data () {
          if (this.data) {
            this.dataLength = this.Length(this.data)
            this.initData(this.deepCopy(this.data), 1, null)
            this.checkGroup = this.renderCheck(this.data)
            if (this.checkGroup.length === this.dataLength) {
              this.checks = true
            } else {
              this.checks = false
            }
            this.showScrollBar()
          }
        },
        columns: {
          handler () {
            this.cloneColumns = this.makeColumns()
          },
          deep: true
        },
        checkGroup (data) {
          this.checkAllGroupChange(data)
        }
      },
      mounted () {
        if (this.data) {
          this.dataLength = this.Length(this.data)
          this.initData(this.deepCopy(this.data), 1, null)
          this.cloneColumns = this.makeColumns()
          this.checkGroup = this.renderCheck(this.data)
          if (this.checkGroup.length === this.dataLength) {
            this.checks = true
          } else {
            this.checks = false
          }
        }
        // 绑定onresize事件 监听屏幕变化设置宽
        this.$nextTick(() => {
          this.screenWidth = document.body.clientWidth
          this.headerHeight = this.showHeader ? this.$refs.header.clientHeight : 0
          this.cloneColumns = this.makeColumns()
          this.showScrollBar()
        })
        window.onresize = () => {
          return (() => {
            window.screenWidth = document.body.clientWidth
            window.screenHeight = document.body.clientHeight
            this.screenWidth = window.screenWidth
            this.headerHeight = this.showHeader ? this.$refs.header.clientHeight : 0
            this.cloneColumns = this.makeColumns()
            this.showScrollBar()
          })()
        }
      },
      methods: {
        showScrollBar () {
          this.$nextTick(() => {
            // console.error(this.$refs.bodyTable.clientHeight, this.$refs.body.clientHeight)
            if (this.$refs.bodyTable.clientHeight > this.$refs.body.clientHeight) {
              if (!this.showVerticalScrollBar) {
                this.showVerticalScrollBar = true
                this.scrollBarWidth = this.getScrollBarSize()
              }
            } else {
              if (this.showVerticalScrollBar) {
                this.showVerticalScrollBar = false
              }
            }
          })
        },
        // 有无多选框折叠位置优化
        iconRow () {
          for (let i = 0, len = this.columns.length; i < len; i++) {
            if (this.columns[i].type === 'selection') {
              return 1
            }
          }
          return 0
        },
        // 排序事件
        handleSort (index, type) {
          this.cloneColumns.forEach(col => {
            col._sortType = 'normal'
          })
          if (this.cloneColumns[index]._sortType === type) {
            this.cloneColumns[index]._sortType = 'normal'
          } else {
            this.cloneColumns[index]._sortType = type
          }
          this.$emit('on-sort-change', this.cloneColumns[index]['key'], this.cloneColumns[index]['_sortType'])
        },
        // 点击某一行事件
        RowClick (data, event, index, text) {
          let result = this.makeData(data)
          this.$emit('on-row-click', result, event, index, text)
        },
        // 点击事件 返回数据处理
        makeData (data) {
          const t = this.type(data)
          let o
          if (t === 'array') {
            o = []
          } else if (t === 'object') {
            o = {}
          } else {
            return data
          }
    
          if (t === 'array') {
            for (let i = 0; i < data.length; i++) {
              o.push(this.makeData(data[i]))
            }
          } else if (t === 'object') {
            for (let i in data) {
              if (i !== 'spaceHtml' && i !== 'parent' && i !== 'level' && i !== 'expanded' && i !== 'isShow' && i !== 'load') {
                o[i] = this.makeData(data[i])
              }
            }
          }
          return o
        },
        // 处理表头数据
        makeColumns () {
          let columns = this.deepCopy(this.columns)
          let tableWidth = this.$el.offsetWidth
          let noWidthLength = 0
          let nanWidthLength = 0
          let widthSum = 0
          columns.forEach((column, index) => {
            column._index = index
            column._sortType = 'normal'
            if (column.width) {
              if (!/^(-?d+)(.d+)?$/.test(column.width)) {
                let width = column.width
                if (width.slice(-1) === '%') {
                  let percent = (column.width).slice(0, -1)
                  column.width = ''
                  column._width = ''
                  nanWidthLength += 1
                  column._nanWidth = percent
                } else {
                  this.$Message.error('请输入正确的宽度:数字(例如:100)或者百分比(例如:10%)')
                }
              } else {
                widthSum += column.width
                column._width = column.width
              }
            } else {
              noWidthLength += 1
              column._width = ''
            }
            column._width = column.width ? column.width : ''
          })
          if (nanWidthLength > 0) {
            columns.forEach((column, index) => {
              if (column._nanWidth) {
                column.width = parseInt((tableWidth - widthSum) * column._nanWidth / 100)
                // column.width = (tableWidth - widthSum) * column._nanWidth / 100
                column._width = column.width
                widthSum += column.width
              }
            })
          }
          if (noWidthLength > 0) {
            columns.forEach((column, index) => {
              if (column._width === '') {
                column.width = parseInt((tableWidth - widthSum) / noWidthLength)
                // column.width = (tableWidth - widthSum) / noWidthLength
                column._width = column.width
              }
            })
          }
          return columns
        },
        // 数据处理 增加自定义属性监听
        initData (data, level, parent) {
          this.initGridData = []
          let spaceHtml = ''
          for (let i = 1; i < level; i++) {
            spaceHtml += '<i class="ms-tree-space"></i>'
          }
          data.forEach((item, index) => {
            item = Object.assign({}, item, {
              'parent': parent,
              'level': level,
              'spaceHtml': spaceHtml
            })
            if ((typeof item.expanded) === 'undefined') {
              item = Object.assign({}, item, {
                'expanded': false
              })
            }
            if ((typeof item.show) === 'undefined') {
              item = Object.assign({}, item, {
                'isShow': false
              })
            }
            if ((typeof item.isChecked) === 'undefined') {
              item = Object.assign({}, item, {
                'isChecked': false
              })
            }
            item = Object.assign({}, item, {
              'load': (item.expanded ? 1 : false)
            })
            this.initGridData.push(item)
            if (item.children && item.expanded) {
              this.initData(item.children, level + 1, item)
            }
          })
        },
        // 隐藏显示
        show (item) {
          return ((item.level === 1) || (item.parent && item.parent.expanded && item.isShow))
        },
        toggle (index, item) {
          let level = item.level + 1
          let spaceHtml = ''
          for (let i = 1; i < level; i++) {
            spaceHtml += '<i class="ms-tree-space"></i>'
          }
          if (item.children) {
            if (item.expanded) {
              item.expanded = !item.expanded
              this.close(index, item)
            } else {
              item.expanded = !item.expanded
              if (item.load) {
                this.open(index, item)
              } else {
                item.load = true
                item.children.forEach((child, childIndex) => {
                  this.initGridData.splice((index + childIndex + 1), 0, child)
                  // 设置监听属性
                  this.$set(this.initGridData[index + childIndex + 1], 'parent', item)
                  this.$set(this.initGridData[index + childIndex + 1], 'level', level)
                  this.$set(this.initGridData[index + childIndex + 1], 'spaceHtml', spaceHtml)
                  this.$set(this.initGridData[index + childIndex + 1], 'isShow', true)
                  this.$set(this.initGridData[index + childIndex + 1], 'expanded', false)
                })
              }
            }
          }
          this.showScrollBar()
        },
        open (index, item) {
          if (item.children) {
            item.children.forEach((child, childIndex) => {
              child.isShow = true
              if (child.children && child.expanded) {
                this.open(index + childIndex + 1, child)
              }
            })
          }
        },
        close (index, item) {
          if (item.children) {
            item.children.forEach((child, childIndex) => {
              child.isShow = false
              child.expanded = false
              if (child.children) {
                this.close(index + childIndex + 1, child)
              }
            })
          }
        },
        // 点击check勾选框, 父子不相关联
        handleCheckClick (data, event, index) {
          data.isChecked = !data.isChecked
          if (data.isChecked) {
            this.checkGroup.push(data.id)
          } else {
            for (let i = 0; i < this.checkGroup.length; i++) {
              if (this.checkGroup[i] === data.id) {
                this.checkGroup.splice(i, 1)
              }
            }
          }
          this.checkGroup = this.getArray(this.checkGroup)
          let itemsIds = this.getArray(this.checkGroup.concat(this.All(this.data)))
          if (this.checkGroup.length === itemsIds.length) {
            this.checks = true
          } else {
            this.checks = false
          }
        },
        // checkbox 全选 选择事件
        handleCheckAll () {
          this.checks = !this.checks
          if (this.checks) {
            this.checkGroup = this.getArray(this.checkGroup.concat(this.All(this.data)))
          } else {
            this.checkGroup = []
          }
          // this.$emit('on-selection-change', this.checkGroup)
        },
        // 数组去重
        getArray (a) {
          let hash = {}
          let len = a.length
          let result = []
          for (let i = 0; i < len; i++) {
            if (!hash[a[i]]) {
              hash[a[i]] = true
              result.push(a[i])
            }
          }
          return result
        },
        checkAllGroupChange (data) {
          if (this.dataLength > 0 && data.length === this.dataLength) {
            this.checks = true
          } else {
            this.checks = false
          }
          this.$emit('on-selection-change', this.checkGroup)
        },
        All (data) {
          let arr = []
          data.forEach((item) => {
            arr.push(item.id)
            if (item.children && item.children.length > 0) {
              arr = arr.concat(this.All(item.children))
            }
          })
          return arr
        },
        // 返回树形数据长度
        Length (data) {
          let length = data.length
          data.forEach((child) => {
            if (child.children) {
              length += this.Length(child.children)
            }
          })
          return length
        },
        // 返回表头
        renderHeader (column, $index) {
          if ('renderHeader' in this.columns[$index]) {
            return this.columns[$index].renderHeader(column, $index)
          } else {
            return column.title || '#'
          }
        },
        // 返回内容
        renderBody (row, column, index) {
          return row[column.key]
        },
        // 默认选中
        renderCheck (data) {
          let arr = []
          data.forEach((item) => {
            if (item._checked) {
              arr.push(item.id)
            }
            if (item.children && item.children.length > 0) {
              arr = arr.concat(this.renderCheck(item.children))
            }
          })
          return arr
        },
        // 深度拷贝函数
        deepCopy (data) {
          let t = this.type(data)
          let o
          let i
          let ni
          if (t === 'array') {
            o = []
          } else if (t === 'object') {
            o = {}
          } else {
            return data
          }
          if (t === 'array') {
            for (i = 0, ni = data.length; i < ni; i++) {
              o.push(this.deepCopy(data[i]))
            }
            return o
          } else if (t === 'object') {
            for (i in data) {
              o[i] = this.deepCopy(data[i])
            }
            return o
          }
        },
        type (obj) {
          let toString = Object.prototype.toString
          let map = {
            '[object Boolean]': 'boolean',
            '[object Number]': 'number',
            '[object String]': 'string',
            '[object Function]': 'function',
            '[object Array]': 'array',
            '[object Date]': 'date',
            '[object RegExp]': 'regExp',
            '[object Undefined]': 'undefined',
            '[object Null]': 'null',
            '[object Object]': 'object'
          }
          return map[toString.call(obj)]
        },
        getScrollBarSize (fresh) {
          if (this.$isServer) return 0
          if (fresh || cached === undefined) {
            const inner = document.createElement('div')
            inner.style.width = '100%'
            inner.style.height = '200px'
            const outer = document.createElement('div')
            const outerStyle = outer.style
            outerStyle.position = 'absolute'
            outerStyle.top = 0
            outerStyle.left = 0
            outerStyle.pointerEvents = 'none'
            outerStyle.visibility = 'hidden'
            outerStyle.width = '200px'
            outerStyle.height = '150px'
            outerStyle.overflow = 'hidden'
            outer.appendChild(inner)
            document.body.appendChild(outer)
            const widthContained = inner.offsetWidth
            outer.style.overflow = 'scroll'
            let widthScroll = inner.offsetWidth
            if (widthContained === widthScroll) {
              widthScroll = outer.clientWidth
            }
            document.body.removeChild(outer)
            cached = widthContained - widthScroll
          }
          return cached
        }
      },
      beforeDestroy () {
        window.onresize = null
      }
    }
    </script>
    <style lang="less">
    .tree-grid {
      @keyframes opacityChild{
        0% { opacity: 0; }
        50% { opacity: .5; }
        100% { opacity: 1; }
      }
       100%;
      color: #1f2d3d;
      color: #495167;
      &.autoTable {
        overflow: auto;
      }
      .tree-grid-body {
        overflow: auto;
      }
      table {
         100%;
        border-spacing: 0;
        border-collapse: collapse;
        &.hl-tree-table {
          &>tbody{
            &>tr {
              height: 50px;
              background-color: #fff;
              border-bottom: 1px solid #e8eaec;
              &:hover {
                background-color: #ebf7ff;
              }
            }
            &>.child-tr {
              background-color: #fff;
            }
          }
          th>label {
            display: inline-block;
            margin: 0 12px;
          }
        }
        .ivu-icon {
          font-size: 18px;
        }
        .tree-grid-arrow {
          cursor: pointer;
           14px;
          text-align: center;
          display: inline-block;
          i {
            position: relative;
            top: -1px;
            transition: all .2s ease-in-out;
            font-size:14px;
            vertical-align:middle;
          }
          &-open {
            i {
              transform:rotate(90deg);
            }
          }
        }
      }
      .table>tbody>tr>td,
      .table>tbody>tr>th,
      .table>thead>tr>td,
      .table>thead>tr>th {
        vertical-align: middle;
        box-sizing: border-box;
        &:last-child {
          border-right: 0;
        }
      }
      // .table>tbody>tr>td,
      // .table>tbody>tr>th {
      //   border-right: 1px solid #ccc;
      // }
      .table>thead>tr>th {
        text-align: left;
      }
      .table-bordered>thead>tr>td,
      .table-bordered>thead>tr>th {
        height: 32px;
        padding: 0;
        vertical-align: middle;
        background: #E1E4E5;
        border-right: 1px solid #fff;
        .tree-grid-cell {
          padding: 0 12px;
        }
      }
      .tree-grid-cell {
        padding: 0 12px;
        font-size: 12px;
      }
      .ms-tree-space {
        position: relative;
        top: 1px;
        display: inline-block;
        font-style: normal;
        font-weight: 400;
        line-height: 1em;
         14px;
        height: 14px;
      }
      .ms-tree-space::before {
        content: "";
      }
      .selection-wrapper {
        position: relative;
        text-align: center;
         18px;
        height: 18px;
        margin: 0 auto;
        vertical-align: middle;
        .selection-checkbox {
          position: absolute;
          left: 0;
          top: 0;
          z-index: 2;
           18px;
          height: 18px;
          vertical-align: middle;
          opacity: 0;
          cursor: pointer;
        }
        .ivu-checkbox-wrapper {
          position: absolute;
          left: 0;
          top: 0;
          z-index: 1;
          margin: 0;
          line-height: 15px;
        }
      }
    }
    </style>
  • 相关阅读:
    第六章脑图
    20192419实验四 《Python程序设计》实验报告
    20192419 实验三《Python程序设计》实验报告
    20192419 实验二《Python程序设计》实验报告
    20192419 实验一《Python程序设计》实验报告
    小组讨论-第十章 密码学及应用
    2019-2020-1学期 20192419 《网络空间安全专业导论》第十二周学习总结 (读书笔记)
    2019-2020-1学期 20192419 《网络空间安全专业导论》第十二周学习总结 (读书心得)
    2019-2020-1学期 20192419 《网络空间安全专业导论》第十一周学习总结 (读书心得)
    2019-2020-1学期 20192419 《网络空间安全专业导论》第十一周学习总结 (读书笔记)
  • 原文地址:https://www.cnblogs.com/ToBeBest/p/10084361.html
Copyright © 2011-2022 走看看