当前位置 : 主页 > 网络编程 > JavaScript >

React实现antdM的级联菜单实例

来源:互联网 收集:自由互联 发布时间:2023-02-08
目录 效果图 需求分析 body head 项目结构 实现body部分 实现head部分 整合head与body 效果图 需求分析 级联菜单分为两部分:head与body。 body 包含两部分:已选项列表,候选菜单 已选项列表
目录
  • 效果图
  • 需求分析
    • 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级联菜单的资料请关注易盾网络其它相关文章!

          网友评论