「这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战」
有几种情况:
- 只有一级标题
{
label: "春梅红",
value: 201,
children: [],
},
- 有二级标题,但二级标题只有一个
{
label: "淡曙红",
value: 401,
children: [
{
label: "粉团花红",
value: 401,
},
],
},
- 有二级标题,二级标题有多个
{
label: "暮云灰",
value: 202,
children: [
{
label: "凤仙花红",
value: 201,
},
{
label: "龙睛鱼紫",
value: 202,
},
],
},
- 有三级标题,有一个或多个
{
label: "槿紫",
value: 301,
children: [
{
label: "满天星紫",
value: 301,
children: [
{
label: "花青",
value: 301,
},
{
label: "井天蓝",
value: 302,
},
],
},
{
label: "青矾绿",
value: 30901,
children: [
{
label: "麦秆黄",
value: 380901,
},
],
},
],
},
先使用三个 select 搭建
import { useEffect, useState, useMemo } from "react";
import { Row, Col, Select } from "antd";
import { intersectionBy } from "lodash-es";
interface ThreeLevelSelectProps {
value: string[];
onChange: (val: string[] | undefined) => void;
options: any[];
disabled: boolean;
}
const ThreeLevelSelect = ({ value, onChange, options, disabled }: ThreeLevelSelectProps) => {
return (
<Row justify="space-between" gutter={12}>
<Col span={8}>
<Select />
</Col>
<Col span={8}>
<Select />
</Col>
<Col span={8}>
<Select />
</Col>
</Row>
);
};
export default ThreeLevelSelect;
设置 value 和 onChange
const ThreeLevelSelect = ({ value, onChange, options, disabled }: ThreeLevelSelectProps) => {
const [firstValue, setFirstValue] = useState<number>();
const [secondValue, setSecondValue] = useState<string[] | number>();
const [thirdValue, setThirdValue] = useState<string[] | number>();
const onFirstChange = (val) => {
setFirstValue(val);
};
const onSecondChange = (val) => {
setSecondValue(val);
};
const onThirdChange = (val) => {
setThirdValue(val);
};
return (
<Row justify="space-between" gutter={12}>
<Col span={8}>
<Select value={firstValue} onChange={onFirstChange} />
</Col>
<Col span={8}>
<Select value={secondValue} onChange={onSecondChange} />
</Col>
<Col span={8}>
<Select value={thirdValue} onChange={onThirdChange} />
</Col>
</Row>
);
};
设置 options
为第一个 select 设置 options,第一个的 options === 传入的 options
const ThreeLevelSelect = ({ value, onChange, options, disabled }: ThreeLevelSelectProps) => {
const [firstValue, setFirstValue] = useState<number>();
const [secondValue, setSecondValue] = useState<string[] | number>();
const [thirdValue, setThirdValue] = useState<string[] | number>();
const onFirstChange = (val) => {
setFirstValue(val);
};
const onSecondChange = (val) => {
setSecondValue(val);
};
const onThirdChange = (val) => {
setThirdValue(val);
};
return (
<Row justify="space-between" gutter={12}>
<Col span={8}>
<Select value={firstValue} onChange={onFirstChange} options={options} />
</Col>
<Col span={8}>
<Select value={secondValue} onChange={onSecondChange} />
</Col>
<Col span={8}>
<Select value={thirdValue} onChange={onThirdChange} />
</Col>
</Row>
);
};
设置 第二个 和 第三个的 options
const [secondOptions, setSecondOptions] = useState<any>([]);
const [thirdOptions, setThirdOptions] = useState<any>([]);
return (
<Row justify="space-between" gutter={12}>
<Col span={8}>
<Select value={firstValue} onChange={onFirstChange} options={options} />
</Col>
<Col span={8}>
<Select value={secondValue} onChange={onSecondChange} options={secondOptions} />
</Col>
<Col span={8}>
<Select value={thirdValue} onChange={onThirdChange} options={thirdOptions} />
</Col>
</Row>
);
const onFirstChange = (val) => {
setFirstValue(val);
const secOptions = options?.find((item) => item?.value === val)?.children;
setSecondOptions(secOptions);
};
const onSecondChange = (val) => {
setSecondValue(val);
const thrOptions = secondOptions?.find((item) => item?.value === val)?.children;
setThirdOptions(thrOptions);
};
设置多选
返回的数据中,可能存在多选现象,因为我们需要设置多选。
const [isSecondMultiple, setIsSecondMultiple] = useState<boolean>(false);
const [isThirdMultiple, setIsThridMultiple] = useState<boolean>(false);
return (
<Row justify="space-between" gutter={12}>
<Col span={8}>
<Select value={firstValue} onChange={onFirstChange} options={options} />
</Col>
<Col span={8}>
<Select
value={secondValue}
onChange={onSecondChange}
options={secondOptions}
mode={isSecondMultiple ? "multiple" : undefined}
/>
</Col>
<Col span={8}>
<Select
value={thirdValue}
onChange={onThirdChange}
options={thirdOptions}
mode={isThirdMultiple ? "multiple" : undefined}
/>
</Col>
</Row>
);
一句话:没有 children 属性时,就是多选,有 chlidren 时,就是单选。
没有 children 属性时,就是证明说已经是最好一级了,那么就可以是多选。如果有 chlidren 属性时,说明还没有到最后一级。
const onFirstChange = (val) => {
setFirstValue(val);
const secOptions = options?.find((item) => item?.value === val)?.children;
setSecondOptions(secOptions);
// 无 children => 多选
const secondIsMutiple = secOptions
?.map((item) => {
return item.children === undefined;
})
.every((i) => i === true);
setIsSecondMultiple(secondIsMutiple);
};
三级标题处就是,长度大于 1,就是多选,只有一个就是单选。
const onSecondChange = (val) => {
setSecondValue(val);
const thrOptions = secondOptions?.find((item) => item?.value === val)?.children;
setThirdOptions(thrOptions);
// 只有一个就是单选
const thirdIsMutiple = thrOptions?.length > 1;
setIsThridMultiple(thirdIsMutiple);
};
设置禁用熟悉 disabled:
第二个:第一个 disabled || secondOptions 不存在
第三个:第一个 disabled || secondOptions 不存在 || thirdOptions 不存在
const secondSelectDisabled = useMemo(
() => disabled || !secondOptions?.length,
[disabled, secondOptions],
);
const thirdSelectDisabled = useMemo(
() => disabled || !thirdOptions?.length || !secondOptions?.length,
[disabled, thirdOptions],
);
return (
<Row justify="space-between" gutter={12}>
<Col span={8}>
<Select value={firstValue} onChange={onFirstChange} options={options} disabled={disabled} />
</Col>
<Col span={8}>
<Select
value={secondValue}
onChange={onSecondChange}
options={secondOptions}
mode={isSecondMultiple ? "multiple" : undefined}
disabled={secondSelectDisabled}
/>
</Col>
<Col span={8}>
<Select
value={thirdValue}
onChange={onThirdChange}
options={thirdOptions}
mode={isThirdMultiple ? "multiple" : undefined}
disabled={thirdSelectDisabled}
/>
</Col>
</Row>
);
兜底提示
return (
<Row justify="space-between" gutter={12}>
<Col span={8}>
<Select
value={firstValue}
onChange={onFirstChange}
options={options}
disabled={disabled}
placeholder={"请选择"}
/>
</Col>
<Col span={8}>
<Select
value={secondValue}
onChange={onSecondChange}
options={secondOptions}
mode={isSecondMultiple ? "multiple" : undefined}
disabled={secondSelectDisabled}
placeholder={secondSelectDisabled ? "" : "请选择"}
/>
</Col>
<Col span={8}>
<Select
value={thirdValue}
onChange={onThirdChange}
options={thirdOptions}
mode={isThirdMultiple ? "multiple" : undefined}
disabled={thirdSelectDisabled}
placeholder={thirdSelectDisabled ? "" : "请选择"}
/>
</Col>
</Row>
);
但是我们发现,联动效果不太好,应该是前一个选择之后,后一个可以自动选择第一个。也就是,选中第一个后面的应该跟着改变。
当然,这个时候需要区分单选或多选,如果是多选,那应该设置数组,如果是单选应该设置数字。
const onFirstChange = (val) => {
setFirstValue(val);
const secOptions = options?.find((item) => item?.value === val)?.children;
// 无二级标题
if (secOptions?.length === 0) {
return;
}
setSecondOptions(secOptions);
// 无 children => 多选
const secondIsMutiple = secOptions
?.map((item) => {
return item.children === undefined;
})
.every((i) => i === true);
setIsSecondMultiple(secondIsMutiple);
// 多选
if (secondIsMutiple) {
setSecondValue([secOptions?.[0]?.value]);
} else {
setSecondValue(secOptions?.[0]?.value);
}
};
const onSecondChange = (val) => {
setSecondValue(val);
const trdOptions = secondOptions?.find((item) => item?.value === val)?.children;
setThirdOptions(trdOptions);
// 只有一个就是单选
const thirdIsMutiple = trdOptions?.length > 1;
setIsThridMultiple(thirdIsMutiple);
if (thirdIsMutiple) {
setThirdValue([trdOptions?.[0]?.value]);
} else {
setThirdValue(trdOptions?.[0]?.value);
}
};
因为我们打算封装成一个组件,那么我们就需要向父组件传递选中的值,以供父组件使用。
const onFirstChange = (val) => {
if (!val) {
onChange([]);
setSecondValue(undefined);
setSecondOptions([]);
return;
}
setFirstValue(val);
const secOptions = options?.find((item) => item?.value === val)?.children;
// 无二级标题
if (secOptions?.length === 0) {
console.log([`${val}_0`]);
onChange([`${val}_0`]);
return;
}
setSecondOptions(secOptions);
// 无 children => 多选
const secondIsMutiple = secOptions
?.map((item) => {
return item.children === undefined;
})
.every((i) => i === true);
setIsSecondMultiple(secondIsMutiple);
// 多选
if (secondIsMutiple) {
setSecondValue([secOptions?.[0]?.value]);
} else {
setSecondValue(secOptions?.[0]?.value);
}
console.log([`${val}_${secOptions?.[0]?.value || 0}`]);
onChange([`${val}_${secOptions?.[0]?.value || 0}`]);
};
const onSecondChange = (val) => {
if (!val) {
onChange([]);
setThirdValue(undefined);
setThirdOptions([]);
return;
}
setSecondValue(val);
const trdOptions = secondOptions?.find((item) => item?.value === val)?.children;
setThirdOptions(trdOptions);
// 只有一个就是单选
const thirdIsMutiple = trdOptions?.length > 1;
setIsThridMultiple(thirdIsMutiple);
if (thirdIsMutiple) {
setThirdValue([trdOptions?.[0]?.value]);
} else {
setThirdValue(trdOptions?.[0]?.value);
}
// 多选
if (isSecondMultiple) {
const res = val.map((e) => `${firstValue}_${e}`);
console.log(res);
onChange(res);
} else {
console.log([`${firstValue}_${val}`]);
onChange([`${firstValue}_${val}`]);
}
};
到这里,我们的功能,差不多就实现了。
但是,就是说有没有那么一种可能,数据是需要渲染初始值的,对,有可能的。["301_301_3022222"], [201_20444]
获取一级标题的值
useEffect(() => {
if (value?.length) {
const firstLevelValue = parseInt(value?.[0]?.split("_")?.[0]);
setFirstValue(firstLevelValue);
}
}, [options, value]);
接下来需要判断二级标题还是三级标题
const [isTwoLevel, setIsTwoLevel] = useState<boolean>(false);
const isTheSecondaryTitle =
value.map((e) => {
const sec = e.split("_");
return sec;
}).length === 2;
setIsTwoLevel(isTheSecondaryTitle);
//....
if (isTwoLevel) {
//....
} else {
//....
}
设置二级标题的 value
const secOptions = options?.find((item) => item?.value === firstLevelValue)?.children || [];
setSecondOptions(secOptions);
const optionsValues = secOptions.map((e) => e.value);
const secondLevelValues = value.map((e) => {
const sec = e.split("_")?.[1];
return sec;
});
const secondLevelValue = intersectionBy(optionsValues, secondLevelValues, parseInt);
同时还需要判断是否需要多选
if (isTwoLevel) {
if (secondLevelValue.length > 1) {
setIsSecondMultiple(true);
}
setSecondValue(secondLevelValue || []);
}
设置三级标题的 value
if (isTwoLevel) {
if (secondLevelValue.length > 1) {
setIsSecondMultiple(true);
}
setSecondValue(secondLevelValue || []);
} else {
setSecondValue(secondLevelValue || []);
const trdOptions =
secOptions?.find((item) => item?.value === secondLevelValue[0])?.children || [];
setThirdOptions(trdOptions);
const _optionsValues = trdOptions.map((e) => e.value);
const thirdLevelValues = value.map((e) => {
const sec = e.split("_")?.[2];
return sec;
});
const thirdLevelValue = intersectionBy(_optionsValues, thirdLevelValues, parseInt);
setIsThridMultiple(true);
setThirdValue(thirdLevelValue || undefined);
}
到此,我们的需要就实现完成了。