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' import Vue from 'vue'
// 给数据添加额外的几个属性,并且扁平化数组 // 扁平化数组
export default function treeToTable( export default function treeToTable(
data, data, children = 'children'
{ parent = null, level = 0, expand = false, children = 'children', show = true, select = false } = {}
) { ) {
let tmp = [] 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, '__level', level)
Vue.set(item, '__expand', expand) Vue.set(item, '__expand', expand)
Vue.set(item, '__parent', parent) Vue.set(item, '__parent', parent)
Vue.set(item, '__show', show) Vue.set(item, '__show', show)
Vue.set(item, '__select', select) Vue.set(item, '__select', select)
tmp.push(item)
if (item[children] && item[children].length > 0) { if (item[children] && item[children].length > 0) {
const res = treeToTable(item[children], { addAttrs(item[children], {
parent: item, parent: item,
level: level + 1, level: level + 1,
expand, expand,
@ -23,8 +34,18 @@ export default function treeToTable(
status, status,
select 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'} " :style="{'padding-left':+scope.row.__level*spreadOffset + 'px'} "
class="el-icon-plus" 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> </span>
</template> </template>
<template v-if="item.key==='__checkbox'"> <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
<el-checkbox v-else :style="{'padding-left':+scope.row.__level*checkboxOffset + 'px'} " v-model="scope.row.__select" @change="handleCheckAllChange(scope.row)"/> 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> </template>
{{ scope.row[item.key] }} {{ scope.row[item.key] }}
</slot> </slot>
@ -39,7 +54,7 @@
</template> </template>
<script> <script>
import treeToArray from './eval.js' import treeToArray, { addAttrs } from './eval.js'
export default { export default {
name: 'TreeTable', name: 'TreeTable',
@ -52,11 +67,11 @@ export default {
type: Array, type: Array,
default: () => [] default: () => []
}, },
/* eslint-disable */ /* eslint-disable */
renderContent: { renderContent: {
type: Function type: Function
}, },
/* eslint-enable */ /* eslint-enable */
defaultExpandAll: { defaultExpandAll: {
type: Boolean, type: Boolean,
default: false default: false
@ -74,21 +89,45 @@ export default {
default: 50 default: 50
} }
}, },
data() {
return {
res: [],
guard: 1
}
},
computed: { computed: {
//
res() {
let tmp
if (!Array.isArray(this.data)) {
tmp = [this.data]
} else {
tmp = this.data
}
const func = this.renderContent || treeToArray
return func(tmp, { expand: this.defaultExpandAll, children: this.defaultChildren })
},
// children
children() { children() {
return this.evalArgs && this.evalArgs.children || '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 = val
}
const func = this.renderContent || treeToArray
if (this.guard > 0) {
addAttrs(tmp, {
expand: this.defaultExpandAll,
children: this.defaultChildren
})
this.guard--
}
const retval = func(tmp, this.defaultChildren)
this.res = retval
},
deep: true,
immediate: true
} }
}, },
methods: { methods: {

View File

@ -1,12 +1,28 @@
## 写在前面 ## 写在前面
此组件仅提供一个创建 TreeTable 的解决思路 此组件仅提供一个创建 TreeTable 的解决思路,本组件充分利用 vue 插槽的特性,方便用户自定义
evel.js 里面。addAttrs 方法会给数据添加几个属性treeTotable 会对数组扁平化。这些操作都不会破坏源数据,只是会新增属性。
调用 addAttrs 后,因\_\_parent 属性,会造成数据循环引用,使用 JSON.stringify()报错,所以转成 JSON 之前需要清除\_\_parent 属性。
## prop 说明 ## prop 说明
- data原始数据要求是一个数组或者对象
- columns列属性,要求是一个数组)
- renderContent数组扁平化方法可选
- defaultExpandAll默认是否全部展开默认全部展开
- defaultChildren子元素名默认为 children
- spreadOffset扩展符号的偏移量默认为 50px
- checkboxOffset复选框的偏移量默认为 50px
---
### 代码示例
- data(**必填**) - data(**必填**)
原始数据,要求是一个数组或者对象 原始数据,
```js ```js
const data = [ const data = [
@ -45,8 +61,6 @@ const data = [
- columns - columns
列属性,要求是一个数组
1. label: 显示在表头的文字 1. label: 显示在表头的文字
2. key: 对应 data 的 key。treeTable 将显示相应的 value 2. key: 对应 data 的 key。treeTable 将显示相应的 value
3. width: 每列的宽度,为一个数字(可选) 3. width: 每列的宽度,为一个数字(可选)
@ -60,19 +74,19 @@ const columns = [
// 如果添加复选框 // 如果添加复选框
{ label: '', key: '__checkbox', width: '200' }, { label: '', key: '__checkbox', width: '200' },
{ {
value: string, label: string,
text: string, key: string,
width: number width: number
}, },
{ {
value: string, label: string,
text: string, key: string,
width: number width: number
} }
] ]
``` ```
#### evalFunc #### renderContent
解析函数function非必须 解析函数function非必须
@ -80,28 +94,6 @@ const columns = [
如果提供了 evalFunc,那么会用提供的 evalFunc 去解析 data并返回 treeTable 渲染所需要的值。 如果提供了 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 如果有其他的需求,请参考[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> <template>
<div class="app-container"> <div>
<div class="app-container">
<el-tag style="margin-bottom:20px;"> <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
</el-tag> href="https://github.com/PanJiaChen/vue-element-admin/tree/master/src/components/TreeTable"
target="_blank"
<tree-table :data="data" :columns="columns" :default-expand-all="true" border style="width: 100%" default-children="son" > >Documentation</a>
<template slot="__selection" > </el-tag>
<el-table-column <TreeTable
type="selection" :data="menus"
width="55"/> :default-expand-all="true"
</template> :columns="columns"
border
<template slot="__expand"> default-children="sub_button"
<el-table-column >
type="expand" <template slot="__selection">
width="55"> <el-table-column type="selection" width="55"/>
<template slot-scope="props"> </template>
<el-button size="mini" type="danger" @click="handleExpandClick(props)">点我</el-button> <template slot="__expand">
</template> <el-table-column type="expand" width="55">
</el-table-column> <template>
</template> <el-tag type="info">
<template slot="__opt" slot-scope="{scope}"> 支持element-ui 的扩展和多选框事件哦
<el-button size="mini" type="primary" @click="handleClick(scope)">点我</el-button> </el-tag>
</template> </template>
</tree-table> </el-table-column>
</template>
<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>
<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> </div>
</template> </template>
<script> <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 { export default {
name: 'CustomTreeTableDemo', components: {
components: { treeTable }, TreeTable
},
data() { data() {
return { return {
defaultMenu,
menus: [],
menu: { ...defaultMenu },
rules: {},
dialogFormVisible: false,
columns: [ columns: [
{ {
label: '操作', label: '',
key: '__checkbox', key: '__sperad'
width: 400
}, },
{ {
label: 'ID', label: 'name',
key: 'id' key: 'name'
}, },
{ {
label: '事件', label: 'type',
key: 'event', key: 'type'
width: 200
}, },
{ {
label: '时间线', label: 'appid',
key: 'timeLine' key: 'appid'
},
{
label: 'key',
key: 'key'
}, },
{ {
label: '操作', label: '操作',
key: '__opt' key: '__opt_parent'
},
{
label: '操作',
key: '__opt',
width: '160px'
} }
], ]
data:
{
id: 1,
event: '事件1',
timeLine: 100,
comment: '无',
son: [
{
id: 1.1,
event: '事件2',
timeLine: 10,
comment: '无'
},
{
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: '无'
}
]
}
]
}
]
},
args: { children: 'son' },
isIndeterminate: false,
children: 'son'
} }
}, },
computed: {
canAddMenuItem() {
return this.menus.length < 3
}
},
created() {
this.getWechatMenu()
},
methods: { methods: {
handleClick(scope) { getWechatMenu() {
this.$message.success(scope.row.event) 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> </template>
<script> <script>
/**
Auth: Lei.j1ang
Created: 2018/1/19-14:54
*/
import treeTable from '@/components/TreeTable' import treeTable from '@/components/TreeTable'
export default { export default {