Quasar 是一个基于
Vue3
的前端UI框架,使用的是 Material Design, 对于国内而言(包括我)觉得确实不太好看,但因为之前使用的是Element-UI
,大概是由于审美疲劳,此次升级我的后台管理UI,没有继续使用Vue3
版本的Element-Plus
。 不久前我也是基于Quasar
开发了一个 Memos 客户端, 感觉效果还不错
前言
Quasar
默认没有树状表格的支持,但我觉得树状表格其实还挺重要的,尤其是在显示一些树状的数据,比如一个部门列表(打比方), 树状表格能够清晰的表明各部门之间的上下关系,目前我看实现树状表格的示例要么是基于 qhierarchy
(使用 q-markup-table
实现,定制太困难), 要么就是实现方式太复杂 tree-table-example, 所以我研究了一下,发现其实还挺简单的,故此记录
实现一(废弃)
虽然 Quasar
没有树状表格的组件, 但 q-table
默认是支持 Expanding rows
的,所以我一开始的想法是使用这个功能实现一个递归的组件, 就像这样
<template> <q-tr :key="row.id" :props="props"> <slot :row="row" :status="status" :indent="indent" :indentStyle="indentStyle(indent)"></slot> </q-tr> <template :key="index" v-for="(child, index) in row.children" v-if="status.expand"> <table-tree :props="props" :indent="indent + 1" :row="child" v-slot="scope"> <slot :row="scope.row" :status="scope.status" :indent="scope.indent" :indentStyle="indentStyle(scope.indent)"></slot> </table-tree> </template> </template>
而在父组件中调用只需要把原来的 props
改成 scope
<template v-slot:body="props"> <table-tree :props="props" :row="props.row" v-slot="scope"> <q-td auto-width> <q-checkbox size="sm" v-model="scope.status.selected" /> </q-td> <q-td> <q-btn flat round dense size="xs" :icon="scope.status.expand ? 'remove' : 'add'" @click="scope.status.expand = !scope.status.expand" v-if="scope.row.children && scope.row.children.length > 0" /> {{ scope.row.name }} </q-td> <q-td>{{ scope.row.desc }}</q-td> </table-tree> </template>
但是后面发现表格在多选状态下无法和原有的表格选择进行联动,只能自己实现选中和取消选中的方法,虽然也能实现,当很麻烦,只好另寻其它方式
实现二
后面研究了一下 Fernando2684 的实现方式, 虽然他的方式同样复杂,当实现原理其实很简单, 那就是修改原始数据,比如第二行需要进行展开,那我就在原来第二行的数据下把第二行的子数据追加上去,取消展开就把子数据删除,恢复原来的数据。有了这个原理后就好办了,我们可以利用 Expanding rows
时用到的 props.expand
参数来控制是否展开子数据, 然后利用计算属性动态的计算展开后的数据
-
表格模版
<q-table flat bordered row-key="id" separator="cell" selection="multiple" - :rows="table.list" + :rows="expandRows" v-model:expanded="table.expanded" v-model:selected="table.selected" v-model:pagination="table.pagination"> <template v-slot:body="props"> <q-tr :props="props"> <q-td auto-width> <q-checkbox size="sm" v-model="props.selected" /> </q-td> <q-td> <q-btn flat round dense size="xs" :icon="props.expand ? 'remove' : 'add'" @click="props.expand = !props.expand" v-if="props.row.children && props.row.children.length > 0" /> {{ props.row.name }} </q-td> <q-td>{{ props.row.desc }}</q-td> </q-tr> </template> </q-table>
-
表格数据
const table = ref({ list: [], expanded: [], selected: [], loading: true, pagination: { page: 1, sortBy: 'desc', descending: false, rowsPerPage: 0, rowsNumber: 0, } }) const expandRows = computed(() => { return makeRows(table.value.list, table.value.expanded) })
这里提一嘴,
pagination.rowsPerPage
默认最好设置为0(不限制每页的数量, 但会由由后端控制返回的行数), 否则展开子数据后的数据会被放到第二页, 影响数据查看 -
动态计算展开后的数据
function makeRows(rows, expanded) { if (!rows) { return [] } if (expanded.length == 0) { return rows } let newRows = [] rows.forEach(row => { newRows.push(row) if (expanded.indexOf(row.id) > -1) { newRows = newRows.concat(makeRows(row.children, expanded)) } }) return newRows }
注意,这里因为要递归计算多级子目录,所以不能直接在
setup
上直接用const
定义 -
缩进:
目前缩进确实有些问题,其中一个解决办法是原始数据会带有parent_id
字段,可以通过判断parent_id
大于0时增加缩进缩进的计算可以通过
makeRows
函数在遍历时添加每行数据的层级function makeRows(rows, expanded, indent) { if (!rows) { return [] } let newRows = [] rows.forEach(row => { row._indent = indent newRows.push(row) if (expanded.indexOf(row.id) > -1) { newRows = newRows.concat(makeRows(row.children, expanded, indent + 1)) } }) return newRows } export default function useTable(table) { const expandRows = computed(() => { return makeRows(table.value.list, table.value.expanded, 0) }) return { expandRows, } }
然后在表格中添加对应的偏移即可, 需要注意的是表格对齐方向需要设置为
left
(默认值)<template v-slot:body-cell-name="props"> <q-td :props="props" :style="'padding-left:' + ((props.row._indent || 0) + 1) + 'rem'"> <q-btn flat round dense size="xs" :icon="props.expand ? 'remove' : 'add'" @click="props.expand = !props.expand" v-if="props.row.children && props.row.children.length > 0" /> {{ props.row.name }} </q-td> </template>