merge master
This commit is contained in:
		
							
								
								
									
										38
									
								
								src/api/role.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/api/role.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| import request from '@/utils/request' | ||||
|  | ||||
| export function getRoutes() { | ||||
|   return request({ | ||||
|     url: '/routes', | ||||
|     method: 'get' | ||||
|   }) | ||||
| } | ||||
|  | ||||
| export function getRoles() { | ||||
|   return request({ | ||||
|     url: '/roles', | ||||
|     method: 'get' | ||||
|   }) | ||||
| } | ||||
|  | ||||
| export function deleteRole(id) { | ||||
|   return request({ | ||||
|     url: `/roles/${id}`, | ||||
|     method: 'delete' | ||||
|   }) | ||||
| } | ||||
|  | ||||
| export function addRole(data) { | ||||
|   return request({ | ||||
|     url: '/roles', | ||||
|     method: 'post', | ||||
|     data | ||||
|   }) | ||||
| } | ||||
|  | ||||
| export function updateRole(key, data) { | ||||
|   return request({ | ||||
|     url: `/roles/${key}`, | ||||
|     method: 'put', | ||||
|     data | ||||
|   }) | ||||
| } | ||||
| @@ -34,8 +34,8 @@ export default { | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     routers() { | ||||
|       return this.$store.getters.permission_routers | ||||
|     routes() { | ||||
|       return this.$store.getters.permission_routes | ||||
|     }, | ||||
|     lang() { | ||||
|       return this.$store.getters.language | ||||
| @@ -43,10 +43,10 @@ export default { | ||||
|   }, | ||||
|   watch: { | ||||
|     lang() { | ||||
|       this.searchPool = this.generateRouters(this.routers) | ||||
|       this.searchPool = this.generateRoutes(this.routes) | ||||
|     }, | ||||
|     routers() { | ||||
|       this.searchPool = this.generateRouters(this.routers) | ||||
|     routes() { | ||||
|       this.searchPool = this.generateRoutes(this.routes) | ||||
|     }, | ||||
|     searchPool(list) { | ||||
|       this.initFuse(list) | ||||
| @@ -60,7 +60,7 @@ export default { | ||||
|     } | ||||
|   }, | ||||
|   mounted() { | ||||
|     this.searchPool = this.generateRouters(this.routers) | ||||
|     this.searchPool = this.generateRoutes(this.routes) | ||||
|   }, | ||||
|   methods: { | ||||
|     click() { | ||||
| @@ -101,10 +101,10 @@ export default { | ||||
|     }, | ||||
|     // Filter out the routes that can be displayed in the sidebar | ||||
|     // And generate the internationalized title | ||||
|     generateRouters(routers, basePath = '/', prefixTitle = []) { | ||||
|     generateRoutes(routes, basePath = '/', prefixTitle = []) { | ||||
|       let res = [] | ||||
|  | ||||
|       for (const router of routers) { | ||||
|       for (const router of routes) { | ||||
|         // skip hidden router | ||||
|         if (router.hidden) { continue } | ||||
|  | ||||
| @@ -126,11 +126,11 @@ export default { | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         // recursive child routers | ||||
|         // recursive child routes | ||||
|         if (router.children) { | ||||
|           const tempRouters = this.generateRouters(router.children, data.path, data.title) | ||||
|           if (tempRouters.length >= 1) { | ||||
|             res = [...res, ...tempRouters] | ||||
|           const tempRoutes = this.generateRoutes(router.children, data.path, data.title) | ||||
|           if (tempRoutes.length >= 1) { | ||||
|             res = [...res, ...tempRoutes] | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|   | ||||
							
								
								
									
										220
									
								
								src/components/TreeTable/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								src/components/TreeTable/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,220 @@ | ||||
|  | ||||
| - [Enlgish](#Brief) | ||||
|  | ||||
| # 中文 | ||||
|  | ||||
| ## 写在前面 | ||||
|  | ||||
| 此组件仅提供一个创建 `TreeTable` 的解决思路。它基于`element-ui`的 table 组件实现,通过`el-table`的`row-style`方法,在里面判断元素是否需要隐藏或者显示,从而实现`TreeTable`的展开与收起。 | ||||
|  | ||||
| 并且本组件充分利用 `vue` 插槽的特性来方便用户自定义。 | ||||
|  | ||||
| `evel.js` 里面,`addAttrs` 方法会给数据添加几个属性,`treeTotable` 会对数组扁平化。这些操作都不会破坏源数据,只是会新增属性。 | ||||
|  | ||||
| ## Props 说明 | ||||
|  | ||||
| |    Attribute     | Description                        |  Type   | Default  | | ||||
| | :--------------: | :--------------------------------- | :-----: | :------: | | ||||
| |       data       | 原始展示数据                       |  Array  |    []    | | ||||
| |     columns      | 列属性                             |  Array  |    []    | | ||||
| | defaultExpandAll | 默认是否全部展开                   | Boolean |  false   | | ||||
| | defaultChildren  | 指定子树为节点对象的某个属性值     | String  | children |  | | ||||
| |      indent      | 相邻级节点间的水平缩进,单位为像素 | Number  |    50    | | ||||
|  | ||||
| > 任何 `el-table` 的属性都支持,例如`border`、`fit`、`size`或者`@select`、`@cell-click`等方法。详情属性见`el-table`文档。 | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### 代码示例 | ||||
|  | ||||
| ```html | ||||
| <tree-table :data="data" :columns="columns" border> | ||||
| ``` | ||||
|  | ||||
| #### data(**必填**) | ||||
|  | ||||
| ```js | ||||
| const data = [ | ||||
|   { | ||||
|     name:'1' | ||||
|     children: [ | ||||
|       { | ||||
|         name: '1-1' | ||||
|       }, | ||||
|       { | ||||
|         name: '1-2' | ||||
|       } | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     name: `2` | ||||
|   } | ||||
| ] | ||||
| ``` | ||||
|  | ||||
| #### columns(**必填**) | ||||
|  | ||||
| - label: 显示在表头的文字 | ||||
| - key: 对应 data 的 key。treeTable 将显示相应的 value | ||||
| - expand: `true` or `false`。若为 true,则在该列显示展开收起图标 | ||||
| - checkbox: `true` or `false`。若为 true,则在该列显示`checkbox` | ||||
| - width: 每列的宽度,为一个数字(可选)。例如`200` | ||||
| - align: 对齐方式 `left/center/right` | ||||
| - header-align: 表头对齐方式 `left/center/right` | ||||
|  | ||||
| ```javascript | ||||
| const columns = [ | ||||
|   { | ||||
|     label: 'Checkbox', | ||||
|     checkbox: true | ||||
|   }, | ||||
|   { | ||||
|     label: '', | ||||
|     key: 'id', | ||||
|     expand: true | ||||
|   }, | ||||
|   { | ||||
|     label: 'Event', | ||||
|     key: 'event', | ||||
|     width: 200, | ||||
|     align: 'left' | ||||
|   }, | ||||
|   { | ||||
|     label: 'Scope', | ||||
|     key: 'scope' | ||||
|   } | ||||
| ] | ||||
| ``` | ||||
|  | ||||
| > 树表组件将会根据 columns 的 key 属性生成具名插槽,如果你需要对列数据进行自定义,通过插槽即可实现 | ||||
|  | ||||
| ```html | ||||
| <template slot="your key" slot-scope="{scope}"> | ||||
|   <el-tag>level: {{ scope.row._level }}</el-tag> | ||||
|   <el-tag>expand: {{ scope.row._expand }}</el-tag> | ||||
|   <el-tag>select: {{ scope.row._select }}</el-tag> | ||||
| </template> | ||||
| ``` | ||||
|  | ||||
| ## Events | ||||
|  | ||||
| 目前提供了几个方法,不过只是`beta`版本,之后很可能会修改。 | ||||
|  | ||||
| ```js | ||||
| this.$refs.TreeTable.addChild(row, data) //添加子元素 | ||||
| this.$refs.TreeTable.addBrother(row, data) //添加兄弟元素 | ||||
| this.$refs.TreeTable.delete(row) //删除该元素 | ||||
| ``` | ||||
|  | ||||
| ## 其他 | ||||
|  | ||||
| 如果有其他的需求,请参考[el-table](http://element-cn.eleme.io/#/en-US/component/table)的 api 自行修改 index.vue | ||||
|  | ||||
| # English | ||||
|  | ||||
| ## Brief | ||||
|  | ||||
| This component only provides a solution for creating `TreeTable`. It is based on the `element-ui` table component. It uses the `row-style` method of `el-table` to determine whether the element needs to be hidden or displayed. | ||||
|  | ||||
| And this component makes full use of the features of the `vue` slot to make it user-friendly. | ||||
|  | ||||
| In `evel.js`, the `addAttrs` method adds several properties to the data, and `treeTotable` flattens the array. None of these operations will destroy the source data, just add properties. | ||||
|  | ||||
| ## Props | ||||
|  | ||||
| |    Attribute     | Description                                                  |  Type   | Default  | | ||||
| | :--------------: | :----------------------------------------------------------- | :-----: | :------: | | ||||
| |       data       | original display data                                        |  Array  |    []    | | ||||
| |     columns      | column attribute                                             |  Array  |    []    | | ||||
| | defaultExpandAll | whether to expand all nodes by default                       | Boolean |  false   | | ||||
| | defaultChildren  | specify which node object is used as the node's subtree      | String  | children |  | | ||||
| |      indent      | horizontal indentation of nodes in adjacent levels in pixels | Number  |    50    | | ||||
|  | ||||
| > Any of the `el-table` properties are supported, such as `border`, `fit`, `size` or `@select`, `@cell-click`. See the ʻel-table` documentation for details. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### Example | ||||
|  | ||||
| ```html | ||||
| <tree-table :data="data" :columns="columns" border> | ||||
| ``` | ||||
|  | ||||
| #### data(**Required**) | ||||
|  | ||||
| ```js | ||||
| const data = [ | ||||
|   { | ||||
|     name:'1' | ||||
|     children: [ | ||||
|       { | ||||
|         name: '1-1' | ||||
|       }, | ||||
|       { | ||||
|         name: '1-2' | ||||
|       } | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     name: `2` | ||||
|   } | ||||
| ] | ||||
| ``` | ||||
|  | ||||
| #### columns(**Required**) | ||||
|  | ||||
| - label: text displayed in the header | ||||
| - key: data.key will show in column | ||||
| - expand: `true` or `false` | ||||
| - checkbox: `true` or `false` | ||||
| - width: column width 。such as `200` | ||||
| - align: alignment `left/center/right` | ||||
| - header-align: alignment of the table header `left/center/right` | ||||
|  | ||||
| ```javascript | ||||
| const columns = [ | ||||
|   { | ||||
|     label: 'Checkbox', | ||||
|     checkbox: true | ||||
|   }, | ||||
|   { | ||||
|     label: '', | ||||
|     key: 'id', | ||||
|     expand: true | ||||
|   }, | ||||
|   { | ||||
|     label: 'Event', | ||||
|     key: 'event', | ||||
|     width: 200, | ||||
|     align: 'left' | ||||
|   }, | ||||
|   { | ||||
|     label: 'Scope', | ||||
|     key: 'scope' | ||||
|   } | ||||
| ] | ||||
| ``` | ||||
|  | ||||
| > The tree table component will generate a named slot based on the key property of columns. If you need to customize the column data, you can do it through the slot. | ||||
|  | ||||
| ```html | ||||
| <template slot="your key" slot-scope="{scope}"> | ||||
|   <el-tag>level: {{ scope.row._level }}</el-tag> | ||||
|   <el-tag>expand: {{ scope.row._expand }}</el-tag> | ||||
|   <el-tag>select: {{ scope.row._select }}</el-tag> | ||||
| </template> | ||||
| ``` | ||||
|  | ||||
| ## Events | ||||
|  | ||||
| Several methods are currently available, but only the `beta` version, which is likely to be modified later. | ||||
|  | ||||
| ```js | ||||
| this.$refs.TreeTable.addChild(row, data) //Add child elements | ||||
| this.$refs.TreeTable.addBrother(row, data) //Add a sibling element | ||||
| this.$refs.TreeTable.delete(row) //Delete the element | ||||
| ``` | ||||
|  | ||||
| ## Other | ||||
|  | ||||
| If you have other requirements, please refer to the [el-table](http://element-cn.eleme.io/#/en-US/component/table) api to modify the index.vue | ||||
| @@ -1,29 +1,48 @@ | ||||
| /** | ||||
| * @Author: jianglei | ||||
| * @Date:   2017-10-12 12:06:49 | ||||
| */ | ||||
| 'use strict' | ||||
| import Vue from 'vue' | ||||
| export default function treeToArray(data, expandAll, parent = null, level = null) { | ||||
|  | ||||
| // Flattened array | ||||
| export default function treeToArray(data, children = 'children') { | ||||
|   let tmp = [] | ||||
|   Array.from(data).forEach(function(record) { | ||||
|     if (record._expanded === undefined) { | ||||
|       Vue.set(record, '_expanded', expandAll) | ||||
|     } | ||||
|     let _level = 1 | ||||
|     if (level !== undefined && level !== null) { | ||||
|       _level = level + 1 | ||||
|     } | ||||
|     Vue.set(record, '_level', _level) | ||||
|     // 如果有父元素 | ||||
|     if (parent) { | ||||
|       Vue.set(record, 'parent', parent) | ||||
|     } | ||||
|     tmp.push(record) | ||||
|     if (record.children && record.children.length > 0) { | ||||
|       const children = treeToArray(record.children, expandAll, record, _level) | ||||
|       tmp = tmp.concat(children) | ||||
|   data.forEach((item, index) => { | ||||
|     Vue.set(item, '_index', index) | ||||
|     tmp.push(item) | ||||
|     if (item[children] && item[children].length > 0) { | ||||
|       const res = treeToArray(item[children], children) | ||||
|       tmp = tmp.concat(res) | ||||
|     } | ||||
|   }) | ||||
|   return tmp | ||||
| } | ||||
|  | ||||
| export function addAttrs(data, { parent = null, preIndex = false, level = 0, expand = false, children = 'children', show = true, select = false } = {}) { | ||||
|   data.forEach((item, index) => { | ||||
|     const _id = (preIndex ? `${preIndex}-${index}` : index) + '' | ||||
|     Vue.set(item, '_id', _id) | ||||
|     Vue.set(item, '_level', level) | ||||
|     Vue.set(item, '_expand', expand) | ||||
|     Vue.set(item, '_parent', parent) | ||||
|     Vue.set(item, '_show', show) | ||||
|     Vue.set(item, '_select', select) | ||||
|     if (item[children] && item[children].length > 0) { | ||||
|       addAttrs(item[children], { | ||||
|         parent: item, | ||||
|         level: level + 1, | ||||
|         expand, | ||||
|         preIndex: _id, | ||||
|         children, | ||||
|         status, | ||||
|         select | ||||
|       }) | ||||
|     } | ||||
|   }) | ||||
| } | ||||
|  | ||||
| export function cleanParentAttr(data, children = 'children') { | ||||
|   data.forEach(item => { | ||||
|     item._parent = null | ||||
|     if (item[children] && item[children].length > 0) { | ||||
|       addAttrs(item[children], children) | ||||
|     } | ||||
|   }) | ||||
|   return data | ||||
| } | ||||
|   | ||||
| @@ -1,128 +1,193 @@ | ||||
| <template> | ||||
|   <el-table :data="formatData" :row-style="showRow" v-bind="$attrs"> | ||||
|     <el-table-column v-if="columns.length===0" width="150"> | ||||
|   <el-table :data="tableData" :row-style="showRow" v-bind="$attrs" v-on="$listeners"> | ||||
|     <slot name="selection" /> | ||||
|     <slot name="pre-column" /> | ||||
|     <el-table-column | ||||
|       v-for="item in columns" | ||||
|       :key="item.key" | ||||
|       :label="item.label" | ||||
|       :width="item.width" | ||||
|       :align="item.align||'center'" | ||||
|       :header-align="item.headerAlign" | ||||
|     > | ||||
|       <template slot-scope="scope"> | ||||
|         <span v-for="space in scope.row._level" :key="space" class="ms-tree-space" /> | ||||
|         <span v-if="iconShow(0,scope.row)" class="tree-ctrl" @click="toggleExpanded(scope.$index)"> | ||||
|           <i v-if="!scope.row._expanded" class="el-icon-plus" /> | ||||
|           <i v-else class="el-icon-minus" /> | ||||
|         </span> | ||||
|         {{ scope.$index }} | ||||
|         <slot :scope="scope" :name="item.key"> | ||||
|           <template v-if="item.expand"> | ||||
|             <span :style="{'padding-left':+scope.row._level*indent + 'px'} " /> | ||||
|             <span v-show="showSperadIcon(scope.row)" class="tree-ctrl" @click="toggleExpanded(scope.$index)"> | ||||
|               <i v-if="!scope.row._expand" class="el-icon-plus" /> | ||||
|               <i v-else class="el-icon-minus" /> | ||||
|             </span> | ||||
|           </template> | ||||
|           <template v-if="item.checkbox"> | ||||
|             <el-checkbox | ||||
|               v-if="scope.row[defaultChildren]&&scope.row[defaultChildren].length>0" | ||||
|               v-model="scope.row._select" | ||||
|               :style="{'padding-left':+scope.row._level*indent + 'px'} " | ||||
|               :indeterminate="scope.row._select" | ||||
|               @change="handleCheckAllChange(scope.row)" | ||||
|             /> | ||||
|             <el-checkbox | ||||
|               v-else | ||||
|               v-model="scope.row._select" | ||||
|               :style="{'padding-left':+scope.row._level*indent + 'px'} " | ||||
|               @change="handleCheckAllChange(scope.row)" | ||||
|             /> | ||||
|           </template> | ||||
|           {{ scope.row[item.key] }} | ||||
|         </slot> | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|     <el-table-column v-for="(column, index) in columns" v-else :key="column.value" :label="column.text" :width="column.width"> | ||||
|       <template slot-scope="scope"> | ||||
|         <!-- Todo --> | ||||
|         <!-- eslint-disable-next-line vue/no-confusing-v-for-v-if --> | ||||
|         <!-- eslint-disable-next-line --> | ||||
|         <span v-for="space in scope.row._level" v-if="index === 0" :key="space" class="ms-tree-space" /> | ||||
|         <span v-if="iconShow(index,scope.row)" class="tree-ctrl" @click="toggleExpanded(scope.$index)"> | ||||
|           <i v-if="!scope.row._expanded" class="el-icon-plus" /> | ||||
|           <i v-else class="el-icon-minus" /> | ||||
|         </span> | ||||
|         {{ scope.row[column.value] }} | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|     <slot /> | ||||
|   </el-table> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| /** | ||||
|   Auth: Lei.j1ang | ||||
|   Created: 2018/1/19-13:59 | ||||
| */ | ||||
| import treeToArray from './eval' | ||||
| import treeToArray, { addAttrs } from './eval.js' | ||||
|  | ||||
| export default { | ||||
|   name: 'TreeTable', | ||||
|   props: { | ||||
|     /* eslint-disable */ | ||||
|     data: { | ||||
|       type: [Array, Object], | ||||
|       required: true | ||||
|       type: Array, | ||||
|       required: true, | ||||
|       default: () => [] | ||||
|     }, | ||||
|     columns: { | ||||
|       type: Array, | ||||
|       default: () => [] | ||||
|     }, | ||||
|     evalFunc: Function, | ||||
|     evalArgs: Array, | ||||
|     expandAll: { | ||||
|     defaultExpandAll: { | ||||
|       type: Boolean, | ||||
|       default: false | ||||
|     }, | ||||
|     defaultChildren: { | ||||
|       type: String, | ||||
|       default: 'children' | ||||
|     }, | ||||
|     indent: { | ||||
|       type: Number, | ||||
|       default: 50 | ||||
|     } | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       guard: 1 | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     // 格式化数据源 | ||||
|     formatData: function() { | ||||
|       let tmp | ||||
|       if (!Array.isArray(this.data)) { | ||||
|         tmp = [this.data] | ||||
|       } else { | ||||
|         tmp = this.data | ||||
|     children() { | ||||
|       return this.defaultChildren | ||||
|     }, | ||||
|     tableData() { | ||||
|       const data = this.data | ||||
|       if (this.data.length === 0) { | ||||
|         return [] | ||||
|       } | ||||
|       const func = this.evalFunc || treeToArray | ||||
|       const args = this.evalArgs ? Array.concat([tmp, this.expandAll], this.evalArgs) : [tmp, this.expandAll] | ||||
|       return func.apply(null, args) | ||||
|       addAttrs(data, { | ||||
|         expand: this.defaultExpandAll, | ||||
|         children: this.defaultChildren | ||||
|       }) | ||||
|  | ||||
|       const retval = treeToArray(data, this.defaultChildren) | ||||
|       return retval | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     showRow: function(row) { | ||||
|       const show = (row.row.parent ? (row.row.parent._expanded && row.row.parent._show) : true) | ||||
|       row.row._show = show | ||||
|       return show ? 'animation:treeTableShow 1s;-webkit-animation:treeTableShow 1s;' : 'display:none;' | ||||
|     addBrother(row, data) { | ||||
|       if (row._parent) { | ||||
|         row._parent.children.push(data) | ||||
|       } else { | ||||
|         this.data.push(data) | ||||
|       } | ||||
|     }, | ||||
|     // 切换下级是否展开 | ||||
|     toggleExpanded: function(trIndex) { | ||||
|       const record = this.formatData[trIndex] | ||||
|       record._expanded = !record._expanded | ||||
|     addChild(row, data) { | ||||
|       if (!row.children) { | ||||
|         this.$set(row, 'children', []) | ||||
|       } | ||||
|       row.children.push(data) | ||||
|     }, | ||||
|     // 图标显示 | ||||
|     iconShow(index, record) { | ||||
|       return (index === 0 && record.children && record.children.length > 0) | ||||
|     delete(row) { | ||||
|       const { _index, _parent } = row | ||||
|       if (_parent) { | ||||
|         _parent.children.splice(_index, 1) | ||||
|       } else { | ||||
|         this.data.splice(_index, 1) | ||||
|       } | ||||
|     }, | ||||
|     getData() { | ||||
|       return this.tableData | ||||
|     }, | ||||
|     showRow: function({ row }) { | ||||
|       const parent = row._parent | ||||
|       const show = parent ? parent._expand && parent._show : true | ||||
|       row._show = show | ||||
|       return show | ||||
|         ? 'animation:treeTableShow 1s;-webkit-animation:treeTableShow 1s;' | ||||
|         : 'display:none;' | ||||
|     }, | ||||
|     showSperadIcon(record) { | ||||
|       return record[this.children] && record[this.children].length > 0 | ||||
|     }, | ||||
|     toggleExpanded(trIndex) { | ||||
|       const record = this.tableData[trIndex] | ||||
|       const expand = !record._expand | ||||
|       record._expand = expand | ||||
|     }, | ||||
|     handleCheckAllChange(row) { | ||||
|       this.selcetRecursion(row, row._select, this.defaultChildren) | ||||
|       this.isIndeterminate = row._select | ||||
|     }, | ||||
|     selcetRecursion(row, select, children = 'children') { | ||||
|       if (select) { | ||||
|         this.$set(row, '_expand', true) | ||||
|         this.$set(row, '_show', true) | ||||
|       } | ||||
|       const sub_item = row[children] | ||||
|       if (sub_item && sub_item.length > 0) { | ||||
|         sub_item.map(child => { | ||||
|           child._select = select | ||||
|           this.selcetRecursion(child, select, children) | ||||
|         }) | ||||
|       } | ||||
|     }, | ||||
|     updateTreeNode(item) { | ||||
|       return new Promise(resolve => { | ||||
|         const { _id, _parent } = item | ||||
|         const index = _id.split('-').slice(-1)[0] // get last index | ||||
|         if (_parent) { | ||||
|           _parent.children.splice(index, 1, item) | ||||
|           resolve(this.data) | ||||
|         } else { | ||||
|           this.data.splice(index, 1, item) | ||||
|           resolve(this.data) | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| <style rel="stylesheet/css"> | ||||
|   @keyframes treeTableShow { | ||||
|     from {opacity: 0;} | ||||
|     to {opacity: 1;} | ||||
|   } | ||||
|   @-webkit-keyframes treeTableShow { | ||||
|     from {opacity: 0;} | ||||
|     to {opacity: 1;} | ||||
|   } | ||||
| </style> | ||||
|  | ||||
| <style lang="scss" rel="stylesheet/scss" scoped> | ||||
|   $color-blue: #2196F3; | ||||
|   $space-width: 18px; | ||||
|   .ms-tree-space { | ||||
|     position: relative; | ||||
|     top: 1px; | ||||
|     display: inline-block; | ||||
|     font-style: normal; | ||||
|     font-weight: 400; | ||||
|     line-height: 1; | ||||
|     width: $space-width; | ||||
|     height: 14px; | ||||
|     &::before { | ||||
|       content: "" | ||||
|     } | ||||
| <style> | ||||
| @keyframes treeTableShow { | ||||
|   from { | ||||
|     opacity: 0; | ||||
|   } | ||||
|   .processContainer{ | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|   to { | ||||
|     opacity: 1; | ||||
|   } | ||||
|   table td { | ||||
|     line-height: 26px; | ||||
| } | ||||
| @-webkit-keyframes treeTableShow { | ||||
|   from { | ||||
|     opacity: 0; | ||||
|   } | ||||
|   to { | ||||
|     opacity: 1; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   .tree-ctrl{ | ||||
|     position: relative; | ||||
|     cursor: pointer; | ||||
|     color: $color-blue; | ||||
|     margin-left: -$space-width; | ||||
|   } | ||||
| .tree-ctrl { | ||||
|   position: relative; | ||||
|   cursor: pointer; | ||||
|   color: #2196f3; | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -1,89 +1,220 @@ | ||||
|  | ||||
| - [Enlgish](#Brief) | ||||
|  | ||||
| # 中文 | ||||
|  | ||||
| ## 写在前面 | ||||
| 此组件仅提供一个创建TreeTable的解决思路 | ||||
|  | ||||
| ## prop说明 | ||||
| #### *data* | ||||
|   **必填** | ||||
| 此组件仅提供一个创建 `TreeTable` 的解决思路。它基于`element-ui`的 table 组件实现,通过`el-table`的`row-style`方法,在里面判断元素是否需要隐藏或者显示,从而实现`TreeTable`的展开与收起。 | ||||
|  | ||||
|   原始数据,要求是一个数组或者对象 | ||||
|   ```javascript | ||||
|     [{ | ||||
|       key1: value1, | ||||
|       key2: value2, | ||||
|       children: [{ | ||||
|         key1: value1 | ||||
| 并且本组件充分利用 `vue` 插槽的特性来方便用户自定义。 | ||||
|  | ||||
| `evel.js` 里面,`addAttrs` 方法会给数据添加几个属性,`treeTotable` 会对数组扁平化。这些操作都不会破坏源数据,只是会新增属性。 | ||||
|  | ||||
| ## Props 说明 | ||||
|  | ||||
| |    Attribute     | Description                        |  Type   | Default  | | ||||
| | :--------------: | :--------------------------------- | :-----: | :------: | | ||||
| |       data       | 原始展示数据                       |  Array  |    []    | | ||||
| |     columns      | 列属性                             |  Array  |    []    | | ||||
| | defaultExpandAll | 默认是否全部展开                   | Boolean |  false   | | ||||
| | defaultChildren  | 指定子树为节点对象的某个属性值     | String  | children |  | | ||||
| |      indent      | 相邻级节点间的水平缩进,单位为像素 | Number  |    50    | | ||||
|  | ||||
| > 任何 `el-table` 的属性都支持,例如`border`、`fit`、`size`或者`@select`、`@cell-click`等方法。详情属性见`el-table`文档。 | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### 代码示例 | ||||
|  | ||||
| ```html | ||||
| <tree-table :data="data" :columns="columns" border> | ||||
| ``` | ||||
|  | ||||
| #### data(**必填**) | ||||
|  | ||||
| ```js | ||||
| const data = [ | ||||
|   { | ||||
|     name:'1' | ||||
|     children: [ | ||||
|       { | ||||
|         name: '1-1' | ||||
|       }, | ||||
|       { | ||||
|         key1: value1 | ||||
|       }] | ||||
|     }, | ||||
|     { | ||||
|       key1: value1 | ||||
|     }] | ||||
|   ``` | ||||
|   或者 | ||||
|  ```javascript | ||||
|     { | ||||
|       key1: value1, | ||||
|       key2: value2, | ||||
|       children: [{ | ||||
|         key1: value1 | ||||
|         name: '1-2' | ||||
|       } | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     name: `2` | ||||
|   } | ||||
| ] | ||||
| ``` | ||||
|  | ||||
| #### columns(**必填**) | ||||
|  | ||||
| - label: 显示在表头的文字 | ||||
| - key: 对应 data 的 key。treeTable 将显示相应的 value | ||||
| - expand: `true` or `false`。若为 true,则在该列显示展开收起图标 | ||||
| - checkbox: `true` or `false`。若为 true,则在该列显示`checkbox` | ||||
| - width: 每列的宽度,为一个数字(可选)。例如`200` | ||||
| - align: 对齐方式 `left/center/right` | ||||
| - header-align: 表头对齐方式 `left/center/right` | ||||
|  | ||||
| ```javascript | ||||
| const columns = [ | ||||
|   { | ||||
|     label: 'Checkbox', | ||||
|     checkbox: true | ||||
|   }, | ||||
|   { | ||||
|     label: '', | ||||
|     key: 'id', | ||||
|     expand: true | ||||
|   }, | ||||
|   { | ||||
|     label: 'Event', | ||||
|     key: 'event', | ||||
|     width: 200, | ||||
|     align: 'left' | ||||
|   }, | ||||
|   { | ||||
|     label: 'Scope', | ||||
|     key: 'scope' | ||||
|   } | ||||
| ] | ||||
| ``` | ||||
|  | ||||
| > 树表组件将会根据 columns 的 key 属性生成具名插槽,如果你需要对列数据进行自定义,通过插槽即可实现 | ||||
|  | ||||
| ```html | ||||
| <template slot="your key" slot-scope="{scope}"> | ||||
|   <el-tag>level: {{ scope.row._level }}</el-tag> | ||||
|   <el-tag>expand: {{ scope.row._expand }}</el-tag> | ||||
|   <el-tag>select: {{ scope.row._select }}</el-tag> | ||||
| </template> | ||||
| ``` | ||||
|  | ||||
| ## Events | ||||
|  | ||||
| 目前提供了几个方法,不过只是`beta`版本,之后很可能会修改。 | ||||
|  | ||||
| ```js | ||||
| this.$refs.TreeTable.addChild(row, data) //添加子元素 | ||||
| this.$refs.TreeTable.addBrother(row, data) //添加兄弟元素 | ||||
| this.$refs.TreeTable.delete(row) //删除该元素 | ||||
| ``` | ||||
|  | ||||
| ## 其他 | ||||
|  | ||||
| 如果有其他的需求,请参考[el-table](http://element-cn.eleme.io/#/en-US/component/table)的 api 自行修改 index.vue | ||||
|  | ||||
| # English | ||||
|  | ||||
| ## Brief | ||||
|  | ||||
| This component only provides a solution for creating `TreeTable`. It is based on the `element-ui` table component. It uses the `row-style` method of `el-table` to determine whether the element needs to be hidden or displayed. | ||||
|  | ||||
| And this component makes full use of the features of the `vue` slot to make it user-friendly. | ||||
|  | ||||
| In `evel.js`, the `addAttrs` method adds several properties to the data, and `treeTotable` flattens the array. None of these operations will destroy the source data, just add properties. | ||||
|  | ||||
| ## Props | ||||
|  | ||||
| |    Attribute     | Description                                                  |  Type   | Default  | | ||||
| | :--------------: | :----------------------------------------------------------- | :-----: | :------: | | ||||
| |       data       | original display data                                        |  Array  |    []    | | ||||
| |     columns      | column attribute                                             |  Array  |    []    | | ||||
| | defaultExpandAll | whether to expand all nodes by default                       | Boolean |  false   | | ||||
| | defaultChildren  | specify which node object is used as the node's subtree      | String  | children |  | | ||||
| |      indent      | horizontal indentation of nodes in adjacent levels in pixels | Number  |    50    | | ||||
|  | ||||
| > Any of the `el-table` properties are supported, such as `border`, `fit`, `size` or `@select`, `@cell-click`. See the ʻel-table` documentation for details. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### Example | ||||
|  | ||||
| ```html | ||||
| <tree-table :data="data" :columns="columns" border> | ||||
| ``` | ||||
|  | ||||
| #### data(**Required**) | ||||
|  | ||||
| ```js | ||||
| const data = [ | ||||
|   { | ||||
|     name:'1' | ||||
|     children: [ | ||||
|       { | ||||
|         name: '1-1' | ||||
|       }, | ||||
|       { | ||||
|         key1: value1 | ||||
|       }] | ||||
|     } | ||||
|   ``` | ||||
|         name: '1-2' | ||||
|       } | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     name: `2` | ||||
|   } | ||||
| ] | ||||
| ``` | ||||
|  | ||||
| #### columns | ||||
|   列属性,要求是一个数组 | ||||
| #### columns(**Required**) | ||||
|  | ||||
|   1. text: 显示在表头的文字 | ||||
|   2. value: 对应data的key。treeTable将显示相应的value | ||||
|   3. width: 每列的宽度,为一个数字(可选) | ||||
| - label: text displayed in the header | ||||
| - key: data.key will show in column | ||||
| - expand: `true` or `false` | ||||
| - checkbox: `true` or `false` | ||||
| - width: column width 。such as `200` | ||||
| - align: alignment `left/center/right` | ||||
| - header-align: alignment of the table header `left/center/right` | ||||
|  | ||||
|   如果你想要每个字段都有自定义的样式或者嵌套其他组件,columns可不提供,直接像在el-table一样写即可,如果没有自定义内容,提供columns将更加的便捷方便 | ||||
| ```javascript | ||||
| const columns = [ | ||||
|   { | ||||
|     label: 'Checkbox', | ||||
|     checkbox: true | ||||
|   }, | ||||
|   { | ||||
|     label: '', | ||||
|     key: 'id', | ||||
|     expand: true | ||||
|   }, | ||||
|   { | ||||
|     label: 'Event', | ||||
|     key: 'event', | ||||
|     width: 200, | ||||
|     align: 'left' | ||||
|   }, | ||||
|   { | ||||
|     label: 'Scope', | ||||
|     key: 'scope' | ||||
|   } | ||||
| ] | ||||
| ``` | ||||
|  | ||||
|   如果你有几个字段是需要自定义的,几个不需要,那么可以将不需要自定义的字段放入columns,将需要自定义的内容放入到slot中,详情见后文 | ||||
|   ```javascript | ||||
|   [{ | ||||
|     value:string, | ||||
|     text:string, | ||||
|     width:number | ||||
|   },{ | ||||
|     value:string, | ||||
|     text:string, | ||||
|     width:number | ||||
|   }] | ||||
|   ``` | ||||
| > The tree table component will generate a named slot based on the key property of columns. If you need to customize the column data, you can do it through the slot. | ||||
|  | ||||
| #### expandAll | ||||
|   是否默认全部展开,boolean值,默认为false | ||||
| ```html | ||||
| <template slot="your key" slot-scope="{scope}"> | ||||
|   <el-tag>level: {{ scope.row._level }}</el-tag> | ||||
|   <el-tag>expand: {{ scope.row._expand }}</el-tag> | ||||
|   <el-tag>select: {{ scope.row._select }}</el-tag> | ||||
| </template> | ||||
| ``` | ||||
|  | ||||
| #### evalFunc | ||||
|   解析函数,function,非必须 | ||||
| ## Events | ||||
|  | ||||
|   如果不提供,将使用默认的[evalFunc](./eval.js) | ||||
| Several methods are currently available, but only the `beta` version, which is likely to be modified later. | ||||
|  | ||||
|   如果提供了evalFunc,那么会用提供的evalFunc去解析data,并返回treeTable渲染所需要的值。如何编写一个evalFunc,请参考[*eval.js*](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/components/TreeTable/eval.js)或[*customEval.js*](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customEval.js) | ||||
| ```js | ||||
| this.$refs.TreeTable.addChild(row, data) //Add child elements | ||||
| this.$refs.TreeTable.addBrother(row, data) //Add a sibling element | ||||
| this.$refs.TreeTable.delete(row) //Delete the element | ||||
| ``` | ||||
|  | ||||
| #### evalArgs | ||||
|   解析函数的参数,是一个数组 | ||||
| ## Other | ||||
|  | ||||
|   **请注意,自定义的解析函数参数第一个为this.data,第二个参数为, this.expandAll,你不需要在evalArgs填写。一定记住,这两个参数是强制性的,并且位置不可颠倒** *this.data为需要解析的数据,this.expandAll为是否默认展开* | ||||
|  | ||||
|   如你的解析函数需要的参数为`(this.data, this.expandAll,1,2,3,4)`,那么你只需要将`[1,2,3,4]`赋值给`evalArgs`就可以了 | ||||
|  | ||||
|   如果你的解析函数参数只有`(this.data, this.expandAll)`,那么就可以不用填写evalArgs了 | ||||
|  | ||||
|   具体可参考[*customEval.js*](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customEval.js)的函数参数和[customTreeTable](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customTreeTable.vue)的`evalArgs`属性值 | ||||
|  | ||||
|  ## slot | ||||
|  这是一个自定义列的插槽。 | ||||
|  | ||||
|  默认情况下,treeTable只有一行行展示数据的功能。但是一般情况下,我们会要给行加上一个操作按钮或者根据当行数据展示不同的样式,这时我们就需要自定义列了。请参考[customTreeTable](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customTreeTable.vue),[实例效果](https://panjiachen.github.io/vue-element-admin/#/table/tree-table) | ||||
|  | ||||
|  `slot`和`columns属性`可同时存在,columns里面的数据列会在slot自定义列的左边展示 | ||||
|  | ||||
|  ## 其他 | ||||
|   如果有其他的需求,请参考[el-table](http://element-cn.eleme.io/#/en-US/component/table)的api自行修改index.vue | ||||
| If you have other requirements, please refer to the [el-table](http://element-cn.eleme.io/#/en-US/component/table) api to modify the index.vue | ||||
|   | ||||
							
								
								
									
										42
									
								
								src/directive/el-table/adaptive.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/directive/el-table/adaptive.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
|  | ||||
| import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event' | ||||
|  | ||||
| /** | ||||
|  * How to use | ||||
|  * <el-table height="100px" v-el-height-adaptive-table="{bottomOffset: 30}">...</el-table> | ||||
|  * el-table height is must be set | ||||
|  *  bottomOffset: 30(default)   // The height of the table from the bottom of the page. | ||||
|  */ | ||||
|  | ||||
| const doResize = (el, binding, vnode) => { | ||||
|   const { componentInstance: $table } = vnode | ||||
|  | ||||
|   const { value } = binding | ||||
|  | ||||
|   if (!$table.height) { | ||||
|     throw new Error(`el-$table must set the height. Such as height='100px'`) | ||||
|   } | ||||
|   const bottomOffset = (value && value.bottomOffset) || 30 | ||||
|  | ||||
|   if (!$table) return | ||||
|  | ||||
|   const height = window.innerHeight - el.getBoundingClientRect().top - bottomOffset | ||||
|   $table.layout.setHeight(height) | ||||
|   $table.doLayout() | ||||
| } | ||||
|  | ||||
| export default { | ||||
|   bind(el, binding, vnode) { | ||||
|     el.resizeListener = () => { | ||||
|       doResize(el, binding, vnode) | ||||
|     } | ||||
|  | ||||
|     addResizeListener(el, el.resizeListener) | ||||
|   }, | ||||
|   inserted(el, binding, vnode) { | ||||
|     doResize(el, binding, vnode) | ||||
|   }, | ||||
|   unbind(el) { | ||||
|     removeResizeListener(el, el.resizeListener) | ||||
|   } | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/directive/el-table/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/directive/el-table/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
|  | ||||
| import adaptive from './adaptive' | ||||
|  | ||||
| const install = function(Vue) { | ||||
|   Vue.directive('el-height-adaptive-table', adaptive) | ||||
| } | ||||
|  | ||||
| if (window.Vue) { | ||||
|   window['el-height-adaptive-table'] = adaptive | ||||
|   Vue.use(install); // eslint-disable-line | ||||
| } | ||||
|  | ||||
| adaptive.install = install | ||||
| export default adaptive | ||||
| @@ -1,42 +1,73 @@ | ||||
| import './waves.css' | ||||
|  | ||||
| export default { | ||||
|   bind(el, binding) { | ||||
|     el.addEventListener('click', e => { | ||||
|       const customOpts = Object.assign({}, binding.value) | ||||
|       const opts = Object.assign({ | ||||
| const context = '@@wavesContext' | ||||
|  | ||||
| function handleClick(el, binding) { | ||||
|   function handle(e) { | ||||
|     const customOpts = Object.assign({}, binding.value) | ||||
|     const opts = Object.assign( | ||||
|       { | ||||
|         ele: el, // 波纹作用元素 | ||||
|         type: 'hit', // hit 点击位置扩散 center中心点扩展 | ||||
|         color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色 | ||||
|       }, customOpts) | ||||
|       const target = opts.ele | ||||
|       if (target) { | ||||
|         target.style.position = 'relative' | ||||
|         target.style.overflow = 'hidden' | ||||
|         const rect = target.getBoundingClientRect() | ||||
|         let ripple = target.querySelector('.waves-ripple') | ||||
|         if (!ripple) { | ||||
|           ripple = document.createElement('span') | ||||
|           ripple.className = 'waves-ripple' | ||||
|           ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px' | ||||
|           target.appendChild(ripple) | ||||
|         } else { | ||||
|           ripple.className = 'waves-ripple' | ||||
|         } | ||||
|         switch (opts.type) { | ||||
|           case 'center': | ||||
|             ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px' | ||||
|             ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px' | ||||
|             break | ||||
|           default: | ||||
|             ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop || document.body.scrollTop) + 'px' | ||||
|             ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft || document.body.scrollLeft) + 'px' | ||||
|         } | ||||
|         ripple.style.backgroundColor = opts.color | ||||
|         ripple.className = 'waves-ripple z-active' | ||||
|         return false | ||||
|       }, | ||||
|       customOpts | ||||
|     ) | ||||
|     const target = opts.ele | ||||
|     if (target) { | ||||
|       target.style.position = 'relative' | ||||
|       target.style.overflow = 'hidden' | ||||
|       const rect = target.getBoundingClientRect() | ||||
|       let ripple = target.querySelector('.waves-ripple') | ||||
|       if (!ripple) { | ||||
|         ripple = document.createElement('span') | ||||
|         ripple.className = 'waves-ripple' | ||||
|         ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px' | ||||
|         target.appendChild(ripple) | ||||
|       } else { | ||||
|         ripple.className = 'waves-ripple' | ||||
|       } | ||||
|     }, false) | ||||
|       switch (opts.type) { | ||||
|         case 'center': | ||||
|           ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px' | ||||
|           ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px' | ||||
|           break | ||||
|         default: | ||||
|           ripple.style.top = | ||||
|             (e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop || | ||||
|               document.body.scrollTop) + 'px' | ||||
|           ripple.style.left = | ||||
|             (e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft || | ||||
|               document.body.scrollLeft) + 'px' | ||||
|       } | ||||
|       ripple.style.backgroundColor = opts.color | ||||
|       ripple.className = 'waves-ripple z-active' | ||||
|       return false | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (!el[context]) { | ||||
|     el[context] = { | ||||
|       removeHandle: handle | ||||
|     } | ||||
|   } else { | ||||
|     el[context].removeHandle = handle | ||||
|   } | ||||
|  | ||||
|   return handle | ||||
| } | ||||
|  | ||||
| export default { | ||||
|   bind(el, binding) { | ||||
|     el.addEventListener('click', handleClick(el, binding), false) | ||||
|   }, | ||||
|   update(el, binding) { | ||||
|     el.removeEventListener('click', el[context].removeHandle, false) | ||||
|     el.addEventListener('click', handleClick(el, binding), false) | ||||
|   }, | ||||
|   unbind(el) { | ||||
|     el.removeEventListener('click', el[context].removeHandle, false) | ||||
|     el[context] = null | ||||
|     delete el[context] | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										1
									
								
								src/icons/svg/tree-table.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/icons/svg/tree-table.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M44.8 0h79.543C126.78 0 128 1.422 128 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H44.8c-2.438 0-3.657-1.422-3.657-4.267V4.267C41.143 1.422 42.362 0 44.8 0zm22.857 48h56.686c2.438 0 3.657 1.422 3.657 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H67.657C65.22 80 64 78.578 64 75.733V52.267C64 49.422 65.219 48 67.657 48zm0 48h56.686c2.438 0 3.657 1.422 3.657 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H67.657C65.22 128 64 126.578 64 123.733v-23.466C64 97.422 65.219 96 67.657 96zM50.286 68.267c2.02 0 3.657-1.91 3.657-4.267 0-2.356-1.638-4.267-3.657-4.267H17.37V32h6.4c2.02 0 3.658-1.91 3.658-4.267V4.267C27.429 1.91 25.79 0 23.77 0H3.657C1.637 0 0 1.91 0 4.267v23.466C0 30.09 1.637 32 3.657 32h6.4v80c0 2.356 1.638 4.267 3.657 4.267h36.572c2.02 0 3.657-1.91 3.657-4.267 0-2.356-1.638-4.267-3.657-4.267H17.37V68.267h32.915z"/></svg> | ||||
| After Width: | Height: | Size: 906 B | 
| @@ -6,6 +6,7 @@ export default { | ||||
|     guide: 'Guide', | ||||
|     permission: 'Permission', | ||||
|     pagePermission: 'Page Permission', | ||||
|     rolePermission: 'Role Permission', | ||||
|     directivePermission: 'Directive Permission', | ||||
|     icons: 'Icons', | ||||
|     components: 'Components', | ||||
| @@ -56,6 +57,7 @@ export default { | ||||
|     excel: 'Excel', | ||||
|     exportExcel: 'Export Excel', | ||||
|     selectExcel: 'Export Selected', | ||||
|     mergeHeader: 'Merge Header', | ||||
|     uploadExcel: 'Upload Excel', | ||||
|     zip: 'Zip', | ||||
|     pdf: 'PDF', | ||||
| @@ -86,9 +88,14 @@ export default { | ||||
|     github: 'Github Repository' | ||||
|   }, | ||||
|   permission: { | ||||
|     addRole: 'New Role', | ||||
|     editPermission: 'Edit Permission', | ||||
|     roles: 'Your roles', | ||||
|     switchRoles: 'Switch roles', | ||||
|     tips: 'In some cases it is not suitable to use v-permission, such as element Tab component or el-table-column and other asynchronous rendering dom cases which can only be achieved by manually setting the v-if.' | ||||
|     tips: 'In some cases it is not suitable to use v-permission, such as element Tab component or el-table-column and other asynchronous rendering dom cases which can only be achieved by manually setting the v-if.', | ||||
|     delete: 'Delete', | ||||
|     confirm: 'Confirm', | ||||
|     cancel: 'Cancel' | ||||
|   }, | ||||
|   guide: { | ||||
|     description: 'The guide page is useful for some people who entered the project for the first time. You can briefly introduce the features of the project. Demo is based on ', | ||||
|   | ||||
| @@ -5,6 +5,7 @@ export default { | ||||
|     documentation: 'Documentación', | ||||
|     guide: 'Guía', | ||||
|     permission: 'Permisos', | ||||
|     rolePermission: 'Permisos de rol', | ||||
|     pagePermission: 'Permisos de la página', | ||||
|     directivePermission: 'Permisos de la directiva', | ||||
|     icons: 'Iconos', | ||||
| @@ -56,6 +57,7 @@ export default { | ||||
|     excel: 'Excel', | ||||
|     exportExcel: 'Exportar a Excel', | ||||
|     selectExcel: 'Export seleccionado', | ||||
|     mergeHeader: 'Merge Header', | ||||
|     uploadExcel: 'Subir Excel', | ||||
|     zip: 'Zip', | ||||
|     pdf: 'PDF', | ||||
| @@ -86,9 +88,14 @@ export default { | ||||
|     github: 'Repositorio Github' | ||||
|   }, | ||||
|   permission: { | ||||
|     addRole: 'Nuevo rol', | ||||
|     editPermission: 'Permiso de edición', | ||||
|     roles: 'Tus permisos', | ||||
|     switchRoles: 'Cambiar permisos', | ||||
|     tips: 'In some cases it is not suitable to use v-permission, such as element Tab component or el-table-column and other asynchronous rendering dom cases which can only be achieved by manually setting the v-if.' | ||||
|     tips: 'In some cases it is not suitable to use v-permission, such as element Tab component or el-table-column and other asynchronous rendering dom cases which can only be achieved by manually setting the v-if.', | ||||
|     delete: 'Borrar', | ||||
|     confirm: 'Confirmar', | ||||
|     cancel: 'Cancelar' | ||||
|   }, | ||||
|   guide: { | ||||
|     description: 'The guide page is useful for some people who entered the project for the first time. You can briefly introduce the features of the project. Demo is based on ', | ||||
|   | ||||
| @@ -5,6 +5,7 @@ export default { | ||||
|     documentation: '文档', | ||||
|     guide: '引导页', | ||||
|     permission: '权限测试页', | ||||
|     rolePermission: '角色权限', | ||||
|     pagePermission: '页面权限', | ||||
|     directivePermission: '指令权限', | ||||
|     icons: '图标', | ||||
| @@ -54,9 +55,10 @@ export default { | ||||
|     page404: '404', | ||||
|     errorLog: '错误日志', | ||||
|     excel: 'Excel', | ||||
|     exportExcel: 'Export Excel', | ||||
|     selectExcel: 'Export Selected', | ||||
|     uploadExcel: 'Upload Excel', | ||||
|     exportExcel: '导出 Excel', | ||||
|     selectExcel: '导出 已选择项', | ||||
|     mergeHeader: '导出 多级表头', | ||||
|     uploadExcel: '上传 Excel', | ||||
|     zip: 'Zip', | ||||
|     pdf: 'PDF', | ||||
|     exportZip: 'Export Zip', | ||||
| @@ -86,9 +88,14 @@ export default { | ||||
|     github: 'Github 地址' | ||||
|   }, | ||||
|   permission: { | ||||
|     addRole: '新增角色', | ||||
|     editPermission: '编辑权限', | ||||
|     roles: '你的权限', | ||||
|     switchRoles: '切换权限', | ||||
|     tips: '在某些情况下,不适合使用 v-permission。例如:Element-UI 的 Tab 组件或 el-table-column 以及其它动态渲染 dom 的场景。你只能通过手动设置 v-if 来实现。' | ||||
|     tips: '在某些情况下,不适合使用 v-permission。例如:Element-UI 的 Tab 组件或 el-table-column 以及其它动态渲染 dom 的场景。你只能通过手动设置 v-if 来实现。', | ||||
|     delete: '删除', | ||||
|     confirm: '确定', | ||||
|     cancel: '取消' | ||||
|   }, | ||||
|   guide: { | ||||
|     description: '引导页对于一些第一次进入项目的人很有用,你可以简单介绍下项目的功能。本 Demo 是基于', | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import loginAPI from './login' | ||||
| import articleAPI from './article' | ||||
| import remoteSearchAPI from './remoteSearch' | ||||
| import transactionAPI from './transaction' | ||||
| import roleAPI from './role' | ||||
|  | ||||
| // 修复在使用 MockJS 情况下,设置 withCredentials = true,且未被拦截的跨域请求丢失 Cookies 的问题 | ||||
| // https://github.com/nuysoft/Mock/issues/300 | ||||
| @@ -23,6 +24,13 @@ Mock.mock(/\/login\/login/, 'post', loginAPI.loginByUsername) | ||||
| Mock.mock(/\/login\/logout/, 'post', loginAPI.logout) | ||||
| Mock.mock(/\/user\/info\.*/, 'get', loginAPI.getUserInfo) | ||||
|  | ||||
| // 角色相关 | ||||
| Mock.mock(/\/routes/, 'get', roleAPI.getRoutes) | ||||
| Mock.mock(/\/roles/, 'get', roleAPI.getRoles) | ||||
| Mock.mock(/\/roles$/, 'post', roleAPI.addRole) | ||||
| Mock.mock(/\/roles\/[A-Za-z0-9]+/, 'put', roleAPI.updateRole) | ||||
| Mock.mock(/\/roles\/[A-Za-z0-9]+/, 'delete', roleAPI.deleteRole) | ||||
|  | ||||
| // 文章相关 | ||||
| Mock.mock(/\/article\/list/, 'get', articleAPI.getList) | ||||
| Mock.mock(/\/article\/detail/, 'get', articleAPI.getArticle) | ||||
|   | ||||
							
								
								
									
										61
									
								
								src/mock/role.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/mock/role.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| import Mock from 'mockjs' | ||||
| import { deepClone } from '@/utils' | ||||
| import { filterAsyncRoutes } from '@/store/modules/permission' | ||||
| import { asyncRoutes, constantRoutes } from '@/router' | ||||
|  | ||||
| const routes = deepClone([...constantRoutes, ...asyncRoutes]) | ||||
|  | ||||
| const roles = [ | ||||
|   { | ||||
|     key: 'admin', | ||||
|     name: 'admin', | ||||
|     description: 'Super Administrator. Have access to view all pages.', | ||||
|     routes: routes | ||||
|   }, | ||||
|   { | ||||
|     key: 'editor', | ||||
|     name: 'editor', | ||||
|     description: 'Normal Editor. Can see all pages except permission page', | ||||
|     routes: filterAsyncRoutes(routes, ['editor']) | ||||
|   }, | ||||
|   { | ||||
|     key: 'visitor', | ||||
|     name: 'visitor', | ||||
|     description: 'Just a visitor. Can only see the home page and the document page', | ||||
|     routes: [{ | ||||
|       path: '', | ||||
|       redirect: 'dashboard', | ||||
|       children: [ | ||||
|         { | ||||
|           path: 'dashboard', | ||||
|           name: 'Dashboard', | ||||
|           meta: { title: 'dashboard', icon: 'dashboard' } | ||||
|         } | ||||
|       ] | ||||
|     }] | ||||
|   } | ||||
| ] | ||||
|  | ||||
| export default { | ||||
|   getRoutes() { | ||||
|     return routes | ||||
|   }, | ||||
|   getRoles() { | ||||
|     return roles | ||||
|   }, | ||||
|   addRole() { | ||||
|     return Mock.mock('@integer(300, 5000)') | ||||
|   }, | ||||
|   updateRole() { | ||||
|     const res = { | ||||
|       data: 'success' | ||||
|     } | ||||
|     return res | ||||
|   }, | ||||
|   deleteRole() { | ||||
|     const res = { | ||||
|       data: 'success' | ||||
|     } | ||||
|     return res | ||||
|   } | ||||
| } | ||||
| @@ -2,41 +2,49 @@ import router from './router' | ||||
| import store from './store' | ||||
| import { Message } from 'element-ui' | ||||
| import NProgress from 'nprogress' // progress bar | ||||
| import 'nprogress/nprogress.css'// progress bar style | ||||
| import { getToken } from '@/utils/auth' // getToken from cookie | ||||
| import 'nprogress/nprogress.css' // progress bar style | ||||
| import { getToken } from '@/utils/auth' // get token from cookie | ||||
|  | ||||
| NProgress.configure({ showSpinner: false })// NProgress Configuration | ||||
| NProgress.configure({ showSpinner: false }) // NProgress Configuration | ||||
|  | ||||
| // permission judge function | ||||
| function hasPermission(roles, permissionRoles) { | ||||
|   if (roles.indexOf('admin') >= 0) return true // admin permission passed directly | ||||
|   if (roles.includes('admin')) return true // admin permission passed directly | ||||
|   if (!permissionRoles) return true | ||||
|   return roles.some(role => permissionRoles.indexOf(role) >= 0) | ||||
| } | ||||
|  | ||||
| const whiteList = ['/login', '/auth-redirect']// no redirect whitelist | ||||
| const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist | ||||
|  | ||||
| router.beforeEach((to, from, next) => { | ||||
|   NProgress.start() // start progress bar | ||||
|   if (getToken()) { // determine if there has token | ||||
|   if (getToken()) { | ||||
|     // determine if there has token | ||||
|  | ||||
|     /* has token*/ | ||||
|     if (to.path === '/login') { | ||||
|       next({ path: '/' }) | ||||
|       NProgress.done() // if current page is dashboard will not trigger	afterEach hook, so manually handle it | ||||
|     } else { | ||||
|       if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息 | ||||
|         store.dispatch('GetUserInfo').then(res => { // 拉取user_info | ||||
|           const roles = res.data.roles // note: roles must be a array! such as: ['editor','develop'] | ||||
|           store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表 | ||||
|             router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表 | ||||
|             next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record | ||||
|       if (store.getters.roles.length === 0) { | ||||
|         // 判断当前用户是否已拉取完user_info信息 | ||||
|         store | ||||
|           .dispatch('GetUserInfo') | ||||
|           .then(res => { | ||||
|             // 拉取user_info | ||||
|             const roles = res.data.roles // note: roles must be a object array! such as: [{id: '1', name: 'editor'}, {id: '2', name: 'developer'}] | ||||
|             store.dispatch('GenerateRoutes', { roles }).then(accessRoutes => { | ||||
|               // 根据roles权限生成可访问的路由表 | ||||
|               router.addRoutes(accessRoutes) // 动态添加可访问路由表 | ||||
|               next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record | ||||
|             }) | ||||
|           }) | ||||
|         }).catch((err) => { | ||||
|           store.dispatch('FedLogOut').then(() => { | ||||
|             Message.error(err) | ||||
|             next({ path: '/' }) | ||||
|           .catch(err => { | ||||
|             store.dispatch('FedLogOut').then(() => { | ||||
|               Message.error(err) | ||||
|               next({ path: '/' }) | ||||
|             }) | ||||
|           }) | ||||
|         }) | ||||
|       } else { | ||||
|         // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓ | ||||
|         if (hasPermission(store.getters.roles, to.meta.roles)) { | ||||
| @@ -49,7 +57,8 @@ router.beforeEach((to, from, next) => { | ||||
|     } | ||||
|   } else { | ||||
|     /* has no token*/ | ||||
|     if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入 | ||||
|     if (whiteList.indexOf(to.path) !== -1) { | ||||
|       // 在免登录白名单,直接进入 | ||||
|       next() | ||||
|     } else { | ||||
|       next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页 | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import Layout from '@/views/layout/Layout' | ||||
| import componentsRouter from './modules/components' | ||||
| import chartsRouter from './modules/charts' | ||||
| import tableRouter from './modules/table' | ||||
| import treeTableRouter from './modules/tree-table' | ||||
| import nestedRouter from './modules/nested' | ||||
|  | ||||
| /** note: sub-menu only appear when children.length>=1 | ||||
| @@ -32,7 +33,7 @@ import nestedRouter from './modules/nested' | ||||
|     affix: true                  if true, the tag will affix in the tags-view | ||||
|   } | ||||
| **/ | ||||
| export const constantRouterMap = [ | ||||
| export const constantRoutes = [ | ||||
|   { | ||||
|     path: '/redirect', | ||||
|     component: Layout, | ||||
| @@ -80,7 +81,6 @@ export const constantRouterMap = [ | ||||
|   { | ||||
|     path: '/documentation', | ||||
|     component: Layout, | ||||
|     redirect: '/documentation/index', | ||||
|     children: [ | ||||
|       { | ||||
|         path: 'index', | ||||
| @@ -108,10 +108,10 @@ export const constantRouterMap = [ | ||||
| export default new Router({ | ||||
|   // mode: 'history', // require service support | ||||
|   scrollBehavior: () => ({ y: 0 }), | ||||
|   routes: constantRouterMap | ||||
|   routes: constantRoutes | ||||
| }) | ||||
|  | ||||
| export const asyncRouterMap = [ | ||||
| export const asyncRoutes = [ | ||||
|   { | ||||
|     path: '/permission', | ||||
|     component: Layout, | ||||
| @@ -140,6 +140,15 @@ export const asyncRouterMap = [ | ||||
|           title: 'directivePermission' | ||||
|           // if do not set roles, means: this page does not require permission | ||||
|         } | ||||
|       }, | ||||
|       { | ||||
|         path: 'role', | ||||
|         component: () => import('@/views/permission/role'), | ||||
|         name: 'RolePermission', | ||||
|         meta: { | ||||
|           title: 'rolePermission', | ||||
|           roles: ['admin'] | ||||
|         } | ||||
|       } | ||||
|     ] | ||||
|   }, | ||||
| @@ -162,6 +171,7 @@ export const asyncRouterMap = [ | ||||
|   chartsRouter, | ||||
|   nestedRouter, | ||||
|   tableRouter, | ||||
|   treeTableRouter, | ||||
|  | ||||
|   { | ||||
|     path: '/example', | ||||
| @@ -269,6 +279,12 @@ export const asyncRouterMap = [ | ||||
|         name: 'SelectExcel', | ||||
|         meta: { title: 'selectExcel' } | ||||
|       }, | ||||
|       { | ||||
|         path: 'export-merge-header', | ||||
|         component: () => import('@/views/excel/mergeHeader'), | ||||
|         name: 'MergeHeader', | ||||
|         meta: { title: 'mergeHeader' } | ||||
|       }, | ||||
|       { | ||||
|         path: 'upload-excel', | ||||
|         component: () => import('@/views/excel/uploadExcel'), | ||||
|   | ||||
| @@ -30,18 +30,6 @@ const tableRouter = { | ||||
|       name: 'InlineEditTable', | ||||
|       meta: { title: 'inlineEditTable' } | ||||
|     }, | ||||
|     { | ||||
|       path: 'tree-table', | ||||
|       component: () => import('@/views/table/treeTable/treeTable'), | ||||
|       name: 'TreeTableDemo', | ||||
|       meta: { title: 'treeTable' } | ||||
|     }, | ||||
|     { | ||||
|       path: 'custom-tree-table', | ||||
|       component: () => import('@/views/table/treeTable/customTreeTable'), | ||||
|       name: 'CustomTreeTableDemo', | ||||
|       meta: { title: 'customTreeTable' } | ||||
|     }, | ||||
|     { | ||||
|       path: 'complex-table', | ||||
|       component: () => import('@/views/table/complexTable'), | ||||
|   | ||||
							
								
								
									
										29
									
								
								src/router/modules/tree-table.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/router/modules/tree-table.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| /** When your routing table is too long, you can split it into small modules**/ | ||||
|  | ||||
| import Layout from '@/views/layout/Layout' | ||||
|  | ||||
| const treeTableRouter = { | ||||
|   path: '/tree-table', | ||||
|   component: Layout, | ||||
|   redirect: '/table/complex-table', | ||||
|   name: 'TreeTable', | ||||
|   meta: { | ||||
|     title: 'treeTable', | ||||
|     icon: 'tree-table' | ||||
|   }, | ||||
|   children: [ | ||||
|     { | ||||
|       path: 'index', | ||||
|       component: () => import('@/views/tree-table/index'), | ||||
|       name: 'TreeTableDemo', | ||||
|       meta: { title: 'treeTable' } | ||||
|     }, | ||||
|     { | ||||
|       path: 'custom', | ||||
|       component: () => import('@/views/tree-table/custom'), | ||||
|       name: 'CustomTreeTableDemo', | ||||
|       meta: { title: 'customTreeTable' } | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| export default treeTableRouter | ||||
| @@ -12,8 +12,8 @@ const getters = { | ||||
|   status: state => state.user.status, | ||||
|   roles: state => state.user.roles, | ||||
|   setting: state => state.user.setting, | ||||
|   permission_routers: state => state.permission.routers, | ||||
|   addRouters: state => state.permission.addRouters, | ||||
|   permission_routes: state => state.permission.routes, | ||||
|   addRoutes: state => state.permission.addRoutes, | ||||
|   errorLogs: state => state.errorLog.logs | ||||
| } | ||||
| export default getters | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { asyncRouterMap, constantRouterMap } from '@/router' | ||||
| import { asyncRoutes, constantRoutes } from '@/router' | ||||
|  | ||||
| /** | ||||
|  * 通过meta.role判断是否与当前用户权限匹配 | ||||
| @@ -15,17 +15,17 @@ function hasPermission(roles, route) { | ||||
|  | ||||
| /** | ||||
|  * 递归过滤异步路由表,返回符合用户角色权限的路由表 | ||||
|  * @param routes asyncRouterMap | ||||
|  * @param routes asyncRoutes | ||||
|  * @param roles | ||||
|  */ | ||||
| function filterAsyncRouter(routes, roles) { | ||||
| export function filterAsyncRoutes(routes, roles) { | ||||
|   const res = [] | ||||
|  | ||||
|   routes.forEach(route => { | ||||
|     const tmp = { ...route } | ||||
|     if (hasPermission(roles, tmp)) { | ||||
|       if (tmp.children) { | ||||
|         tmp.children = filterAsyncRouter(tmp.children, roles) | ||||
|         tmp.children = filterAsyncRoutes(tmp.children, roles) | ||||
|       } | ||||
|       res.push(tmp) | ||||
|     } | ||||
| @@ -36,27 +36,27 @@ function filterAsyncRouter(routes, roles) { | ||||
|  | ||||
| const permission = { | ||||
|   state: { | ||||
|     routers: [], | ||||
|     addRouters: [] | ||||
|     routes: [], | ||||
|     addRoutes: [] | ||||
|   }, | ||||
|   mutations: { | ||||
|     SET_ROUTERS: (state, routers) => { | ||||
|       state.addRouters = routers | ||||
|       state.routers = constantRouterMap.concat(routers) | ||||
|     SET_ROUTES: (state, routes) => { | ||||
|       state.addRoutes = routes | ||||
|       state.routes = constantRoutes.concat(routes) | ||||
|     } | ||||
|   }, | ||||
|   actions: { | ||||
|     GenerateRoutes({ commit }, data) { | ||||
|       return new Promise(resolve => { | ||||
|         const { roles } = data | ||||
|         let accessedRouters | ||||
|         let accessedRoutes | ||||
|         if (roles.includes('admin')) { | ||||
|           accessedRouters = asyncRouterMap | ||||
|           accessedRoutes = asyncRoutes | ||||
|         } else { | ||||
|           accessedRouters = filterAsyncRouter(asyncRouterMap, roles) | ||||
|           accessedRoutes = filterAsyncRoutes(asyncRoutes, roles) | ||||
|         } | ||||
|         commit('SET_ROUTERS', accessedRouters) | ||||
|         resolve() | ||||
|         commit('SET_ROUTES', accessedRoutes) | ||||
|         resolve(accessedRoutes) | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -83,19 +83,26 @@ | ||||
|  | ||||
|   .hideSidebar { | ||||
|     .sidebar-container { | ||||
|       width: 36px !important; | ||||
|       width: 54px !important; | ||||
|     } | ||||
|  | ||||
|     .main-container { | ||||
|       margin-left: 36px; | ||||
|       margin-left: 54px; | ||||
|     } | ||||
|  | ||||
|     .svg-icon { | ||||
|       margin-right: 0px; | ||||
|     } | ||||
|  | ||||
|     .submenu-title-noDropdown { | ||||
|       padding-left: 10px !important; | ||||
|       padding: 0 !important; | ||||
|       position: relative; | ||||
|  | ||||
|       .el-tooltip { | ||||
|         padding: 0 10px !important; | ||||
|         padding: 0 !important; | ||||
|         .svg-icon { | ||||
|           margin-left: 20px; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
| @@ -103,7 +110,10 @@ | ||||
|       overflow: hidden; | ||||
|  | ||||
|       &>.el-submenu__title { | ||||
|         padding-left: 10px !important; | ||||
|         padding: 0 !important; | ||||
|         .svg-icon { | ||||
|           margin-left: 20px; | ||||
|         } | ||||
|  | ||||
|         .el-submenu__icon-arrow { | ||||
|           display: none; | ||||
|   | ||||
| @@ -19,9 +19,10 @@ $menuHover:#263445; | ||||
| $subMenuBg:#1f2d3d; | ||||
| $subMenuHover:#001528; | ||||
|  | ||||
| $sideBarWidth: 180px; | ||||
| $sideBarWidth: 210px; | ||||
|  | ||||
| // the :export directive is the magic sauce for webpack | ||||
| // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass | ||||
| :export { | ||||
|   menuText: $menuText; | ||||
|   menuActiveText: $menuActiveText; | ||||
|   | ||||
| @@ -136,7 +136,8 @@ export function param2Obj(url) { | ||||
|       decodeURIComponent(search) | ||||
|         .replace(/"/g, '\\"') | ||||
|         .replace(/&/g, '","') | ||||
|         .replace(/=/g, '":"') + | ||||
|         .replace(/=/g, '":"') | ||||
|         .replace(/\+/g, ' ') + | ||||
|       '"}' | ||||
|   ) | ||||
| } | ||||
|   | ||||
							
								
								
									
										14
									
								
								src/vendor/Export2Excel.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								src/vendor/Export2Excel.js
									
									
									
									
										vendored
									
									
								
							| @@ -145,9 +145,11 @@ export function export_table_to_excel(id) { | ||||
| } | ||||
|  | ||||
| export function export_json_to_excel({ | ||||
|   multiHeader, | ||||
|   header, | ||||
|   data, | ||||
|   filename, | ||||
|   merges, | ||||
|   autoWidth = true, | ||||
|   bookType=  'xlsx' | ||||
| } = {}) { | ||||
| @@ -155,10 +157,22 @@ export function export_json_to_excel({ | ||||
|   filename = filename || 'excel-list' | ||||
|   data = [...data] | ||||
|   data.unshift(header); | ||||
|  | ||||
|   for (let header of multiHeader) { | ||||
|     data.unshift(header) | ||||
|   } | ||||
|  | ||||
|   var ws_name = "SheetJS"; | ||||
|   var wb = new Workbook(), | ||||
|     ws = sheet_from_array_of_arrays(data); | ||||
|  | ||||
|   if (merges.length > 0) { | ||||
|     if (!ws['!merges']) ws['!merges'] = []; | ||||
|     merges.forEach(item => { | ||||
|       ws['!merges'].push(XLSX.utils.decode_range(item)) | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   if (autoWidth) { | ||||
|     /*设置worksheet每列的最大宽度*/ | ||||
|     const colWidth = data.map(row => row.map(val => { | ||||
|   | ||||
| @@ -46,12 +46,10 @@ | ||||
| <script> | ||||
| import { fetchList } from '@/api/article' | ||||
| import { parseTime } from '@/utils' | ||||
|  | ||||
| // options components | ||||
| import FilenameOption from './components/FilenameOption' | ||||
| import AutoWidthOption from './components/AutoWidthOption' | ||||
| import BookTypeOption from './components/BookTypeOption' | ||||
|  | ||||
| export default { | ||||
|   name: 'ExportExcel', | ||||
|   components: { FilenameOption, AutoWidthOption, BookTypeOption }, | ||||
| @@ -114,4 +112,3 @@ export default { | ||||
|   padding: 0 12px 0 30px; | ||||
| } | ||||
| </style> | ||||
|  | ||||
|   | ||||
							
								
								
									
										101
									
								
								src/views/excel/mergeHeader.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/views/excel/mergeHeader.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| <template> | ||||
|   <div class="app-container"> | ||||
|  | ||||
|     <el-button :loading="downloadLoading" style="margin-bottom:20px" type="primary" icon="document" @click="handleDownload">Export</el-button> | ||||
|  | ||||
|     <el-table | ||||
|       ref="multipleTable" | ||||
|       v-loading="listLoading" | ||||
|       :data="list" | ||||
|       element-loading-text="Loading" | ||||
|       border | ||||
|       fit | ||||
|       highlight-current-row | ||||
|     > | ||||
|       <el-table-column align="center" label="Id" width="95"> | ||||
|         <template slot-scope="scope"> | ||||
|           {{ scope.$index }} | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column label="Main Information" align="center"> | ||||
|         <el-table-column label="Title"> | ||||
|           <template slot-scope="scope"> | ||||
|             {{ scope.row.title }} | ||||
|           </template> | ||||
|         </el-table-column> | ||||
|         <el-table-column label="Author" width="110" align="center"> | ||||
|           <template slot-scope="scope"> | ||||
|             <el-tag>{{ scope.row.author }}</el-tag> | ||||
|           </template> | ||||
|         </el-table-column> | ||||
|         <el-table-column label="Readings" width="115" align="center"> | ||||
|           <template slot-scope="scope"> | ||||
|             {{ scope.row.pageviews }} | ||||
|           </template> | ||||
|         </el-table-column> | ||||
|       </el-table-column> | ||||
|       <el-table-column align="center" label="Date" width="220"> | ||||
|         <template slot-scope="scope"> | ||||
|           <i class="el-icon-time" /> | ||||
|           <span>{{ scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|     </el-table> | ||||
|  | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { fetchList } from '@/api/article' | ||||
| import { parseTime } from '@/utils' | ||||
|  | ||||
| export default { | ||||
|   name: 'MergeHeader', | ||||
|   data() { | ||||
|     return { | ||||
|       list: null, | ||||
|       listLoading: true, | ||||
|       downloadLoading: false | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     this.fetchData() | ||||
|   }, | ||||
|   methods: { | ||||
|     fetchData() { | ||||
|       this.listLoading = true | ||||
|       fetchList(this.listQuery).then(response => { | ||||
|         this.list = response.data.items | ||||
|         this.listLoading = false | ||||
|       }) | ||||
|     }, | ||||
|     handleDownload() { | ||||
|       this.downloadLoading = true | ||||
|         import('@/vendor/Export2Excel').then(excel => { | ||||
|           const multiHeader = [['Id', 'Main Information', '', '', 'Date']] | ||||
|           const header = ['', 'Title', 'Author', 'Readings', ''] | ||||
|           const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time'] | ||||
|           const list = this.list | ||||
|           const data = this.formatJson(filterVal, list) | ||||
|           const merges = ['A1:A2', 'B1:D1', 'E1:E2'] | ||||
|           excel.export_json_to_excel({ | ||||
|             multiHeader, | ||||
|             header, | ||||
|             merges, | ||||
|             data | ||||
|           }) | ||||
|           this.downloadLoading = false | ||||
|         }) | ||||
|     }, | ||||
|     formatJson(filterVal, jsonData) { | ||||
|       return jsonData.map(v => filterVal.map(j => { | ||||
|         if (j === 'timestamp') { | ||||
|           return parseTime(v[j]) | ||||
|         } else { | ||||
|           return v[j] | ||||
|         } | ||||
|       })) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| @@ -9,7 +9,7 @@ | ||||
|       </app-link> | ||||
|     </template> | ||||
|  | ||||
|     <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)"> | ||||
|     <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body> | ||||
|       <template slot="title"> | ||||
|         <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="generateTitle(item.meta.title)" /> | ||||
|       </template> | ||||
|   | ||||
| @@ -6,9 +6,10 @@ | ||||
|       :background-color="variables.menuBg" | ||||
|       :text-color="variables.menuText" | ||||
|       :active-text-color="variables.menuActiveText" | ||||
|       :collapse-transition="false" | ||||
|       mode="vertical" | ||||
|     > | ||||
|       <sidebar-item v-for="route in permission_routers" :key="route.path" :item="route" :base-path="route.path" /> | ||||
|       <sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" /> | ||||
|     </el-menu> | ||||
|   </el-scrollbar> | ||||
| </template> | ||||
| @@ -22,7 +23,7 @@ export default { | ||||
|   components: { SidebarItem }, | ||||
|   computed: { | ||||
|     ...mapGetters([ | ||||
|       'permission_routers', | ||||
|       'permission_routes', | ||||
|       'sidebar' | ||||
|     ]), | ||||
|     variables() { | ||||
|   | ||||
| @@ -47,8 +47,8 @@ export default { | ||||
|     visitedViews() { | ||||
|       return this.$store.state.tagsView.visitedViews | ||||
|     }, | ||||
|     routers() { | ||||
|       return this.$store.state.permission.routers | ||||
|     routes() { | ||||
|       return this.$store.state.permission.routes | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
| @@ -95,7 +95,7 @@ export default { | ||||
|       return tags | ||||
|     }, | ||||
|     initTags() { | ||||
|       const affixTags = this.affixTags = this.filterAffixTags(this.routers) | ||||
|       const affixTags = this.affixTags = this.filterAffixTags(this.routes) | ||||
|       for (const tag of affixTags) { | ||||
|         // Must have tag name | ||||
|         if (tag.name) { | ||||
|   | ||||
							
								
								
									
										268
									
								
								src/views/permission/role.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								src/views/permission/role.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,268 @@ | ||||
| <template> | ||||
|   <div class="app-container"> | ||||
|     <el-button type="primary" @click="handleAddRole">{{ $t('permission.addRole') }}</el-button> | ||||
|  | ||||
|     <el-table :data="rolesList" style="width: 100%;margin-top:30px;" border> | ||||
|       <el-table-column align="center" label="Role Key" width="220"> | ||||
|         <template slot-scope="scope">{{ scope.row.key }}</template> | ||||
|       </el-table-column> | ||||
|       <el-table-column align="center" label="Role Name" width="220"> | ||||
|         <template slot-scope="scope">{{ scope.row.name }}</template> | ||||
|       </el-table-column> | ||||
|       <el-table-column align="header-center" label="Description"> | ||||
|         <template slot-scope="scope">{{ scope.row.description }}</template> | ||||
|       </el-table-column> | ||||
|       <el-table-column align="center" label="Operations"> | ||||
|         <template slot-scope="scope"> | ||||
|           <el-button type="primary" size="small" @click="handleEdit(scope)">{{ $t('permission.editPermission') }}</el-button> | ||||
|           <el-button type="danger" size="small" @click="handleDelete(scope)">{{ $t('permission.delete') }}</el-button> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|     </el-table> | ||||
|  | ||||
|     <el-dialog :visible.sync="dialogVisible" :title="dialogType==='edit'?'Edit Role':'New Role'"> | ||||
|       <el-form :model="role" label-width="80px" label-position="left"> | ||||
|         <el-form-item label="Name"> | ||||
|           <el-input v-model="role.name" placeholder="Role Name" /> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="Desc"> | ||||
|           <el-input | ||||
|             v-model="role.description" | ||||
|             :autosize="{ minRows: 2, maxRows: 4}" | ||||
|             type="textarea" | ||||
|             placeholder="Role Description" | ||||
|           /> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="Menus"> | ||||
|           <el-tree ref="tree" :check-strictly="checkStrictly" :data="routesData" :props="defaultProps" show-checkbox node-key="path" class="permission-tree" /> | ||||
|         </el-form-item> | ||||
|       </el-form> | ||||
|       <div style="text-align:right;"> | ||||
|         <el-button type="danger" @click="dialogVisible=false">{{ $t('permission.cancel') }}</el-button> | ||||
|         <el-button type="primary" @click="confirmRole">{{ $t('permission.confirm') }}</el-button> | ||||
|       </div> | ||||
|     </el-dialog> | ||||
|   </div> | ||||
|  | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import path from 'path' | ||||
| import { deepClone } from '@/utils' | ||||
| import { getRoutes, getRoles, addRole, deleteRole, updateRole } from '@/api/role' | ||||
| import i18n from '@/lang' | ||||
|  | ||||
| const defaultRole = { | ||||
|   key: '', | ||||
|   name: '', | ||||
|   description: '', | ||||
|   routes: [] | ||||
| } | ||||
|  | ||||
| export default { | ||||
|   data() { | ||||
|     return { | ||||
|       role: Object.assign({}, defaultRole), | ||||
|       routes: [], | ||||
|       rolesList: [], | ||||
|       dialogVisible: false, | ||||
|       dialogType: 'new', | ||||
|       checkStrictly: false, | ||||
|       defaultProps: { | ||||
|         children: 'children', | ||||
|         label: 'title' | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     routesData() { | ||||
|       return this.routes | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     // Mock: get all routes and roles list from server | ||||
|     this.getRoutes() | ||||
|     this.getRoles() | ||||
|   }, | ||||
|   methods: { | ||||
|     async getRoutes() { | ||||
|       const res = await getRoutes() | ||||
|       this.serviceRoutes = res.data | ||||
|       const routes = this.generateRoutes(res.data) | ||||
|       this.routes = this.i18n(routes) | ||||
|     }, | ||||
|     async getRoles() { | ||||
|       const res = await getRoles() | ||||
|       this.rolesList = res.data | ||||
|     }, | ||||
|     i18n(routes) { | ||||
|       const app = routes.map(route => { | ||||
|         route.title = i18n.t(`route.${route.title}`) | ||||
|         if (route.children) { | ||||
|           route.children = this.i18n(route.children) | ||||
|         } | ||||
|         return route | ||||
|       }) | ||||
|       return app | ||||
|     }, | ||||
|     // Reshape the routes structure so that it looks the same as the sidebar | ||||
|     generateRoutes(routes, basePath = '/') { | ||||
|       const res = [] | ||||
|  | ||||
|       for (let route of routes) { | ||||
|         // skip some route | ||||
|         if (route.hidden) { continue } | ||||
|  | ||||
|         const onlyOneShowingChild = this.onlyOneShowingChild(route.children, route) | ||||
|  | ||||
|         if (route.children && onlyOneShowingChild && !route.alwaysShow) { | ||||
|           route = onlyOneShowingChild | ||||
|         } | ||||
|  | ||||
|         const data = { | ||||
|           path: path.resolve(basePath, route.path), | ||||
|           title: route.meta && route.meta.title | ||||
|  | ||||
|         } | ||||
|  | ||||
|         // recursive child routes | ||||
|         if (route.children) { | ||||
|           data.children = this.generateRoutes(route.children, data.path) | ||||
|         } | ||||
|         res.push(data) | ||||
|       } | ||||
|       return res | ||||
|     }, | ||||
|     generateArr(routes) { | ||||
|       let data = [] | ||||
|       routes.forEach(route => { | ||||
|         data.push(route) | ||||
|         if (route.children) { | ||||
|           const temp = this.generateArr(route.children) | ||||
|           if (temp.length > 0) { | ||||
|             data = [...data, ...temp] | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|       return data | ||||
|     }, | ||||
|     handleAddRole() { | ||||
|       this.role = Object.assign({}, defaultRole) | ||||
|       if (this.$refs.tree) { | ||||
|         this.$refs.tree.setCheckedNodes([]) | ||||
|       } | ||||
|       this.dialogType = 'new' | ||||
|       this.dialogVisible = true | ||||
|     }, | ||||
|     handleEdit(scope) { | ||||
|       this.dialogType = 'edit' | ||||
|       this.dialogVisible = true | ||||
|       this.checkStrictly = true | ||||
|       this.role = deepClone(scope.row) | ||||
|       this.$nextTick(() => { | ||||
|         const routes = this.generateRoutes(this.role.routes) | ||||
|         this.$refs.tree.setCheckedNodes(this.generateArr(routes)) | ||||
|         // set checked state of a node not affects its father and child nodes | ||||
|         this.checkStrictly = false | ||||
|       }) | ||||
|     }, | ||||
|     handleDelete({ $index, row }) { | ||||
|       this.$confirm('Confirm to remove the role?', 'Warning', { | ||||
|         confirmButtonText: 'Confirm', | ||||
|         cancelButtonText: 'Cancel', | ||||
|         type: 'warning' | ||||
|       }) | ||||
|         .then(async() => { | ||||
|           await deleteRole(row.id) | ||||
|           this.rolesList.splice($index, 1) | ||||
|           this.$message({ | ||||
|             type: 'success', | ||||
|             message: 'Delete succed!' | ||||
|           }) | ||||
|         }) | ||||
|         .catch(err => { console.error(err) }) | ||||
|     }, | ||||
|     generateTree(routes, basePath = '/', checkedKeys) { | ||||
|       const res = [] | ||||
|  | ||||
|       for (const route of routes) { | ||||
|         const routePath = path.resolve(basePath, route.path) | ||||
|  | ||||
|         // recursive child routes | ||||
|         if (route.children) { | ||||
|           route.children = this.generateTree(route.children, routePath, checkedKeys) | ||||
|         } | ||||
|  | ||||
|         if (checkedKeys.includes(routePath) || (route.children && route.children.length >= 1)) { | ||||
|           res.push(route) | ||||
|         } | ||||
|       } | ||||
|       return res | ||||
|     }, | ||||
|     async confirmRole() { | ||||
|       const isEdit = this.dialogType === 'edit' | ||||
|  | ||||
|       const checkedKeys = this.$refs.tree.getCheckedKeys() | ||||
|       this.role.routes = this.generateTree(deepClone(this.serviceRoutes), '/', checkedKeys) | ||||
|  | ||||
|       if (isEdit) { | ||||
|         await updateRole(this.role.key, this.role) | ||||
|         for (let index = 0; index < this.rolesList.length; index++) { | ||||
|           if (this.rolesList[index].key === this.role.key) { | ||||
|             this.rolesList.splice(index, 1, Object.assign({}, this.role)) | ||||
|             break | ||||
|           } | ||||
|         } | ||||
|       } else { | ||||
|         const { data } = await addRole(this.role) | ||||
|         this.role.key = data | ||||
|         this.rolesList.push(this.role) | ||||
|       } | ||||
|  | ||||
|       const { description, key, name } = this.role | ||||
|       this.dialogVisible = false | ||||
|       this.$notify({ | ||||
|         title: 'Success', | ||||
|         dangerouslyUseHTMLString: true, | ||||
|         message: ` | ||||
|             <div>Role Key: ${key}</div> | ||||
|             <div>Role Nmae: ${name}</div> | ||||
|             <div>Description: ${description}</div> | ||||
|           `, | ||||
|         type: 'success' | ||||
|       }) | ||||
|     }, | ||||
|     // reference: src/view/layout/components/Sidebar/SidebarItem.vue | ||||
|     onlyOneShowingChild(children = [], parent) { | ||||
|       let onlyOneChild = null | ||||
|       const showingChildren = children.filter(item => !item.hidden) | ||||
|  | ||||
|       // When there is only one child route, the child route is displayed by default | ||||
|       if (showingChildren.length === 1) { | ||||
|         onlyOneChild = showingChildren[0] | ||||
|         onlyOneChild.path = path.resolve(parent.path, onlyOneChild.path) | ||||
|         return onlyOneChild | ||||
|       } | ||||
|  | ||||
|       // Show parent if there are no child route to display | ||||
|       if (showingChildren.length === 0) { | ||||
|         onlyOneChild = { ... parent, path: '', noShowingChildren: true } | ||||
|         return onlyOneChild | ||||
|       } | ||||
|  | ||||
|       return false | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .app-container { | ||||
|   .roles-table { | ||||
|     margin-top: 30px; | ||||
|   } | ||||
|   .permission-tree { | ||||
|     margin-bottom: 30px; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
| @@ -1,48 +0,0 @@ | ||||
| /** | ||||
| * @Author: jianglei | ||||
| * @Date:   2017-10-12 12:06:49 | ||||
| */ | ||||
| 'use strict' | ||||
| import Vue from 'vue' | ||||
| export default function treeToArray(data, expandAll, parent, level, item) { | ||||
|   const marLTemp = [] | ||||
|   let tmp = [] | ||||
|   Array.from(data).forEach(function(record) { | ||||
|     if (record._expanded === undefined) { | ||||
|       Vue.set(record, '_expanded', expandAll) | ||||
|     } | ||||
|     let _level = 1 | ||||
|     if (level !== undefined && level !== null) { | ||||
|       _level = level + 1 | ||||
|     } | ||||
|     Vue.set(record, '_level', _level) | ||||
|     // 如果有父元素 | ||||
|     if (parent) { | ||||
|       Vue.set(record, 'parent', parent) | ||||
|       // 如果父元素有偏移量,需要计算在this的偏移量中 | ||||
|       // 偏移量还与前面同级元素有关,需要加上前面所有元素的长度和 | ||||
|       if (!marLTemp[_level]) { | ||||
|         marLTemp[_level] = 0 | ||||
|       } | ||||
|       Vue.set(record, '_marginLeft', marLTemp[_level] + parent._marginLeft) | ||||
|       Vue.set(record, '_width', record[item] / parent[item] * parent._width) | ||||
|       // 在本次计算过偏移量后加上自己长度,以供下一个元素使用 | ||||
|       marLTemp[_level] += record._width | ||||
|     } else { | ||||
|       // 如果为根 | ||||
|       // 初始化偏移量存储map | ||||
|       marLTemp[record.id] = [] | ||||
|       // map中是一个数组,存储的是每级的长度和 | ||||
|       // 初始情况下为0 | ||||
|       marLTemp[record.id][_level] = 0 | ||||
|       Vue.set(record, '_marginLeft', 0) | ||||
|       Vue.set(record, '_width', 1) | ||||
|     } | ||||
|     tmp.push(record) | ||||
|     if (record.children && record.children.length > 0) { | ||||
|       const children = treeToArray(record.children, expandAll, record, _level, item) | ||||
|       tmp = tmp.concat(children) | ||||
|     } | ||||
|   }) | ||||
|   return tmp | ||||
| } | ||||
							
								
								
									
										51
									
								
								src/views/tree-table/custom/data.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/views/tree-table/custom/data.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| const data = [ | ||||
|   { | ||||
|     name: '1', | ||||
|     timeLine: 100, | ||||
|     children: [ | ||||
|       { | ||||
|         name: '1-1', | ||||
|         timeLine: 20 | ||||
|       }, | ||||
|       { | ||||
|         name: '1-2', | ||||
|         timeLine: 60, | ||||
|         children: [ | ||||
|           { | ||||
|             name: '1-2-1', | ||||
|             timeLine: 35 | ||||
|           }, | ||||
|           { | ||||
|             name: '1-2-2', | ||||
|             timeLine: 25 | ||||
|           } | ||||
|         ] | ||||
|       } | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     name: '2', | ||||
|     timeLine: 80, | ||||
|     children: [ | ||||
|       { | ||||
|         name: '2-1', | ||||
|         timeLine: 30 | ||||
|       }, | ||||
|       { | ||||
|         name: '2-2', | ||||
|         timeLine: 50 | ||||
|       }, | ||||
|       { | ||||
|         name: '2-3', | ||||
|         timeLine: 60 | ||||
|       } | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     name: '3', | ||||
|     timeLine: 40 | ||||
|   } | ||||
| ] | ||||
|  | ||||
| export default data | ||||
|  | ||||
							
								
								
									
										158
									
								
								src/views/tree-table/custom/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								src/views/tree-table/custom/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,158 @@ | ||||
| <template> | ||||
|   <div> | ||||
|     <div class="app-container"> | ||||
|  | ||||
|       <el-button type="primary" size="small" style="margin:0 0 20px 0;"> | ||||
|         <a href="https://github.com/PanJiaChen/vue-element-admin/tree/master/src/components/TreeTable" target="_blank">Documentation</a> | ||||
|       </el-button> | ||||
|  | ||||
|       <tree-table | ||||
|         ref="TreeTable" | ||||
|         :data="tableData" | ||||
|         :default-expand-all="true" | ||||
|         :columns="columns" | ||||
|         border | ||||
|         default-children="children" | ||||
|         @selection-change="selectChange" | ||||
|       > | ||||
|  | ||||
|         <template slot="selection"> | ||||
|           <el-table-column type="selection" align="center" width="55" /> | ||||
|         </template> | ||||
|  | ||||
|         <template slot="pre-column"> | ||||
|           <el-table-column type="expand" width="55"> | ||||
|             <template> | ||||
|               <el-tag type="info"> | ||||
|                 Here is just a placeholder slot, you can display anything. | ||||
|               </el-tag> | ||||
|             </template> | ||||
|           </el-table-column> | ||||
|         </template> | ||||
|  | ||||
|         <template slot="timeline" slot-scope="{scope}"> | ||||
|  | ||||
|           <el-tooltip :content="scope.row.timeLine+'ms'" effect="dark" placement="left"> | ||||
|             <div class="processContainer"> | ||||
|               <div | ||||
|                 :style="{ width:(scope.row.timeLine||0) * 3+'px', | ||||
|                           background:scope.row.timeLine>50?'rgba(233,0,0,.5)':'rgba(0,0,233,0.5)', | ||||
|                           marginLeft:scope.row._level * 50+'px' }" | ||||
|                 class="process" | ||||
|               > | ||||
|                 <span style="display:inline-block" /> | ||||
|               </div> | ||||
|             </div> | ||||
|           </el-tooltip> | ||||
|  | ||||
|         </template> | ||||
|  | ||||
|         <template slot="append" slot-scope="{scope}"> | ||||
|           <el-button | ||||
|             size="mini" | ||||
|             type="primary" | ||||
|             @click="addMenuItem(scope.row,'brother')" | ||||
|           >Append Brother | ||||
|           </el-button> | ||||
|           <el-button | ||||
|             size="mini" | ||||
|             type="primary" | ||||
|             @click="addMenuItem(scope.row,'children')" | ||||
|           >Append Child | ||||
|           </el-button> | ||||
|         </template> | ||||
|         <template slot="operation" slot-scope="{scope}"> | ||||
|           <el-button size="mini" type="success" @click="editItem(scope.row)">Edit</el-button> | ||||
|           <el-button size="mini" type="danger" @click="deleteItem(scope.row)">Delete</el-button> | ||||
|         </template> | ||||
|       </tree-table> | ||||
|     </div> | ||||
|  | ||||
|     <el-dialog :visible.sync="dialogFormVisible" title="Edit"> | ||||
|       <el-form :model="tempItem" label-width="100px" style="width:600px"> | ||||
|         <el-form-item label="Name"> | ||||
|           <el-input v-model.trim="tempItem.name" placeholder="Name" /> | ||||
|         </el-form-item> | ||||
|       </el-form> | ||||
|       <span slot="footer" class="dialog-footer"> | ||||
|         <el-button @click="dialogFormVisible = false">Cancel</el-button> | ||||
|         <el-button type="primary" @click="updateItem">Confirm</el-button> | ||||
|       </span> | ||||
|     </el-dialog> | ||||
|  | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import TreeTable from '@/components/TreeTable' | ||||
| import data from './data.js' | ||||
|  | ||||
| export default { | ||||
|   components: { TreeTable }, | ||||
|   data() { | ||||
|     return { | ||||
|       tableData: [], | ||||
|       tempItem: {}, | ||||
|       dialogFormVisible: false, | ||||
|       columns: [ | ||||
|         { | ||||
|           label: 'Name', | ||||
|           key: 'name', | ||||
|           expand: true | ||||
|         }, | ||||
|         { | ||||
|           label: 'Timeline', | ||||
|           key: 'timeline' | ||||
|         }, | ||||
|         { | ||||
|           label: 'Append', | ||||
|           key: 'append', | ||||
|           width: 300 | ||||
|         }, | ||||
|         { | ||||
|           label: 'Operation', | ||||
|           key: 'operation', | ||||
|           width: 160 | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     this.getData() | ||||
|   }, | ||||
|   methods: { | ||||
|     getData() { | ||||
|       this.tableData = data | ||||
|     }, | ||||
|     editItem(row) { | ||||
|       this.tempItem = Object.assign({}, row) | ||||
|       this.dialogFormVisible = true | ||||
|     }, | ||||
|     async updateItem() { | ||||
|       await this.$refs.TreeTable.updateTreeNode(this.tempItem) | ||||
|       this.dialogFormVisible = false | ||||
|     }, | ||||
|     addMenuItem(row, type) { | ||||
|       if (type === 'children') { | ||||
|         this.$refs.TreeTable.addChild(row, { name: 'child', timeLine: this.randomNum() }) | ||||
|       } | ||||
|  | ||||
|       if (type === 'brother') { | ||||
|         this.$refs.TreeTable.addBrother(row, { name: 'brother', timeLine: this.randomNum() }) | ||||
|       } | ||||
|     }, | ||||
|     deleteItem(row) { | ||||
|       this.$refs.TreeTable.delete(row) | ||||
|     }, | ||||
|     selectChange(val) { | ||||
|       console.log(val) | ||||
|     }, | ||||
|     randomNum() { | ||||
|       // return 1~100 | ||||
|       const max = 100 | ||||
|       const min = 1 | ||||
|       return Math.floor(Math.random() * (max - min + 1) + min) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										80
									
								
								src/views/tree-table/data.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/views/tree-table/data.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
|  | ||||
| const data = [ | ||||
|   { | ||||
|     id: 0, | ||||
|     event: 'Event-0', | ||||
|     timeLine: 50 | ||||
|   }, | ||||
|   { | ||||
|     id: 1, | ||||
|     event: 'Event-1', | ||||
|     timeLine: 100, | ||||
|     children: [ | ||||
|       { | ||||
|         id: 2, | ||||
|         event: 'Event-2', | ||||
|         timeLine: 10 | ||||
|  | ||||
|       }, | ||||
|       { | ||||
|         id: 3, | ||||
|         event: 'Event-3', | ||||
|         timeLine: 90, | ||||
|         children: [ | ||||
|           { | ||||
|             id: 4, | ||||
|             event: 'Event-4', | ||||
|             timeLine: 5 | ||||
|  | ||||
|           }, | ||||
|           { | ||||
|             id: 5, | ||||
|             event: 'Event-5', | ||||
|             timeLine: 10 | ||||
|  | ||||
|           }, | ||||
|           { | ||||
|             id: 6, | ||||
|             event: 'Event-6', | ||||
|             timeLine: 75, | ||||
|  | ||||
|             children: [ | ||||
|               { | ||||
|                 id: 7, | ||||
|                 event: 'Event-7', | ||||
|                 timeLine: 50, | ||||
|  | ||||
|                 children: [ | ||||
|                   { | ||||
|                     id: 71, | ||||
|                     event: 'Event-7-1', | ||||
|                     timeLine: 25 | ||||
|  | ||||
|                   }, | ||||
|                   { | ||||
|                     id: 72, | ||||
|                     event: 'Event-7-2', | ||||
|                     timeLine: 5 | ||||
|  | ||||
|                   }, | ||||
|                   { | ||||
|                     id: 73, | ||||
|                     event: 'Event-7-3', | ||||
|                     timeLine: 20 | ||||
|                   } | ||||
|                 ] | ||||
|               }, | ||||
|               { | ||||
|                 id: 8, | ||||
|                 event: 'Event-8', | ||||
|                 timeLine: 25 | ||||
|               } | ||||
|             ] | ||||
|           } | ||||
|         ] | ||||
|       } | ||||
|     ] | ||||
|   } | ||||
| ] | ||||
|  | ||||
| export default data | ||||
							
								
								
									
										128
									
								
								src/views/tree-table/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/views/tree-table/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | ||||
| <template> | ||||
|   <div class="app-container"> | ||||
|  | ||||
|     <div style="margin-bottom:20px;"> | ||||
|  | ||||
|       <el-button type="primary" size="small" class="option-item"> | ||||
|         <a href="https://github.com/PanJiaChen/vue-element-admin/tree/master/src/components/TreeTable" target="_blank">Documentation</a> | ||||
|       </el-button> | ||||
|  | ||||
|       <div class="option-item"> | ||||
|         <el-tag>Expand All</el-tag> | ||||
|         <el-switch | ||||
|           v-model="defaultExpandAll" | ||||
|           active-color="#13ce66" | ||||
|           inactive-color="#ff4949" | ||||
|           @change="reset" | ||||
|         /> | ||||
|       </div> | ||||
|  | ||||
|       <div class="option-item"> | ||||
|         <el-tag>Show Checkbox</el-tag> | ||||
|         <el-switch | ||||
|           v-model="showCheckbox" | ||||
|           active-color="#13ce66" | ||||
|           inactive-color="#ff4949" | ||||
|         /> | ||||
|       </div> | ||||
|  | ||||
|     </div> | ||||
|  | ||||
|     <tree-table :key="key" :default-expand-all="defaultExpandAll" :data="data" :columns="columns" border> | ||||
|       <template slot="scope" slot-scope="{scope}"> | ||||
|         <el-tag>level: {{ scope.row._level }}</el-tag> | ||||
|         <el-tag>expand: {{ scope.row._expand }}</el-tag> | ||||
|         <el-tag>select: {{ scope.row._select }}</el-tag> | ||||
|       </template> | ||||
|       <template slot="operation" slot-scope="{scope}"> | ||||
|         <el-button type="primary" size="" @click="click(scope)">Click</el-button> | ||||
|       </template> | ||||
|     </tree-table> | ||||
|  | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import treeTable from '@/components/TreeTable' | ||||
| import data from './data' | ||||
|  | ||||
| export default { | ||||
|   name: 'TreeTableDemo', | ||||
|   components: { treeTable }, | ||||
|   data() { | ||||
|     return { | ||||
|       defaultExpandAll: false, | ||||
|       showCheckbox: true, | ||||
|       key: 1, | ||||
|       columns: [ | ||||
|         { | ||||
|           label: 'Checkbox', | ||||
|           checkbox: true | ||||
|         }, | ||||
|         { | ||||
|           label: '', | ||||
|           key: 'id', | ||||
|           expand: true | ||||
|         }, | ||||
|         { | ||||
|           label: 'Event', | ||||
|           key: 'event', | ||||
|           width: 200, | ||||
|           align: 'left' | ||||
|         }, | ||||
|         { | ||||
|           label: 'Scope', | ||||
|           key: 'scope' | ||||
|         }, | ||||
|         { | ||||
|           label: 'Operation', | ||||
|           key: 'operation' | ||||
|         } | ||||
|       ], | ||||
|       data: data | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     showCheckbox(val) { | ||||
|       if (val) { | ||||
|         this.columns.unshift({ | ||||
|           label: 'Checkbox', | ||||
|           checkbox: true | ||||
|         }) | ||||
|       } else { | ||||
|         this.columns.shift() | ||||
|       } | ||||
|       this.reset() | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     reset() { | ||||
|       ++this.key | ||||
|     }, | ||||
|     click(scope) { | ||||
|       console.log(scope) | ||||
|  | ||||
|       const row = scope.row | ||||
|       const message = Object.keys(row) | ||||
|         .map(i => { | ||||
|           return `<p>${i}: ${row[i]}</p>` | ||||
|         }) | ||||
|         .join('') | ||||
|  | ||||
|       this.$notify({ | ||||
|         title: 'Success', | ||||
|         dangerouslyUseHTMLString: true, | ||||
|         message: message, | ||||
|         type: 'success' | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .option-item{ | ||||
|   display: inline-block; | ||||
|   margin-right: 15px; | ||||
| } | ||||
| </style> | ||||
		Reference in New Issue
	
	Block a user