Merge branch 'master' into pr/gaoshijun1993/1605

This commit is contained in:
Pan 2019-03-15 18:05:38 +08:00
commit d189593b6b
27 changed files with 1028 additions and 591 deletions

View 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

View File

@ -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
}

View File

@ -1,127 +1,190 @@
<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"
:label="item.label"
:key="item.key"
: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"
:style="{'padding-left':+scope.row._level*indent + 'px'} "
:indeterminate="scope.row._select"
v-model="scope.row._select"
@change="handleCheckAllChange(scope.row)" />
<el-checkbox
v-else
:style="{'padding-left':+scope.row._level*indent + 'px'} "
v-model="scope.row._select"
@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 -->
<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>

View File

@ -1,89 +0,0 @@
## 写在前面
此组件仅提供一个创建TreeTable的解决思路
## prop说明
#### *data*
**必填**
原始数据,要求是一个数组或者对象
```javascript
[{
key1: value1,
key2: value2,
children: [{
key1: value1
},
{
key1: value1
}]
},
{
key1: value1
}]
```
或者
```javascript
{
key1: value1,
key2: value2,
children: [{
key1: value1
},
{
key1: value1
}]
}
```
#### columns
列属性,要求是一个数组
1. text: 显示在表头的文字
2. value: 对应data的key。treeTable将显示相应的value
3. width: 每列的宽度,为一个数字(可选)
如果你想要每个字段都有自定义的样式或者嵌套其他组件columns可不提供直接像在el-table一样写即可如果没有自定义内容提供columns将更加的便捷方便
如果你有几个字段是需要自定义的几个不需要那么可以将不需要自定义的字段放入columns将需要自定义的内容放入到slot中详情见后文
```javascript
[{
value:string,
text:string,
width:number
},{
value:string,
text:string,
width:number
}]
```
#### expandAll
是否默认全部展开boolean值默认为false
#### evalFunc
解析函数function非必须
如果不提供,将使用默认的[evalFunc](./eval.js)
如果提供了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)
#### evalArgs
解析函数的参数,是一个数组
**请注意自定义的解析函数参数第一个为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

View 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)
}
}

View 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

View File

@ -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]
}
}

View 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

View File

@ -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
@ -170,6 +171,7 @@ export const asyncRoutes = [
chartsRouter,
nestedRouter,
tableRouter,
treeTableRouter,
{
path: '/example',

View File

@ -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'),

View 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

View File

@ -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;

View File

@ -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;

View File

@ -91,20 +91,19 @@ export function getQueryObject(url) {
}
/**
*get getByteLen
* @param {Sting} val input value
* @param {Sting} input value
* @returns {number} output value
*/
export function getByteLen(val) {
let len = 0
for (let i = 0; i < val.length; i++) {
if (val[i].match(/[^\x00-\xff]/gi) != null) {
len += 1
} else {
len += 0.5
}
export function byteLength(str) {
// returns the byte length of an utf8 string
let s = str.length
for (var i = str.length - 1; i >= 0; i--) {
const code = str.charCodeAt(i)
if (code > 0x7f && code <= 0x7ff) s++
else if (code > 0x7ff && code <= 0xffff) s += 2
if (code >= 0xDC00 && code <= 0xDFFF) i--
}
return Math.floor(len)
return s
}
export function cleanArray(actual) {
@ -137,7 +136,8 @@ export function param2Obj(url) {
decodeURIComponent(search)
.replace(/"/g, '\\"')
.replace(/&/g, '","')
.replace(/=/g, '":"') +
.replace(/=/g, '":"')
.replace(/\+/g, ' ') +
'"}'
)
}

View File

@ -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>

View File

@ -6,6 +6,7 @@
: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_routes" :key="route.path" :item="route" :base-path="route.path"/>

View File

@ -14,16 +14,21 @@ export default {
left: 0
}
},
computed: {
scrollWrapper() {
return this.$refs.scrollContainer.$refs.wrap
}
},
methods: {
handleScroll(e) {
const eventDelta = e.wheelDelta || -e.deltaY * 40
const $scrollWrapper = this.$refs.scrollContainer.$refs.wrap
const $scrollWrapper = this.scrollWrapper
$scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
},
moveToTarget(currentTag) {
const $container = this.$refs.scrollContainer.$el
const $containerWidth = $container.offsetWidth
const $scrollWrapper = this.$refs.scrollContainer.$refs.wrap
const $scrollWrapper = this.scrollWrapper
const tagList = this.$parent.$refs.tag
let firstTag = null
@ -44,6 +49,7 @@ export default {
const currentIndex = tagList.findIndex(item => item === currentTag)
const prevTag = tagList[currentIndex - 1]
const nextTag = tagList[currentIndex + 1]
// the tag's offsetLeft after of nextTag
const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + tagAndTagSpacing

View File

@ -26,7 +26,7 @@
</template>
<script>
import ScrollPane from '@/components/ScrollPane'
import ScrollPane from './ScrollPane'
import { generateTitle } from '@/utils/i18n'
import path from 'path'
@ -75,8 +75,10 @@ export default {
let tags = []
routes.forEach(route => {
if (route.meta && route.meta.affix) {
const tagPath = path.resolve(basePath, route.path)
tags.push({
path: path.resolve(basePath, route.path),
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta }
})
@ -88,7 +90,6 @@ export default {
}
}
})
return tags
},
initTags() {
@ -113,12 +114,10 @@ export default {
for (const tag of tags) {
if (tag.to.path === this.$route.path) {
this.$refs.scrollPane.moveToTarget(tag)
// when query is different then update
if (tag.to.fullPath !== this.$route.fullPath) {
this.$store.dispatch('updateVisitedView', this.$route)
}
break
}
}
@ -176,8 +175,8 @@ export default {
} else {
this.left = left
}
this.top = e.clientY
this.top = e.clientY
this.visible = true
this.selectedTag = tag
},

View File

@ -1,4 +1,4 @@
export { default as Navbar } from './Navbar'
export { default as Sidebar } from './Sidebar/index.vue'
export { default as TagsView } from './TagsView'
export { default as TagsView } from './TagsView/index.vue'
export { default as AppMain } from './AppMain'

View File

@ -1,7 +1,7 @@
<template>
<div class="app-container">
<!-- Note that row-key is necessary to get a correct row order. -->
<el-table v-loading="listLoading" :data="list" row-key="id" border fit highlight-current-row style="width: 100%">
<el-table v-loading="listLoading" ref="dragTable" :data="list" row-key="id" border fit highlight-current-row style="width: 100%">
<el-table-column align="center" label="ID" width="65">
<template slot-scope="scope">
@ -107,7 +107,7 @@ export default {
})
},
setSort() {
const el = document.querySelectorAll('.el-table__body-wrapper > table > tbody')[0]
const el = this.$refs.dragTable.$el.querySelectorAll('.el-table__body-wrapper > table > tbody')[0]
this.sortable = Sortable.create(el, {
ghostClass: 'sortable-ghost', // Class name for the drop placeholder,
setData: function(dataTransfer) {

View File

@ -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
}

View File

@ -1,138 +0,0 @@
<template>
<div class="app-container">
<el-tag style="margin-bottom:20px;">
<a href="https://github.com/PanJiaChen/vue-element-admin/tree/master/src/components/TreeTable" target="_blank">Documentation</a>
</el-tag>
<tree-table :data="data" :eval-func="func" :eval-args="args" :expand-all="expandAll" border>
<el-table-column label="事件">
<template slot-scope="scope">
<span style="color:sandybrown">{{ scope.row.event }}</span>
<el-tag>{{ scope.row.timeLine+'ms' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="时间线">
<template slot-scope="scope">
<el-tooltip :content="scope.row.timeLine+'ms'" effect="dark" placement="left">
<div class="processContainer">
<div
:style="{ width:scope.row._width * 500+'px',
background:scope.row._width>0.5?'rgba(233,0,0,.5)':'rgba(0,0,233,0.5)',
marginLeft:scope.row._marginLeft * 500+'px' }"
class="process">
<span style="display:inline-block"/>
</div>
</div>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template slot-scope="scope">
<el-button type="text" @click="message(scope.row)">点击</el-button>
</template>
</el-table-column>
</tree-table>
</div>
</template>
<script>
/**
Auth: Lei.j1ang
Created: 2018/1/19-14:54
*/
import treeTable from '@/components/TreeTable'
import treeToArray from './customEval'
export default {
name: 'CustomTreeTableDemo',
components: { treeTable },
data() {
return {
func: treeToArray,
expandAll: false,
data:
{
id: 1,
event: '事件1',
timeLine: 100,
comment: '无',
children: [
{
id: 2,
event: '事件2',
timeLine: 10,
comment: '无'
},
{
id: 3,
event: '事件3',
timeLine: 90,
comment: '无',
children: [
{
id: 4,
event: '事件4',
timeLine: 5,
comment: '无'
},
{
id: 5,
event: '事件5',
timeLine: 10,
comment: '无'
},
{
id: 6,
event: '事件6',
timeLine: 75,
comment: '无',
children: [
{
id: 7,
event: '事件7',
timeLine: 50,
comment: '无',
children: [
{
id: 71,
event: '事件71',
timeLine: 25,
comment: 'xx'
},
{
id: 72,
event: '事件72',
timeLine: 5,
comment: 'xx'
},
{
id: 73,
event: '事件73',
timeLine: 20,
comment: 'xx'
}
]
},
{
id: 8,
event: '事件8',
timeLine: 25,
comment: '无'
}
]
}
]
}
]
},
args: [null, null, 'timeLine']
}
},
methods: {
message(row) {
this.$message.info(row.event)
}
}
}
</script>

View File

@ -1,129 +0,0 @@
<template>
<div class="app-container">
<el-tag style="margin-bottom:20px;">
<a href="https://github.com/PanJiaChen/vue-element-admin/tree/master/src/components/TreeTable" target="_blank">Documentation</a>
</el-tag>
<tree-table :data="data" :columns="columns" border/>
</div>
</template>
<script>
/**
Auth: Lei.j1ang
Created: 2018/1/19-14:54
*/
import treeTable from '@/components/TreeTable'
export default {
name: 'TreeTableDemo',
components: { treeTable },
data() {
return {
columns: [
{
text: '事件',
value: 'event',
width: 200
},
{
text: 'ID',
value: 'id'
},
{
text: '时间线',
value: 'timeLine'
},
{
text: '备注',
value: 'comment'
}
],
data: [
{
id: 0,
event: '事件1',
timeLine: 50,
comment: '无'
},
{
id: 1,
event: '事件1',
timeLine: 100,
comment: '无',
children: [
{
id: 2,
event: '事件2',
timeLine: 10,
comment: '无'
},
{
id: 3,
event: '事件3',
timeLine: 90,
comment: '无',
children: [
{
id: 4,
event: '事件4',
timeLine: 5,
comment: '无'
},
{
id: 5,
event: '事件5',
timeLine: 10,
comment: '无'
},
{
id: 6,
event: '事件6',
timeLine: 75,
comment: '无',
children: [
{
id: 7,
event: '事件7',
timeLine: 50,
comment: '无',
children: [
{
id: 71,
event: '事件71',
timeLine: 25,
comment: 'xx'
},
{
id: 72,
event: '事件72',
timeLine: 5,
comment: 'xx'
},
{
id: 73,
event: '事件73',
timeLine: 20,
comment: 'xx'
}
]
},
{
id: 8,
event: '事件8',
timeLine: 25,
comment: '无'
}
]
}
]
}
]
}
]
}
}
}
</script>

View 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

View File

@ -0,0 +1,157 @@
<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>

View 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

View File

@ -0,0 +1,127 @@
<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>