|
@@ -3,31 +3,81 @@
|
|
|
<!-- 表头 -->
|
|
|
<ul class="t-header" :class="{'bottom-line':showLine}">
|
|
|
<!-- 复选框 -->
|
|
|
- <li v-if="selection" class="check-cls"><span @click="lock=false,selectAll=!selectAll" class="fdcheck" :class="{check_active:selectAll}"></span></li>
|
|
|
- <!-- -->
|
|
|
- <li v-for="(item,i) in header" :key="i" :style="{textAlign: item.textAlign, width:item.width ?item.width+'px':(100/header.length)+'%'}">
|
|
|
- <slot :data='item' name='header'></slot>
|
|
|
+ <div
|
|
|
+ v-if="selection && !isAllSelected"
|
|
|
+ class="checkbox"
|
|
|
+ @click="onClickSelectAll"
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <img
|
|
|
+ v-if="isAllSelected"
|
|
|
+ class="checkbox active"
|
|
|
+ @click="onClickSelectAll"
|
|
|
+ src="@/assets/images/icons/checkbox.png"
|
|
|
+ draggable="false"
|
|
|
+ />
|
|
|
+ <!-- 表头各项 -->
|
|
|
+ <li
|
|
|
+ v-for="(headerItem,i) in header"
|
|
|
+ :key="i"
|
|
|
+ :style="{
|
|
|
+ textAlign: headerItem.textAlign,
|
|
|
+ width: headerItem.width ? headerItem.width+'px' : (100/header.length)+'%'
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <!-- 名字叫header的作用域插槽,通过headerItem作用域提供表头各项 -->
|
|
|
+ <slot :headerItem='headerItem' name='header'></slot>
|
|
|
</li>
|
|
|
</ul>
|
|
|
<!-- 表格内容区域 -->
|
|
|
- <div class="t-con"
|
|
|
+ <div
|
|
|
+ class="t-con"
|
|
|
v-infinite-scroll="requestMoreData"
|
|
|
:infinite-scroll-disabled="!canRequestMoreData"
|
|
|
ref="t-con"
|
|
|
:style="`margin-right: ${hasScrollBar ? -6 : 0}px`"
|
|
|
>
|
|
|
- <ul class="t-item" active-txt :class="{'bottom-line': showLine}" v-for="(item, i) in fixdata" :key="i">
|
|
|
+ <!-- 表格每一行 -->
|
|
|
+ <ul
|
|
|
+ class="t-item"
|
|
|
+ :class="{'bottom-line': showLine}"
|
|
|
+ v-for="(lineData, i) in data"
|
|
|
+ :key="i"
|
|
|
+ >
|
|
|
<!-- 复选框 -->
|
|
|
- <li v-if="selection" class="check-cls" ><span @click="selectItem(item,i)" class="fdcheck" :class="{check_active:item.hasAuth}"></span></li>
|
|
|
- <!-- -->
|
|
|
+ <div
|
|
|
+ v-if="selection && !selectedIdList.some(id => id === lineData.id)"
|
|
|
+ class="checkbox"
|
|
|
+ @click="selectItem(lineData, i)"
|
|
|
+ />
|
|
|
+ <img
|
|
|
+ v-if="selection && selectedIdList.some(id => id === lineData.id)"
|
|
|
+ class="checkbox active"
|
|
|
+ @click="selectItem(lineData, i)"
|
|
|
+ src="@/assets/images/icons/checkbox.png"
|
|
|
+ draggable="false"
|
|
|
+ />
|
|
|
+ <!-- 表格各项 -->
|
|
|
<li
|
|
|
- v-for="(sub,j) in header"
|
|
|
+ v-for="(headerItem, j) in header"
|
|
|
:key='j'
|
|
|
- :style="{textAlign:item.textAlign, width:sub.width ? sub.width+'px':(100/header.length)+'%'}"
|
|
|
- :class="{ themetxt: sub.fontweight, showWhenHover: sub.showWhenHover }"
|
|
|
- :title="sub.key === 'name'? item[sub.key]: ''"
|
|
|
+ :style="{
|
|
|
+ textAlign: lineData.textAlign,
|
|
|
+ width: headerItem.width ? headerItem.width + 'px' : (100 / header.length) + '%'
|
|
|
+ }"
|
|
|
+ :class="{
|
|
|
+ themetxt: headerItem.fontweight,
|
|
|
+ showWhenHover: headerItem.showWhenHover
|
|
|
+ }"
|
|
|
+ :title="headerItem.key === 'name' ? lineData[headerItem.key] : ''"
|
|
|
>
|
|
|
- <slot :data='item[sub.key]' :item="item" :sub='sub' name='item'></slot>
|
|
|
+ <!-- 名字叫tableItem的作用域插槽,通过itemData作用域把这一项表格数据向外暴露,通过lineData作用域把每一行向外暴露,通过headerItem作用域把这一项对应的表头项向外暴露 -->
|
|
|
+ <slot
|
|
|
+ :itemData='lineData[headerItem.key]'
|
|
|
+ :lineData="lineData"
|
|
|
+ :headerItem='headerItem'
|
|
|
+ name='tableItem'
|
|
|
+ ></slot>
|
|
|
</li>
|
|
|
</ul>
|
|
|
</div>
|
|
@@ -72,58 +122,68 @@ export default {
|
|
|
},
|
|
|
data () {
|
|
|
return {
|
|
|
- selectAll: false,
|
|
|
- lock: true,
|
|
|
hasScrollBar: false,
|
|
|
+ selectedIdList: [],
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
- ...mapState({
|
|
|
- // props中data中hasAuth一律写成false,即未选中。
|
|
|
- fixdata () {
|
|
|
- let data = this.data && this.data.map(item => {
|
|
|
- item.hasAuth = false
|
|
|
- return item
|
|
|
- })
|
|
|
- return data
|
|
|
+ isAllSelected() {
|
|
|
+ if (this.data && this.data.length > 0) {
|
|
|
+ return this.data.length === this.selectedIdList.length
|
|
|
+ } else {
|
|
|
+ return false
|
|
|
}
|
|
|
- })
|
|
|
-
|
|
|
+ },
|
|
|
},
|
|
|
watch: {
|
|
|
- data () {
|
|
|
- this.selectAll = false
|
|
|
- },
|
|
|
- selectAll: function (newVal) {
|
|
|
- if (!this.lock) {
|
|
|
- this.fixdata.forEach(item => {
|
|
|
- item.hasAuth = newVal
|
|
|
- })
|
|
|
- this.handleSelect()
|
|
|
- }
|
|
|
+ data: {
|
|
|
+ handler(v) {
|
|
|
+ const newSelectedIdList = []
|
|
|
+ for (const selectedId of this.selectedIdList) {
|
|
|
+ if (this.data.some((item) => {
|
|
|
+ return item.id === selectedId
|
|
|
+ })) {
|
|
|
+ newSelectedIdList.push(selectedId)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.selectedIdList = newSelectedIdList
|
|
|
+ },
|
|
|
+ deep: true,
|
|
|
}
|
|
|
},
|
|
|
methods: {
|
|
|
+ onClickSelectAll() {
|
|
|
+ if (this.isAllSelected) {
|
|
|
+ this.selectedIdList = []
|
|
|
+ } else {
|
|
|
+ this.selectedIdList = []
|
|
|
+ this.data.forEach(item => {
|
|
|
+ this.selectedIdList.push(item.id)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ this.emitSelectionChange()
|
|
|
+ },
|
|
|
requestMoreData() {
|
|
|
this.$emit('request-more-data')
|
|
|
},
|
|
|
- handleSelect () {
|
|
|
- let arr = this.fixdata.filter(item => {
|
|
|
- return item.hasAuth
|
|
|
+ emitSelectionChange () {
|
|
|
+ let arr = this.data.filter(item => {
|
|
|
+ return this.selectedIdList.some((selectedId) => {
|
|
|
+ return selectedId === item.id
|
|
|
+ })
|
|
|
})
|
|
|
this.$emit('selection-change', arr)
|
|
|
},
|
|
|
- selectItem (item, i) {
|
|
|
- item.hasAuth = !item.hasAuth
|
|
|
- this.$set(this.fixdata, i, item)
|
|
|
- this.lock = true
|
|
|
- this.selectAll = true
|
|
|
- this.fixdata.forEach(sub => {
|
|
|
- if (!sub.hasAuth) {
|
|
|
- this.selectAll = false
|
|
|
- }
|
|
|
+ selectItem (selectedItem, i) {
|
|
|
+ const idx = this.selectedIdList.findIndex((alreadySelectedId) => {
|
|
|
+ return alreadySelectedId === selectedItem.id
|
|
|
})
|
|
|
- this.handleSelect()
|
|
|
+ if (idx > -1) {
|
|
|
+ this.selectedIdList.splice(idx, 1)
|
|
|
+ } else {
|
|
|
+ this.selectedIdList.push(selectedItem.id)
|
|
|
+ }
|
|
|
+ this.emitSelectionChange()
|
|
|
}
|
|
|
},
|
|
|
mounted () {
|
|
@@ -141,5 +201,101 @@ export default {
|
|
|
</script>
|
|
|
|
|
|
<style lang="less" scoped>
|
|
|
-@import './style.less';
|
|
|
+.table-layout {
|
|
|
+ width: 100%;
|
|
|
+ color: #777;
|
|
|
+ font-size: 14px;
|
|
|
+
|
|
|
+ .checkbox {
|
|
|
+ display: inline-block;
|
|
|
+ margin-right: 20px;
|
|
|
+ flex-grow: 1;
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ border-radius: 2px;
|
|
|
+ border: 1px solid #D5D8DE;
|
|
|
+ cursor: pointer;
|
|
|
+ user-select: none;
|
|
|
+ &.active {
|
|
|
+ border-radius: initial;
|
|
|
+ border: initial;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .t-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ width: 100%;
|
|
|
+ padding: 15px 16px 15px 16px;
|
|
|
+ background-color: #F5F7FA;
|
|
|
+ color: #646566;
|
|
|
+ line-height: 19px;
|
|
|
+ position: relative; // 为了设置z-index
|
|
|
+ z-index: 1; // 为了避免.t-con向下滚动后覆盖本元素。因为.t-con的margin-top是负值。
|
|
|
+ li {
|
|
|
+ text-align: left;
|
|
|
+ margin-right: 20px;
|
|
|
+ flex-grow: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 表格内容
|
|
|
+ .t-con {
|
|
|
+ padding-top: 49px; // 为了能上下滚动,overflow的值不能是visible,导致此元素是个BFC,导致绝对定位的tip在第一行无法显示在此元素区域外,只好让此元素具有padding-top以供第一行的tip显示。
|
|
|
+ margin-top: -49px;
|
|
|
+ height: calc(100vh - 325px); // 必须指定高度,element-ui的无限滚动指令才能生效
|
|
|
+ overflow: auto;
|
|
|
+ .t-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ width: 100%;
|
|
|
+ padding: 10px 16px 10px 16px;
|
|
|
+ .showWhenHover {
|
|
|
+ visibility: hidden;
|
|
|
+ }
|
|
|
+ &:hover {
|
|
|
+ background: #F7F7F7;
|
|
|
+ .showWhenHover {
|
|
|
+ visibility: visible;
|
|
|
+ }
|
|
|
+ &[active-txt] {
|
|
|
+ // 关键列,会加粗显示且有hover效果。
|
|
|
+ >.themetxt {
|
|
|
+ span {
|
|
|
+ word-break: break-all;
|
|
|
+ font-weight: bold;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: #1FE4DC !important;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ li {
|
|
|
+ text-align: left;
|
|
|
+ display: inline-block;
|
|
|
+ margin-right: 20px;
|
|
|
+ flex-grow: 1;
|
|
|
+ span {
|
|
|
+ color: #323233;
|
|
|
+ // word-break: keep-all;
|
|
|
+ // white-space: nowrap;
|
|
|
+ word-break: break-all;
|
|
|
+ white-space: normal;
|
|
|
+ display: -webkit-box;
|
|
|
+ -webkit-box-orient: vertical;
|
|
|
+ -webkit-line-clamp: 2;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .bottom-line {
|
|
|
+ min-height: 50px;
|
|
|
+ border-bottom: 1px solid rgba(#202020, 0.1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|