Quasar默认没有树状选择器的支持,而在Vue中通常会使用一个现成的vue-treeselect, 这是一个非常不错的Select组件, 此次不使用该组件,而是利用Quasar中的q-select和q-tree结合,实现一个类似的树状选择器
源数据结构
{
"id": 1,
"name": "admin",
"desc": "管理员",
"children": [{
"id": 6,
"name": "superadmin",
"desc": "超级管理员",
"children": [{
"id": 10,
"name": "vvvvv",
"desc": "dddd",
}]
}]
}
组件模版
<template> <q-select dense outlined emit-value map-options option-value="id" option-label="name" label="角色列表" :options="list" :loading="table.loading" :multiple="multiple" :use-chips="multiple" @filter="filterFn" :model-value="modelValue" @update:model-value="value => $emit('update:modelValue', value)" > <template v-slot:option="{ itemProps, opt, selected, toggleOption }"> <q-tree :nodes="[opt]" node-key="id" label-key="name" :tick-strategy="multiple?'strict':'none'" v-model:ticked="table.ticked" v-model:selected="table.selected" v-if="opt.parent_id == 0"> <template v-slot:default-header="prop"> <div class="items-center"> {{ prop.node.desc }}({{ prop.node.name }}) </div> </template> </q-tree> </template> </q-select> </template>
这里利用 slot 方式添加 q-tree 组件, 由于只能传递单个选项(option), 所以在传递给 q-tree 的参数 nodes 需要修改成 [opt]。
而在 q-tree 中,单选和多选同样有区别,单选所使用的参数为 v-model:ticked, 点击选项即可选中,多选所使用的参数为 v-model:selected, 配置为多选状态时每个树状列表选项前会添加一个 checkbox,多选状态需要配置 tick-strategy 参数
-
strict: 父选项和子选项的选中状态相互独立 -
leaf: 选中父选项同时选中父选项下的子选项 -
none: 禁用多选
组件选项
const props = defineProps({ multiple: { type: Boolean, default: false, }, modelValue: { default: null, required: true, }, })
父组件可以使用
<role-select multiple v-model="form.roles"></role-select>
同时,为了区分多选和单选, 需要将没有用到的选项设置为 undefined, 同时监听选项的修改
const emit = defineEmits(['update:model-value']) const { modelValue, multiple } = toRefs(props) if (multiple.value) { table.value.selected = undefined watch(() => table.value.ticked, (val) => { emit('update:model-value', val) }) } else { table.value.ticked = undefined watch(() => table.value.selected, (val) => { emit('update:model-value', val) }) }
为了让子选项在选中状态时也能够显示 .name,而不是 .id,需要遍历所有选项,将其合成一个列表,在 q-tree 组件中通过判断 row.parent_id 只显示父组件, 这里也可以通过定义 q-select 的 selected 或者是 selected-item 插槽来显示正确的名称
function makeRows(rows) { if (!rows) { return [] } let newRows = [] rows.forEach(row => { newRows.push(row) if (row.children && row.children.length > 0) { newRows = newRows.concat(makeRows(row.children)) } }) return newRows } const list = computed(() => { return makeRows(table.value.list) })
最后就是从后端获取列表数据
const table = ref({ list: [], ticked: [], expanded: [], selected: [], loading: false, }) const handleFetch = (props) => { ... table.value.loading = true return api.fetchList(query).then(response => { table.value.list = response.data.list }).finally(() => { table.value.loading = false }) } const filterFn = (val, update, abort) => { if (table.value.list && table.value.list.length > 0) { update() return } update(() => {handleFetch()}) }
最终实现
知识共享署名-非商业性使用-相同方式共享4.0国际许可协议