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级联菜单的资料请关注脚本之家其它相关文章!
最新评论