# vue实现树形表格的封装（vue-tree-grid）

## 前言
> 由于公司产品（基于vue2.0）需要，要实现一个[树形表格](https://github.com/Inception-entry/vue-tree-grid)的功能，百度、google找了一通，并没有发现很靠谱的，也不是很灵活。所以就用vue自己写了一个，还望大家多多指教。

### 注意：鉴于本组件包含样式和布局等，而且对于需要此组件的你来说，需要定制化调整，故而没有打包成npm下载可直接引入的形式，因为没有太大的实际意义，人生有限，不要把时间浪费在无意义的事情上。请自行下载并手动引入组件，并修改成你想要的样子。[传送门](https://github.com/Inception-entry/vue-tree-grid)

### 主要技术点：`vue子组件的递归实现及相关样式的实现`

## 路由讲解
- 根路由：拓展的内部表格数据过多, 是时间分片方式处理的
- /virtual: 拓展的内部表格数据过多, 是虚拟列表方式处理的(强烈建议用此方式)

## 树形表格实现
- 效果图([Demo](https://inception-entry.github.io/dist/#/))![效果图](https://inception-entry.github.io/dist/20230328_202702.gif)
- 主要代码
> index.vue页面实现业务逻辑代码，比如树表格上面的一些操作按钮的实现及数据获取。
>
``` html
<template>
  <div class="common-css contains">
    <tree-grid
      ref="recTree"
      :list.sync="treeDataSource"
      @handlerFold="handlerFold"
      @handlerExpand="handlerExpand"
      :treeColumnList="treeColumns"
      :columnList="tableColumns"
      :tableListName="childrenAlias">
    </tree-grid>
  </div>
</template>
<script>
import dataJson from './data1.json';
import treeGrid from '@/components/tree-grid.vue';
export default {
  data() {
    return {
      treeDataSource: dataJson,
      treeColumns: [], // 树表头
      tableColumns: [],// 内部表格表头
      childrenAlias: 'sub_account_list',
      treeDataSource: [] // 组合成树表格接收的数据
    }
  },
  components: {
    treeGrid
  },
  methods: {
    handlerFold(val) {
      console.log('展开/折叠')
    },
    handlerExpand(val) {
      console.log('扩展/收起');
    },
  }
}
</script>
```
``` bash
原始数据`list`：是不包含子数据的数据结构，即没有层级结构，通过parentId来获取对应父子层级结构，例如：
[{id:111,parentId:0,name:'父级'},{id:111,parentId:111,name:'子级'}...]

`treeDataSource`：是树表格需要的数据结构，例如：
[{id:0,name:'父级',children:[{id:10,name:'子级',children:[]}]},...]
```
> 如果后台返回给你的是原始数据格式，就可以用下面方法封装成树表格可以使用的数据结构：
``` bash
  getTreeData() {
    // 取父节点
    let parentArr = this.list.filter(l => l.parentId === 0)
    this.treeDataSource = this.getTreeData(this.list, parentArr)
  },
  // 这里处理没有children结构的数据
  getTreeData(list, dataArr) {
    dataArr.map((pNode, i) => {
      let childObj = []
      list.map((cNode, j) => {
        if (pNode.id === cNode.parentId) {
          childObj.push(cNode)
        }
      })
      pNode.children = childObj
      if (childObj.length > 0) {
        this.getTreeData(list, childObj)
      }
    })
    return dataArr
  }
```
> tree-grid.vue页面。此页面是实现树表格的关健页面。主要代码如下：
``` html
<template>
	<div class="tree-grid">
		<div class="tree-head">
			<table>
				<tr>
					<th
            :class="`th${index + 1}`"
            :style="treeHeaderRender(item, index, treeColumnList)"
            v-for="(item, index) in treeColumnList"
            :key="item.key">
            {{ item.name }}
          </th>
				</tr>
			</table>
		</div>
		<div id="scrollWrap" class="tree-wrap">
			<div class="tree-body">
        <table v-if="treeDataSource.length > 0">
          <tbody>
            <tr>
              <td>
                <tree-unit
                  v-for="(model, i) in treeDataSource"
                  :key="'root_node_' + i"
                  :root="0"
                  :num="i"
                  @handlerFold="handlerFold"
                  @handlerExpand="handlerExpand"
                  :nodes="treeDataSource.length"
                  :trees.sync="treeDataSource"
                  :model="model"
                  :treeColumnList="treeColumnList"
                  :columnList="columnList"
                  :tableListName="tableListName">
                </tree-unit>
              </td>
            </tr>
          </tbody>
        </table>
			</div>
		</div>
	</div>
</template>
```

## API

### Table Attributes

| 属性 | 说明 | 类型 | 参数 | 默认值 |
| ---- | ---- | ---- | ---- | ---- |
| list | 树表格需要的数据结构 | Array | - | [] |
| treeColumnList | 树表格的最外层表头 | Array | - | [] |
| columnList | 内部表格表头（具体见下文：Columns Configs） | Array | - | [] |
| tableListName | 树表格结构中内部表格的属性名 | String | - | '' |


### Columns Configs

| 属性 | 说明 | 类型 | 默认值 |
| ---- | ---- | ---- | ---- |
| key | 列唯一标识 | Number | 无 |
| name | 列标题名称 | String | '' |
| fieldName | 对应列内容的属性名 | String | '' |
| minWidth | 列最小宽度 |  Number | 无 |
| maxWidth | 列最大宽度 | Number | 无 |
| expandFunc | 是否有拓展功能 | Boolean | false |
| mainAccountSlotName | 列类型为复杂结构时，对应的主账号插槽名称 | String | '' |
| slotName | 列类型为 'template'(自定义列模板) 时，对应的插槽名称（它可以获取到 row）[作用域插槽] | Object | {} |

### Table Events

| 事件名 | 说明 | 参数 |
| ---- | ---- | ---- |
| handlerFold | 鼠标单击树形icon | row |
| handlerExpand | 鼠标单击展开单元格 | row |


> 补充一点：不要只看js部分，css部分才是这个树表格的关健所在。当然里面我采用了大量的计算属性去判断各种样式的展示，还有一种方法，就是在`initTreeData`方法里面去实现，这个方法就是处理或添加一些我们树表格所使用的信息。比如我现在在里面实现的层级线的偏移量`m.bLeft = level === 1 ? 65 : (level - 2) * 14 + 65` 这个计算如果没有看明白，可以留言。

- 最后，如有问题，还请多多包含，多多指教！！！顺便给我好久没有更新的博客打个广告,
欢迎点击（[<span style="color:#f24c27;font-weight:600">sanks的博客</span>](https://www.sanks-blog.com/)）

## 参考资料
- 源码地址[github](https://github.com/Inception-entry/vue-tree-grid)，欢迎star。
- 参考资源[隔壁家的老黄](https://www.cnblogs.com/ychl/p/6075106.html)
- 参考资源[城池](https://juejin.cn/post/6844903645624926215)


## Build Setup

``` bash
# install dependencies
npm install

# serve with hot reload at localhost:8080
npm run dev

# build for production with minification
npm run build

# build for production and view the bundle analyzer report
npm run build --report
```

For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).
