基于Quasar实现一个树状选择器TreeSelect


Quasar 默认没有树状选择器的支持,而在 Vue 中通常会使用一个现成的vue-treeselect, 这是一个非常不错的 Select 组件, 此次不使用该组件,而是利用 Quasar 中的 q-selectq-tree 结合,实现一个类似的树状选择器

源数据结构

 1{
 2    "id": 1,
 3    "name": "admin",
 4    "desc": "管理员",
 5    "children": [{
 6        "id": 6,
 7        "name": "superadmin",
 8        "desc": "超级管理员",
 9        "children": [{
10            "id": 10,
11            "name": "vvvvv",
12            "desc": "dddd",
13        }]
14    }]
15}

组件模版

 1<template>
 2  <q-select dense outlined
 3            emit-value map-options
 4            option-value="id"
 5            option-label="name"
 6            label="角色列表"
 7            :options="list"
 8            :loading="table.loading"
 9            :multiple="multiple"
10            :use-chips="multiple"
11            @filter="filterFn"
12            :model-value="modelValue"
13            @update:model-value="value => $emit('update:modelValue', value)" >
14    <template v-slot:option="{ itemProps, opt, selected, toggleOption }">
15      <q-tree :nodes="[opt]"
16              node-key="id"
17              label-key="name"
18              :tick-strategy="multiple?'strict':'none'"
19              v-model:ticked="table.ticked"
20              v-model:selected="table.selected"
21              v-if="opt.parent_id == 0">
22        <template v-slot:default-header="prop">
23          <div class="items-center">
24            {{ prop.node.desc }}({{ prop.node.name }})
25          </div>
26        </template>
27      </q-tree>
28    </template>
29  </q-select>
30</template>

这里利用 slot 方式添加 q-tree 组件, 由于只能传递单个选项(option), 所以在传递给 q-tree 的参数 nodes 需要修改成 [opt]

而在 q-tree 中,单选和多选同样有区别,单选所使用的参数为 v-model:ticked, 点击选项即可选中,多选所使用的参数为 v-model:selected, 配置为多选状态时每个树状列表选项前会添加一个 checkbox,多选状态需要配置 tick-strategy 参数

  • strict: 父选项和子选项的选中状态相互独立

  • leaf: 选中父选项同时选中父选项下的子选项

  • none: 禁用多选

组件选项

 1const props = defineProps({
 2    multiple: {
 3        type: Boolean,
 4        default: false,
 5    },
 6    modelValue: {
 7        default: null,
 8        required: true,
 9    },
10})

父组件可以使用

1<role-select multiple v-model="form.roles"></role-select>

同时,为了区分多选和单选, 需要将没有用到的选项设置为 undefined, 同时监听选项的修改

 1const emit = defineEmits(['update:model-value'])
 2const { modelValue, multiple } = toRefs(props)
 3
 4if (multiple.value) {
 5    table.value.selected = undefined
 6
 7    watch(() => table.value.ticked, (val) => {
 8        emit('update:model-value', val)
 9    })
10
11} else {
12    table.value.ticked = undefined
13
14    watch(() => table.value.selected, (val) => {
15        emit('update:model-value', val)
16    })
17}

为了让子选项在选中状态时也能够显示 .name,而不是 .id,需要遍历所有选项,将其合成一个列表,在 q-tree 组件中通过判断 row.parent_id 只显示父组件, 这里也可以通过定义 q-selectselected 或者是 selected-item 插槽来显示正确的名称

 1function makeRows(rows) {
 2    if (!rows) {
 3        return []
 4    }
 5    let newRows = []
 6    rows.forEach(row => {
 7        newRows.push(row)
 8        if (row.children && row.children.length > 0) {
 9            newRows = newRows.concat(makeRows(row.children))
10        }
11    })
12    return newRows
13}
14
15const list = computed(() => {
16    return makeRows(table.value.list)
17})

最后就是从后端获取列表数据

 1const table = ref({
 2    list: [],
 3    ticked: [],
 4    expanded: [],
 5    selected: [],
 6    loading: false,
 7})
 8
 9const handleFetch = (props) => {
10    ...
11    table.value.loading = true
12    return api.fetchList(query).then(response => {
13        table.value.list = response.data.list
14    }).finally(() => {
15        table.value.loading = false
16    })
17}
18
19const filterFn = (val, update, abort) => {
20    if (table.value.list && table.value.list.length > 0) {
21        update()
22        return
23    }
24    update(() => {handleFetch()})
25}

最终实现

quasar-tree-select.png quasar-tree-select-multiple.png

作者: honmaple
链接: https://honmaple.me/articles/2023/05/基于Quasar实现一个树状选择器TreeSelect.html
版权: CC BY-NC-SA 4.0 知识共享署名-非商业性使用-相同方式共享4.0国际许可协议
wechat
alipay

加载评论