React实现antdM的级联菜单实例

 更新时间:2022年10月08日 16:01:18   作者:jie19100  
这篇文章主要为大家介绍了React实现antdM的级联菜单实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

效果图

需求分析

级联菜单分为两部分:head与body。

body

包含两部分:已选项列表,候选菜单

已选项列表

  • body展示当前菜单的所有option,可上下滚动。
  • body中选一个option后,会在head的已选列表中进行展示,并且body将显示下一级的菜单。
  • 选中的option,背景色和字体需要改变,以示区分。

候选菜单

  • 依次显示每个选中的option,当所有option的长度超过屏幕宽度时,可左右滚动
  • 每个option固定宽度,超出宽度显示省略号。
  • 当点击其中一个option时候,该项高亮,并且body显示为该级的菜单。

head

  • 包含取消和确定两个按钮。
  • 点击取消,将不做任何处理
  • 确定按钮需要在级联菜单选到没有下一级的时候才可点击
  • 点击确定,将触发回调,携带已选的参数

项目结构

├─ src
│  ├─ App.js
│  ├─ components
│  │  └─ Cascader
│  │     ├─ CascaderContent // content部分
│  │     │  ├─ CascaderContent.js
│  │     │  └─ style.js
│  │     ├─ CascaderHead // head部分
│  │     │  ├─ CascaderHead.js
│  │     │  └─ style.js
│  │     ├─ index.js // 入口
│  │     ├─ style.js
│  │     ├─ Cascader.js
│  │     └─ testData // 测试数据
│  │        └─ data.js
│  ├─ index.css
│  └─ index.js

实现body部分

levels

根据数据源dataSource与value生成一个数组,数据结构如下。

数组的长度为已选项数加一,最大为数据源的最大深度

onChange

点击菜单项,如果为不可选状态,则return。如果有onSelect回调,则将已选的value传递给回调函数

value

初始化的时候,value为默认值,后面在此基础上进行修改

loading

有时候数据可能是异步请求获取的,增加一个定时器,可以在数据未加载完的时候,显示loading效果。

tabActiveIndex

当前候选的菜单的索引,,选中一项后,值加一,如果已经选到了最大深度,那么索引为最后一页。

classPrefix

是一个变量,方便设置公共变量

import React, { useMemo, useState } from "react";
import { useCallback, useEffect } from "react";
import { Wrapper } from "./style";
const classPrefix = `antdm-cascader-view`
export const CascaderContent = function ({ visible = false, ...props }) {
  // 当前页
  const [tabActiveIndex, setTabActiveIndex] = useState(0);
  // 初始值
  const [value, setValue] = useState(props.value || props.defaultValue || []);
  // loading效果
  const [loading, setLoading] = useState(true);
  const levels = useMemo(() => {
    const ret = []
    let currentOptions = props.options
    let reachedEnd = false
    for (const v of value) {
      const target = currentOptions.find(option => option.value === v)
      ret.push({
        selected: target,
        options: currentOptions,
      })
      // 没有下一项的时候中止遍历
      if (!target || !Array.isArray(target.children) || target.children.length === 0) {
        reachedEnd = true
        break
      }
      currentOptions = target.children
    }
    if (!reachedEnd) {
      ret.push({
        selected: undefined,
        options: currentOptions,
      })
    }
    return ret;
  }, [props.options, value])
  // 点击选项的时候
  const onChange = useCallback((item, index) => {
    if (item?.disabled) {
      return
    }
    const newValue = [...value.slice(0, index), item.value];
    setValue(newValue);
    props.onSelect?.(newValue)
  }, [value, props.onSelect])
  // 选中数据后,切换下一级菜单
  useEffect(() => {
    const max = levels.length - 1
    if (tabActiveIndex > max) {
      setTabActiveIndex(max)
    }
  }, [tabActiveIndex, levels])
  useEffect(() => {
    setTabActiveIndex(levels.length - 1)
  }, [value])
  useEffect(() => {
    if (visible) {
      setValue(props.value || props.defaultValue || []);
    }
  }, [visible])
  useEffect(() => {
    setValue(props.value || props.defaultValue || [])
  }, [props.value, props.defaultValue])
  // 设置定时器,使用loading效果
  useEffect(() => {
    const timer = setTimeout(() => {
      if (props.options?.length === 0) {
        setLoading(false)
      }
      return () => {
        clearTimeout(timer)
      }
    }, 3000);
  }, [])
  // 数据加载完毕后取消loading效果
  useEffect(() => {
    if (props.options.length !== 0) {
      setLoading(false)
    }
  }, [props.options])
  return <Wrapper>
    <div className={classPrefix}>
      <div className={`${classPrefix}-tabs`}>
        {levels.map((item, index) => {
          return <div
            key={index}
            onClick={() => { setTabActiveIndex(index) }}
            className={`${classPrefix}-tab ${tabActiveIndex === index && classPrefix + "-tab-active"}`}>
            {item?.selected?.label ? item?.selected?.label : item?.selected?.label === "" ? "" : "请选择"}
          </div>
        })}
      </div>
      <div className={`${classPrefix}-content`}>
        {!loading ? levels.map((item, index) => {
          return <div
            key={index.toString()}
            style={{ display: index === tabActiveIndex ? "block" : "none" }}
            className={`${classPrefix}-list`} >
            {item.options.map((o, i) => {
              return <div key={i.toString()} className={`${classPrefix}-item ${o.value === item?.selected?.value && classPrefix + "-item-active"}`}>
                <div
                  onClick={() => onChange(o, index)}
                  className={`${classPrefix}-item-main ${o?.disabled && classPrefix + "-item-disabled"}`}>
                  {o.label}
                </div>
                {o.value === item?.selected?.value && <div className={`${classPrefix}-item-extra`}>✓</div>}
              </div>
            })}
          </div>
        }) : "loading..."}
      </div>
    </div>
  </Wrapper>
}

实现head部分

当已经没有下一级菜单的时候,确定按钮变为可点击状态

import React from "react";
import { Wrapper } from "./style";
const classPrefix = `antdm-cascader`
export const CascaderHead = function (props) {
  return <Wrapper>
    <div className={classPrefix}>
      <div className={`${classPrefix}-header`}>
        <a
          className={`${classPrefix}-header-button`}
          onClick={() => {
            props.onCancel?.()
          }}
        >
          {props.cancelText || "取消"}
        </a>
        <div className={`${classPrefix}-header-title`}>{props.title}</div>
        <a
          className={`${classPrefix}-header-button ${props.canCommit ? '' : classPrefix + '-header-confirm-disable'}`}
          onClick={() => {
            props.canCommit && props.onConfirm?.();
          }}
        >
          {props.confirmText || "确定"}
        </a>
      </div>
    </div>
  </Wrapper>
}

整合head与body

import React, { useState, useCallback, useEffect } from "react";
import { Popup } from "antd-mobile";
import { CascaderHead } from "./CascaderHead/CascaderHead";
import { CascaderContent } from "./CascaderContent/CascaderContent";
import { Wrapper } from "./style";
export const CascaderModal = function (props) {
  const [value, setValue] = useState(props.value || props.defaultValue || []);
  const [canCommit, setCanCommit] = useState(false);
  const onChange = useCallback((v) => {
    setValue(v);
    props.onSelect?.(v)
  }, [props.onSelect])
  // 将选择的数据提交出去
  const onConfirm = useCallback(() => {
    props.onConfirm?.(value)
  }, [props.onConfirm, value])
  // 取消
  const onCancel = useCallback(() => {
    props.onCancel?.()
  }, [props.onCancel])
  useEffect(() => {
    if (value.length === 0) {
      return;
    }
    let children = props.options;
    let i = 0;
    for (i; i < value.length; i++) {
      const obj = children.find(item => item.value === value[i]);
      if (!obj) {
        children = undefined;
        break;
      } else {
        children = obj.children
      }
    }
    setCanCommit(!Array.isArray(children) || children.length === 0)
  }, [value, props.options])
  useEffect(() => {
    setValue(props.value || props.defaultValue || [])
  }, [props.value, props.defaultValue])
  useEffect(() => {
    if (props.visible) {
      setCanCommit(false);
    }
  }, [props.visible])
  return <Wrapper>
    <Popup
      className="antdm-cascader-modal"
      visible={props.visible}
      onClose={onCancel}
      animationType="slide-up"
      popup={true}
    >
      <CascaderHead {...props} canCommit={canCommit} onCancel={onCancel} onConfirm={onConfirm} />
      <CascaderContent {...props} visible={props.visible} onSelect={onChange} />
    </Popup>
  </Wrapper>
}

以上就是React实现antdM的级联菜单实例的详细内容,更多关于React antdM级联菜单的资料请关注脚本之家其它相关文章!

相关文章

  • react+zarm实现底部导航栏的示例代码

    react+zarm实现底部导航栏的示例代码

    本文主要介绍了react + zarm 实现底部导航栏的示例代码,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • React特征学习之Form格式示例详解

    React特征学习之Form格式示例详解

    这篇文章主要为大家介绍了React特征学习之Form格式示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • 利用React高阶组件实现一个面包屑导航的示例

    利用React高阶组件实现一个面包屑导航的示例

    这篇文章主要介绍了利用React高阶组件实现一个面包屑导航的示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • React路由封装的实现浅析

    React路由封装的实现浅析

    路由是React项目中相当重要的概念,对于功能较为复杂的网页来说,必然会涉及到不同功能间的页面跳转,本篇文章将对React官方维护的路由库React-Router-Dom的使用和常用组件进行讲解,同时对路由组件传递param参数的方式进行讲解,希望对各位读者有所参考
    2022-08-08
  • React项目中decorators装饰器报错问题解决方案

    React项目中decorators装饰器报错问题解决方案

    这篇文章主要介绍了React项目中decorators装饰器报错,本文给大家分享问题所在原因及解决方案,通过图文实例相结合给大家介绍的非常详细,需要的朋友可以参考下
    2023-01-01
  • 用React实现一个完整的TodoList的示例代码

    用React实现一个完整的TodoList的示例代码

    本篇文章主要介绍了用React实现一个完整的TodoList的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10
  • 详解React组件卸载怎么中止递归方法

    详解React组件卸载怎么中止递归方法

    最近在处理项目代码的时候,出现了一个bug,组件中的方法在组件卸载后仍然在执行,代码片段发给我看,但是变量的用意我也不懂,只看到有方法调用自身方法,这不就是递归嘛,所以本文详细给大家介绍了React组件卸载怎么中止递归方法,需要的朋友可以参考下
    2024-01-01
  • React中事件的类型定义方式

    React中事件的类型定义方式

    这篇文章主要介绍了React中事件的类型定义方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12
  • ReactNative错误采集原理在Android中实现详解

    ReactNative错误采集原理在Android中实现详解

    这篇文章主要为大家介绍了ReactNative错误采集原理在Android中实现详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-02-02
  • React文件分段上传实现方法详解

    React文件分段上传实现方法详解

    这篇文章主要介绍了React文件分段上传实现方法,将文件切成多个小的文件;将切片并行上传;所有切片上传完成后,服务器端进行切片合成;当分片上传失败,可以在重新上传时进行判断,只上传上次失败的部分实现断点续传;当切片合成为完整的文件,通知客户端上传成功
    2022-11-11

最新评论