import React, { useState } from 'react'; import './Cascader.css'; /* 级联选择器(移动端)— 规格 §44。 多级联动:点某一级后下一级选项变化;点路径条里的上级可回退(规格 §44.5)。 级数不写死:路径条按已选深度渲染,面板**只渲染当前一层** —— 375px 宽下并排多列会把 每列压到 100px 以内,无法阅读(规格 §44.2)。 路径的当前位置以 data-level 暴露,选中项以 is-selected + aria-selected 双通道表达。 本端只回传下一段路径(change),确认由宿主决定是否采纳。 */ /* 选项可以是字符串,也可以是 { label, value, disabled, children } 结点(规格 §44.7) */ function nodeOf(option, i) { if (option === null || option === undefined) return { label: '', value: String(i) }; if (typeof option === 'object') { return { label: String(option.label === undefined ? '' : option.label), value: String(option.value === undefined ? i : option.value), disabled: !!option.disabled, children: Array.isArray(option.children) ? option.children : [] }; } return { label: String(option), value: String(option) }; } function normTree(options) { return (Array.isArray(options) ? options : []).map(nodeOf); } /* 沿 path 走到第 depth 级的选项数组(depth=0 是根) */ function levelList(tree, path, depth) { let list = tree; for (let i = 0; i < depth; i++) { const hit = list.find((n) => n.value === String(path[i])); if (!hit || !hit.children.length) return []; list = hit.children; } return list; } /* 当前展示层级:末项是叶子时停在它所在层(可改选同级),否则展示下一级 */ function displayDepth(tree, path) { let depth = path.length; if (depth > 0) { const parent = levelList(tree, path, depth - 1); const last = parent.find((n) => n.value === String(path[depth - 1])); if (last && !last.children.length) depth -= 1; } return depth; } /* 路径条:已选各级的结点(用于渲染可点回的按钮) */ function pathItems(tree, path) { const out = []; let list = tree; for (let i = 0; i < path.length; i++) { const hit = list.find((n) => n.value === String(path[i])); if (!hit) break; out.push(hit); list = hit.children; } return out; } const LAYER_LABELS = ['省份', '城市', '区县']; export default function Cascader({ mode = 'panel', round = false, showPath = true, open = false, closeOnMask = true, value = [], options = [], disabled = false, title = '', label = '级联选项', onChange, onConfirm, onClose, children = null, }) { const tree = normTree(options); const path = Array.isArray(value) ? value.map(String) : []; /* null = 跟随 value 属性;点选后本地记账,确认前不改宿主的值 */ const [draft, setDraft] = useState(null); const picked = draft || path; const depth = displayDepth(tree, picked); const list = levelList(tree, picked, depth); const pickedValue = picked[depth]; const items = pathItems(tree, picked); const total = items.length; const cls = 'kole-m-cascader' + ` kole-m-cascader--${mode === 'popup' ? 'popup' : 'panel'}` + (showPath ? '' : ' kole-m-cascader--no-path') + (round ? ' kole-m-cascader--round' : '') + (open ? ' is-open' : '') + (disabled ? ' is-disabled' : ''); function emitPath(next) { setDraft(next); if (onChange) onChange(next); } /* 点选项:截断更深的层级,写入本级选中值(规格 §44.5) */ function pick(node) { if (disabled || node.disabled) return; const next = picked.slice(0, depth); next[depth] = node.value; emitPath(next); } /* 点路径条上的上级:退回该级,重新展示它的下一级 */ function back(d) { if (disabled) return; emitPath(picked.slice(0, d + 1)); } const pathEl = showPath ? (
{items.map((item, i) => { const isLast = i === items.length - 1; return ( {isLast ? null : ( )} ); })}
) : null; const panelEl = list.length > 0 ? ( ) : (

该级暂无可选项

); if (mode === 'popup') { return ( <> {children}