先来看一下需求
选择一个步数 x 数量,或者两个步数求区间 11 - 24,然后点击确定回填到select中,其实就是一个自定义组件
选中效果如下:
- 1-9 已经选过了.
- 10 和上次的选择有重复或者跳步选择了 如:
- 第一次选择的1-10,本次选择10-24,那么10为重复,需要提示重复生产.
- 第一次选择1-9,本次选择11-24,那么10为跳步,需要提示跳步存在.
- 11-24 为本次选中正常的步数.
- 25-50 为未选择的步数.
使用效果
<Form.Item initialValue="25-35" name="select">
<CustomSelect total={100} selected="1-24" />
</Form.Item>
- 基于antd实现,可以配合Form使用,为自定义表单组件 initialValue 默认值
- props: total 选择步数显示的小方块总数 selected 上一次已经选择的
- scss文件直接贴到最后了
开始实现
import { useEffect, useMemo, useState } from 'react';
import { Select, Button, InputNumber } from 'antd';
import styles from './index.module.less';
type ValueType = (number)[];
interface Iprops {
value?: string | null;
onChange?: (v: string | null) => void;
total?: number;
selected?: string;
}
enum errorType {
SKIP = 'skip',
REPEAT = 'repeat',
}
const { itemColorHas, itemColorWaring, itemColorSelect, itemColorDefault } = styles;
const CustomSelect = (props: Iprops) => {
const { value, onChange, total = 100, selected = '0-0' } = props;
// 本次选择的步数
const [startAndEnd, setStartAndEnd] = useState<ValueType>([]);
// 选择1步时的数量
const [number, setNumber] = useState<number | string | null>();
// 关闭打开下拉
const [open, setOpen] = useState<boolean | undefined>(false);
// select的受控值
const [selectValue, setSelectValue] = useState<string | null>(null);
// 显示可选择的总数
const showItemNumber = useMemo(
() => {
return new Array(total).fill(1).map((item, index) => {
return index + 1;
});
},
[total]
);
// 以前选中提交的
const selectedList = useMemo(
() => {
if (selected.includes('-')) {
const [start, end] = selected.split('-').map(v => +v);
return showItemNumber.slice(start - 1, end);
}
return [];
},
[selected, showItemNumber]
);
// 本次选中的
const selectItem = useMemo<ValueType>(
() => {
const [start, end] = startAndEnd;
if (!start && !end) {
return [];
}
if (startAndEnd.length === 1) {
return startAndEnd;
}
return showItemNumber.slice(start - 1, end);
},
[startAndEnd]
);
// error选择的
const errorSelect = useMemo(
() => {
const start = selectedList[selectedList.length - 1] || 0;
const [end] = startAndEnd;
if (end - start > 1) {
// 有跳步产生
return {
error: errorType.SKIP,
list: showItemNumber.slice(start, end - 1),
};
}
if (end - start < 1) {
// 有重复产生
return {
error: errorType.REPEAT,
list: selectItem.filter(v => selectedList.includes(v)),
};
}
return {};
},
[selectedList, startAndEnd, showItemNumber, selectItem]
);
// 回填
useEffect(
() => {
if (value) {
if (value.includes('-')) {
setStartAndEnd(value.split('-').map(item => +item));
setSelectValue(value);
}
if (value.includes('*')) {
setStartAndEnd([value.split('*').map(item => +item)[0]]);
setNumber(value.split('*').map(item => +item)[1]);
setSelectValue(value);
}
}
},
[value]
);
const itemClassName = (index: number) => {
let className = itemColorDefault;
if (selectItem.includes(index)) {
className = `${itemColorDefault} ${itemColorSelect}`;
}
if (selectedList.includes(index)) {
className = `${itemColorDefault} ${itemColorHas}`;
}
if (errorSelect.error && errorSelect.list.includes(index)) {
className = `${itemColorDefault} ${itemColorWaring}`;
}
return className;
};
const currentValue = useMemo(
() => {
const [start, end] = startAndEnd;
if ((!start || !end) && !number) {
return null;
}
if (start && number) {
return `${start}*${number}`;
}
return `${start}-${end}`;
},
[startAndEnd, number]
);
const dropdownItem = (index: number) => {
return (
<div
className={`${styles.dropdownItem} ${itemClassName(index)}`}
onClick={() => {
setNumber(null);
let current;
if (startAndEnd.includes(index)) {
current = startAndEnd.filter(v => v !== index);
setStartAndEnd(current);
return;
}
if (startAndEnd.length === 2) {
current = [index];
} else {
current = [...startAndEnd, index].sort((a: any, b: any) => a - b);
}
setStartAndEnd(current);
}}
key={index}>
{index}
</div>
);
};
const dropdownRender = () => {
return (
<div className={styles.dropdown}>
<div className={styles.dropdownContent}>
{showItemNumber.map(text => {
return dropdownItem(text);
})}
</div>
<div className={styles.tip}>
{errorSelect.error &&
<div className={styles.error}>
{errorSelect.error === errorType.REPEAT ? '重复生产' : '跳步生产'}
</div>}
{startAndEnd.length === 1 &&
<div className={styles.numberInput}>
<div>输入数量:</div>
<InputNumber
addonAfter={<div>副</div>}
precision={0}
value={number}
onChange={v => {
setNumber(v);
}}
/>
</div>}
</div>
<div className={styles.bottom}>
<div>
<span>已选择:</span>
<span>
{currentValue}
</span>
</div>
<Button
onClick={() => {
onChange && onChange(currentValue);
setSelectValue(currentValue);
setOpen(false);
}}
type="primary">
确定
</Button>
</div>
</div>
);
};
return (
<div className={styles.customSelect}>
<Select
dropdownRender={dropdownRender}
onFocus={() => {
setOpen(undefined);
}}
open={open}
placeholder="请选择"
value={selectValue}
style={{ width: '609px' }}
/>
</div>
);
};
export default CustomSelect;
本来想好好写写,但是这个也不难,关键我没办法把代码分的太细,刚开始写,有一些心里的想法表达不出来,见谅
github:github.com/promiseLC/v…