import { computed, reactive, ref, watch, watchEffect } from "vue"; type Item = { id: string; children?: Item[] } & T; export type TreeOption = { label: string; level?: number; value: any; children?: TreeOption[]; }; export type Props = { modelValue: string; label?: string; hideAll?: boolean; allText?: string; notDefault?: boolean; disabled?: boolean; }; export type Mapper = { label: string; level: string; }; const treeSelectOptionCover = ( data: Item[], mapping: Mapper ): TreeOption[] => data.map( (item) => ({ label: (item as any)[mapping.label], value: item.id, level: (item as any)[mapping.level], children: item.children && item.children.length ? treeSelectOptionCover(item.children, mapping) : null, } as any) ); const getTreePath = (options: TreeOption[], value: string): null | string[] => { for (const option of options) { if (option.value === value) { return [option.value]; } else if (option.children) { const cpath = getTreePath(option.children, value); if (cpath) { return [option.value, ...cpath]; } } } return null; }; const getTreeSelect = ( options: TreeOption[], value: string ): null | TreeOption => { for (const option of options) { if (option.value === value) { return option; } else if (option.children) { const coption = getTreeSelect(option.children, value); if (coption) { return coption; } } } return null; }; const allOption: TreeOption = { label: "全部", value: "" }; export const useTreeSelect = ( props: Props, request: () => Promise[]>, update: (val: string) => void, mapping: Mapper = { label: "name", level: "level" } ) => { // 原始数据 const optionsRaw = ref[]>([]); // 级联控件需要数据 const options = computed(() => { const dataOptions = treeSelectOptionCover(optionsRaw.value, mapping); return props.hideAll ? dataOptions : [allOption, ...dataOptions]; }); // 级联控件续高亮value const path = computed({ get: () => getTreePath(options.value, props.modelValue)!, set: (path) => { update(path ? path[path.length - 1] : ""); }, }); const currentOption = computed(() => getTreeSelect(options.value, props.modelValue) ); const label = computed( () => options.value.find((option) => option.value === props.modelValue) ?.label || "" ); watch( () => props.modelValue, () => { if (!props.notDefault && !props.modelValue && options.value.length) { update(options.value[0].value); } }, { immediate: true } ); request().then((data) => (optionsRaw.value = data as any)); return reactive({ label, path, options, raw: optionsRaw, currentOption, }); };