|
@@ -0,0 +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>
|