Skip to content

Cascader 级联控件

文件夹、组织架构、生物分类、国家地区等等,世间万物的大多数结构都是树形结构。使用级联控件可以清晰展现其中的层级关系,并具有展开收起选择等交互功能。

组件注册

js
import { FCascader } from '@fesjs/fes-design';

app.use(FCascader);

代码演示

基础用法

基础的树形结构展示。

play
<template>
    <FForm :labelWidth="160">
        <FFormItem label="选中可取消:">
            <FRadioGroup v-model="cancelable">
                <FRadio :value="true">是(默认)</FRadio>
                <FRadio :value="false"></FRadio>
            </FRadioGroup>
        </FFormItem>
        <FFormItem label="展开次级菜单:">
            <FRadioGroup v-model="expandTrigger">
                <FRadio value="click">click(默认)</FRadio>
                <FRadio value="hover">hover</FRadio>
            </FRadioGroup>
        </FFormItem>
        <FFormItem label="父节点可选中:">
            <FRadioGroup v-model="checkStrictly">
                <FRadio value="all"></FRadio>
                <FRadio value="">否(默认)</FRadio>
            </FRadioGroup>
        </FFormItem>
    </FForm>

    <FDivider />

    <FCascader
        :data="data"
        :cancelable="cancelable"
        :expandTrigger="expandTrigger"
        :checkStrictly="checkStrictly"
    />
</template>

<script>
import { reactive, ref } from 'vue';

function createData(level = 1, baseKey = '') {
    if (!level) {
        return undefined;
    }
    return Array.apply(null, { length: 2 }).map((_, index) => {
        const key = `${baseKey}${level}${index}`;
        return {
            label: createLabel(level),
            value: key,
            children: createData(level - 1, key),
        };
    });
}

function createLabel(level) {
    if (level === 4) {
        return '道生一';
    }
    if (level === 3) {
        return '一生二';
    }
    if (level === 2) {
        return '二生三';
    }
    if (level === 1) {
        return '三生万物';
    }
}

export default {
    setup() {
        const data = reactive(createData(4));
        const cancelable = ref(true);
        const expandTrigger = ref('hover');
        const checkStrictly = ref('');
        return {
            data,
            cancelable,
            expandTrigger,
            checkStrictly,
        };
    },
};
</script>

可选择多个节点

可以选择多个节点。

play
<template>
    <FForm :labelWidth="160">
        <FFormItem label="可选中多个节点:">
            <FRadioGroup v-model="multiple">
                <FRadio :value="false">否(默认)</FRadio>
                <FRadio :value="true"></FRadio>
            </FRadioGroup>
        </FFormItem>
    </FForm>

    <FDivider />

    <FCascader :data="data" :multiple="multiple" />
</template>

<script>
import { reactive, ref } from 'vue';

function createData(level = 1, baseKey = '') {
    if (!level) {
        return undefined;
    }
    return Array.apply(null, { length: 2 }).map((_, index) => {
        const key = `${baseKey}${level}${index}`;
        return {
            label: createLabel(level),
            value: key,
            children: createData(level - 1, key),
        };
    });
}

function createLabel(level) {
    if (level === 4) {
        return '道生一';
    }
    if (level === 3) {
        return '一生二';
    }
    if (level === 2) {
        return '二生三';
    }
    if (level === 1) {
        return '三生万物';
    }
}

export default {
    setup() {
        const data = reactive(createData(4));
        const multiple = ref(true);
        return {
            data,
            multiple,
        };
    },
};
</script>

可勾选

适用于需要选择层级时使用。

play
<template>
    <FForm :labelWidth="160">
        <FFormItem label="父子关联:">
            <FRadioGroup v-model="cascade">
                <FRadio :value="true">是(默认)</FRadio>
                <FRadio :value="false"></FRadio>
            </FRadioGroup>
        </FFormItem>
    </FForm>

    <FDivider />

    <FCascader
        :data="data"
        checkable
        :cascade="cascade"
        :selectable="false"
    />
</template>

<script>
import { reactive, ref } from 'vue';

function createData(level = 1, baseKey = '') {
    if (!level) {
        return undefined;
    }
    return Array.apply(null, { length: 2 }).map((_, index) => {
        const key = `${baseKey}${level}${index}`;
        return {
            label: createLabel(level),
            value: key,
            children: createData(level - 1, key),
        };
    });
}

function createLabel(level) {
    if (level === 4) {
        return '道生一';
    }
    if (level === 3) {
        return '一生二';
    }
    if (level === 2) {
        return '二生三';
    }
    if (level === 1) {
        return '三生万物';
    }
}

export default {
    setup() {
        const data = reactive(createData(4));
        const cascade = ref(true);
        return {
            data,
            cascade,
        };
    },
};
</script>

默认展开 + 默认选中 + 默认勾选

默认配置并不会做校验处理,比如cascade = true的时候,默认仅勾选父节点,子节点并不会自动勾选。

  • 通过expandedKeys配置默认展开节点;
  • 通过selectedKeys配置默认选择节点;
  • 通过checkedKeys配置默认勾选节点;
play
<template>
    <FCascader
        v-model:selectedKeys="selectedKeys"
        v-model:expandedKeys="expandedKeys"
        v-model:checkedKeys="checkedKeys"
        :data="data"
        checkable
    />
</template>

<script>
import { reactive, ref } from 'vue';

function createData(level = 1, baseKey = '') {
    if (!level) {
        return undefined;
    }
    return Array.apply(null, { length: 2 }).map((_, index) => {
        const key = `${baseKey}${level}${index}`;
        return {
            label: createLabel(level),
            value: key,
            children: createData(level - 1, key),
        };
    });
}

function createLabel(level) {
    if (level === 4) {
        return '道生一';
    }
    if (level === 3) {
        return '一生二';
    }
    if (level === 2) {
        return '二生三';
    }
    if (level === 1) {
        return '三生万物';
    }
}

export default {
    setup() {
        const data = reactive(createData(4));
        const selectedKeys = ref(['4031']);
        const expandedKeys = ref(['40']);
        const checkedKeys = ref(['40']);

        return {
            data,
            selectedKeys,
            expandedKeys,
            checkedKeys,
        };
    },
};
</script>

禁用节点

无法被选中和点击。

play
<template>
    <FForm :labelWidth="160">
        <FFormItem label="禁用选项:">
            <FRadioGroup v-model="disabledOption">
                <FRadio value="parent">仅父节点</FRadio>
                <FRadio value="leaf">仅叶子节点</FRadio>
                <FRadio value="specific">指定项</FRadio>
            </FRadioGroup>
        </FFormItem>
    </FForm>

    <FDivider />

    <FCascader :data="data" checkable />
</template>

<script>
import { computed, ref } from 'vue';
import { cloneDeep } from 'lodash-es';

function createData(level = 1, baseKey = '') {
    if (!level) {
        return undefined;
    }
    return Array.apply(null, { length: 2 }).map((_, index) => {
        const key = `${baseKey}${level}${index}`;
        return {
            label: createLabel(level),
            value: key,
            children: createData(level - 1, key),
        };
    });
}
function createLabel(level) {
    if (level === 4) {
        return '道生一';
    }
    if (level === 3) {
        return '一生二';
    }
    if (level === 2) {
        return '二生三';
    }
    if (level === 1) {
        return '三生万物';
    }
}

function flatNodes(nodes = []) {
    return nodes.reduce((res, node) => {
        res.push(node);
        if (node.children && node.children.length) {
            res = res.concat(flatNodes(node.children));
        }
        return res;
    }, []);
}

export default {
    setup() {
        const originData = createData(4);
        const disabledOption = ref('parent');
        const data = computed(() => {
            const data = cloneDeep(originData);
            const allNodes = flatNodes(data);
            if (disabledOption.value === 'specific') {
                data.forEach((item) => {
                    item.disabled = true;
                });
            } else if (disabledOption.value === 'parent') {
                allNodes.forEach((item) => {
                    item.children
                    && item.children.length
                    && (item.disabled = true);
                });
            } else if (disabledOption.value === 'leaf') {
                allNodes.forEach((item) => {
                    !(item.children && item.children.length)
                    && (item.disabled = true);
                });
            }

            return data;
        });
        return {
            data,
            disabledOption,
        };
    },
};
</script>

异步加载

点击展开节点时加载子选项。

play
<template>
    <FCascader :data="data" :loadData="loadData" checkable remote />
</template>

<script>
import { reactive } from 'vue';

function createData(level = 1, baseKey = '') {
    if (!level) {
        return undefined;
    }
    return Array.apply(null, { length: 2 }).map((_, index) => {
        const key = `${baseKey}${level}${index}`;
        return {
            label: createLabel(level),
            value: key,
            children: createData(level - 1, key),
        };
    });
}

function createLabel(level) {
    if (level === 4) {
        return '道生一';
    }
    if (level === 3) {
        return '一生二';
    }
    if (level === 2) {
        return '二生三';
    }
    if (level === 1) {
        return '三生万物';
    }
}

export default {
    setup() {
        const data = reactive([]);
        const loadData = (node) => {
            return new Promise((resolve) => {
                setTimeout(() => {
                    let children = [];
                    if (node) {
                        children = [
                            {
                                label: `${node.label}1`,
                                value: `${node.value}-1`,
                                isLeaf:
                                    node.value.split('-').length > 1,
                            },
                            {
                                label: `${node.label}2`,
                                value: `${node.value}-2`,
                                isLeaf:
                                    node.value.split('-').length > 1,
                            },
                        ];
                    } else {
                        children = createData(2);
                    }
                    resolve(children);
                }, 2000);
            });
        };

        return {
            loadData,
            data,
        };
    },
};
</script>

前缀与后缀

放一些附加展示或操作。

play
<template>
    <FCascader :data="data" checkable />
</template>

<script>
import { h, reactive } from 'vue';
import { PictureOutlined } from '@fesjs/fes-design/icon';

function createData(level = 1, baseKey = '', prefix, suffix) {
    if (!level) {
        return undefined;
    }
    return Array.apply(null, { length: 2 }).map((_, index) => {
        const key = `${baseKey}${level}${index}`;
        const children = createData(level - 1, key, prefix, suffix);
        return {
            label: createLabel(level),
            value: key,
            children,
            prefix: prefix ? () => h(PictureOutlined) : null,
            suffix:
                suffix && children && children.length
                    ? () => h('span', null, children.length)
                    : null,
        };
    });
}

function createLabel(level) {
    if (level === 4) {
        return '道生一';
    }
    if (level === 3) {
        return '一生二';
    }
    if (level === 2) {
        return '二生三';
    }
    if (level === 1) {
        return '三生万物';
    }
}

export default {
    setup() {
        const data = reactive(createData(4, '', true, true));

        return {
            data,
        };
    },
};
</script>

文字超长溢出

play
<template>
    <FCascader :data="data" />
</template>

<script>
import { reactive, ref } from 'vue';

function repeatString(str, n) {
    return new Array(n + 1).join(str);
}

function createData(level = 1, baseKey = '') {
    if (!level) {
        return undefined;
    }
    return Array.apply(null, { length: 2 }).map((_, index) => {
        const key = `${baseKey}${level}${index}`;
        return {
            label: repeatString(createLabel(level), 10),
            value: key,
            children: createData(level - 1, key),
        };
    });
}

function createLabel(level) {
    if (level === 4) {
        return '道生一';
    }
    if (level === 3) {
        return '一生二';
    }
    if (level === 2) {
        return '二生三';
    }
    if (level === 1) {
        return '三生万物';
    }
}

export default {
    setup() {
        const data = reactive(createData(4));
        const cancelable = ref(true);
        return {
            data,
            cancelable,
        };
    },
};
</script>

Cascader Props

属性说明类型默认值
data展示数据Array<CascaderOption>[]
expandedKeys(v-model)展开的节点的 key 的数组Array<string | number>[]
selectable是否可选中节点booleantrue
selectedKeys(v-model)设置选中的节点Array<string | number>[]
multiple是否能选中多个节点booleanfalse
cancelable选中后是否可以再次点击取消选中booleantrue
checkable是否显示 Checkbox 选择框booleanfalse
cascade当勾选选择框时,父子节点的选择框勾选状态是否关联,相互影响booleantrue
checkStrictly设置勾选策略来指定勾选回调返回的值。多选时,all 表示回调函数值为全部选中节点;parent 表示回调函数值为父节点(当父节点下所有子节点都选中时);child 表示回调函数值为子节点。单选时,all 表示回调函数值可为父节点。stringchild
checkedKeys(v-model)勾选节点 key 的数组Array<string | number>[]
childrenField替代 CascaderOption 中的 children 字段名stringchildren
valueField替代 CascaderOption 中的 value 字段名stringvalue
labelField替代 CascaderOption 中的 label 字段名stringlabel
remote是否异步获取选项,和 loadData 配合booleanfalse
loadData异步加载数据的回调函数(node: null | CascaderOption) => Promise<CascaderOption[]>-
expandTrigger次级菜单的展开方式,可选值为 click,hoverstringclick

Cascader Events

事件名称说明回调参数
check点击节点中的选择框时触发({ checkedKeys, node, event, checked }) => void
expand展开、收起节点时触发({ expandedKeys, node, event, expanded }) => void
select点击节点内容时触发({ selectedKeys, node, event, selected }) => void

Cascader Methods

方法名称说明参数
selectNode选中节点(value)
expandNode展开树节点(value)
checkNodecheck 节点(value)

CascaderOption props

属性说明类型默认值
value节点的 key,需要唯一,可使用 valueField 修改字段名string / number-
label节点的内容,可使用 labelField 修改字段名string-
children?节点的子节点CascaderOption[][]
disabled?是否禁用节点, 默认为Cascader组件的disabledboolean-
selectable?是否禁用选中节点,默认为Cascader组件的selectableboolean-
checkable?是否禁用勾选节点,默认为Cascader组件的checkableboolean-
isLeaf?节点是否是叶节点,在 remote 模式下是必须的booleanfalse
prefix?节点的前缀string / (() => VNodeChild)null
suffix?节点的后缀string / (() => VNodeChild)null