skyouc
2024-12-21 c9c263dc43ad90f95f24a036cee9e6b47afb596c
uni_modules/uni-table/components/uni-table/uni-table.vue
@@ -1,455 +1,455 @@
<template>
   <view class="uni-table-scroll" :class="{ 'table--border': border, 'border-none': !noData }">
      <!-- #ifdef H5 -->
      <table class="uni-table" border="0" cellpadding="0" cellspacing="0" :class="{ 'table--stripe': stripe }" :style="{ 'min-width': minWidth + 'px' }">
         <slot></slot>
         <view v-if="noData" class="uni-table-loading">
            <view class="uni-table-text" :class="{ 'empty-border': border }">{{ emptyText }}</view>
         </view>
         <view v-if="loading" class="uni-table-mask" :class="{ 'empty-border': border }"><div class="uni-table--loader"></div></view>
      </table>
      <!-- #endif -->
      <!-- #ifndef H5 -->
      <view class="uni-table" :style="{ 'min-width': minWidth + 'px' }" :class="{ 'table--stripe': stripe }">
         <slot></slot>
         <view v-if="noData" class="uni-table-loading">
            <view class="uni-table-text" :class="{ 'empty-border': border }">{{ emptyText }}</view>
         </view>
         <view v-if="loading" class="uni-table-mask" :class="{ 'empty-border': border }"><div class="uni-table--loader"></div></view>
      </view>
      <!-- #endif -->
   </view>
</template>
<script>
/**
 * Table 表格
 * @description 用于展示多条结构类似的数据
 * @tutorial https://ext.dcloud.net.cn/plugin?id=3270
 * @property {Boolean}    border             是否带有纵向边框
 * @property {Boolean}    stripe             是否显示斑马线
 * @property {Boolean}    type                是否开启多选
 * @property {String}    emptyText          空数据时显示的文本内容
 * @property {Boolean}    loading          显示加载中
 * @event {Function}    selection-change    开启多选时,当选择项发生变化时会触发该事件
 */
export default {
   name: 'uniTable',
   options: {
      virtualHost: true
   },
   emits:['selection-change'],
   props: {
      data: {
         type: Array,
         default() {
            return []
         }
      },
      // 是否有竖线
      border: {
         type: Boolean,
         default: false
      },
      // 是否显示斑马线
      stripe: {
         type: Boolean,
         default: false
      },
      // 多选
      type: {
         type: String,
         default: ''
      },
      // 没有更多数据
      emptyText: {
         type: String,
         default: '没有更多数据'
      },
      loading: {
         type: Boolean,
         default: false
      },
      rowKey: {
         type: String,
         default: ''
      }
   },
   data() {
      return {
         noData: true,
         minWidth: 0,
         multiTableHeads: []
      }
   },
   watch: {
      loading(val) {},
      data(newVal) {
         let theadChildren = this.theadChildren
         let rowspan = 1
         if (this.theadChildren) {
            rowspan = this.theadChildren.rowspan
         }
         // this.trChildren.length - rowspan
         this.noData = false
         // this.noData = newVal.length === 0
      }
   },
   created() {
      // 定义tr的实例数组
      this.trChildren = []
      this.thChildren = []
      this.theadChildren = null
      this.backData = []
      this.backIndexData = []
   },
   methods: {
      isNodata() {
         let theadChildren = this.theadChildren
         let rowspan = 1
         if (this.theadChildren) {
            rowspan = this.theadChildren.rowspan
         }
         this.noData = this.trChildren.length - rowspan <= 0
      },
      /**
       * 选中所有
       */
      selectionAll() {
         let startIndex = 1
         let theadChildren = this.theadChildren
         if (!this.theadChildren) {
            theadChildren = this.trChildren[0]
         } else {
            startIndex = theadChildren.rowspan - 1
         }
         let isHaveData = this.data && this.data.length.length > 0
         theadChildren.checked = true
         theadChildren.indeterminate = false
         this.trChildren.forEach((item, index) => {
            if (!item.disabled) {
               item.checked = true
               if (isHaveData && item.keyValue) {
                  const row = this.data.find(v => v[this.rowKey] === item.keyValue)
                  if (!this.backData.find(v => v[this.rowKey] === row[this.rowKey])) {
                     this.backData.push(row)
                  }
               }
               if (index > (startIndex - 1) && this.backIndexData.indexOf(index - startIndex) === -1) {
                  this.backIndexData.push(index - startIndex)
               }
            }
         })
         // this.backData = JSON.parse(JSON.stringify(this.data))
         this.$emit('selection-change', {
            detail: {
               value: this.backData,
               index: this.backIndexData
            }
         })
      },
      /**
       * 用于多选表格,切换某一行的选中状态,如果使用了第二个参数,则是设置这一行选中与否(selected 为 true 则选中)
       */
      toggleRowSelection(row, selected) {
         // if (!this.theadChildren) return
         row = [].concat(row)
         this.trChildren.forEach((item, index) => {
            // if (item.keyValue) {
            const select = row.findIndex(v => {
               //
               if (typeof v === 'number') {
                  return v === index - 1
               } else {
                  return v[this.rowKey] === item.keyValue
               }
            })
            let ischeck = item.checked
            if (select !== -1) {
               if (typeof selected === 'boolean') {
                  item.checked = selected
               } else {
                  item.checked = !item.checked
               }
               if (ischeck !== item.checked) {
                  this.check(item.rowData||item, item.checked, item.rowData?item.keyValue:null, true)
               }
            }
            // }
         })
         this.$emit('selection-change', {
            detail: {
               value: this.backData,
               index:this.backIndexData
            }
         })
      },
      /**
       * 用于多选表格,清空用户的选择
       */
      clearSelection() {
         let theadChildren = this.theadChildren
         if (!this.theadChildren) {
            theadChildren = this.trChildren[0]
         }
         // if (!this.theadChildren) return
         theadChildren.checked = false
         theadChildren.indeterminate = false
         this.trChildren.forEach(item => {
            // if (item.keyValue) {
               item.checked = false
            // }
         })
         this.backData = []
         this.backIndexData = []
         this.$emit('selection-change', {
            detail: {
               value: [],
               index: []
            }
         })
      },
      /**
       * 用于多选表格,切换所有行的选中状态
       */
      toggleAllSelection() {
         let list = []
         let startIndex = 1
         let theadChildren = this.theadChildren
         if (!this.theadChildren) {
            theadChildren = this.trChildren[0]
         } else {
            startIndex = theadChildren.rowspan - 1
         }
         this.trChildren.forEach((item, index) => {
            if (!item.disabled) {
               if (index > (startIndex - 1) ) {
                  list.push(index-startIndex)
               }
            }
         })
         this.toggleRowSelection(list)
      },
      /**
       * 选中\取消选中
       * @param {Object} child
       * @param {Object} check
       * @param {Object} rowValue
       */
      check(child, check, keyValue, emit) {
         let theadChildren = this.theadChildren
         if (!this.theadChildren) {
            theadChildren = this.trChildren[0]
         }
         let childDomIndex = this.trChildren.findIndex((item, index) => child === item)
         if(childDomIndex < 0){
            childDomIndex = this.data.findIndex(v=>v[this.rowKey] === keyValue) + 1
         }
         const dataLen = this.trChildren.filter(v => !v.disabled && v.keyValue).length
         if (childDomIndex === 0) {
            check ? this.selectionAll() : this.clearSelection()
            return
         }
         if (check) {
            if (keyValue) {
               this.backData.push(child)
            }
            this.backIndexData.push(childDomIndex - 1)
         } else {
            const index = this.backData.findIndex(v => v[this.rowKey] === keyValue)
            const idx = this.backIndexData.findIndex(item => item === childDomIndex - 1)
            if (keyValue) {
               this.backData.splice(index, 1)
            }
            this.backIndexData.splice(idx, 1)
         }
         const domCheckAll = this.trChildren.find((item, index) => index > 0 && !item.checked && !item.disabled)
         if (!domCheckAll) {
            theadChildren.indeterminate = false
            theadChildren.checked = true
         } else {
            theadChildren.indeterminate = true
            theadChildren.checked = false
         }
         if (this.backIndexData.length === 0) {
            theadChildren.indeterminate = false
         }
         if (!emit) {
            this.$emit('selection-change', {
               detail: {
                  value: this.backData,
                  index: this.backIndexData
               }
            })
         }
      }
   }
}
</script>
<style lang="scss">
$border-color: #ebeef5;
.uni-table-scroll {
   width: 100%;
   /* #ifndef APP-NVUE */
   overflow-x: auto;
   /* #endif */
}
.uni-table {
   position: relative;
   width: 100%;
   border-radius: 5px;
   // box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.1);
   background-color: #fff;
   /* #ifndef APP-NVUE */
   box-sizing: border-box;
   display: table;
   overflow-x: auto;
   ::v-deep .uni-table-tr:nth-child(n + 2) {
      &:hover {
         background-color: #f5f7fa;
      }
   }
   ::v-deep .uni-table-thead {
      .uni-table-tr {
         // background-color: #f5f7fa;
         &:hover {
            background-color:#fafafa;
         }
      }
   }
   /* #endif */
}
.table--border {
   border: 1px $border-color solid;
   border-right: none;
}
.border-none {
   /* #ifndef APP-NVUE */
   border-bottom: none;
   /* #endif */
}
.table--stripe {
   /* #ifndef APP-NVUE */
   ::v-deep .uni-table-tr:nth-child(2n + 3) {
      background-color: #fafafa;
   }
   /* #endif */
}
/* 表格加载、无数据样式 */
.uni-table-loading {
   position: relative;
   /* #ifndef APP-NVUE */
   display: table-row;
   /* #endif */
   height: 50px;
   line-height: 50px;
   overflow: hidden;
   box-sizing: border-box;
}
.empty-border {
   border-right: 1px $border-color solid;
}
.uni-table-text {
   position: absolute;
   right: 0;
   left: 0;
   text-align: center;
   font-size: 14px;
   color: #999;
}
.uni-table-mask {
   position: absolute;
   top: 0;
   bottom: 0;
   left: 0;
   right: 0;
   background-color: rgba(255, 255, 255, 0.8);
   z-index: 99;
   /* #ifndef APP-NVUE */
   display: flex;
   margin: auto;
   transition: all 0.5s;
   /* #endif */
   justify-content: center;
   align-items: center;
}
.uni-table--loader {
   width: 30px;
   height: 30px;
   border: 2px solid #aaa;
   // border-bottom-color: transparent;
   border-radius: 50%;
   /* #ifndef APP-NVUE */
   animation: 2s uni-table--loader linear infinite;
   /* #endif */
   position: relative;
}
@keyframes uni-table--loader {
   0% {
      transform: rotate(360deg);
   }
   10% {
      border-left-color: transparent;
   }
   20% {
      border-bottom-color: transparent;
   }
   30% {
      border-right-color: transparent;
   }
   40% {
      border-top-color: transparent;
   }
   50% {
      transform: rotate(0deg);
   }
   60% {
      border-top-color: transparent;
   }
   70% {
      border-left-color: transparent;
   }
   80% {
      border-bottom-color: transparent;
   }
   90% {
      border-right-color: transparent;
   }
   100% {
      transform: rotate(-360deg);
   }
}
</style>
<template>
   <view class="uni-table-scroll" :class="{ 'table--border': border, 'border-none': !noData }">
      <!-- #ifdef H5 -->
      <table class="uni-table" border="0" cellpadding="0" cellspacing="0" :class="{ 'table--stripe': stripe }" :style="{ 'min-width': minWidth + 'px' }">
         <slot></slot>
         <view v-if="noData" class="uni-table-loading">
            <view class="uni-table-text" :class="{ 'empty-border': border }">{{ emptyText }}</view>
         </view>
         <view v-if="loading" class="uni-table-mask" :class="{ 'empty-border': border }"><div class="uni-table--loader"></div></view>
      </table>
      <!-- #endif -->
      <!-- #ifndef H5 -->
      <view class="uni-table" :style="{ 'min-width': minWidth + 'px' }" :class="{ 'table--stripe': stripe }">
         <slot></slot>
         <view v-if="noData" class="uni-table-loading">
            <view class="uni-table-text" :class="{ 'empty-border': border }">{{ emptyText }}</view>
         </view>
         <view v-if="loading" class="uni-table-mask" :class="{ 'empty-border': border }"><div class="uni-table--loader"></div></view>
      </view>
      <!-- #endif -->
   </view>
</template>
<script>
/**
 * Table 表格
 * @description 用于展示多条结构类似的数据
 * @tutorial https://ext.dcloud.net.cn/plugin?id=3270
 * @property {Boolean}    border             是否带有纵向边框
 * @property {Boolean}    stripe             是否显示斑马线
 * @property {Boolean}    type                是否开启多选
 * @property {String}    emptyText          空数据时显示的文本内容
 * @property {Boolean}    loading          显示加载中
 * @event {Function}    selection-change    开启多选时,当选择项发生变化时会触发该事件
 */
export default {
   name: 'uniTable',
   options: {
      virtualHost: true
   },
   emits:['selection-change'],
   props: {
      data: {
         type: Array,
         default() {
            return []
         }
      },
      // 是否有竖线
      border: {
         type: Boolean,
         default: false
      },
      // 是否显示斑马线
      stripe: {
         type: Boolean,
         default: false
      },
      // 多选
      type: {
         type: String,
         default: ''
      },
      // 没有更多数据
      emptyText: {
         type: String,
         default: '没有更多数据'
      },
      loading: {
         type: Boolean,
         default: false
      },
      rowKey: {
         type: String,
         default: ''
      }
   },
   data() {
      return {
         noData: true,
         minWidth: 0,
         multiTableHeads: []
      }
   },
   watch: {
      loading(val) {},
      data(newVal) {
         let theadChildren = this.theadChildren
         let rowspan = 1
         if (this.theadChildren) {
            rowspan = this.theadChildren.rowspan
         }
         // this.trChildren.length - rowspan
         this.noData = false
         // this.noData = newVal.length === 0
      }
   },
   created() {
      // 定义tr的实例数组
      this.trChildren = []
      this.thChildren = []
      this.theadChildren = null
      this.backData = []
      this.backIndexData = []
   },
   methods: {
      isNodata() {
         let theadChildren = this.theadChildren
         let rowspan = 1
         if (this.theadChildren) {
            rowspan = this.theadChildren.rowspan
         }
         this.noData = this.trChildren.length - rowspan <= 0
      },
      /**
       * 选中所有
       */
      selectionAll() {
         let startIndex = 1
         let theadChildren = this.theadChildren
         if (!this.theadChildren) {
            theadChildren = this.trChildren[0]
         } else {
            startIndex = theadChildren.rowspan - 1
         }
         let isHaveData = this.data && this.data.length.length > 0
         theadChildren.checked = true
         theadChildren.indeterminate = false
         this.trChildren.forEach((item, index) => {
            if (!item.disabled) {
               item.checked = true
               if (isHaveData && item.keyValue) {
                  const row = this.data.find(v => v[this.rowKey] === item.keyValue)
                  if (!this.backData.find(v => v[this.rowKey] === row[this.rowKey])) {
                     this.backData.push(row)
                  }
               }
               if (index > (startIndex - 1) && this.backIndexData.indexOf(index - startIndex) === -1) {
                  this.backIndexData.push(index - startIndex)
               }
            }
         })
         // this.backData = JSON.parse(JSON.stringify(this.data))
         this.$emit('selection-change', {
            detail: {
               value: this.backData,
               index: this.backIndexData
            }
         })
      },
      /**
       * 用于多选表格,切换某一行的选中状态,如果使用了第二个参数,则是设置这一行选中与否(selected 为 true 则选中)
       */
      toggleRowSelection(row, selected) {
         // if (!this.theadChildren) return
         row = [].concat(row)
         this.trChildren.forEach((item, index) => {
            // if (item.keyValue) {
            const select = row.findIndex(v => {
               //
               if (typeof v === 'number') {
                  return v === index - 1
               } else {
                  return v[this.rowKey] === item.keyValue
               }
            })
            let ischeck = item.checked
            if (select !== -1) {
               if (typeof selected === 'boolean') {
                  item.checked = selected
               } else {
                  item.checked = !item.checked
               }
               if (ischeck !== item.checked) {
                  this.check(item.rowData||item, item.checked, item.rowData?item.keyValue:null, true)
               }
            }
            // }
         })
         this.$emit('selection-change', {
            detail: {
               value: this.backData,
               index:this.backIndexData
            }
         })
      },
      /**
       * 用于多选表格,清空用户的选择
       */
      clearSelection() {
         let theadChildren = this.theadChildren
         if (!this.theadChildren) {
            theadChildren = this.trChildren[0]
         }
         // if (!this.theadChildren) return
         theadChildren.checked = false
         theadChildren.indeterminate = false
         this.trChildren.forEach(item => {
            // if (item.keyValue) {
               item.checked = false
            // }
         })
         this.backData = []
         this.backIndexData = []
         this.$emit('selection-change', {
            detail: {
               value: [],
               index: []
            }
         })
      },
      /**
       * 用于多选表格,切换所有行的选中状态
       */
      toggleAllSelection() {
         let list = []
         let startIndex = 1
         let theadChildren = this.theadChildren
         if (!this.theadChildren) {
            theadChildren = this.trChildren[0]
         } else {
            startIndex = theadChildren.rowspan - 1
         }
         this.trChildren.forEach((item, index) => {
            if (!item.disabled) {
               if (index > (startIndex - 1) ) {
                  list.push(index-startIndex)
               }
            }
         })
         this.toggleRowSelection(list)
      },
      /**
       * 选中\取消选中
       * @param {Object} child
       * @param {Object} check
       * @param {Object} rowValue
       */
      check(child, check, keyValue, emit) {
         let theadChildren = this.theadChildren
         if (!this.theadChildren) {
            theadChildren = this.trChildren[0]
         }
         let childDomIndex = this.trChildren.findIndex((item, index) => child === item)
         if(childDomIndex < 0){
            childDomIndex = this.data.findIndex(v=>v[this.rowKey] === keyValue) + 1
         }
         const dataLen = this.trChildren.filter(v => !v.disabled && v.keyValue).length
         if (childDomIndex === 0) {
            check ? this.selectionAll() : this.clearSelection()
            return
         }
         if (check) {
            if (keyValue) {
               this.backData.push(child)
            }
            this.backIndexData.push(childDomIndex - 1)
         } else {
            const index = this.backData.findIndex(v => v[this.rowKey] === keyValue)
            const idx = this.backIndexData.findIndex(item => item === childDomIndex - 1)
            if (keyValue) {
               this.backData.splice(index, 1)
            }
            this.backIndexData.splice(idx, 1)
         }
         const domCheckAll = this.trChildren.find((item, index) => index > 0 && !item.checked && !item.disabled)
         if (!domCheckAll) {
            theadChildren.indeterminate = false
            theadChildren.checked = true
         } else {
            theadChildren.indeterminate = true
            theadChildren.checked = false
         }
         if (this.backIndexData.length === 0) {
            theadChildren.indeterminate = false
         }
         if (!emit) {
            this.$emit('selection-change', {
               detail: {
                  value: this.backData,
                  index: this.backIndexData
               }
            })
         }
      }
   }
}
</script>
<style lang="scss">
$border-color: #ebeef5;
.uni-table-scroll {
   width: 100%;
   /* #ifndef APP-NVUE */
   overflow-x: auto;
   /* #endif */
}
.uni-table {
   position: relative;
   width: 100%;
   border-radius: 5px;
   // box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.1);
   background-color: #fff;
   /* #ifndef APP-NVUE */
   box-sizing: border-box;
   display: table;
   overflow-x: auto;
   ::v-deep .uni-table-tr:nth-child(n + 2) {
      &:hover {
         background-color: #f5f7fa;
      }
   }
   ::v-deep .uni-table-thead {
      .uni-table-tr {
         // background-color: #f5f7fa;
         &:hover {
            background-color:#fafafa;
         }
      }
   }
   /* #endif */
}
.table--border {
   border: 1px $border-color solid;
   border-right: none;
}
.border-none {
   /* #ifndef APP-NVUE */
   border-bottom: none;
   /* #endif */
}
.table--stripe {
   /* #ifndef APP-NVUE */
   ::v-deep .uni-table-tr:nth-child(2n + 3) {
      background-color: #fafafa;
   }
   /* #endif */
}
/* 表格加载、无数据样式 */
.uni-table-loading {
   position: relative;
   /* #ifndef APP-NVUE */
   display: table-row;
   /* #endif */
   height: 50px;
   line-height: 50px;
   overflow: hidden;
   box-sizing: border-box;
}
.empty-border {
   border-right: 1px $border-color solid;
}
.uni-table-text {
   position: absolute;
   right: 0;
   left: 0;
   text-align: center;
   font-size: 14px;
   color: #999;
}
.uni-table-mask {
   position: absolute;
   top: 0;
   bottom: 0;
   left: 0;
   right: 0;
   background-color: rgba(255, 255, 255, 0.8);
   z-index: 99;
   /* #ifndef APP-NVUE */
   display: flex;
   margin: auto;
   transition: all 0.5s;
   /* #endif */
   justify-content: center;
   align-items: center;
}
.uni-table--loader {
   width: 30px;
   height: 30px;
   border: 2px solid #aaa;
   // border-bottom-color: transparent;
   border-radius: 50%;
   /* #ifndef APP-NVUE */
   animation: 2s uni-table--loader linear infinite;
   /* #endif */
   position: relative;
}
@keyframes uni-table--loader {
   0% {
      transform: rotate(360deg);
   }
   10% {
      border-left-color: transparent;
   }
   20% {
      border-bottom-color: transparent;
   }
   30% {
      border-right-color: transparent;
   }
   40% {
      border-top-color: transparent;
   }
   50% {
      transform: rotate(0deg);
   }
   60% {
      border-top-color: transparent;
   }
   70% {
      border-left-color: transparent;
   }
   80% {
      border-bottom-color: transparent;
   }
   90% {
      border-right-color: transparent;
   }
   100% {
      transform: rotate(-360deg);
   }
}
</style>