React实现antdM的级联菜单实例

目录
  • 效果图
  • 需求分析
    • body
    • head
  • 项目结构
  • 实现body部分
  • 实现head部分
  • 整合head与body

效果图

需求分析

级联菜单分为两部分: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级联菜单的资料请关注我们其它相关文章!

(0)

相关推荐

  • 比ant更丰富Modal组件功能实现示例详解

    目录 有哪些比ant更丰富的功能 render部分 渲染黑色蒙层 渲染弹框主体 设置body overflow:hiiden 有哪些比ant更丰富的功能 普通的modal组件如下: 我们写的modal额外支持,后面没有蒙版,并且Modal框能够拖拽 还支持渲染在文档流里,上面的都是fixed布局,我们这个正常渲染到文档下面: render部分 <RenderDialog {...restState} visible={visible} prefixCls={prefixCls} header={

  • 解决Ant Design Modal内嵌Form表单initialValue值不动态更新问题

    场景描述: 如下图所示,点击减免天数会出现一个弹窗, 输入天数后点击确定,保存这个值, 但是我在点第二行的减免天数的时候初始应该是空的, 可是现在显示的是第一行输入的值: <Modal title="减免天数" visible={that.state.visible} onOk={that.handleOk.bind(that)} onCancel={that.handleCancel} > <Form horizontal form={form}> <F

  • react最流行的生态替代antdpro搭建轻量级后台管理

    目录 前言 项目初始化 数据请求 + mock 配置 axios 配置 react-query mock 路由权限配置 路由文件 main.tsx App.tsx 页面编写 login 页面 BasicLayout 动态菜单栏 封装页面通用面包屑 总结 前言 你是否经历过公司的产品和 ui 要求左侧菜单栏要改成设计图上的样子? 苦恼 antd-pro 强绑定的 pro-layout 菜单栏不能自定义?你可以使用 umi,但是就要根据它的约定来开发,捆绑全家桶等等.手把手教你搭一个轻量级的后台模版

  • React弹窗使用方式NiceModal重新思考

    目录 一些有趣的真实场景 案例一:全局弹窗 案例二:存在分支以及依赖关系的弹窗 不那么通用的解决方案 可能是最好的弹窗实践 创建弹窗 使用弹窗 简单实现思路 推荐阅读 一些有趣的真实场景 在开始进入正题之前,先看看一些与弹窗有关的有趣场景

  • Modal.confirm是否违反了React模式分析

    目录 引言 什么是“React模式”? 引言 如何评价 Ant Design 这个项目(一个设计语言)? 这是一篇临时起意的文章,我参与了一点上图中的讨论,正好有点时间,索性拉篇文章专门聊聊. 首先说结论:我不认为Modal.confirm以及类似的API是anti-pattern,尽管antd作者 @偏右悄悄地 也说Modal.confirm“并不符合React哲学”. 什么是“React模式”? 很多人都见过这个公式—— view = f(data) React确实就是这么运作的. 然而Re

  • react中使用antd及immutable示例详解

    目录 一.react中使用antd组件库 二.Immutable 2.1 深拷贝和浅拷贝的关系 2.2 immutable优化性能方式 2.3 immutable的Map使用 2.4 immutable的List使用 2.5 实际场景formJS 三.redux中使用immutable 一.react中使用antd组件库 运行命令create-react-app antd-react创建新项目: 运行命令npm i antd安装: 使用: import React from 'react' im

  • React实现antdM的级联菜单实例

    目录 效果图 需求分析 body head 项目结构 实现body部分 实现head部分 整合head与body 效果图 需求分析 级联菜单分为两部分:head与body. body 包含两部分:已选项列表,候选菜单 已选项列表 body展示当前菜单的所有option,可上下滚动. body中选一个option后,会在head的已选列表中进行展示,并且body将显示下一级的菜单. 选中的option,背景色和字体需要改变,以示区分. 候选菜单 依次显示每个选中的option,当所有option的

  • jQuery实现可收缩展开的级联菜单实例代码

    如果用纯JavaScript代码而不使用框架的话,那么做一个级联菜单也许是一件让人生畏的事情,但有了框架,这件事情就很容易了,这就是框架的好处,极大地提高了开发效率,并且更可靠和易于维护.使用jQuery来实现级联菜单的一般步骤如下: •1.首先使用<ul>和<li>创建一个级联菜单  复制代码 代码如下: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"        "

  • Ajax级联菜单实例代码

    1.Ajax.html 复制代码 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"><head>    <title

  • ajax级联菜单实现方法实例分析

    本文实例讲述了ajax级联菜单实现方法.分享给大家供大家参考,具体如下: 效果如下: 选择第一项,第二项.第三项的内容跟着改变. 选择第二项,第三项的内容跟着改变. 第三项则不影响第一项和第二项. 有几点值得提: 1.html到底是前台拼接还是后台拼接. 我选择的是前台拼接,这样可以节省流量,和后台的资源.这也比较符合程序处理,一般后台只负责提供数据. 通过json传递给前台,完了前台获取进行处理. ajax函数 function ajaxgetbigclass(val){ $.ajax({ t

  • 简单实现jQuery级联菜单

    本文实例为大家分享了jQuery实现级联菜单的具体代码,供大家参考,具体内容如下 层叠样式表: .button { border: 1px ridge #ffffff; line-height:18px; height: 20px; width: 45px; padding-top: 0px; background:#CBDAF7; color:#000000; font-size: 9pt; } HTML正文: <div style="border:1px dashed #E6E6E6;

  • JavaScript实现级联菜单的方法

    本文实例讲述了JavaScript实现级联菜单的方法.分享给大家供大家参考.具体实现方法如下: <html> <head> <title>test</title> <script type="text/javascript"> function f() { var sel=document.getElementById("country"); var sel2=document.getElementById

  • js实现网页多级级联菜单代码

    本文实例讲述了js实现网页多级级联菜单.分享给大家供大家参考.具体如下: 这是大家在网页上经常会见到的级联菜单特效,不过这一个代码是由JS来实现,将级联菜单中的内容保存在了JS数组中,为了页面美观,本次还美化了一下表格边框,整体看上去更和谐实用了,多级的下拉菜单对网页设计来说比较实用. 运行效果如下图所示: 在线演示地址如下: http://demo.jb51.net/js/2015/js-table-select-menu-style-codes/ 具体代码如下: <!DOCTYPE HTML

  • javascript实现二级级联菜单的简单制作

    本文实例讲述了javascript实现二级级联菜单的简单制作方法.分享给大家供大家参考.具体如下: 运行效果截图如下: 具体代码如下: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title></title> <meta http-equiv="pragma" content="

  • js实现城市级联菜单的2种方法

    本文实例为大家分享了js实现城市级联菜单的具体代码,供大家参考,具体内容如下 方法一:用switch方法判断. <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script type="text/javascript"> function ch

  • 原生JavaScript实现动态省市县三级联动下拉框菜单实例代码

    像平时购物选择地址时一样,通过选择的省动态加载城市列表,通过选择的城市动态加载县区列表,从而可以实现省市县的三级联动,下面使用原生的JavaScript来实现这个功能: 先给大家展示下测试结果: 未做任何选择时: 选择时: 代码如下所示: <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>三级联动测试</titl

随机推荐