├── .DS_Store ├── README.md ├── app.js ├── app.json ├── app.wxss ├── assets └── date_48.png ├── pages └── index │ ├── index.js │ ├── index.json │ ├── index.wxml │ ├── index.wxss │ └── mockdata.js ├── project.config.json └── utils └── util.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cachecats/wechat-table/ddeff64d041f8152d7ca707e30bb5b94843fe30f/.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wechat-table 2 | 微信小程序跨行跨列多列表格实现 3 | 4 | 5 | 效果 6 | 7 | ![](https://user-gold-cdn.xitu.io/2018/8/3/164ff1a7971a340d?w=640&h=1118&f=png&s=119536) 8 | 9 | 如图,这是个列数不确定,有的单元格还要跨行跨列的复杂表格。 10 | 11 | 这里暂时最多支持4列,列数再多就放不下了。 12 | 13 | ## 实现原理 14 | 实现原理比较简单,通过多个嵌套的循环将数据取出。 15 | 16 | 上面的例子中,最外层一共有4行:基础工资,加班工资,岗位工资,合计。第一层数据的 `name` 展示为第一列,如果每组数据有 `children`,取出 `children` 展示为第二列… 如果 `children` 长度为0,则直接显示工资数额。 17 | 18 | 这样一层一层把数据剖开,就做到了上面的效果。 19 | 20 | ## 数据格式 21 | 模拟的数据如下,如果是最后一层 `value` 值为工资数额,否则值为 `null`。嵌套的数据在 `children` 中。 22 | 23 | 24 | ``` 25 | // 模拟的数据 26 | export default { 27 | status: 200, 28 | code: "ok", 29 | data: [{ 30 | id: 'table001', 31 | name: '基础工资', 32 | value: null, 33 | children: [{ 34 | id: 'table0011', 35 | name: '基本工资', 36 | value: 3000.0, 37 | children: [] 38 | }, 39 | { 40 | id: 'table0012', 41 | name: '绩效工资', 42 | value: 1200.0, 43 | children: [] 44 | }, 45 | { 46 | id: 'table0013', 47 | name: '基本工作量', 48 | value: null, 49 | children: [{ 50 | id: 'table00131', 51 | name: '课时工资', 52 | value: 800.0, 53 | children: [] 54 | }, 55 | { 56 | id: 'table00132', 57 | name: '超课时工资', 58 | value: 200.0, 59 | children: [] 60 | }, 61 | ] 62 | }, 63 | ] 64 | }, 65 | { 66 | id: 'table002', 67 | name: '加班工资', 68 | value: null, 69 | children: [{ 70 | id: 'table0021', 71 | name: '工作日加班', 72 | value: 1000.0, 73 | children: [] 74 | }, 75 | { 76 | id: 'table0022', 77 | name: '周末加班', 78 | value: 600.0, 79 | children: [] 80 | }, 81 | ] 82 | }, 83 | { 84 | id: 'table003', 85 | name: '岗位工资', 86 | value: 1800.0, 87 | children: [ 88 | 89 | ] 90 | }, 91 | { 92 | id: 'table004', 93 | name: '合计', 94 | value: 8600.0, 95 | children: [] 96 | }, 97 | ] 98 | } 99 | ``` 100 | 博客地址: 101 | [小程序跨行跨列多列复杂表格实现](https://juejin.im/post/5b64117e6fb9a04fbb1140db) 102 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | //app.js 2 | App({ 3 | onLaunch: function () { 4 | // 展示本地存储能力 5 | var logs = wx.getStorageSync('logs') || [] 6 | logs.unshift(Date.now()) 7 | wx.setStorageSync('logs', logs) 8 | 9 | // 登录 10 | wx.login({ 11 | success: res => { 12 | // 发送 res.code 到后台换取 openId, sessionKey, unionId 13 | } 14 | }) 15 | // 获取用户信息 16 | wx.getSetting({ 17 | success: res => { 18 | if (res.authSetting['scope.userInfo']) { 19 | // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框 20 | wx.getUserInfo({ 21 | success: res => { 22 | // 可以将 res 发送给后台解码出 unionId 23 | this.globalData.userInfo = res.userInfo 24 | 25 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回 26 | // 所以此处加入 callback 以防止这种情况 27 | if (this.userInfoReadyCallback) { 28 | this.userInfoReadyCallback(res) 29 | } 30 | } 31 | }) 32 | } 33 | } 34 | }) 35 | }, 36 | globalData: { 37 | userInfo: null 38 | } 39 | }) -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages":[ 3 | "pages/index/index" 4 | ], 5 | "window":{ 6 | "backgroundTextStyle":"light", 7 | "navigationBarBackgroundColor": "#fff", 8 | "navigationBarTitleText": "WeChat", 9 | "navigationBarTextStyle":"black" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app.wxss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | .container { 3 | width: 100%; 4 | height: 100%; 5 | display: flex; 6 | flex-direction: column; 7 | box-sizing: border-box; 8 | } 9 | -------------------------------------------------------------------------------- /assets/date_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cachecats/wechat-table/ddeff64d041f8152d7ca707e30bb5b94843fe30f/assets/date_48.png -------------------------------------------------------------------------------- /pages/index/index.js: -------------------------------------------------------------------------------- 1 | import MockData from './mockdata.js' 2 | import { 3 | formatTime 4 | } from '../../utils/util.js' 5 | 6 | Page({ 7 | data: { 8 | currentDate: '', 9 | username: '张三', 10 | list: '' 11 | }, 12 | 13 | onLoad: function () { 14 | wx.setNavigationBarTitle({ 15 | title: '工资查询', 16 | }) 17 | //设置当前年月 18 | this.setCurrentDate() 19 | this._getSalary() 20 | }, 21 | 22 | setCurrentDate(){ 23 | //获取当前年月 24 | let date = new Date() 25 | let fmtDate = formatTime(date).substring(0, 7) 26 | this.setData({ 27 | currentDate: fmtDate, 28 | }) 29 | console.log(fmtDate) 30 | }, 31 | 32 | //日期变化回调 33 | dateChange(res) { 34 | console.log(res) 35 | let value = res.detail.value 36 | this.setData({ 37 | currentDate: value 38 | }) 39 | //请求数据 40 | this._getSalary() 41 | }, 42 | 43 | //模拟请求数据 44 | _getSalary(){ 45 | this.setData({ 46 | list: MockData.data 47 | }) 48 | } 49 | 50 | }) 51 | -------------------------------------------------------------------------------- /pages/index/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{currentDate}} 6 | 7 | 8 | 9 | {{username + " 老师 " + currentDate + " 月工资表"}} 10 | 单位:元 11 | 12 | 13 | 本月暂无工资数据 14 | 15 | {{item.name}} 16 | 17 | {{item.value}} 18 | 19 | {{item2.name}} 20 | 21 | {{item2.value}} 22 | 23 | {{item3.name}} 24 | 25 | {{item3.value}} 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | box-sizing: border-box; 6 | background: white; 7 | } 8 | 9 | .picker { 10 | width: 100%; 11 | } 12 | 13 | .date-text { 14 | font-size: 32rpx; 15 | padding: 20rpx 10rpx; 16 | text-align: center; 17 | } 18 | 19 | .title-wrapper { 20 | display: flex; 21 | width: 100%; 22 | justify-content: center; 23 | align-items: center; 24 | padding: 20rpx; 25 | box-sizing: border-box; 26 | } 27 | 28 | .title { 29 | flex: 1; 30 | font-size: 34rpx; 31 | text-align: center; 32 | font-weight: 700; 33 | color: #09bb07; 34 | } 35 | 36 | .yuan { 37 | font-size: 24rpx; 38 | color: #09bb07; 39 | } 40 | 41 | .table-wrapper { 42 | width: 100%; 43 | display: flex; 44 | flex-direction: column; 45 | border-top: 1rpx solid #DCDFE6; 46 | } 47 | 48 | .row1 { 49 | width: 100%; 50 | display: flex; 51 | flex-direction: row; 52 | align-items: center; 53 | font-size: 32rpx; 54 | box-sizing: border-box; 55 | border-bottom: 1rpx solid #DCDFE6; 56 | 57 | } 58 | 59 | .text { 60 | flex: 1; 61 | padding: 10rpx; 62 | line-height: 60rpx; 63 | height: 60rpx; 64 | } 65 | 66 | .column2-wrapper { 67 | display: flex; 68 | flex-direction: column; 69 | flex: 3; 70 | justify-content: center; 71 | border-left: 1rpx solid #DCDFE6; 72 | } 73 | 74 | .column2 { 75 | display: flex; 76 | flex: 1; 77 | align-items: center; 78 | border-bottom: 1rpx solid #DCDFE6; 79 | } 80 | 81 | .column2:last-child{ 82 | border-bottom: none; 83 | } 84 | 85 | .column3-wrapper { 86 | display: flex; 87 | flex-direction: column; 88 | flex: 2; 89 | justify-content: center; 90 | border-left: 1rpx solid #DCDFE6; 91 | } 92 | 93 | .column3 { 94 | display: flex; 95 | flex: 1; 96 | align-items: center; 97 | border-bottom: 1rpx solid #DCDFE6; 98 | } 99 | 100 | .column3:last-child{ 101 | border-bottom: none; 102 | } 103 | 104 | .column-value{ 105 | display: flex; 106 | align-self: flex-end; 107 | margin-right: 10rpx; 108 | padding: 10rpx; 109 | line-height: 60rpx; 110 | height: 60rpx; 111 | } 112 | 113 | .column4-wrapper{ 114 | display: flex; 115 | flex-direction: column; 116 | flex: 1; 117 | justify-content: center; 118 | border-left: 1rpx solid #DCDFE6; 119 | } 120 | 121 | .picker-content{ 122 | display: flex; 123 | width: 100%; 124 | justify-content: center; 125 | align-items: center; 126 | border-bottom: 1rpx solid rgba(7, 17, 27, 0.1); 127 | } 128 | 129 | .date-icon{ 130 | width: 80rpx; 131 | height: 80rpx; 132 | } 133 | 134 | .nodata{ 135 | width: 100%; 136 | text-align: center; 137 | font-size: 32rpx; 138 | color: #666; 139 | padding: 20rpx; 140 | } -------------------------------------------------------------------------------- /pages/index/mockdata.js: -------------------------------------------------------------------------------- 1 | // 模拟的数据 2 | export default { 3 | status: 200, 4 | code: "ok", 5 | data: [{ 6 | id: 'table001', 7 | name: '基础工资', 8 | value: null, 9 | children: [{ 10 | id: 'table0011', 11 | name: '基本工资', 12 | value: 3000.0, 13 | children: [] 14 | }, 15 | { 16 | id: 'table0012', 17 | name: '绩效工资', 18 | value: 1200.0, 19 | children: [] 20 | }, 21 | { 22 | id: 'table0013', 23 | name: '基本工作量', 24 | value: null, 25 | children: [{ 26 | id: 'table00131', 27 | name: '课时工资', 28 | value: 800.0, 29 | children: [] 30 | }, 31 | { 32 | id: 'table00132', 33 | name: '超课时工资', 34 | value: 200.0, 35 | children: [] 36 | }, 37 | ] 38 | }, 39 | ] 40 | }, 41 | { 42 | id: 'table002', 43 | name: '加班工资', 44 | value: null, 45 | children: [{ 46 | id: 'table0021', 47 | name: '工作日加班', 48 | value: 1000.0, 49 | children: [] 50 | }, 51 | { 52 | id: 'table0022', 53 | name: '周末加班', 54 | value: 600.0, 55 | children: [] 56 | }, 57 | ] 58 | }, 59 | { 60 | id: 'table003', 61 | name: '岗位工资', 62 | value: 1800.0, 63 | children: [ 64 | 65 | ] 66 | }, 67 | { 68 | id: 'table004', 69 | name: '合计', 70 | value: 8600.0, 71 | children: [] 72 | }, 73 | ] 74 | } -------------------------------------------------------------------------------- /project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目配置文件。", 3 | "packOptions": { 4 | "ignore": [] 5 | }, 6 | "setting": { 7 | "urlCheck": true, 8 | "es6": true, 9 | "postcss": true, 10 | "minified": true, 11 | "newFeature": true 12 | }, 13 | "compileType": "miniprogram", 14 | "libVersion": "2.2.1", 15 | "appid": "wx5de271f06bea2472", 16 | "projectname": "wechat-table", 17 | "isGameTourist": false, 18 | "condition": { 19 | "search": { 20 | "current": -1, 21 | "list": [] 22 | }, 23 | "conversation": { 24 | "current": -1, 25 | "list": [] 26 | }, 27 | "game": { 28 | "currentL": -1, 29 | "list": [] 30 | }, 31 | "miniprogram": { 32 | "current": -1, 33 | "list": [] 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /utils/util.js: -------------------------------------------------------------------------------- 1 | const formatTime = date => { 2 | const year = date.getFullYear() 3 | const month = date.getMonth() + 1 4 | const day = date.getDate() 5 | const hour = date.getHours() 6 | const minute = date.getMinutes() 7 | const second = date.getSeconds() 8 | 9 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':') 10 | } 11 | 12 | const formatNumber = n => { 13 | n = n.toString() 14 | return n[1] ? n : '0' + n 15 | } 16 | 17 | module.exports = { 18 | formatTime: formatTime 19 | } 20 | --------------------------------------------------------------------------------