描述
原组件使用中的不足
1、点击全选:Computed property "isSelectAll" was assigned to but it has no setter.
2、使用table组件,设置表格固定高度:height会导致全选报错,去除此设置则正常。【全选与固定高度冲突】
3、没有全选标识,当全部选中时会触发on-select-all返回全部选中的数据,
而取消全选时则需要通过on-selection-change去判断返回的数组为空确认取消全选。
前提:on-select-all和on-selection-change同时存在】
4、开启optional选择标识,通过on-selection-change返回值确认全选/全不选。
5、表格高度属性支持Number / String 类型,却对String类型未做任何类型处理,直接使用,导致String类型值入参无效。
6、无法实现表格行或列的合并。
7、自定义扩展局限。
修复的问题
1、修复 Elements in iteration expect to have 'v-bind:key' directives.
2、限制 height 属性的类型为Number,并设置默认值为500
组件使用
<template>
<div class="page-bg">
<tt :size="tableSize"
:stripe="true"
:border="true"
:optional="true"
:columns="columns1"
:data="data3"
:pagination="true"
:page-size="10"
:show-page-total="true"
:show-page-sizer="true"
:show-page-quickjump="true"
:height="432"
@on-selection-change="handleSelectionChange"
@on-page-change="handlePageChange"
@on-page-size-change="handlePageSizeChange"></tt>
</div>
</template>
<script>
import tt from '../components/table/src/table'
export default {
data () {
return {
tableSize: 'large', // small normal
columns1: [
{
title: '姓名',
key: 'name'
},
{
title: '年龄',
key: 'age',
sortType: 'normal',
render: (a, row) => {
return a('AtTag',{
props: {
color: row.item.age > 20 ? 'success' : 'yellow'
}
}, row.item.age)
}
},
{
title: '地址',
key: 'address',
render: (t, row) => {
return t('p', {
style: {
width: '150px'
},
class: 'over-text'
}, row.item.address)
}
},
{
title: '操作',
render: (h, params) => {
return h('div', [
h('AtButton', {
props: {
size: 'small',
hollow: true
},
style: {
marginRight: '8px'
},
on: {
click: () => {
this.$Message(params.item.name)
}
}
}, '查看姓名'),
h('AtButton', {
props: {
size: 'small',
hollow: true
},
on: {
click: () => {
this.$Message(params.item.address)
}
}
}, '查看地址')
])
}
}
],
data3: this.makePageData(),
selectAllList: []
}
},
computed:{
},
components: {
tt
},
methods: {
makePageData () {
const data = [
{
name: '库里',
age: 18,
address: '深圳市宝安区创业一路|深圳市宝安区创业一路深圳市宝安区创业一路深圳市宝安区创业一路深圳市宝安区创业一路'
},
{
name: '詹姆斯',
age: 25,
address: '广州市天河区岗顶'
},
{
name: '科比',
age: 24,
address: '上海市浦东新区'
},
{
name: '杜兰特',
age: 22,
address: '深圳市南山区深南大道'
},
{
name: '威斯布鲁克',
age: 21,
address: '北京市朝阳区'
},
{
name: '邓肯',
age: 26,
address: '深圳市罗湖区万象城'
},
{
name: '帕克',
age: 25,
address: '深圳市福田区中心书城'
},
{
name: '欧文',
age: 20,
address: '广州市番禺区大学城'
},
{
name: '托马斯',
age: 19,
address: '北京市朝阳区'
}
]
let pageData = []
for (let i = 0; i < 5; i++) {
pageData = pageData.concat(data)
}
return pageData
},
handleSelectionChange(rows) {
console.log('handleSelectionChange==', rows);
},
handleSelectAll(list) {
console.log('handleSelectAll==', list);
},
handlePageChange(page) {
console.log('handlePageChange==', page);
},
handlePageSizeChange(size) {
console.log('handlePageSizeChange==', size);
}
}
}
</script>
<style>
.over-text{
text-overflow:ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
</style>
容错后的组件
<template>
<div
class="at-table"
:class="{
'at-table--fixHeight': this.height,
'at-table--stripe': this.stripe,
[`at-table--${this.size}`]: this.size,
[`at-table--border`]: this.border
}"
:style="tableStyles">
<!-- S Content -->
<div class="at-table__content" :style="contentStyle">
<!-- S Header -->
<div class="at-table__header" v-if="height">
<table>
<colgroup>
<col v-for="(column, index) in columnsData" :key="index" :width="setCellWidth(column, index)">
</colgroup>
<thead class="at-table__thead" ref="header">
<tr>
<!-- S Checkbox -->
<th v-if="optional" class="at-table__cell at-table__column-selection">
<at-checkbox v-model="isSelectAll" @click.native.prevent="handleSelectAll"></at-checkbox>
</th>
<!-- E Checkbox -->
<!-- S Column th -->
<th
v-for="(column, index) in columnsData"
class="at-table__cell at-table__column"
:key="index"
:class="column.className"
:style="{
cursor: column.sortType ? 'pointer' : 'text'
}"
@click="column.sortType && handleSort(index)">
{{ column.title }}
<template v-if="column.sortType">
<div class="at-table__column-sorter"
:class="{
'sort-asc': column._sortType === 'asc',
'sort-desc': column._sortType === 'desc'
}">
<span class="at-table__column-sorter-up" @click.stop="handleSort(index, 'asc')"><i class="icon icon-chevron-up"></i></span>
<span class="at-table__column-sorter-down" @click.stop="handleSort(index, 'desc')"><i class="icon icon-chevron-down"></i></span>
</div>
</template>
</th>
<!-- E Column th -->
</tr>
</thead>
</table>
</div>
<!-- E Header -->
<!-- S Body -->
<div class="at-table__body" :style="bodyStyle">
<table>
<colgroup>
<col v-for="(column, index) in columnsData" :key="index" :width="setCellWidth(column, index)">
</colgroup>
<thead class="at-table__thead" v-if="!height" ref="header">
<tr>
<!-- S Checkbox -->
<th v-if="optional" class="at-table__cell at-table__column-selection">
<at-checkbox v-model="isSelectAll" @click.native.prevent="handleSelectAll"></at-checkbox>
</th>
<!-- E Checkbox -->
<!-- S Column th -->
<th
v-for="(column, index) in columnsData"
class="at-table__cell at-table__column"
:key="index"
:class="column.className"
:style="{
cursor: column.sortType ? 'pointer' : 'text'
}"
@click="column.sortType && handleSort(index)">
{{ column.title }}
<template v-if="column.sortType">
<div class="at-table__column-sorter"
:class="{
'sort-asc': column._sortType === 'asc',
'sort-desc': column._sortType === 'desc'
}">
<span class="at-table__column-sorter-up" @click.stop="handleSort(index, 'asc')"><i class="icon icon-chevron-up"></i></span>
<span class="at-table__column-sorter-down" @click.stop="handleSort(index, 'desc')"><i class="icon icon-chevron-down"></i></span>
</div>
</template>
</th>
<!-- E Column th -->
</tr>
</thead>
<tbody class="at-table__tbody" v-if="sortData.length" ref="body">
<template v-for="(item, index) in sortData">
<tr :key="index">
<td v-if="optional" class="at-table__cell at-table__column-selection">
<at-checkbox v-model="objData[index].isChecked" @on-change="changeRowSelection"></at-checkbox>
</td>
<td v-for="(column, cindex) in columns" :key="cindex" class="at-table__cell">
<template v-if="column.render">
<Cell :item="item" :column="column" :index="index" :render="column.render"></Cell>
</template>
<template v-else>
{{ item[column.key] }}
</template>
</td>
</tr>
</template>
</tbody>
<tbody class="at-table__tbody" v-else>
<tr>
<td class="at-table__cell at-table__cell--nodata" :colspan="optional ? columns.length + 1 : columns.length">
<slot name="emptyText">{{ t('at.table.emptyText') }}</slot>
</td>
</tr>
</tbody>
</table>
</div>
<!-- E Body -->
</div>
<!-- E Content -->
<!-- S Pagination -->
<div v-if="pagination && total" class="at-table__footer" ref="footer">
<at-pagination
:current="currentPage"
:size="size"
:total="total"
:page-size="pageSize"
:show-total="showPageTotal"
:show-sizer="showPageSizer"
:show-quickjump="showPageQuickjump"
@page-change="pageChange"
@pagesize-change="pageSizeChange"></at-pagination>
</div>
<!-- E Pagination -->
</div>
</template>
<script>
import Locale from 'at-ui/src/mixins/locale'
import { getStyle, deepCopy } from 'at-ui/src/utils/util'
import Cell from './render'
import Checkbox from 'at-ui/src/components/checkbox'
import Pagination from 'at-ui/src/components/pagination'
export default {
name: 'AtTable',
components: {
Checkbox,
Pagination,
Cell
},
mixins: [Locale],
props: {
size: {
type: String,
default: 'normal'
},
stripe: {
type: Boolean,
default: false
},
border: {
type: Boolean,
default: false
},
data: {
type: Array,
default () {
return []
}
},
columns: {
type: Array,
default () {
return []
}
},
optional: {
type: Boolean,
default: false
},
pagination: {
type: Boolean,
default: false
},
pageSize: {
type: Number,
default: 10
},
showPageTotal: {
type: Boolean,
default: true
},
showPageSizer: {
type: Boolean,
default: false
},
showPageQuickjump: {
type: Boolean,
default: false
},
height: [Number]
},
data () {
return {
objData: this.makeObjData(), // use for checkbox to select all
sortData: [], // use for sort or paginate
allData: [],
columnsData: this.makeColumns(),
total: 0,
bodyHeight: 0,
pageCurSize: this.pageSize,
columnsWidth: {},
currentPage: 1
}
},
watch: {
height () {
this.calculateBodyHeight()
},
allData () {
this.total = this.allData.length
},
sortData () {
this.handleResize()
},
pageCurSize () {
this.sortData = this.makeDataWithPaginate()
},
data () {
this.sortData = this.makeDataWithSortAndPage()
}
},
computed: {
tableStyles () {
const styles = {}
if (this.height) {
styles.height = `${this.height}px`
}
if (this.width) {
styles.width = `${this.width}px`
}
return styles
},
isSelectAll () {
let isAll = true
if (!this.sortData.length) {
isAll = false
}
for (let i = 0, len = this.sortData.length; i < len; i++) {
if (!this.objData[this.sortData[i].index].isChecked) {
isAll = false
break
}
}
return isAll
},
bodyStyle () {
const styles = {}
if (this.bodyHeight !== 0) {
const headerHeight = parseInt(getStyle(this.$refs.header, 'height')) || 0
styles.height = `${this.bodyHeight}px`
styles.marginTop = `${headerHeight}px`
}
return styles
},
contentStyle () {
const styles = {}
if (this.bodyHeight !== 0) {
const headerHeight = parseInt(getStyle(this.$refs.header, 'height')) || 0
styles.height = `${this.bodyHeight + headerHeight}px`
}
return styles
}
},
methods: {
calculateBodyHeight () {
if (this.height) {
this.$nextTick(() => {
const headerHeight = parseInt(getStyle(this.$refs.header, 'height')) || 0
const footerHeight = parseInt(getStyle(this.$refs.footer, 'height')) || 0
this.bodyHeight = this.height - headerHeight - footerHeight
})
} else {
this.bodyHeight = 0
}
},
makeColumns () {
const columns = deepCopy(this.columns)
columns.forEach((column, idx) => {
column._index = idx
column._sortType = 'normal'
if (column.sortType) {
column._sortType = column.sortType
column.sortType = column.sortType
}
})
return columns
},
makeData () {
const data = deepCopy(this.data)
data.forEach((row, idx) => {
row.index = idx
})
return data
},
makeObjData () {
const rowData = {}
this.data.forEach((row, index) => {
const newRow = deepCopy(row)
newRow.isChecked = !!newRow.isChecked
rowData[index] = newRow
})
return rowData
},
makeDataWithSortAndPage (pageNum) {
let data = []
let allData = []
allData = this.makeDataWithSort()
this.allData = allData
data = this.makeDataWithPaginate(pageNum)
return data
},
makeDataWithPaginate (page) {
page = page || 1
const pageStart = (page - 1) * this.pageCurSize
const pageEnd = pageStart + this.pageCurSize
let pageData = []
if (this.pagination) {
pageData = this.allData.slice(pageStart, pageEnd)
} else {
pageData = this.allData
}
return pageData
},
makeDataWithSort () {
let data = this.makeData()
let sortType = 'normal'
let sortIndex = -1
for (let i = 0, len = this.columnsData.length; i < len; i++) {
if (this.columnsData[i].sortType && this.columnsData[i].sortType !== 'normal') {
sortType = this.columnsData[i].sortType
sortIndex = i
break
}
}
if (sortType !== 'normal') {
data = this.sort(data, sortType, sortIndex)
}
return data
},
handleSelectAll () {
const status = !this.isSelectAll
for (const data of this.sortData) {
this.objData[data.index].isChecked = status
}
const selection = this.getSelection()
status && this.$emit('on-select-all', selection)
this.$emit('on-selection-change', selection)
},
handleSort (index, type) {
const key = this.columnsData[index].key
const sortType = this.columnsData[index]._sortType
const sortNameArr = ['normal', 'desc', 'asc']
if (this.columnsData[index].sortType) {
if (!type) {
const tmpIdx = sortNameArr.indexOf(sortType)
if (tmpIdx >= 0) {
type = sortNameArr[(tmpIdx + 1) > 2 ? 0 : tmpIdx + 1]
}
}
if (type === 'normal') {
this.sortData = this.makeDataWithSortAndPage(this.currentPage)
} else {
this.sortData = this.sort(this.sortData, type, index)
}
}
this.columnsData[index]._sortType = type
this.$emit('on-sort-change', {
column: JSON.parse(JSON.stringify(this.columns[this.columnsData[index]._index])),
order: type,
key
})
},
sort (data, type, index) {
const key = this.columnsData[index].key
data.sort((a, b) => {
if (this.columnsData[index].sortMethod) {
return this.columnsData[index].sortMethod(a[key], b[key], type)
} else if (type === 'asc') {
return a[key] > b[key] ? 1 : -1
}
return a[key] < b[key] ? 1 : -1
})
return data
},
getSelection () {
const selectionIndexArray = []
for (const i in this.objData) {
if (this.objData[i].isChecked) {
selectionIndexArray.push(i | 0)
}
}
return JSON.parse(JSON.stringify(this.data.filter((data, index) => selectionIndexArray.indexOf(index) > -1)))
},
changeRowSelection () {
const selection = this.getSelection()
this.$emit('on-selection-change', selection)
},
pageChange (page) {
this.$emit('on-page-change', page)
this.currentPage = page
this.sortData = this.makeDataWithPaginate(page)
},
pageSizeChange (size) {
this.$emit('on-page-size-change', size)
this.pageCurSize = size
},
handleResize () {
this.$nextTick(() => {
const columnsWidth = {}
if (this.data.length) {
const $td = this.$refs.body.querySelectorAll('tr')[0].querySelectorAll('td')
for (let i = 0; i < $td.length; i++) {
const column = this.columnsData[i]
let width = parseInt(getStyle($td[i], 'width'))
if (column) {
if (column.width) {
width = column.width
}
columnsWidth[column._index] = { width }
}
}
}
this.columnsWidth = columnsWidth
})
},
setCellWidth (column, index) {
let width = ''
if (column.width) {
width = column.width
} else if (this.columnsWidth[column._index]) {
width = this.columnsWidth[column._index].width
}
width = width === '0' ? '' : width
return width
}
},
created () {
this.sortData = this.makeDataWithSortAndPage()
},
mounted () {
this.calculateBodyHeight()
window.addEventListener('resize', this.handleResize)
},
beforeDestory () {
window.removeEventListener('resize', this.handleResize)
}
}
</script>