复杂表格设计数据格式

1. 表头设计

原理:
和多叉树的原理类似,参考了它的展示形式。


多叉树.png

表头说明:
如果没有孩子节点就只返回如下一个字段:

  • name :名字

如果有孩子节点,就把数据加在children里面,层层嵌套,返回字段如下:

  • name :名字
  • children : 孩子节点

数据结构格式,参考如下代码:

headerData:[
            {
                name: '地区',
            },
            {
                name: '总数据',
                children: [
                    {
                        name: '数据1',
                        children: [
                            {
                                name: '数据11',
                                children: [
                                    {
                                        name: '数据111',
                                    },
                                    {
                                        name: '数据112',
                                    }
                                ]
                            },
                            {
                                name: '数据12',
                                children: [
                                    {
                                        name: '数据121',
                                    },
                                    {
                                        name: '数据122',
                                    }
                                ]
                            },
                            {
                                name: '数据13',
                                children: [
                                    {
                                        name: '数据131',
                                    },
                                    {
                                        name: '数据132',
                                    }
                                ]
                            },
                            {
                                name: '数据14',
                            },

                        ]
                    }
                ]
            }
        ];

表头的宽高方面,前端计算,后端不用管,按照如下格式返回数据即可。

2. 表格数据格式

每一项按照表头展示的顺序返回,通过数组的形式
返回一个参数:

  • bodyData:总数据

数据结构格式参考代码如下:

bodyData:[
        ["地区最先","数据111","数据112","数据121","数据122","数据131","数据132","数据14"],
        ["地区","数据111","数据112","数据121","数据122","数据131","数据132","数据14"],
        ["地区","数据111","数据112","数据121","数据122","数据131","数据132","数据14"],
        ["地区","数据111","数据112","数据121","数据122","数据131","数据132","数据14"],
        ["地区","数据111","数据112","数据121","数据122","数据131","数据132","数据14"],
        ["地区","数据111","数据112","数据121","数据122","数据131","数据132","数据14"],
        ["地区","数据111","数据112","数据121","数据122","数据131","数据132","数据14"],
        ["地区","数据111","数据112","数据121","数据122","数据131","数据132","数据14"], 
        ["地区","数据111","数据112","数据121","数据122","数据131","数据132","数据14"], 
        ["地区最后","数据111","数据112","数据121","数据122","数据131","数据132","数据14"], 
    ]

3. 效果

如上表头与表格数据代码生成的效果如图:


效果.png

4. 代码

语法高亮用到 codemirror 插件

/**
 * 递归遍历 格式化数组
 * @param { Array } paramArr 目标数组
 * @param { Number } level 层级
 */
export function formatArray(paramArr, level) {
  let levelFirst = Number(level)
  const arr = []
  let childArr = []
  for (let i = 0; i < paramArr.length; i++) {
      let obj = {}
      for (let j in paramArr[i]) {
          if (j != 'children') {
              obj[j] = paramArr[i][j]
          }
          obj['level'] = levelFirst
          obj['width'] = getLeafCountTree(paramArr[i])
          if (!paramArr[i].children) {
              obj['childrenNumber'] = 0
              // LeafNode: 叶子节点就是树中最底段的节点
              // obj['isLeafNode'] = true
          } else {
              // obj['isLeafNode'] = false
              obj['childrenNumber'] = paramArr[i].children.length
          }
      }
      arr.push(obj)
      if (paramArr[i].children) {
          let lev = Number(levelFirst) + 1
          childArr = childArr.concat(formatArray(paramArr[i].children, lev));
      }
  }
  let endArr = arr.concat(childArr)
  return endArr
}
/**
 * 获取 节点的所有叶子节点个数
 * @param {Object} json Object对象
 */
export function getLeafCountTree(json) {
  if(!json.children){
      return 1;
  }else{
      var leafCount = 0;
      for(var i = 0 ; i < json.children.length ; i++){
          leafCount = leafCount + getLeafCountTree(json.children[i]);
      }
      return leafCount;
  }
}

// json对对象字符串的格式化,美化
export function  jsonFromat (text_value){
    if(text_value == ""){
       alert("不能为空");  
       return false;
    } else {
          var json=eval('(' + text_value + ')');
          text_value=JSON.stringify(json);
          var res="";
          for(var i=0,j=0,k=0,ii,ele;i<text_value.length;i++)
          {//k:缩进,j:""个数
              ele=text_value.charAt(i);
              if(j%2==0&&ele=="}")
              {
                  k--;                
                  for(ii=0;ii<k;ii++) ele="    "+ele;
                  ele="\n"+ele;
              }
              else if(j%2==0&&ele=="{")
              {
                  ele+="\n";
                  k++;     
                  for(ii=0;ii<k;ii++) ele+="    ";
              }
              else if(j%2==0&&ele==",")
              {
                  ele+="\n";
                  for(ii=0;ii<k;ii++) ele+="    ";
              }
              else if(ele=="\"") j++;
              res+=ele;        
          }
          return res
    }
  }

<template>
    <div class="pages-tables " id="pages-tables">
        <div class="textarea">
            <h1 class="title">复杂表头 json 数据格式验证:</h1>
            <p class="message">表头展示效果如下:</p>
            <div class="rolling-table auto-table" ref="tableBox" :style="{height: maxHeight + 'px'}">
                <table class="table" id="table" cellpadding="0" cellspacing="0" ref="rollingTable">
                    <thead ref="thead">
                        <tr v-for="(x,i) in headerList" :key="i">
                            <th class="rows " :class="{'cross': index == 0 && i == 0}" v-for="(l,index) in x" :key="index" :colspan="l.width" :rowspan="l.height">{{l.name}}</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr v-for="(b,i) in bodyList" :key="i + 'a'">
                            <template v-for="(x, index) in b">
                                <td :class="{'cols':  index == 0 }" :key="index + 'b'">
                                    {{ x | valueFromt }}
                                </td>
                            </template>
                        </tr>
                        <tr></tr>
                    </tbody>
                </table>
            </div>
            <p class="message">提示:输入 json 覆盖原来的即可,且有验证 json 格式是否正确的功能</p>
            <div class="control">
                <!-- <div class="content fl">
                    <p>select a theme:
                        <select @change="selectTheme" v-model="selected">
                            <option>default</option>
                            <option>night</option>
                            <option>monokai</option>
                            <option>neat</option>
                            <option>elegant</option>
                            <option>cobalt</option>
                            <option>eclipse</option>
                            <option>rubyblue</option>
                            <option>lesser-dark</option>
                            <option>xq-dark</option>
                        </select>
                    </p>
                </div>
                <div class="content fl ml20">
                    <p>select the editor language:
                        <select @change="selectMode" v-model="mode">
                            <option>javascript</option>
                            <option>php</option>
                            <option>python</option>
                            <option>vue</option>
                            <option>xml</option>
                            <option>sql</option>
                            <option>http</option>
                            <option>css</option>
                            <option>sass</option>
                            <option>jsx</option>
                            <option>django</option>
                        </select>
                    </p>
                </div>
                <div class="content fl ml20">
                    <p>select keyMap:
                        <select @change="selectKeyMap" v-model="keyMap">
                            <option>default</option>
                            <option>emacs</option>
                            <option>sublime</option>
                            <option>vim</option>
                        </select>
                    </p>
                </div> -->
                <div class="fl ml20 mt20 mb10 submit">
                    <mt-button type="primary"  size="small" @click.native="setInputValue">提交</mt-button>
                </div>
                <div class="clearfix"></div>
            </div>
            <textarea id="code" name="code">
                [
                    {
                        name: '地区',
                    },
                    {
                        name: '总数据',
                        children: [
                            {
                                name: '数据1',
                                children: [
                                    {
                                        name: '数据11',
                                        children: [{
                                            name: '数据111',
                                        },
                                        {
                                            name: '数据112',
                                        }
                                        ]
                                    },
                                    {
                                        name: '数据12',
                                        children: [{
                                            name: '数据121',
                                        },
                                        {
                                            name: '数据122',
                                        }
                                        ]
                                    },
                                    {
                                        name: '数据13',
                                        children: [{
                                            name: '数据131',
                                        },
                                        {
                                            name: '数据132',
                                        }
                                        ]
                                    },
                                    {
                                        name: '数据14',
                                    },
                                    {
                                        name: '数据15',
                                    },
                                    {
                                        name: '数据16数据16数据16数据16',
                                    },
                                    {
                                        name: '数据17',
                                    },
                                ]
                            }
                        ]
                    }
                ];
            </textarea>
        </div>

    </div>
</template>
<script>

// 说明这个 demo 是给 pc 端用的,单位要为 px
import { formatArray, getLeafCountTree, jsonFromat } from "libs/common/common";
import { Button, MessageBox } from 'mint-ui';
import * as CodeMirror from 'codemirror/lib/codemirror'
// 根据设置的主题,引入相应的主题包,主题包存储在theme下,使用其他主题包时设置option中theme为对应主题
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/monokai.css'
import 'codemirror/theme/neat.css'
import 'codemirror/theme/elegant.css'
import 'codemirror/theme/night.css'
import 'codemirror/theme/cobalt.css'
import 'codemirror/theme/eclipse.css'
import 'codemirror/theme/rubyblue.css'
import 'codemirror/theme/xq-dark.css'
// styleActiveLine: 设置光标所在行高亮true/false,需引入工具包:
import 'codemirror/addon/selection/active-line'
// 根据设置的编辑器语言,引入相应工具包,以下为常用语言包
import 'codemirror/mode/javascript/javascript'
import 'codemirror/mode/go/go'
import 'codemirror/mode/php/php'
import 'codemirror/mode/python/python'
import 'codemirror/mode/http/http'
import 'codemirror/mode/sql/sql'
import 'codemirror/mode/vue/vue'
import 'codemirror/mode/xml/xml'
import 'codemirror/mode/css/css'
import 'codemirror/mode/sass/sass'
import 'codemirror/mode/jsx/jsx'
import 'codemirror/mode/django/django'
// keyMap:快捷键,default使用默认快捷键,除此之外包括emacs,sublime,vim快捷键,使用需引入工具
import 'codemirror/keymap/sublime.js'
import 'codemirror/keymap/emacs.js'
import 'codemirror/keymap/vim.js'
// extraKeys 快捷键,例如 {“Ctrl-Q”: “autocomplete”}:自动补全使用需要引入工具
import 'codemirror/addon/hint/show-hint'
import 'codemirror/addon/hint/javascript-hint'
import 'codemirror/addon/hint/sql-hint'
import 'codemirror/addon/hint/html-hint'
import 'codemirror/addon/hint/xml-hint'
import 'codemirror/addon/hint/anyword-hint'
import 'codemirror/addon/hint/css-hint'
import 'codemirror/addon/hint/show-hint'

export default {
    data() {
        return {
            mapArray: [],
            keyMap: 'default',
            mode: 'javascript',
            editor: '',
            selected: 'monokai',
            header: '',
            maxHeight: '100%',
            theadHeight: '100%',
            offsetHeight: 0,
            scroll: {
                scroller: null
            },
            headerList: [],
            bodyList: [],

        }
    },
    filters: {
        valueFromt: function (value) {
            let realValue = ''
            if (!value) return ''
            value = value.toString()
            if (value.length > 20) {
                realValue = value.slice(0, 15) + '...'
            } else {
                realValue = value
            }
            return realValue
        },
    },
    methods: {
        selectKeyMap(){
            this.editor.addKeyMap(this.keyMap)  
        },
        selectMode(){
            this.editor.setOption("mode",this.mode)   
        },
        selectTheme() {
            this.editor.setOption("theme", this.selected);
        },
        setInputValue() {
            this.header = this.editor.getValue();
            if(this.header){
                this.change()
            }
        },
        change() {
            try {
                const newData = formatArray(eval(this.header), 0)
                let maxLevel = newData[newData.length - 1].level
                this.setHeight(newData, maxLevel + 1)
                this.arayLayered(newData, maxLevel)
                this.headerList = this.arayLayered(newData, maxLevel)
            } catch (e) {
                console.log('e:', e)
                MessageBox('提示', '请检查 json 格式是否正确!!!');
            }
        },
        setHeight(arr, maxLevel) {
            // console.log("setHeight maxLevel", maxLevel)
            for (let i = maxLevel; i >= 0; i--) {
                for (let j = 0; j < arr.length; j++) {
                    // 设置高
                    if (arr[j].childrenNumber) {
                        arr[j].height = 1
                    } else {
                        arr[j].height = maxLevel - arr[j].level
                    }
                }
            }
            return arr
        },
        arayLayered(arr, maxLevel) {
            let returnArr = []
            for (let i = 0; i <= maxLevel; i++) {
                let arrLevel = []
                for (let j = 0; j < arr.length; j++) {
                    if (arr[j].level == i) {
                        arrLevel.push(arr[j])
                    }
                }
                returnArr[i] = arrLevel
            }
            return returnArr
        }
    },
    mounted() {
        let bodyListA = [
            ["地区最先", "数据111", "数据112", "数据121", "数据122", "数据131", "数据132", "数据14"],
            ["地区", "数据111", "数据112", "数据121", "数据122", "数据131", "数据132", "数据14"],
            ["地区", "数据111", "数据112", "数据121", "数据122", "数据131", "数据132", "数据14"],
            ["地区", "数据111", "数据112", "数据121", "数据122", "数据131", "数据132", "数据14"],
            ["地区", "数据111", "数据112", "数据121", "数据122", "数据131", "数据132", "数据14"],
            ["地区", "数据111", "数据112", "数据121", "数据122", "数据131", "数据132", "数据14"],
            ["地区", "数据111", "数据112", "数据121", "数据122", "数据131", "数据132", "数据14"],
            ["地区", "数据111", "数据112", "数据121", "数据122", "数据131", "数据132", "数据14"],
            ["地区", "数据111", "数据112", "数据121", "数据122", "数据131", "数据132", "数据14"],
            ["地区最后", "数据111", "数据112", "数据121", "数据122", "数据131", "数据132", "数据14"],
        ]

        const data = [
            {
                name: '地区',
            },
            {
                name: '总数据',
                children: [
                    {
                        name: '数据1',
                        children: [
                            {
                                name: '数据11',
                                children: [{
                                    name: '数据111',
                                },
                                {
                                    name: '数据112',
                                }
                                ]
                            },
                            {
                                name: '数据12',
                                children: [{
                                    name: '数据121',
                                },
                                {
                                    name: '数据122',
                                }
                                ]
                            },
                            {
                                name: '数据13',
                                children: [{
                                    name: '数据131',
                                },
                                {
                                    name: '数据132',
                                }
                                ]
                            },
                            {
                                name: '数据14',
                            },
                            {
                                name: '数据15',
                            },
                            {
                                name: '数据16数据16数据16数据16',
                            },
                            {
                                name: '数据17',
                            },
                        ]
                    }
                ]
            }
        ];
        this.header = jsonFromat(JSON.stringify(data))
        const newData = formatArray(data, 0)
        let maxLevel = newData[newData.length - 1].level
        this.setHeight(newData, maxLevel + 1)
        this.arayLayered(newData, maxLevel)
        this.headerList = this.arayLayered(newData, maxLevel)

        this.editor = CodeMirror.fromTextArea(document.getElementById("code"), {
            // value : data,  // 文本域默认显示的文本
            lineNumbers: true, /* 定义是否显示行号 */
            mode: "javascript",  /* 定义语法的类型,如果是html则为:text/html */
            theme: "monokai", /* 定义主题 */
            smartIndent: true,  // 是否智能缩进
            styleActiveLine: true,
            keymap:"defaule"

        });
        // this.editor.on("changes",() =>{
        //     //编译器内容更改事件
        //     this.setInputValue();
        // });
        this.editor.setSize(1200,500)
    }
}

</script>
<style lang="less" >
.CodeMirror {
    border: 1px solid black;
    line-height: 16px;
    font-size: 16px;
    text-align: left;
}
</style>
<style lang="less" scoped>
.submit{
    margin-left: 580px;
    margin-top: 20px;
}
textarea{
    width: 1300px;
    height: 1300px;
}
.content{
    font-size: 16px;
}
.textarea{
    text-align: center;
    font-size: 20px;
    .title{
        margin-top: 20px;
        font-size: 30px;
        color: #333;
    }
    textarea{
        border: 1px solid #eee;
        font-size: 16px;
        resize: both;
        width: 800px;
        min-height: 900px;
    }
}
.message{
    color: red;
    font-size: 16px;
}
.waterMask {
    position: absolute;
    width: 100%;
    height: 100%;
    z-index: 4;
    pointer-events: none;
}
.pages-tables {
  -webkit-overflow-scrolling: touch; // ios滑动顺畅
  position: relative;
  margin-left: 5%;
  padding-bottom: 160px;
  margin: 0 auto;
  width: 1200px;
}
.rolling-table {
    height: 100%;
    font-size: 0.28rem;
    color: #86939a;
    background-color: #fff;
    width: 100%;
    -webkit-overflow-scrolling: touch;
    position: relative;
    top: 0;
    // overflow: hidden;
  }
.rows {
    position: relative;
    z-index: 3;
}
.cross {
    position: relative;
    z-index: 5;
}
table td {
  border: 0px solid #000;
  font-size: 25px;
  background: #fff;
}
::-webkit-scrollbar {
    display: none;
}
.table {
  border-collapse: collapse; //去掉重复的border
  color: #86939e;
  font-size: 25px;
  border: 0px solid #000;
  min-height: 100%;
  text-align: center;
  td {
    border-bottom: 1px solid #eee;
    height: 30px;
    line-height: 30px;
    padding: 0 0.2rem;
    // white-space: nowrap;
    white-space: inherit;
    max-width: 500px;
    min-width: 50px;
    // overflow:hidden; 
    // text-overflow:ellipsis;
    // -webkit-line-clamp:2; 
  }
  th {
    color: #43484d;
    white-space: pre-wrap;
    height: 36px;
    line-height: 36px;
    padding: 5px 6px;
    background-color: #f3f4f6;
    font-weight: normal;
    padding-bottom: 0;
    padding-top: 0;
    max-width: 200px;
    border: 1px solid red;
    &:last-child{
        // border-right: 0rem solid #e4e8f5;
    }
  }
}
tr{
    position: relative;
    background-color: #fff;
    &:nth-of-type(odd){
        td{
            background-color: #ebf9fc;
        }
    }
}
</style>

5. 效果链接:

效果链接如下:

复杂表格设计数据格式

动态效果:


动态效果.gif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342