feat: treeTable add CURD demo

This commit is contained in:
liugq 2019-03-04 11:18:54 +08:00
parent e81febac06
commit c8709d5109
7 changed files with 422 additions and 185 deletions

View File

@ -1,21 +1,32 @@
import Vue from 'vue'
// 给数据添加额外的几个属性,并且扁平化数组
// 扁平化数组
export default function treeToTable(
data,
{ parent = null, level = 0, expand = false, children = 'children', show = true, select = false } = {}
data, children = 'children'
) {
let tmp = []
data.forEach(item => {
data.forEach((item, idx) => {
Vue.set(item, '__index', idx)
tmp.push(item)
if (item[children] && item[children].length > 0) {
const res = treeToTable(item[children], children)
tmp = tmp.concat(res)
}
})
return tmp
}
// 给数据添加额外的几个属性
// 清除__parent属性因数据循环引用使用JSON.stringify()报错
export function addAttrs(data, { parent = null, level = 0, expand = false, children = 'children', show = true, select = false } = {}) {
data.forEach((item, idx) => {
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)
tmp.push(item)
if (item[children] && item[children].length > 0) {
const res = treeToTable(item[children], {
addAttrs(item[children], {
parent: item,
level: level + 1,
expand,
@ -23,8 +34,18 @@ export default function treeToTable(
status,
select
})
tmp = tmp.concat(res)
}
})
return tmp
}
// 清除__parent属性
export function cleanAttrs(data, children = 'children') {
data.forEach(item => {
item.__parent = null
if (item[children] && item[children].length > 0) {
addAttrs(item[children], children)
}
})
return data
}

View File

@ -24,12 +24,27 @@
:style="{'padding-left':+scope.row.__level*spreadOffset + 'px'} "
class="el-icon-plus"
/>
<i v-else :style="{'padding-left':+scope.row.__level*spreadOffset + 'px'} " class="el-icon-minus"/>
<i
v-else
:style="{'padding-left':+scope.row.__level*spreadOffset + 'px'} "
class="el-icon-minus"
/>
</span>
</template>
<template v-if="item.key==='__checkbox'">
<el-checkbox v-if="scope.row[defaultChildren]&&scope.row[defaultChildren].length>0" :style="{'padding-left':+scope.row.__level*checkboxOffset + 'px'} " :indeterminate="scope.row.__select" v-model="scope.row.__select" @change="handleCheckAllChange(scope.row)"/>
<el-checkbox v-else :style="{'padding-left':+scope.row.__level*checkboxOffset + 'px'} " v-model="scope.row.__select" @change="handleCheckAllChange(scope.row)"/>
<el-checkbox
v-if="scope.row[defaultChildren]&&scope.row[defaultChildren].length>0"
:style="{'padding-left':+scope.row.__level*checkboxOffset + 'px'} "
:indeterminate="scope.row.__select"
v-model="scope.row.__select"
@change="handleCheckAllChange(scope.row)"
/>
<el-checkbox
v-else
:style="{'padding-left':+scope.row.__level*checkboxOffset + 'px'} "
v-model="scope.row.__select"
@change="handleCheckAllChange(scope.row)"
/>
</template>
{{ scope.row[item.key] }}
</slot>
@ -39,7 +54,7 @@
</template>
<script>
import treeToArray from './eval.js'
import treeToArray, { addAttrs } from './eval.js'
export default {
name: 'TreeTable',
@ -74,21 +89,45 @@ export default {
default: 50
}
},
data() {
return {
res: [],
guard: 1
}
},
computed: {
//
res() {
let tmp
if (!Array.isArray(this.data)) {
tmp = [this.data]
children() {
return this.defaultChildren
}
},
watch: {
data: {
// deep watchdeep watch
handler(val) {
if (Array.isArray(val) && val.length === 0) {
this.res = []
return
}
let tmp = ''
if (!Array.isArray(val)) {
tmp = [val]
} else {
tmp = this.data
tmp = val
}
const func = this.renderContent || treeToArray
return func(tmp, { expand: this.defaultExpandAll, children: this.defaultChildren })
if (this.guard > 0) {
addAttrs(tmp, {
expand: this.defaultExpandAll,
children: this.defaultChildren
})
this.guard--
}
const retval = func(tmp, this.defaultChildren)
this.res = retval
},
// children
children() {
return this.evalArgs && this.evalArgs.children || 'children'
deep: true,
immediate: true
}
},
methods: {

View File

@ -1,12 +1,28 @@
## 写在前面
此组件仅提供一个创建 TreeTable 的解决思路
此组件仅提供一个创建 TreeTable 的解决思路,本组件充分利用 vue 插槽的特性,方便用户自定义
evel.js 里面。addAttrs 方法会给数据添加几个属性treeTotable 会对数组扁平化。这些操作都不会破坏源数据,只是会新增属性。
调用 addAttrs 后,因\_\_parent 属性,会造成数据循环引用,使用 JSON.stringify()报错,所以转成 JSON 之前需要清除\_\_parent 属性。
## prop 说明
- data原始数据要求是一个数组或者对象
- columns列属性,要求是一个数组)
- renderContent数组扁平化方法可选
- defaultExpandAll默认是否全部展开默认全部展开
- defaultChildren子元素名默认为 children
- spreadOffset扩展符号的偏移量默认为 50px
- checkboxOffset复选框的偏移量默认为 50px
---
### 代码示例
- data(**必填**)
原始数据,要求是一个数组或者对象
原始数据,
```js
const data = [
@ -45,8 +61,6 @@ const data = [
- columns
列属性,要求是一个数组
1. label: 显示在表头的文字
2. key: 对应 data 的 key。treeTable 将显示相应的 value
3. width: 每列的宽度,为一个数字(可选)
@ -60,19 +74,19 @@ const columns = [
// 如果添加复选框
{ label: '', key: '__checkbox', width: '200' },
{
value: string,
text: string,
label: string,
key: string,
width: number
},
{
value: string,
text: string,
label: string,
key: string,
width: number
}
]
```
#### evalFunc
#### renderContent
解析函数function非必须
@ -80,28 +94,6 @@ const columns = [
如果提供了 evalFunc,那么会用提供的 evalFunc 去解析 data并返回 treeTable 渲染所需要的值。
#### evalArgs
解析函数的参数,是一个对象,
- parent = null
树的顶层节点默认为 null
- level = 0
默认第一层级为0然后依次递增
- expand = false
如果需要展开所有的数据,那么就传入`{expand:true}`
- children = 'children'
如果后台返回的数据不都是带有children字段那么修改一下即可
## 其他
如果有其他的需求,请参考[el-table](http://element-cn.eleme.io/#/en-US/component/table)的 api 自行修改 index.vue

0
src/mock/table.js Normal file
View File

View File

@ -1,149 +1,250 @@
<template>
<div>
<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>
<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" :default-expand-all="true" border style="width: 100%" default-children="son" >
<TreeTable
:data="menus"
:default-expand-all="true"
:columns="columns"
border
default-children="sub_button"
>
<template slot="__selection">
<el-table-column
type="selection"
width="55"/>
<el-table-column type="selection" width="55"/>
</template>
<template slot="__expand">
<el-table-column
type="expand"
width="55">
<template slot-scope="props">
<el-button size="mini" type="danger" @click="handleExpandClick(props)">点我</el-button>
<el-table-column type="expand" width="55">
<template>
<el-tag type="info">
支持element-ui 的扩展和多选框事件哦
</el-tag>
</template>
</el-table-column>
</template>
<template slot="__opt" slot-scope="{scope}">
<el-button size="mini" type="primary" @click="handleClick(scope)">点我</el-button>
<template slot="name" slot-scope="{scope}">
<span :style="{'padding-left':+scope.row.__level*50 + 'px'} ">
<a
v-if="scope.row.type === 'view'"
:href="scope.row.url"
class="link-type"
>{{ scope.row.name }}</a>
<span v-else>{{ scope.row.name }}</span>
</span>
</template>
</tree-table>
<template slot="__opt_parent" slot-scope="{scope}">
<el-button
v-if="scope.row.__level === 0"
size="mini"
type="primary"
@click="addMenuItem(defaultMenu,1,scope.row.__index)"
>添加子菜单</el-button>
</template>
<template slot="__opt" slot-scope="{scope}">
<el-button size="mini" type="primary" @click="editMenuItem(scope.row,'update')">编辑</el-button>
<el-button size="mini" type="danger" @click="deleteMenuItem(scope.row)">删除</el-button>
</template>
</TreeTable>
</div>
<el-dialog :visible.sync="dialogFormVisible" title="编辑菜单">
<el-form ref="menuForm" :model="menu" :rules="rules" label-width="100px" style="width:600px">
<el-form-item label="type">
<el-select v-model="menu.type" clearable placeholder="请选择">
<el-option label="view" value="view"/>
<el-option label="click" value="click"/>
<el-option label="miniprogram" value="miniprogram"/>
</el-select>
</el-form-item>
<template v-if="menu.type==='click'">
<el-form-item label="key">
<el-input v-model.trim="menu.key" placeholder="请输入key"/>
</el-form-item>
</template>
<template v-else>
<el-form-item label="url">
<el-input v-model.trim="menu.url" placeholder="请输入url"/>
</el-form-item>
</template>
<el-form-item label="名称">
<el-input v-model.trim="menu.name" placeholder="请输入name"/>
</el-form-item>
<el-form-item label="appid">
<el-input v-model.trim="menu.appid" placeholder="请输入appid"/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="clickToUpsertMenuItem('menuForm')">确定</el-button>
</el-form-item>
</el-form>
</el-dialog>
</div>
</template>
<script>
const defaultMenu = {
name: undefined,
appid: Math.random() * 10000,
key: undefined,
page_path: undefined,
type: undefined,
url: undefined
}
import treeTable from '@/components/TreeTable'
import TreeTable from '@/components/TreeTable'
import { data } from './data.js'
export default {
name: 'CustomTreeTableDemo',
components: { treeTable },
components: {
TreeTable
},
data() {
return {
defaultMenu,
menus: [],
menu: { ...defaultMenu },
rules: {},
dialogFormVisible: false,
columns: [
{
label: '操作',
key: '__checkbox',
width: 400
label: '',
key: '__sperad'
},
{
label: 'ID',
key: 'id'
label: 'name',
key: 'name'
},
{
label: '事件',
key: 'event',
width: 200
label: 'type',
key: 'type'
},
{
label: '时间线',
key: 'timeLine'
label: 'appid',
key: 'appid'
},
{
label: 'key',
key: 'key'
},
{
label: '操作',
key: '__opt'
}
],
data:
{
id: 1,
event: '事件1',
timeLine: 100,
comment: '无',
son: [
{
id: 1.1,
event: '事件2',
timeLine: 10,
comment: '无'
key: '__opt_parent'
},
{
id: 1.2,
event: '事件3',
timeLine: 90,
comment: '无',
son: [
{
id: '1.2.1',
event: '事件4',
timeLine: 5,
comment: '无'
},
{
id: '1.2.2',
event: '事件5',
timeLine: 10,
comment: '无'
},
{
id: '1.2.3',
event: '事件6',
timeLine: 75,
comment: '无',
son: [
{
id: '1.2.3.1',
event: '事件7',
timeLine: 50,
comment: '无',
son: [
{
id: '1.2.3.1.1',
event: '事件71',
timeLine: 25,
comment: 'xx'
},
{
id: '1.2.3.1.2',
event: '事件72',
timeLine: 5,
comment: 'xx'
},
{
id: '1.2.3.1.3',
event: '事件73',
timeLine: 20,
comment: 'xx'
}
]
},
{
id: '1.2.3.2',
event: '事件8',
timeLine: 25,
comment: '无'
label: '操作',
key: '__opt',
width: '160px'
}
]
}
]
}
]
},
args: { children: 'son' },
isIndeterminate: false,
children: 'son'
computed: {
canAddMenuItem() {
return this.menus.length < 3
}
},
created() {
this.getWechatMenu()
},
methods: {
handleClick(scope) {
this.$message.success(scope.row.event)
getWechatMenu() {
this.menus = data.button
},
//
updateMenu() {
const button = JSON.parse(
JSON.stringify(this.menus, [
'name',
'type',
'appid',
'url',
'key',
'media_id',
'page_path',
'sub_button'
])
)
//
console.log('button', button)
// upsertWechatMenu({ button }).then(() => {
// this.$message.success('')
// })
},
clickToUpsertMenuItem(formName) {
this.$refs[formName].validate(valid => {
if (valid) {
this.updateMenuItem(this.menu)
this.dialogFormVisible = false
} else {
console.log('error submit!!')
return false
}
})
},
editMenuItem(menuItem) {
this.dialogFormVisible = true
this.menu = { ...menuItem }
},
//
//
addMenuItem(menuItem, level, index) {
if (level === 0) {
this.menus.push({
...menuItem,
sub_button: [],
__level: 0,
__expand: true,
__parent: null,
__show: true,
__select: false
})
}
if (level === 1) {
this.menus[index]['sub_button'].push({
...menuItem,
__level: 1,
__expand: true,
__parent: this.menus[index],
__show: true,
__select: false
})
}
},
deleteMenuItem(menuItem) {
if (menuItem.__level === 0) {
this.menus.splice(menuItem.__index, 1)
}
if (menuItem.__level === 1) {
this.menus[menuItem.__parent.__index]['sub_button'].splice(
menuItem.__index,
1
)
}
},
updateMenuItem(menuItem) {
if (menuItem.type === 'view') {
if (!menuItem.url) {
this.$message.error('请输入url')
return
}
}
if (menuItem.type === 'click') {
if (!menuItem.key) {
this.$message.error('请输入key')
return
}
}
if (menuItem.__level === 0) {
this.menus.splice(menuItem.__index, 1, menuItem)
}
if (menuItem.__level === 1) {
this.menus[menuItem.__parent.__index]['sub_button'].splice(
menuItem.__index,
1,
menuItem
)
}
}
}
}

View File

@ -0,0 +1,87 @@
export const data = {
button: [
{
type: '',
name: '账号绑定',
key: '',
url: '',
media_id: '',
appid: '',
page_path: '',
sub_button: [
{
type: 'view',
name: '推送开关',
key: '',
url: 'https://activity.wallstreetcn.com/wechat-notice/#/',
media_id: '',
appid: '',
page_path: '',
sub_button: []
},
{
type: 'view',
name: '绑定账号',
key: '',
url: 'https://m.wallstreetcn.com/bind/wechat',
media_id: '',
appid: '',
page_path: '',
sub_button: []
}
]
},
{
type: '',
name: '兑礼品卡',
key: '',
url: '',
media_id: '',
appid: '',
page_path: '',
sub_button: [
{
type: 'view',
name: '兑礼品卡',
key: '',
url: 'https://activity.wallstreetcn.com/giftredemption/?from=hrjsxk',
media_id: '',
appid: '',
page_path: '',
sub_button: []
},
{
type: 'view',
name: '下载APP',
key: '',
url:
'https://activity.wallstreetcn.com/newpackaget/receive.html?ngsem=111',
media_id: '',
appid: '',
page_path: '',
sub_button: []
},
{
type: 'view',
name: '在线客服',
key: '',
url: 'https://wdl.wallstreetcn.com/WechatIMG26.jpeg',
media_id: '',
appid: '',
page_path: '',
sub_button: []
}
]
},
{
type: 'view',
name: '关注我',
key: '',
url: 'https://liugq5713.github.io',
media_id: '',
appid: '',
page_path: '',
sub_button: []
}
]
}

View File

@ -11,10 +11,7 @@
</template>
<script>
/**
Auth: Lei.j1ang
Created: 2018/1/19-14:54
*/
import treeTable from '@/components/TreeTable'
export default {