微信小程序自定义DataGrid数据网格组件
前言
提示:在微信小程序官方帮助文档并无表格控件相关内容, 需要自己进使用基本表单组件进行拼装:
这是写两种方式:一、采用纯CSS+View实现, 二、采用Component方式引用
一、实现方式(1)CSS+View?
实现效果:
实现步骤下以下
1、添加CSS
在根目录下的 app.wxss文件增加以下样式
/* 表格开始 */
.table {
border: 0px solid darkgray;
font-size: 12px;
margin:10px 0;
}
.tr {
display: flex;
width: 100%;
justify-content: center;
height: 3rem;
align-items: center;
}
.tr:nth-child(2n+1){
background: #E6F3F9;
}
.tr .sel{
background: #29bbff;
}
.td,
.th {
width:60%;
justify-content: center;
text-align: center;
}
.th {
font-weight: bold;
}
.th:first-child,.td:first-child{
width: 15%;
}
.th:last-child,.td:last-child{
width:20%;
}
/* 表格结束 */
2、WXML文件中使用
在全局app.wxcc中已引入的样式, 则在Page页面可直接使用
<view class="table">
<view class="tr">
<view class="th" style="width:10%">年</view>
<view class="th">分数</view>
<view class="th" style="width:40%">排名</view>
<view class="th" style="width:30%">同分人数</view>
</view>
<block wx:for="{{MyGrades.SameScoreData}}" wx:key="index">
<view class="tr" data-item="{{item}}">
<view class="td left" style="width:10%">{{item.GK_YEAR}}</view>
<view class="td left">{{item.OMOP_MARK}}</view>
<view class="td left" style="width:40%">{{item.OMOP_SORT}}</view>
<view class="td left" style="width:30%">{{item.OMOP_NUMS}}</view>
</view>
</block>
</view>
二、使用组件注册方式
实现效果如下截图
第二种方案优于第一种, 可以进行数据的筛选, 点击行可展示全部等
实现步骤如下
1、创建组件文件及文件夹
在component下创建文件夹DataGrid, 再创建Index Pgae页面 WXML,JS,和CSS文件如下:
a)wxml文件
<scroll-view bindscroll="rightScroll" scroll-x="false" style="width:100%;" class="table table-border">
<!-- 表格头 start -->
<view class="thead {{ border ? 'thead-border' : ''}} header-row-class-name" style="width:{{ scrolWidth }}rpx;">
<view wx:for="{{ headers }}" wx:key="tthis" class="td" style="width:{{ item.width }}{{widthType?widthType:'rpx;'}}">
<picker wx:if="{{item.showpicker}}" range="{{item.pickers}}" bindchange="bindPickerItemChange" data-item="{{item.pickers}}" data-key="{{item.prop}}" range-key="_picker">
{{ item.label }}
<image style="height: 12px;width:12px;" src="{{item.filterImg||'/image/iocn/filter.png'}}"></image>
</picker>
<label wx:else> {{ item.label }}</label>
</view>
</view>
<!-- 表格头 end -->
<!-- 表格体 start -->
<scroll-view scroll-y="false" class="tbody" style="width:{{ scrolWidth }}rpx; height:{{ height ? height : 'auto' }};">
<block wx:for-item="it" wx:for="{{ data }}" wx:key="thisp" wx:for-index="idx" wx:if="{{data.length > 0 &&it.showRow!=-1}}">
<view wx:if="{{ShowRowIndex!=idx||!ShowRowDetail}}" class="tbody-tr {{ stripe ? 'tbody-tr-stripe' : '' }} {{ border ? 'tbody-tr-border' : ''}} row-class-name">
<view wx:for-item="head" wx:for="{{ headers }}" wx:key="thischild" class="td cell-class-name" data-it="{{it}}" data-row="{{idx}}" data-column="{{index+1}}" style="width:{{ headers[index].width}}rpx;color:{{ headers[index].color }};display: flex;align-items: center;justify-content: center;{{headers[index].style }}" bindtap="onRowClick">
{{head["prop"]=="index"?idx+1:it[head["prop"]]}}
</view>
</view>
<!-- <view>dasdf</view> 这里可扩展为同微信统计一样的功能 -->
<view style="background-color: black;color:white;position: relative;width:100%;left: {{ShowRowLeft-150}}px;padding-left: 150px;" wx:if="{{ShowRowIndex==idx&&ShowRowDetail}}" data-it="{{it}}" bindtap="onCloseShowRow">
<view style="margin:5px;" wx:for="{{ headers }}" wx:key="kkkk">
{{ item.label }} : {{item["prop"]=="index"?idx+1:it[item["prop_L"]]||it[item["prop"]]}}
</view>
</view>
</block>
<!-- 列表无数据处理 -->
<block wx:if="{{ data.length === 0 }}">
<view class="no-data">{{ msg }}</view>
</block>
</scroll-view>
<!-- 表格体 end -->
</scroll-view>
b) js文件
Component({
externalClasses: ['header-row-class-name', 'row-class-name', 'cell-class-name'],
/**
* 组件样式隔离
*/
options: {
styleIsolation: "isolated",
multipleSlots: true // 支持多个slot
},
/**
* 组件的属性列表
*/
properties: {
data: {
type: Array,
value: []
},
headers: {
type: Array,
value: []
},
// table的高度, 溢出可滚动
height: {
type: String,
value: 'auto'
},
width: {
type: Number || String,
value: '100%'
},
// 单元格的宽度
tdWidth: {
type: Number,
value: 35
},
outBorder: {
type: Boolean,
value: true
}
,
// 固定表头 thead达到Header的位置时就应该被fixed了
offsetTop: {
type: Number,
value: 150
},
// 斑马条
stripe: {
type: Boolean,
value: false
},
// 是否带有纵向边框
border: {
type: Boolean,
value: false
},
msg: {
type: String,
value: '暂无数据~'
},
ShowRowDetail: {
type: Boolean,
value: true
}
},
/**
* 组件的初始数据
*/
data: {
scrolWidth: '100%',
OpenShowRow: false,
ShowRowLeft: 0
},
/**
* 组件的监听属性
*/
observers: {
// 在 numberA 或者 numberB 被设置时,执行这个函数
'headers': function tableHeader(_headers) {
var reducer = function reducer(accumulator, currentValue) {
return accumulator + Number(currentValue.width);
};
var scrolWidth = _headers.reduce(reducer, 0);
this.setData({
scrolWidth: scrolWidth
});
},
'data': function row(_row) {
this.data.headers.forEach(h => {
let cn = []
if (h.showpicker) {
let pic = { '_picker': '-ALL-' };
cn.push(pic);
this.data.data.forEach(r => {
if (h.showpicker) {
let exirow = false;
cn.forEach(c => {
if (c['_picker'] == r[h.prop]) {
exirow = true;
}
})
if (!exirow) {
r._picker = r[h.prop];
cn.push({ '_picker': r[h.prop] });
}
}
})
h.pickers = cn;
}
})
this.setData({
ShowRowIndex: -1,
headers: this.data.headers,
})
},
'HideRowDetail': function setRowIndex(ri) {
if (ri) {
this.setData({
ShowRowIndex: -1
})
}
}
},
/**
* 组件的方法列表
*/
methods: {
onRowClick: function onRowClick(e) {
let that = this;
that.setData({
ShowRowIndex: e.currentTarget.dataset.row
})
let aares = e.currentTarget.dataset;
this.triggerEvent('rowClick', aares);
},
onCloseShowRow() {
this.setData({
ShowRowIndex: -1
})
},
rightScroll(e) {
this.setData({
ShowRowLeft: e.detail.scrollLeft
})
},
bindPickerItemChange(e) {
let item = e.currentTarget.dataset.item;
let key = e.currentTarget.dataset.key;
let rows = [];
let value = item[e.detail.value]['_picker'];
rows = this.data.data;
let isfilter = false;
//点击第一个ALL显示所有
if (e.detail.value == 0) {
rows.forEach(r => {
r.showRow = 1
})
}
else {
isfilter = true;
rows.forEach(r => {
if (r[key] == value) {
r.showRow = 1
}
else {
r.showRow = -1
}
})
}
//更改过滤器图标为选中 橙色
this.data.headers.forEach(h => {
if (h.showpicker) {
h.filterImg = '/image/iocn/filter.png'
}
if (isfilter) {
if (h.prop == key) {
h.filterImg = '/image/iocn/filtered.png'
}
}
});
this.setData(
{
data: rows,
headers: this.data.headers
}
)
},
onHeaderOrder: function onHeaderOrder_(e) {
let item = e.currentTarget.dataset.item;
this.data.headers.forEach(h => {
h.filterImg = null;
if (h.Order && h.prop == item.prop) {
if (h.orderImg == '/image/iocn/order.png' || h.orderImg == '/image/iocn/order_A.png' || !h.orderImg) {
h.orderImg = '/image/iocn/order_D.png'
h.orderdirection = 'Desc'
}
else {
h.orderImg = '/image/iocn/order_A.png'
h.orderdirection = 'ASC'
}
item = h;
}
else {
h.orderImg = '/image/iocn/order.png'
h.orderdirection = 'ASC'
}
});
this.setData({ headers: this.data.headers })
this.triggerEvent('HeaderOrder', item);
},
onReload: function onReload_(e) {
this.data.headers.forEach(h => {
h.filterImg = null;
h.orderImg = null;
h.orderdirection = null;
});
this.setData({ headers: this.data.headers })
let item = e.currentTarget.dataset.item;
this.triggerEvent('Reload', item);
}
}
});
c) css文件
.table {
position: relative;
font-size: 28rpx;
background: #fff;
border-right:none;
border-radius: 8rpx;
overflow: hidden;
}
.thead{
border-bottom: none;
display: flex;
justify-content: flex-start;
border-top-right-radius: 8rpx;
border-top-left-radius: 8rpx;
overflow: visible;
color: #909399;
border: 1px solid #ebeef5;
box-sizing: border-box;
}
.thead .td {
padding: 20rpx 10rpx;
font-weight: bold;
display: inline-block;
text-align: center;
border-right: 1rpx solid #fff;
}
.thead .td:last-child {
border-right: none;
}
.thead-border .td {
border-right: 1rpx solid #ebeef5;
}
.thead-border .td:last-child {
border-right: none;
}
/* .tr{
display: flex;
white-space:nowrap;
} */
.tbody {
box-sizing: border-box;
font-size: 28rpx;
color: #666;
border: 1px solid #ebeef5;
border-top: none;
border-bottom-left-radius: 8rpx;
border-bottom-right-radius: 8rpx;
}
.tbody-tr {
display: flex;
border-bottom: 1px solid #ebeef5;
}
.tbody-tr:last-child {
border-bottom-left-radius: 8rpx;
border-bottom-right-radius: 8rpx;
}
.tbody-tr-stripe {
background: #fff;
border-bottom: none;
}
.tbody-tr-stripe:nth-child(2n) {
background: #eefffb;
}
.tbody-tr .td {
white-space: wrap;
padding:20rpx 10rpx;
text-align: center;
}
.tbody-tr-border .td {
border-right: 1rpx solid #F6F6F6;
}
.tbody-tr-border .td:last-child {
border-right: none;
}
.no-data {
display: flex;
padding: 50rpx;
color: #666;
justify-content: center;
}
.triangle {
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid red;
}
2、引用与使用
a) 在app.json中注册 刚创建的DtatGrid组件
"usingComponents": {
"table-view": "/component/DataGrid",
}
在页面中使用
- 使用页面 wxml
<table-view
headers="{{tableGrid.tableHeader}}"
data="{{ tableGrid.row }}"
stripe="{{ tableGrid.stripe }}"
border="{{ tableGrid.border }}"
msg="{{tableGrid.msg}}"
height="400px"
rowDetaile="false"
bind:rowClick="thisonRowClick"
></table-view>
</view>
- 使用页面 JS
// pages/test/table.js
Page({
/**
* 页面的初始数据
*/
data: {
tableGrid: {
tableHeader: [
{
prop: 'datetime',
width: 150,
label: '日期',
color: '#55C355'
},
{
prop: 'sign_in_time',
width: 152,
label: '上班时间'
},
{
prop: 'sign_out_time',
width: 152,
label: '下班时间'
},
{
prop: 'work_hour',
width: 110,
label: '工时'
},
{
prop: 'status',
width: 210,
label: '状态'
}
],
stripe: true,
border: false,
outBorder: true,
row: [
{
"id": 1,
"status": '正常',
"datetime": "04-01",
"sign_in_time": '09:30:00',
"sign_out_time": '18:30:00',
"work_hour": 8,
}, {
"id": 2,
"status": '迟到',
"datetime": "04-02",
"sign_in_time": '10:30:00',
"sign_out_time": '18:30:00',
"work_hour": 7,
}, {
"id": 29,
"status": '正常',
"datetime": "04-03",
"sign_in_time": '09:30:00',
"sign_out_time": '18:30:00',
"work_hour": 8,
}, {
"id": 318,
"status": '休息日',
"datetime": "04-04",
"sign_in_time": '',
"sign_out_time": '',
"work_hour": '',
},
{
"id": 1,
"status": '正常',
"datetime": "04-01",
"sign_in_time": '09:30:00',
"sign_out_time": '18:30:00',
"work_hour": 8,
}, {
"id": 2,
"status": '迟到',
"datetime": "04-02",
"sign_in_time": '10:30:00',
"sign_out_time": '18:30:00',
"work_hour": 7,
}, {
"id": 29,
"status": '正常',
"datetime": "04-03",
"sign_in_time": '09:30:00',
"sign_out_time": '18:30:00',
"work_hour": 8,
}, {
"id": 318,
"status": '休息日',
"datetime": "04-04",
"sign_in_time": '',
"sign_out_time": '',
"work_hour": '',
},
{
"id": 1,
"status": '正常',
"datetime": "04-01",
"sign_in_time": '09:30:00',
"sign_out_time": '18:30:00',
"work_hour": 8,
}, {
"id": 2,
"status": '迟到',
"datetime": "04-02",
"sign_in_time": '10:30:00',
"sign_out_time": '18:30:00',
"work_hour": 7,
}, {
"id": 29,
"status": '正常',
"datetime": "04-03",
"sign_in_time": '09:30:00',
"sign_out_time": '18:30:00',
"work_hour": 8,
}, {
"id": 318,
"status": '休息日',
"datetime": "04-04",
"sign_in_time": '',
"sign_out_time": '',
"work_hour": '',
},
{
"id": 1,
"status": '正常',
"datetime": "04-01",
"sign_in_time": '09:30:00',
"sign_out_time": '18:30:00',
"work_hour": 8,
}, {
"id": 2,
"status": '迟到',
"datetime": "04-02",
"sign_in_time": '10:30:00',
"sign_out_time": '18:30:00',
"work_hour": 7,
}, {
"id": 29,
"status": '正常',
"datetime": "04-03",
"sign_in_time": '09:30:00',
"sign_out_time": '18:30:00',
"work_hour": 8,
}, {
"id": 318,
"status": '休息日',
"datetime": "04-04",
"sign_in_time": '',
"sign_out_time": '',
"work_hour": '',
},
{
"id": 1,
"status": '正常',
"datetime": "04-01",
"sign_in_time": '09:30:00',
"sign_out_time": '18:30:00',
"work_hour": 8,
}, {
"id": 2,
"status": '迟到',
"datetime": "04-02",
"sign_in_time": '10:30:00',
"sign_out_time": '18:30:00',
"work_hour": 7,
}, {
"id": 29,
"status": '正常',
"datetime": "04-03",
"sign_in_time": '09:30:00',
"sign_out_time": '18:30:00',
"work_hour": 8,
}, {
"id": 318,
"status": '休息日',
"datetime": "04-04",
"sign_in_time": '',
"sign_out_time": '',
"work_hour": '',
}, {
"id": 319,
"status": '正常',
"datetime": "04-05",
"sign_in_time": '09:30:00',
"sign_out_time": '18:30:00',
"work_hour": 8,
}
],
msg: '暂无数据'
},
ShowRowIndex:2
},
thisonRowClick: function (e,that) {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
})
增加排序效果
以 上是个人开发中实现, 如各位码友有更好的实现方式请留言和拍砖。