feat: 固定表格 - 通过 prop 设置参数
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
<view class="table-fixed">
|
<view class="table-fixed">
|
||||||
<!-- S 固定列 -->
|
<!-- S 固定列 -->
|
||||||
<view class="table__fixed-columns">
|
<view class="table__fixed-columns">
|
||||||
|
<!-- S 横竖方向都要固定的左上角单元格 -->
|
||||||
<view class="table__fixed-common tr">
|
<view class="table__fixed-common tr">
|
||||||
<view class="fixed-th th"
|
<view class="fixed-th th"
|
||||||
wx:for="{{fixedCols[0]}}"
|
wx:for="{{fixedCols[0]}}"
|
||||||
@@ -10,9 +11,13 @@
|
|||||||
{{item}}
|
{{item}}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<!-- E 横竖方向都要固定的左上角单元格 -->
|
||||||
|
|
||||||
|
<!-- S 固定列(除表头) -->
|
||||||
<scroll-view class="table__fixed-others"
|
<scroll-view class="table__fixed-others"
|
||||||
scroll-y
|
scroll-y
|
||||||
scroll-top="{{scrollTop}}">
|
scroll-top="{{scrollTop}}"
|
||||||
|
style="height: {{tbodyHeight}}rpx;">
|
||||||
<view wx:for="{{firstColsOther}}"
|
<view wx:for="{{firstColsOther}}"
|
||||||
wx:for-item="row"
|
wx:for-item="row"
|
||||||
wx:for-index="rowIndex"
|
wx:for-index="rowIndex"
|
||||||
@@ -28,26 +33,33 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
<!-- E 固定列(除表头) -->
|
||||||
</view>
|
</view>
|
||||||
<!-- E 固定列 -->
|
<!-- E 固定列 -->
|
||||||
|
|
||||||
|
<!-- S 完整的表格 -->
|
||||||
<scroll-view class="table"
|
<scroll-view class="table"
|
||||||
scroll-x>
|
scroll-x>
|
||||||
<!-- S 固定表头 -->
|
<!-- S 固定表头(完整) -->
|
||||||
<view class="thead"
|
<view class="thead"
|
||||||
style="width: {{totalWidth}}rpx;">
|
style="width: {{totalWidth}}rpx;">
|
||||||
<view class="tr">
|
<view class="tr">
|
||||||
<view class="th"
|
<view class="th"
|
||||||
wx:for="{{thead}}"
|
wx:for="{{thead}}"
|
||||||
wx:key="{{index}}"
|
wx:key="{{index}}"
|
||||||
style="width: {{colWidths[index]}}rpx;">{{item}}</view>
|
style="width: {{colWidths[index]}}rpx;">
|
||||||
|
{{item}}
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- E 固定表头 -->
|
<!-- E 固定表头(完整) -->
|
||||||
|
|
||||||
|
<!-- S tbody(完整) -->
|
||||||
<scroll-view class="tbody"
|
<scroll-view class="tbody"
|
||||||
scroll-y
|
scroll-y
|
||||||
throttle="{{false}}"
|
throttle="{{false}}"
|
||||||
@scroll="scrollVertical"
|
@scroll="scrollVertical"
|
||||||
style="width: {{totalWidth}}rpx;">
|
style="width: {{totalWidth}}rpx; height: {{tbodyHeight}}rpx;">
|
||||||
<view class="tr"
|
<view class="tr"
|
||||||
wx:for="{{tbody}}"
|
wx:for="{{tbody}}"
|
||||||
wx:for-item="tr"
|
wx:for-item="tr"
|
||||||
@@ -58,72 +70,127 @@
|
|||||||
wx:for-item="td"
|
wx:for-item="td"
|
||||||
wx:for-index="tdIndex"
|
wx:for-index="tdIndex"
|
||||||
wx:key="{{tdIndex}}"
|
wx:key="{{tdIndex}}"
|
||||||
style="width: {{colWidths[tdIndex]}}rpx;">{{td}}</view>
|
style="width: {{colWidths[tdIndex]}}rpx;">
|
||||||
|
{{td}}
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
<!-- S tbody(完整) -->
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
<!-- E 完整的表格 -->
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import wepy from 'wepy'
|
import wepy from 'wepy'
|
||||||
|
import utils from '@/utils/index'
|
||||||
|
|
||||||
const FAKE_DATA = [
|
const getTextWidth = utils.getTextWidth
|
||||||
['保单年度', '年龄', '当年生存金', '累积生存金', '账户价值', '我是测试']
|
|
||||||
]
|
|
||||||
const count = 100
|
|
||||||
const age = 30
|
|
||||||
for(let i = 0; i < count; i++) {
|
|
||||||
FAKE_DATA.push([
|
|
||||||
i,
|
|
||||||
age + i,
|
|
||||||
400,
|
|
||||||
500,
|
|
||||||
600,
|
|
||||||
700
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Table extends wepy.component {
|
export default class Table extends wepy.component {
|
||||||
data = {
|
props = {
|
||||||
table: FAKE_DATA,
|
table: {
|
||||||
colWidths: [
|
type: Array,
|
||||||
160, 80, 200, 200, 160, 160
|
required: true
|
||||||
],
|
|
||||||
fixedColsNum: 2,
|
|
||||||
scrollTop: 0
|
|
||||||
}
|
|
||||||
computed = {
|
|
||||||
totalWidth () {
|
|
||||||
let result = 0
|
|
||||||
this.colWidths.forEach(item => result += item)
|
|
||||||
return result
|
|
||||||
},
|
},
|
||||||
fixedCols () {
|
fixedColsNum: {
|
||||||
const result = []
|
type: [Number, String],
|
||||||
this.table.forEach(row => {
|
default: 1
|
||||||
result.push(row
|
|
||||||
.slice(0, this.fixedColsNum)
|
|
||||||
.map(col => col))
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
},
|
},
|
||||||
firstColsOther () {
|
tbodyHeight: {
|
||||||
return this.fixedCols.slice(1)
|
type: [Number, String],
|
||||||
},
|
default: 504
|
||||||
thead () {
|
|
||||||
return this.table[0]
|
|
||||||
},
|
|
||||||
tbody () {
|
|
||||||
return this.table.slice(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
colWidths: [],
|
||||||
|
scrollTop: 0,
|
||||||
|
totalWidth: 0,
|
||||||
|
fixedCols: [],
|
||||||
|
firstColsOther: [],
|
||||||
|
thead: [],
|
||||||
|
tbody: []
|
||||||
|
}
|
||||||
|
|
||||||
|
watch = {
|
||||||
|
/**
|
||||||
|
* $apply() 的触发都会导致 computed 属性内所有值都运行一次,
|
||||||
|
* 即使 computed 属性所依赖的属性未变更。
|
||||||
|
* 故通过 watch 判断依赖属性是否发生更改,避免不必的繁重运算。
|
||||||
|
*/
|
||||||
|
table () {
|
||||||
|
this.init()
|
||||||
|
this.$apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
methods = {
|
methods = {
|
||||||
scrollVertical (event) {
|
scrollVertical (event) {
|
||||||
const scrollTop = event.detail.scrollTop
|
const scrollTop = event.detail.scrollTop
|
||||||
this.scrollTop = scrollTop
|
this.scrollTop = scrollTop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init () {
|
||||||
|
this.colWidths = this.getColWidths()
|
||||||
|
this.totalWidth = this.getTotalWidth()
|
||||||
|
this.fixedCols = this.getFixedCols()
|
||||||
|
this.firstColsOther = this.getFirstColsOther()
|
||||||
|
this.thead = this.getThead()
|
||||||
|
this.tbody = this.getTbody()
|
||||||
|
}
|
||||||
|
|
||||||
|
getTbody () {
|
||||||
|
return this.table.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
getThead () {
|
||||||
|
return this.table[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
getFirstColsOther () {
|
||||||
|
return this.fixedCols.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
getFixedCols (table) {
|
||||||
|
const result = []
|
||||||
|
this.table.forEach(row => {
|
||||||
|
result.push(row
|
||||||
|
.slice(0, this.fixedColsNum)
|
||||||
|
.map(col => col))
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
getTotalWidth () {
|
||||||
|
return this.colWidths.reduce((acc, cur) => {
|
||||||
|
return acc + cur
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算每列的宽度,依据单元格的字符串像素宽度
|
||||||
|
*/
|
||||||
|
getColWidths () {
|
||||||
|
const table = this.table
|
||||||
|
const result = []
|
||||||
|
const TH_FONT_SIZE = 24
|
||||||
|
const TD_FONT_SIZE = 28
|
||||||
|
const SCALE_RATIO = 1.5
|
||||||
|
for (let colIndex = 0, colLen = table[0].length; colIndex < colLen; colIndex++) {
|
||||||
|
let maxWidth = getTextWidth(table[0][colIndex], TH_FONT_SIZE)
|
||||||
|
for (let rowIndex = 1, rowLen = table.length; rowIndex < rowLen; rowIndex++) {
|
||||||
|
const cell = table[rowIndex][colIndex]
|
||||||
|
const cellWidth = getTextWidth(cell, TD_FONT_SIZE)
|
||||||
|
if (cellWidth > maxWidth) {
|
||||||
|
maxWidth = cellWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.push(Math.ceil(maxWidth * SCALE_RATIO))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -138,19 +205,11 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
pointer-events: none;
|
pointer-events: none; // 固定列不接受触摸,相当于滚动表格本身
|
||||||
}
|
|
||||||
|
|
||||||
.table__fixed-others {
|
|
||||||
height: 400rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
width: 750rpx;
|
width: 100%;
|
||||||
}
|
|
||||||
|
|
||||||
.tbody {
|
|
||||||
height: 400rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tr {
|
.tr {
|
||||||
@@ -168,8 +227,8 @@
|
|||||||
.td,
|
.td,
|
||||||
.th {
|
.th {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
text-align: center;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
color: #666;
|
color: #666;
|
||||||
border-right: 1px solid $borderColor;
|
border-right: 1px solid $borderColor;
|
||||||
border-bottom: 1px solid $borderColor;
|
border-bottom: 1px solid $borderColor;
|
||||||
@@ -188,7 +247,7 @@
|
|||||||
|
|
||||||
.td {
|
.td {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
line-height: 40rpx;
|
line-height: 38rpx;
|
||||||
padding: 16rpx 0;
|
padding: 16rpx 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,55 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="container">
|
<view class="container">
|
||||||
<table></table>
|
<table
|
||||||
</view>
|
:table.sync="tableData"></table>
|
||||||
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import wepy from 'wepy'
|
import wepy from 'wepy'
|
||||||
import table from '../components/table'
|
import table from '../components/table'
|
||||||
|
|
||||||
export default class Index extends wepy.page {
|
const FAKE_DATA = [
|
||||||
config = {
|
['Full Name', 'Age', 'Column 1', 'Column 2', 'Column 3', 'Column 4']
|
||||||
navigationBarTitleText: 'test'
|
]
|
||||||
}
|
|
||||||
components = {
|
const count = 100
|
||||||
table
|
const age = 30
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
FAKE_DATA.push([
|
||||||
|
i,
|
||||||
|
age + i,
|
||||||
|
400,
|
||||||
|
5,
|
||||||
|
6666,
|
||||||
|
70
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
mixins = []
|
export default class Index extends wepy.page {
|
||||||
|
config = {
|
||||||
|
navigationBarTitleText: '固定表头和首N列'
|
||||||
|
}
|
||||||
|
components = {
|
||||||
|
table
|
||||||
|
}
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
tableData: []
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.tableData = FAKE_DATA
|
||||||
|
this.$apply()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
computed = {
|
|
||||||
}
|
|
||||||
|
|
||||||
methods = {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
events = {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoad() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
padding: 20rpx 40rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
38
src/utils/index.js
Normal file
38
src/utils/index.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* 根据字符串长度和字体大小计算文本长度,中文为 fontSize,其余为 fontSize / 2
|
||||||
|
* https://segmentfault.com/a/1190000016405843
|
||||||
|
* @param {String} text - 文本
|
||||||
|
* @param {Number} fontSize - 字体大小
|
||||||
|
* @returns {Number} 长度
|
||||||
|
*/
|
||||||
|
function getTextWidth (text, fontSize) {
|
||||||
|
text = String(text)
|
||||||
|
text = text.split('')
|
||||||
|
let width = 0
|
||||||
|
text.forEach(function (item) {
|
||||||
|
if (/[a-zA-Z]/.test(item)) {
|
||||||
|
width += 7
|
||||||
|
} else if (/[0-9]/.test(item)) {
|
||||||
|
width += 5.5
|
||||||
|
} else if (/\./.test(item)) {
|
||||||
|
width += 2.7
|
||||||
|
} else if (/-/.test(item)) {
|
||||||
|
width += 3.25
|
||||||
|
} else if (/[\u4e00-\u9fa5]/.test(item)) { // 中文匹配
|
||||||
|
width += 10
|
||||||
|
} else if (/\(|\)/.test(item)) {
|
||||||
|
width += 3.73
|
||||||
|
} else if (/\s/.test(item)) {
|
||||||
|
width += 2.5
|
||||||
|
} else if (/%/.test(item)) {
|
||||||
|
width += 8
|
||||||
|
} else {
|
||||||
|
width += 10
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return width * fontSize / 10
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getTextWidth
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user