├── .gitignore ├── .npmignore ├── index.html ├── src ├── index.js ├── Checkbox │ └── index.vue ├── TableTree │ ├── utils │ │ ├── tableTree.js │ │ └── index.js │ └── index.vue └── App.vue ├── package.json ├── webpack.config.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.md 3 | node_modules/ 4 | src/ -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | vue-tree-to-table 13 |
14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @version: 4 | * @Author: huangwei 5 | * @Date: 2021-03-06 12:44:41 6 | * @LastEditors: huangwei 7 | * @LastEditTime: 2021-03-12 00:16:37 8 | */ 9 | 10 | import Vue from 'vue' 11 | import App from './App.vue' 12 | Vue.config.productionTip = false 13 | new Vue({ 14 | render: h => h(App), 15 | }).$mount('#app') 16 | 17 | // import VueTreeToTable from './TableTree/index.vue' 18 | // export default VueTreeToTable -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-tree-to-table", 3 | "version": "1.0.4", 4 | "description": "", 5 | "main": "dist/vueTreeToTable.min.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "webpack-dev-server --hot --inline --open", 9 | "build": "webpack --display-error-details --config webpack.config.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/hamuPP/npmStudy.git" 14 | }, 15 | "author": "", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/hamuPP/npmStudy/issues" 19 | }, 20 | "homepage": "https://github.com/hamuPP/npmStudy#readme", 21 | "devDependencies": { 22 | "babel-core": "^6.26.0", 23 | "babel-loader": "^7.1.2", 24 | "babel-plugin-component": "^1.1.1", 25 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 26 | "babel-plugin-transform-runtime": "^6.23.0", 27 | "babel-polyfill": "^6.26.0", 28 | "babel-preset-es2015": "^6.24.1", 29 | "css-loader": "^0.28.7", 30 | "es6-promise": "^4.1.1", 31 | "less": "^2.7.3", 32 | "less-loader": "^4.0.5", 33 | "style-loader": "^0.19.0", 34 | "url-loader": "^0.6.2", 35 | "vue-hot-reload-api": "^2.2.4", 36 | "vue-html-loader": "^1.2.4", 37 | "vue-loader": "^13.5.0", 38 | "vue-router": "^3.0.1", 39 | "vue-style-loader": "^3.0.3", 40 | "vue-template-compiler": "^2.5.9", 41 | "vuex": "^3.0.1", 42 | "webpack": "^3.9.1", 43 | "webpack-dev-server": "^2.9.5" 44 | }, 45 | "dependencies": { 46 | "vue": "^2.6.12" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @version: 4 | * @Author: huangwei 5 | * @Date: 2021-03-06 12:45:05 6 | * @LastEditors: huangwei 7 | * @LastEditTime: 2021-03-06 12:45:15 8 | */ 9 | const path = require("path"); 10 | const webpack = require("webpack"); 11 | const uglify = require("uglifyjs-webpack-plugin"); 12 | 13 | module.exports = { 14 | devtool: 'source-map', 15 | entry: "./src/index.js",//入口文件,就是上步骤的src目录下的index.js文件, 16 | output: { 17 | path: path.resolve(__dirname, './dist'),//输出路径,就是上步骤中新建的dist目录, 18 | publicPath: '/dist/', 19 | filename: 'vueTreeToTable.min.js', 20 | libraryTarget: 'umd', 21 | umdNamedDefine: true 22 | }, 23 | module: { 24 | rules: [{ 25 | test: /\.vue$/, 26 | loader: 'vue-loader' 27 | }, 28 | { 29 | test: /\.less$/, 30 | use: [ 31 | { loader: "style-loader" }, 32 | { loader: "css-loader" }, 33 | { loader: "less-loader" } 34 | ] 35 | }, 36 | { 37 | test: /\.js$/, 38 | exclude: /node_modules|vue\/dist|vue-router\/|vue-loader\/|vue-hot-reload-api\//, 39 | loader: 'babel-loader' 40 | }, 41 | { 42 | test: /\.(png|jpg|gif|ttf|svg|woff|eot)$/, 43 | loader: 'url-loader', 44 | query: { 45 | limit: 30000, 46 | name: '[name].[ext]?[hash]' 47 | } 48 | } 49 | ] 50 | }, 51 | plugins: [ 52 | new webpack.DefinePlugin({ 53 | "process.env": { 54 | NODE_ENV: JSON.stringify("production") 55 | } 56 | }) 57 | ] 58 | }; -------------------------------------------------------------------------------- /src/Checkbox/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 17 | 18 | 71 | 123 | -------------------------------------------------------------------------------- /src/TableTree/utils/tableTree.js: -------------------------------------------------------------------------------- 1 | class TableTree { 2 | constructor(config) { 3 | this.tree = config.tree 4 | this.maxColumnNum = config.maxColumnNum 5 | this.treeList = [] 6 | } 7 | 8 | // 生成树状表格数据 9 | generate(extraColumn, extraColumnObj) { 10 | this.treeList = this.recursionTreeForList(this.tree) 11 | // 如果需要后面追加额外的列 12 | if (extraColumn) { 13 | this.recursionTreeListForAppend(this.treeList, extraColumnObj.list) 14 | // console.log(this.treeList) 15 | } else { 16 | this.recursionTreeListForAppend(this.treeList, []) 17 | } 18 | this.uniqTreeList = this.recursionTreeListForUniq(this.treeList) 19 | // console.log(this.uniqTreeList) 20 | return this.uniqTreeList 21 | } 22 | 23 | // 映射树结构为list 24 | recursionTreeForList(tree, stack = [], path = []) { 25 | tree.forEach(e => { 26 | Object.assign(e, { rowSpan: this.recursionNodeForLeafNum(e), colSpan: e.step }) 27 | stack.push(e) 28 | if (Array.isArray(e.chidrenList) && e.chidrenList.length === 0) { 29 | path.push([...stack]) 30 | } else { 31 | this.recursionTreeForList(e.chidrenList, stack, path) 32 | } 33 | stack.pop(e) 34 | }) 35 | return path 36 | } 37 | 38 | // 获取某个节点下所有的叶子节点个数 39 | recursionNodeForLeafNum(node) { 40 | let num = 0 41 | const rev = child => { 42 | // 此处展开折叠时只能以子节点数判断是否为叶子节点 43 | if (Array.isArray(child.chidrenList) && child.chidrenList.length === 0) { 44 | num++ 45 | } else { 46 | child.chidrenList.forEach(e => { 47 | rev(e) 48 | }) 49 | } 50 | } 51 | rev(node) 52 | return num 53 | } 54 | 55 | // tree数据生成table数据后过滤重复的节点 56 | recursionTreeListForUniq(list) { 57 | const map = new Map() 58 | const arr = [] 59 | list.forEach(tr => { 60 | const trLlist = tr.filter(td => { 61 | return !map.has(td.id) && map.set(td.id, 1) 62 | }) 63 | arr.push(trLlist) 64 | }) 65 | return arr 66 | } 67 | 68 | // tree数据生成的table数据补全一个td并在后面追加额外的td 69 | recursionTreeListForAppend(tableList, extraList) { 70 | tableList.map((tr, trIndex) => { 71 | const currentLength = tableList[trIndex].reduce((acc, cur) => { 72 | return acc + cur.colSpan 73 | }, 0) 74 | const waitLength = this.maxColumnNum - currentLength 75 | if (waitLength) { 76 | const lastNode = tr[tr.length - 1] 77 | tr.push({ 78 | nodeType: 'blank', 79 | id: 'blank' + lastNode.id, 80 | colSpan: waitLength, 81 | rowSpan: 1, 82 | parentId: lastNode.id, 83 | nodeName: '', 84 | chidrenList: [], 85 | value: '' 86 | }) 87 | } 88 | if (extraList[trIndex]) { 89 | const handleExtraList = [] 90 | extraList[trIndex].forEach((extra, extraIndex) => { 91 | const obj = { 92 | nodeType: 'extra', 93 | id: `extra-${trIndex}-${extraIndex}-${(new Date()).getTime()}`, 94 | colSpan: 1, 95 | rowSpan: 1 96 | } 97 | Object.assign(obj, extra) 98 | handleExtraList.push(obj) 99 | }) 100 | tr.push(...handleExtraList) 101 | } 102 | return tr 103 | }) 104 | } 105 | } 106 | 107 | export default TableTree 108 | -------------------------------------------------------------------------------- /src/TableTree/utils/index.js: -------------------------------------------------------------------------------- 1 | // 判空 2 | export function isEmpty(val) { 3 | return val === undefined || val === null || val === '' 4 | } 5 | 6 | /** 7 | * tree每个node映射成一个map 8 | * @param {Array} tree 9 | * @return {Map} 10 | */ 11 | export function recursionTreeForMap(tree) { 12 | const map = new Map() 13 | const rev = arr => { 14 | arr.forEach(e => { 15 | map.set(e.id, e) 16 | if (Array.isArray(e.chidrenList)) { 17 | rev(e.chidrenList) 18 | } 19 | }) 20 | } 21 | rev(tree) 22 | return map 23 | } 24 | 25 | /** 26 | * 递归获取tree最大层级 27 | * @param {Array} tree 28 | * @return {Number} 29 | */ 30 | export function getTreeMaxLevel(tree) { 31 | let maxLevel = 0 32 | const rev = arr => { 33 | arr.forEach(e => { 34 | if (e.nodeLevel > maxLevel) { 35 | maxLevel = e.nodeLevel 36 | } 37 | if (Array.isArray(e.chidrenList)) { 38 | rev(e.chidrenList) 39 | } 40 | }) 41 | } 42 | rev(tree) 43 | return maxLevel 44 | } 45 | 46 | /** 47 | * 递归设置树结构展开收起状态 48 | * @param {Array} tree 49 | * @return {null} 50 | */ 51 | export function setTreeFoldStatus(tree, foldNum) { 52 | tree.map(e => { 53 | e.isFold = false 54 | if (e.nodeLevel > foldNum && foldNum > 0) { 55 | e.isFold = true 56 | } 57 | if (e.nodeLevel <= foldNum || foldNum === 0) { 58 | e.showFold = true 59 | } else { 60 | e.showFold = false 61 | } 62 | e.showFold = e.chidrenList.length === 0 ? false : e.showFold 63 | if (Array.isArray(e.chidrenList)) { 64 | setTreeFoldStatus(e.chidrenList, foldNum) 65 | } 66 | return e 67 | }) 68 | } 69 | 70 | /** 71 | * 递归设置树结构展开或者收起节点 72 | * @param {Array} tree 73 | * @param {Object} node 74 | * @param {Number} foldNum 75 | * @return {null} 76 | */ 77 | export function setTreeFold(tree, node, foldNum) { 78 | tree.map(e => { 79 | if (node.id === e.id) { 80 | e.isFold = !e.isFold 81 | } else { 82 | if (Array.isArray(e.chidrenList)) { 83 | setTreeFold(e.chidrenList, node, foldNum) 84 | } 85 | } 86 | return e 87 | }) 88 | } 89 | 90 | /** 91 | * 递归过滤树结构收起的节点 92 | * @param {Array} tree 93 | * @return {null} 94 | */ 95 | export function filterTreeByFoldStatus(arr) { 96 | arr.map(item => { 97 | if (item.isFold) { 98 | if (item && item.chidrenList.length > 0) { 99 | filterTreeByFoldStatus(item.chidrenList) 100 | } 101 | } else { 102 | item.chidrenList = [] 103 | } 104 | return item 105 | }) 106 | } 107 | 108 | /** 109 | * 递归获取树结构所有选中的节点 110 | * @param {Array} tree 111 | * @return {Array} 112 | */ 113 | export function getAllCheckedNodeList(tree, trueCheckLabel) { 114 | const checkedList = [] 115 | const rev = data => { 116 | data.forEach(e => { 117 | if (e.isChecked === trueCheckLabel) { 118 | checkedList.push(e) 119 | } 120 | if (Array.isArray(e.chidrenList)) { 121 | rev(e.chidrenList) 122 | } 123 | }) 124 | } 125 | rev(tree) 126 | return checkedList 127 | } 128 | 129 | /** 130 | * 递归设置树结构的某个节点选中 131 | * @param {Array} tree 132 | * @param {Object} node 133 | * @param {String} status 134 | * @return {null} 135 | */ 136 | export function setNodeStatus(tree, node, status) { 137 | tree.map(e => { 138 | if (node.id === e.id) { 139 | e.isChecked = status 140 | } else { 141 | if (Array.isArray(e.chidrenList)) { 142 | setNodeStatus(e.chidrenList, node, status) 143 | } 144 | } 145 | return e 146 | }) 147 | } 148 | 149 | /** 150 | * 递归获取树结构所有父级节点 151 | * @param {Array} tree 152 | * @param {String} nodeId 153 | * @return {Array} 154 | */ 155 | export function getAllParentNodeList(tree, nodeId) { 156 | var arrRes = [] 157 | if (tree && tree.length === 0) { 158 | if (nodeId) { 159 | arrRes.unshift(nodeId) 160 | } 161 | return arrRes 162 | } 163 | const rev = (data, id) => { 164 | for (var i = 0, length = data.length; i < length; i++) { 165 | const node = data[i] 166 | if (node.id === id) { 167 | arrRes.unshift(id) 168 | if (isEmpty(node.parentId)) { 169 | break 170 | } else { 171 | rev(tree, node.parentId) 172 | break 173 | } 174 | } else { 175 | if (Array.isArray(node.chidrenList)) { 176 | rev(node.chidrenList, id) 177 | } 178 | } 179 | } 180 | return arrRes 181 | } 182 | arrRes = rev(tree, nodeId) 183 | return arrRes 184 | } 185 | 186 | /** 187 | * 递归设置树结构的节点选中 188 | * @param {Array} tree 189 | * @param {String} checkedNodeList 190 | * @param {String} status 191 | * @return {null} 192 | */ 193 | export function setParentCheckYes(tree, checkedNodeList, status, trueEditLabel) { 194 | tree.map(e => { 195 | if (checkedNodeList.includes(e.id) && e.editable === trueEditLabel) { 196 | e.isChecked = status 197 | } 198 | if (Array.isArray(e.chidrenList)) { 199 | setParentCheckYes(e.chidrenList, checkedNodeList, status, trueEditLabel) 200 | } 201 | return e 202 | }) 203 | } 204 | 205 | /** 206 | * 递归设置树结构的节点取消选中 207 | * @param {Array} tree 208 | * @param {Object} node 209 | * @param {String} status 210 | * @return {null} 211 | */ 212 | export function setChildrenCheck(tree, node, status, trueEditLabel) { 213 | tree.map(item => { 214 | if (node.id === item.id) { 215 | const rev = (data) => { 216 | data.map(e => { 217 | if (e.editable === trueEditLabel) { 218 | e.isChecked = status 219 | } 220 | if (e && e.chidrenList.length > 0) { 221 | rev(e.chidrenList) 222 | } 223 | return e 224 | }) 225 | } 226 | rev(item.chidrenList) 227 | } else { 228 | if (item && item.chidrenList.length > 0) { 229 | setChildrenCheck(item.chidrenList, node, status, trueEditLabel) 230 | } 231 | } 232 | return item 233 | }) 234 | } 235 | 236 | /** 237 | * 递归获取树中当前id的node 238 | * @param {Array} tree 239 | * @param {String} nodeId 240 | * @return {null} 241 | */ 242 | export function recursionTreeForNode(tree, nodeId) { 243 | let node = {} 244 | const rev = data => { 245 | data.forEach(e => { 246 | if (e.id === nodeId) { 247 | node = e 248 | } 249 | if (Array.isArray(e.chidrenList)) { 250 | rev(e.chidrenList) 251 | } 252 | }) 253 | } 254 | rev(tree) 255 | return node 256 | } 257 | 258 | /** 259 | * 递归取消节点的父节点是否应该取消 260 | * @param {Array} tree 261 | * @param {Object} node 262 | * @return {null} 263 | */ 264 | export function parentShouldUnCheck(arr, node, statusObj) { 265 | const parentNode = recursionTreeForNode(arr, node.parentId) 266 | if (!parentNode.chidrenList) return 267 | const isShouldCheck = parentNode.chidrenList.some(e => { 268 | return e.isChecked === statusObj.trueCheckLabel 269 | }) 270 | // 如果子级全部未勾选,父级也取消勾选 271 | if (!isShouldCheck) { 272 | parentNode.isChecked = statusObj.falseCheckLabel 273 | } 274 | if (parentNode.nodeLevel > 1) { 275 | parentShouldUnCheck(arr, parentNode, statusObj) 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-tree-to-table 2 | [Online Demo](https://a869057476.github.io/vue-tree-to-table/) 3 | ## Introduce 4 | > Default show 5 | ![vue-tree-to-table Screenshot](https://github.com/a869057476/huangwei.github.io/blob/master/2.png) 6 | > checkbox 7 | ![vue-tree-to-table Screenshot](https://github.com/a869057476/huangwei.github.io/blob/master/3.png) 8 | > slot 9 | ![vue-tree-to-table Screenshot](https://github.com/a869057476/huangwei.github.io/blob/master/6.png) 10 | > fold 11 | ![vue-tree-to-table Screenshot](https://github.com/a869057476/huangwei.github.io/blob/master/8.png) 12 | > hide head 13 | ![vue-tree-to-table Screenshot](https://github.com/a869057476/huangwei.github.io/blob/master/10.png) 14 | > fixed height 15 | ![vue-tree-to-table Screenshot](https://github.com/a869057476/huangwei.github.io/blob/master/11.png) 16 | > extra column 17 | ![vue-tree-to-table Screenshot](https://github.com/a869057476/huangwei.github.io/blob/master/15.png) 18 | 19 | ## Features 20 | 21 | - Tree data to table view 22 | - checkbox, supporting relevance and independence 23 | - Folding, supporting themselves and specified level 24 | - Extra column 25 | - Node and head slot 26 | - Head hide 27 | - Fixed height 28 | - Etc in project 29 | 30 | # API 31 | ### Important!(All the example how to use in this repository, you can download and reference it.) 32 | 33 | | Parameters | Description | Type | Optional value |Default value | 34 | |---------- |-------- |---------- |---------- |---------- | 35 | | tableColumns | Dynamic header | Array | Y | [] | 36 | | originData | Tree data | Array | Y | [] | 37 | | isFold | Whether the folding | Boolean | - | false | 38 | | foldNum | Folding level, effected only if foldNum is true, to a certain level the child can fully deployed, the default is one layer | Number | - | 0 | 39 | | showCheckbox | Whether to display the check box | Boolean | - | false | 40 | | showCheckboxAndSlot | Whether to display both check boxes and slot, only if showCheckBox is true | Boolean | - | false | 41 | | checkStrictly | Whether to strictly follow the parent-child correlation, only if showCheckbox is true | Boolean | - | false | 42 | | trueCheckLabel | Alias for checkbox checked, only if showCheckbox is true | Boolean | - | false | 43 | | falseCheckLabel | Alias for checkbox unchecked, only if showCheckbox is true | Boolean | - | false | 44 | | trueEditLabel | Alias for checkbox disabled, only if showCheckbox is true | Boolean | - | false | 45 | | falseEditLabel | Alias for checkbox not disabled, only if showCheckbox is true | Boolean | - | false | 46 | | disabledCheckboxNodeLevel | The first few levels of the disabled checkbox, only if showCheckbox is true | Number | - | 0 | 47 | | disabledCheckboxLevels | The level of the disabled checkbox, only if showCheckbox is true | Array | - | [] | 48 | | disabledCheckboxIds | Disabled the IDS of the checkbox, only if showCheckbox is true | Array | - | [] | 49 | | isShowTableHead | Whether to display the header | Boolean | - | false | 50 | | height | Table height | String, Number | - | 'auto' | 51 | | extraColumn | Whether there is additional fixed column concatenation behind it | Boolean | - | false | 52 | | extraColumnObj | Data for additional columns, only if extraColumn is true | Object | - | {columns: [], list: []} | 53 | 54 | # methods 55 | ### Important!(All the example how to use in this repository, you can download and reference it.) 56 | 57 | | name | Description | parameter | 58 | |----------|----------|----------| 59 | | getTreeData() | Gets the data for the tree | - | 60 | | getCheckedNodeList() | Gets all selected nodes | - | 61 | | resetCheckedNode(list) | Reset the node checkbox status | list: Array | 62 | | changeNodeValue(list, key, value) | Modify the value of the node attribute | - | 63 | | getExtraNodeList() | Gets the node data for the additional columns | - | 64 | 65 | ## Getting started 66 | 67 | ```bash 68 | # clone the project 69 | git clone https://github.com/a869057476/vue-tree-to-table.git 70 | 71 | # enter the project directory 72 | cd vue-tree-to-table 73 | 74 | # install dependency 75 | npm install 76 | 77 | # develop 78 | npm run start 79 | ``` 80 | 81 | This will automatically open http://localhost:8080 82 | 83 | # Build 84 | npm run build 85 | 86 | ## Usage in project 87 | npm install vue-tree-to-table --save 88 | 89 | `vue` 90 | ``` 91 | 104 | 105 | 448 | ``` 449 | 450 | 451 | -------------------------------------------------------------------------------- /src/TableTree/index.vue: -------------------------------------------------------------------------------- 1 | 95 | 96 | 461 | 462 | 514 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 9 | 739 | 740 | 1223 | 1224 | 1234 | --------------------------------------------------------------------------------