最终效果
为什么要使用material-ui组件?
因为公司最近想要产品升级,并且发现material-ui组件的美观程度很合适,因此选择了material-ui的前端组件库.
material-ui组件的评价
material-ui组件库的美观程度还是很好的,让人眼前一亮,但是对于组件库的封装就有点一言难尽了,对国内这种只使用轮子的cv模式来说 不是很友好,且因为是英文文档 中文社区也很少 对国内不是很友好.
下拉树形选择实现的功能
因为以前一直使用antd组件,使用对下拉树形组件的实现功能基本上是参考antd的树形下拉去实现的 因此支持
- 1.支持自定义label value children字段
- 2.支持下拉多选.单选
首选进行组件介绍
- 1.Autocomplete自动完成组件:因为## material-ui组件的下拉选择不支持搜索功能 所有选择了自动完成组件
import { RichTreeView } from "@mui/x-tree-view/RichTreeView";
- 2.RichTreeView 树形下拉组件:实现下拉组件的树形效果
import { Autocomplete, FormHelperText, TextField } from "@mui/material";
代码详情
import * as React from "react";
import FormControl from "@mui/material/FormControl";
import { RichTreeView } from "@mui/x-tree-view/RichTreeView";
import { Autocomplete, FormHelperText, TextField } from "@mui/material";
import { generateTreeList } from "@/utils/index";
import { isEmpty } from "lodash";
import { MyObject } from "@/utils/index";
interface MyProps {
multiple?: boolean;
items?: MyObject[];
value?: string | string[];
valueExpr?: string;
displayExpr?: string;
label?: string;
width?: number;
placeholder?: string;
disabled?: boolean;
onChange?: Function;
childenExpr?: string;
eventList?: MyObject;
size?: "small" | "medium" | string;
errors?: any;
name?: string;
}
//树形下拉组件
const TreeSelect = (props: MyProps, ref: any) => {
const {
multiple = false,
items = [],
valueExpr = "id",
displayExpr = "label",
childenExpr = "children",
width = undefined,
disabled = false,
onChange,
label,
eventList = {},
size,
name,
errors,
placeholder = "请选择",
} = props;
const value = props.value ?? (multiple ? [] : undefined);
const treevalue = multiple ? [] : null;
const [open, setOpen] = React.useState(false);
const [treeList, setTreeList] = React.useState<MyObject[]>([]);
const [treeValue, setTreeValue] = React.useState<any>(treevalue); //存储回显的字段
const [valueField, setValueField] = React.useState(value); //存储当初的value值 用于回显
const [searchText, setSearchText] = React.useState(""); //搜索查询
// 默认让组件全部展开(返回他们的全部id)
const getExpandedItems = (items: any) => {
let dataList: any[] = [];
const expandeList = (option: any) => {
dataList.push(option.id);
if (option[childenExpr] && option[childenExpr].length) {
option[childenExpr].forEach((item: any) => {
expandeList(item);
});
}
};
expandeList(items);
return dataList;
};
// 遍历查找点击的当前组件的对象
const getTreeData = (list: any, id: string) => {
let data: any = null;
const expandeList = (Option: any) => {
if (Option[valueExpr] == id) {
data = Option;
return;
} else if (Option[childenExpr] && Option[childenExpr].length) {
Option[childenExpr].forEach((item: any) => {
expandeList(item);
});
}
};
list.forEach((listitem: any) => {
expandeList(listitem);
});
return data;
};
//在选择时不会触发自动完成的下拉事件 因此这里是树形组件的选择事件
const handleItemSelectionToggle = (event: any, itemId: any) => {
// console.log("handleItemSelectionToggle", event, itemId);
if (multiple) {
let data: any = getTreeData(displayedOptions, itemId[0]);
setValueField(itemId);
treeValue.push(data);
setTreeValue([...treeValue]);
if (onChange) {
onChange(itemId);
}
} else {
let data = getTreeData(displayedOptions, itemId);
setValueField(itemId);
setTreeValue(data);
if (onChange) {
onChange(itemId);
}
setOpen(false);
}
};
// 用于返回当前数据 主要是用于配合下拉搜索功能
const containsText = (text: any, searchText: string) => {
let flag: Boolean = false;
const judgment = (option: any) => {
let data = option[displayExpr]
.toLowerCase()
.indexOf(searchText.toLowerCase());
if (data > -1) {
flag = true;
return;
} else if (option[childenExpr] && option[childenExpr].length) {
// 进行遍历孩子组件的每一个数据
option[childenExpr].forEach((item: any) => {
judgment(item);
});
} else {
return;
}
};
if (!searchText) {
flag = true;
} else {
judgment(text);
}
return flag;
};
// 因为需要进行下拉搜索过滤 所有树形数据为动态获取
const displayedOptions = React.useMemo(() => {
if (!isEmpty(items)) {
return (
treeList.filter((option) => containsText(option, searchText)) || []
);
} else {
return [];
}
}, [searchText, treeList]);
// 自动完成的搜索事件
const handleInputValue = (event: any) => {
setSearchText(event.target.value);
};
// 自动完成的改变事件(注意 选择下拉时不会触发这个事件 只有在使用自动完成组件自带的 清除时才会触发 所有才需要写)
const handleChange = (event: any, data: any) => {
const {
target: { value },
} = event;
// console.log("111333", value, data);
let newData;
if (multiple) {
newData = data.map((item: any) => {
return item[valueExpr];
});
setValueField(newData ?? []);
} else {
newData = value;
}
setValueField(newData);
setTreeValue(data);
setSearchText("");
setOpen(false);
if (onChange) {
onChange(newData);
}
};
React.useEffect(() => {
if (!isEmpty(items)) {
// 将树形的children进行绑定
let TreeListData = generateTreeList(items, childenExpr);
setTreeList(TreeListData);
if (multiple) {
//初始化 如果是多选 初始值必须是数组 如果是单选 初始值必须是null或对象
let values: string[] = props.value as string[];
if (typeof props.value == "string" && props.value) {
values = props.value.split(",");
} else if (!props.value) {
values = [];
}
let data: any = [];
values.map((item: string) => {
let list = getTreeData(displayedOptions, item);
list && data.push(list);
});
// console.log("data", data, values);
setTreeValue(data ?? []);
setValueField(values);
} else {
let data = getTreeData(displayedOptions, props.value as string);
setValueField(props.value);
setTreeValue(data ?? null);
}
}
}, [items,multiple]);
return (
<FormControl fullWidth sx={{ width: width }} size="small">
<Autocomplete
id="grouped-demo"
getOptionLabel={(option: any) => option[displayExpr]} //显示label字段
options={displayedOptions} //接收数据 树形下拉类型数据
value={multiple ? treeValue?? []:treeValue} //配合getOptionLabel 就得接收一个对象 并且空值时只能为null 多选必须是数组类型
open={open} //控制下拉弹出打开关闭
size={size} //控制组件的大小
disabled={disabled}
placeholder={placeholder}
multiple={multiple} //多选
{...eventList} //其他额外配置
renderInput={(params) => (
<TextField
{...params}
label={label}
onChange={handleInputValue}
error={name ? !!props?.errors[name] : false}
/>
)}
onChange={(e, data: any) => {
handleChange(e, data);
}}
onFocus={() => {
setOpen(true);
}}
onBlur={() => {
setSearchText('')
setOpen(false);
}}
filterOptions={(options) => options}
renderOption={(
props,
option //自定义渲染内容
) => (
<li key={option.id ?? option[valueExpr]}>
<RichTreeView //树形下拉
checkboxSelection={multiple} //下拉多选带前置复选框
multiSelect={multiple} //多选带着个才能多选
selectedItems={valueField ?? (multiple ? [] : null)} //用于点开下拉时回显已选中的选项
onSelectedItemsChange={handleItemSelectionToggle} //当点击下拉选项时触发的事件 相对于onChange事件
expandedItems={getExpandedItems(option)} //设置它可以让下拉数据默认全部展开
items={[option]} //对每一项就行绑定
/>
</li>
)}
/>
{name && props?.errors[name] && (
<FormHelperText error>{props?.errors[name].message}</FormHelperText>
)}
</FormControl>
);
};
export default React.forwardRef(TreeSelect);