是一个成熟的程序猿了,要懂得自己造轮子(uniApp的插件市场找了一遍都没发现符合需求的插件,没有办法了,只能自己动手造个轮子)。本文旨在复盘+记录。
使用场景: uniApp 、 移动端(兼容小程序、App、H5)
根据需求整理下具体功能:
config.js
并在其中设定默认值,起到字段说明及默认状态管理的作用iconfont
图标库由于使用环境限制:uniApp
实现的表格相关组件比较简单,对于非H5环境限制比较大(例如不能设置rowspan
,colspan
),使用起来也显得比较麻烦,达不到项目的需求,最后决定自己造个轮子。
主要难点在于 多级表头的处理,怎么样做到根据数据来驱动显示。刚开始是打算按 html table
的方式实现,开发过程中遇到的问题比较多,首先数据处理比较麻烦,要计算有多少行、每行单元格的colspan
、rowspan
。而且没有td, tr
等组件,需要自己额外实现。
columns
的数据是树形的,如下
columns = [
{ "title": "区域", "dataIndex": "区域" },
{
"title": "广州一区",
"children": [
{ "title": "销售", "dataIndex": "广州一区销售"},
{ "title": "计划销售", "dataIndex": "广州一区计划销售" },
{ "title": "达成", "dataIndex": "广州一区达成"}
]
},
// ...
]
似乎用 flex
布局就能实现了
每个格子设置垂直居中,如果存在children
则遍历递归渲染,由于需要递归调用渲染,把递归的部分在分出来一个组件:titleColumn
。先贴个代码(代码已发布到社区,有兴趣可以去看看 传送门):
titleColumn.vue
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V12RXmac-1654849182530)(https://tva1.sinaimg.cn/large/005YCUMZgy1h0vlo8lrmaj30qj0lhgtn.jpg)]
这里有个坑 :
在正常的vue
中递归组件是不需要引入的,在 uniApp
则需要。
// titleColumn.vue
import titleColumn from "./title-column.vue"
样式方面不展开了,不好写。直接看看效果(自我感觉很好,哈哈哈):
这里先要处理下columns
的数据(要考虑到多级表头情况),根据上面的columns
,得到实际要渲染的列:
dataIndexs
,用于保存需要实际渲染的列数据columns
拿到最终的 叶子节点 并保存起来。// 根据Column 获取body中实际渲染的列
fmtColumns(list) {
// 保存叶子节点
this.dataIndexs = []
if (!list || !list.length) return
// 获取实际行
this.columnsDeal(list)
},
//
columnsDeal(list, level = 0) {
list.forEach(item => {
let { children, ...res } = item
if (children && children.length) {
this.columnsDeal(children, level + 1)
} else {
this.dataIndexs.push({ ...res })
}
})
},
接下来就是处理列表数据中的树形结构了。
先看看数据结构 tableData
:
tableData = [
{
"key": 1,
"区域": "广州",
"销售": 100,
"计划销售": 200,
"达成": "50.0%",
"达成排名": 1,
"GroupIndex": 1,
"GroupLayer": 1,
"GroupKey": "广州",
"children": [{
"key": 11,
"区域": "广州一区",
"小区": "广州一区",
"销售": 60,
"计划销售": 120,
"达成": "50.0%",
"达成排名": 1,
children: [{
"key": 111,
"区域": "广州一区1",
"小区": "广州一区1",
"销售": 60,
"计划销售": 120,
"达成": "50.0%",
"达成排名": 1,
}]
},
{ "key": 12, "区域": "广州二区", "小区": "广州二区", "销售": 40, "计划销售": 80, "达成": "50.0%", "达成排名": 1 },
],
},
]
树形的结构,key
是唯一值。
有想过使用递归组件的方式实现,但是考虑到会涉及到展开、收起的操作。也是比较麻烦。
最终的方案是把数据扁平化处理,为每条数据添加 层级、是否子数据、父级ID 等属性。并通过一个数组变量来记录展开的行,并以此控制子数据的显示与否。处理后的数据存放在 dataList
中
扁平化处理函数:
// 递归处理数据,tree => Array
listFmt(list, level, parentIds = []) {
return list.reduce((ls, item) => {
let { children, ...res } = item
// 错误提示
if (res[this.idKey] === undefined || !res[this.idKey] === null) {
// console.error(`tableData 数据中存在 [idKey] 属性不存在数据,请检查`)
}
let nowItem = {
...res,
level,
hasChildren: children && children.length,
parentIds,
children,
[this.idKey]: res[this.idKey] && res[this.idKey].toString()
}
ls.push(nowItem)
if (children && children.length) {
this.isTree = true
ls = ls.concat(this.listFmt(children, level + 1, [...parentIds, nowItem[this.idKey]]))
}
return ls
}, [])
},
数据处理完可以渲染了,
需要嵌套两层遍历:
第一层 遍历 dataList
得到行
第二层 遍历 dataIndexs
得到列
最终完成渲染:
使用css
属性:position: sticky
实现。粘性定位元素(stickily positioned element)。大家都是成熟的前端程序猿啦~~,就不具体介绍了。说说一些需要注意的细节:
兼容性
uniapp中小程序模式、App模式是支持的!!!
限制
设置了position:sticky
之后必现指定top left right bottom
其中任一,才会生效。不设置的话表现和相对定位相同。top
和 bottom
或者 left
和 right
同时设置的情况下,top
、 left
的优先级高。
设定为 position:sticky
元素的任意父节点的 overflow
属性必须是visible
,否则 不会生效 (都不能滚动还能咋办)。
造个轮子不难,造个好用的轮子不易。
涉及一些布局上和css部分的东西在文章中不好表达,不细说了,有兴趣的可以拉代码看看。
传送门
开发过程中也遇到过不少的问题,都是一路修修补补过来,前期没有构思好会导致后面的开发磕磕碰碰(刚开始模块、参数没有划分好,整个东西逻辑都比较乱,后面停下来从新思考调整了,有种豁然开朗的痛快)
搬砖去了~