react+ts实现时间选择器

236 阅读2分钟

效果展示:

image.png

image.png

在父组件中使用:

<DatePicker
                    minDate={new Date(selectCompany?.node?.createdAt)}
                    //关闭该时间选择器
                    onClose={setShowDatePicker.off}
                    //传递已经选择好的时间
                    selectedDate={selectedDate}
                    //将选择好的时间置空,并且关闭该时间选择器
                    onClear={() => {
                      setSelectedDate(null);
                      setShowDatePicker.off();
                    }}
                    //将选择好的时间传递给父组件
                    setSelectedDate={(date: Date) => {
                      setSelectedDate(date);
                      setValue("month", format(date, "yyyy/M"));
                    }}
                  />

DatePicker组件:

type PropsType = {
  minDate: Date;
  onClear: () => void;
  onClose: () => void;
  selectedDate: Date | null;
  setSelectedDate: (date: Date) => void;
};

export const DatePicker: React.FC<PropsType> = ({
  minDate,
  onClose,
  setSelectedDate,
  selectedDate,
  onClear
}) => {
  const toast = useToast();
  const { t } = useTranslation(["admin"]);
  const [step, setStep] = useState(1);
  const yearRef = useRef<HTMLButtonElement | null>(null);

  const currentYear = new Date().getFullYear();
  const currentMonth = new Date().getMonth() + 1;

  const [selectedYear, setSelectedYear] = useState<number | null>(
    selectedDate ? new Date(selectedDate).getFullYear() : currentYear
  );
  const [selectedMonth, setSelectedMonth] = useState<number | null>(
    selectedDate ? new Date(selectedDate).getMonth() + 1 : currentMonth
  );

  const years = Array.from(
    { length: currentYear - 1990 + 1 },
    (_, index) => 1990 + index
  ).reverse();
  const months = Array.from({ length: 12 }, (_, index) => index + 1);

  const handleSet = useCallback(
    (month: number) => {
      if (selectedYear && month) {
        const enterpriseRegisterYear = new Date(minDate).getFullYear();
        const enterpriseRegisterMonth = new Date(minDate).getMonth() + 1;

        if (
          new Date(selectedYear + "-" + month) >=
            new Date(enterpriseRegisterYear + "-" + enterpriseRegisterMonth) &&
          new Date(selectedYear + "-" + month) <= new Date()
        ) {
          setSelectedDate(new Date(selectedYear + "-" + month));
          setSelectedMonth(month);
        } else {
        //错误消息提示
          toast({
            position: "top",
            status: "error",
            duration: 3000,
            isClosable: true,
            description:
              enterpriseRegisterYear +
              t("csv.year") +
              "-" +
              enterpriseRegisterMonth +
              t("csv.month") +
              t("csv.monthMessage")
          });
        }

        onClose();
      }
    },
    [minDate, onClose, selectedYear, setSelectedDate, t, toast]
  );

  useEffect(() => {
    if (yearRef.current && step === 1) {
      yearRef.current.scrollIntoView({ block: "center" });
    }
  }, [step]);

  return (
    <Flex
      width="100%"
      p="20px"
      flexDir="column"
      boxShadow="1px 1px 2px 2px #bebebe"
      pos="absolute"
      backgroundColor={theme.text.white}
      zIndex="2"
    >
      <Flex
        align="center"
        p="0 5px 5px"
        maxW="140px"
        cursor="pointer"
        mb="10px"
        onClick={() => {
          step === 1 ? setStep(2) : setStep(1);
        }}
      >
        <Text mr="10px" fontWeight="700">
          {selectedYear + t("csv.year") + selectedMonth + t("csv.month")}
        </Text>
        <ChevronDownIcon
          transform={step === 1 ? "rotate(180deg)" : "rotate(0deg)"}
          transition="transform 0.5s ease-in-out"
        />
      </Flex>
      {step === 1 && (
        <Flex
          overflowY="scroll"
          display="grid"
          gap="15px"
          gridTemplateColumns="1fr 1fr 1fr 1fr"
          h="200px"
        >
          {years.map((year, idx) => (
            <Button
              key={idx}
              p="5px 0"
              borderRadius="20px"
              bgColor={selectedYear === year ? theme.service.turquoise400 : theme.text.white}
              onClick={() => {
                setSelectedYear(year);
                setStep(2);
              }}
              ref={selectedYear === year ? yearRef : null}
            >
              <Text
                textAlign="center"
                color={selectedYear === year ? theme.text.white : theme.text.black900}
              >
                {year + "年"}
              </Text>
            </Button>
          ))}
        </Flex>
      )}
      {step === 2 && (
        <Flex
          display="grid"
          columnGap="20px"
          rowGap="10px"
          gridTemplateColumns="1fr 1fr 1fr"
          h="200px"
        >
          {months.map((month, idx) => (
            <Button
              key={idx}
              bgColor={selectedMonth === month ? theme.service.turquoise400 : theme.text.white}
              p="5px 0"
              borderRadius="20px"
              onClick={() => {
                handleSet(month);
              }}
            >
              <Text
                textAlign="center"
                color={selectedMonth === month ? theme.text.white : theme.text.black900}
              >
                {month + "月"}
              </Text>
            </Button>
          ))}
        </Flex>
      )}
      <Flex justify="space-between" mt="10px">
        <Button
          onClick={() => {
            setSelectedMonth(null);
            setSelectedYear(null);
            onClear();
          }}
        >
        取消
        </Button>
        <Flex gap="20px">
          <Button
            onClick={() => {
              setSelectedDate(new Date());
              onClose();
            }}
          >
          今月
          </Button>
        </Flex>
      </Flex>
    </Flex>
  );
};