diff --git a/packages/devui-vue/devui/cascader/components/cascader-item/index.scss b/packages/devui-vue/devui/cascader/components/cascader-item/index.scss
index 459a91366bdb41b186b4a21c989d59286dfb9404..f3368ead2587b8c5198d0ee63af5989b4645eff2 100644
--- a/packages/devui-vue/devui/cascader/components/cascader-item/index.scss
+++ b/packages/devui-vue/devui/cascader/components/cascader-item/index.scss
@@ -9,8 +9,15 @@
color: $devui-text;
cursor: pointer;
@include flex(flex-start);
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ }
+ .cascader-li__wraper {
+ flex: 1;
+ @include flex(flex-start);
}
-
.dropdown-item-label {
display: inline-block;
flex: 1;
@@ -19,4 +26,32 @@
text-overflow: ellipsis;
font-size: $devui-font-size;
}
+
+ &.devui-leaf-active {
+ background: $devui-list-item-hover-bg;
+
+ span {
+ color: $devui-brand-active;
+ }
+ }
+
+ &.disabled {
+ background-color: $devui-disabled-bg;
+ cursor: not-allowed;
+ }
+
+ .cascader-li {
+ &__checkbox {
+ margin-right: 4px;
+ }
+
+ &__icon {
+ margin-right: 4px;
+ font-size: $devui-font-size-icon;
+ color: $devui-text;
+ &.disabled {
+ color: $devui-disabled-text !important;
+ }
+ }
+ }
}
diff --git a/packages/devui-vue/devui/cascader/components/cascader-item/index.tsx b/packages/devui-vue/devui/cascader/components/cascader-item/index.tsx
index 5182dd221b1773743c2b7bf4a48cd6ff4f27aa9f..c221a86aeca109a3790150b29a0483deaf4bc33c 100644
--- a/packages/devui-vue/devui/cascader/components/cascader-item/index.tsx
+++ b/packages/devui-vue/devui/cascader/components/cascader-item/index.tsx
@@ -1,21 +1,67 @@
-import { getRootClass } from './use-class'
-import { optionsHandles } from '../../hooks/use-cascader-options'
+import { computed, ref } from 'vue'
+import { CascaderItemPropsType } from '../../src/cascader-types'
+import { useListClassName } from '../../hooks/use-cascader-class'
+import { updateCheckOptionStatus } from '../../hooks/use-cascader-multiple'
+import { singleChoose } from '../../hooks/use-cascader-single'
import './index.scss'
-export const DCascaderItem = (props) => {
- const { cascaderli, ulIndex } = props
- const { changeCascaderIndexs } = optionsHandles()
- const rootClasses = getRootClass()
- const mouseHover = () => {
- changeCascaderIndexs(cascaderli, ulIndex)
+export const DCascaderItem = (props: CascaderItemPropsType) => {
+ // console.log('item index',props)
+ const { cascaderItem, ulIndex, liIndex, cascaderItemNeedProps, cascaderOptions } = props
+ const { multiple, stopDefault, valueCache, activeIndexs, trigger, confirmInputValueFlg, tagList} = cascaderItemNeedProps
+ const isTriggerHover = trigger === 'hover'
+ const rootClasses = useListClassName(props)
+ const { updateStatus } = updateCheckOptionStatus(tagList)
+ const disbaled = computed(() => cascaderItem?.disabled) // 当前项是否被禁用
+ // 触发联动更新
+ const updateValues = () => {
+ if (stopDefault.value) return
+ // 删除当前联动级之后的所有级
+ activeIndexs.splice(ulIndex, activeIndexs.length - ulIndex)
+ // 更新当前渲染视图的下标数组
+ activeIndexs[ulIndex] = liIndex
+ if (!multiple) { // 单选点击选项就更新,多选是通过点击checkbox触发数据更新
+ singleChoose(ulIndex, valueCache, cascaderItem)
+ }
+ }
+ // 鼠标hover(多选模式下只能点击操作触发)
+ const mouseEnter = () => {
+ if (disbaled.value || multiple) return
+ updateValues()
+ }
+ const mouseenter = {
+ [ isTriggerHover && 'onMouseenter' ]: mouseEnter
+ }
+ // 鼠标click
+ const mouseClick = () => {
+ if (disbaled.value) return
+ updateValues()
+ if (!multiple && (!cascaderItem.children || cascaderItem?.children?.length === 0)) {
+ confirmInputValueFlg.value = !confirmInputValueFlg.value
+ }
+ }
+ const checkboxChange = () => {
+ updateStatus(cascaderItem, cascaderOptions, ulIndex)
}
return (
-
-
- { cascaderli.label }
-
- {
- cascaderli?.children?.length > 0 &&
+
+ { multiple &&
+
+
+
}
+
+ { cascaderItem.icon &&
+
+
+
+ }
+
+ { cascaderItem.label }
+
+ {
+ cascaderItem?.children?.length > 0 &&
+ }
+
)
}
diff --git a/packages/devui-vue/devui/cascader/components/cascader-item/use-class.ts b/packages/devui-vue/devui/cascader/components/cascader-item/use-class.ts
deleted file mode 100644
index 5a5ec3e21b6bd16e8fafc7da41ea55a592f0760b..0000000000000000000000000000000000000000
--- a/packages/devui-vue/devui/cascader/components/cascader-item/use-class.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * 定义组件class
- */
- import { computed, ComputedRef } from 'vue';
-
- export const getRootClass = (): ComputedRef => {
- return computed(() => ({
- 'devui-cascader-li devui-dropdown-item': true,
- }))
- }
-
\ No newline at end of file
diff --git a/packages/devui-vue/devui/cascader/components/cascader-list/cascader-list-types.ts b/packages/devui-vue/devui/cascader/components/cascader-list/cascader-list-types.ts
deleted file mode 100644
index 85e2152df95d7ff7843a6db28e084074b8c38f24..0000000000000000000000000000000000000000
--- a/packages/devui-vue/devui/cascader/components/cascader-list/cascader-list-types.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import type { PropType, ExtractPropTypes } from 'vue'
-import { CascaderItem } from '../../src/cascader-types'
-export const cascaderulProps = {
- /**
- * 每个ul中的li
- * @type {CascaderItem[]}
- * @default []
- */
- cascaderlis: {
- type: Array as PropType,
- default: [],
- },
- /**
- * 当前选中的ul下标
- * @type {Number}
- * @default 0
- */
- ulIndex: {
- type: Number,
- default: 0
- }
-}
-export type CascaderulProps = ExtractPropTypes
diff --git a/packages/devui-vue/devui/cascader/components/cascader-list/index.scss b/packages/devui-vue/devui/cascader/components/cascader-list/index.scss
index a5ff2f80f31e199faf7c02531b3cbcaebf8d86d7..76a260c86f4545d6b1bb5a1ce004d9d54b7a777a 100644
--- a/packages/devui-vue/devui/cascader/components/cascader-list/index.scss
+++ b/packages/devui-vue/devui/cascader/components/cascader-list/index.scss
@@ -1,8 +1,8 @@
@import '../../../style/mixins/flex';
@import '../../../style/theme/color';
+@import '../../../style/theme/font';
.devui-cascader-ul {
- width: 200px;
height: 180px;
background: $devui-connected-overlay-bg;
display: block;
@@ -11,4 +11,17 @@
padding: 0;
overflow-y: auto;
border-left: 1px solid $devui-dividing-line;
+
+ &.devui-drop-no-data {
+ color: $devui-disabled-text;
+ padding: 8px;
+ display: block;
+ height: 36px;
+ line-height: 36px;
+ overflow-y: hidden;
+ background-color: $devui-disabled-bg;
+ font-size: $devui-font-size;
+ @include flex(center, flex-start);
+ @include flex-direction();
+ }
}
diff --git a/packages/devui-vue/devui/cascader/components/cascader-list/index.tsx b/packages/devui-vue/devui/cascader/components/cascader-list/index.tsx
index 4fa19244988f5997d37de3f902b5ab75bf8b2ecf..fb367f22f1c0043928da413d7252bb6112dde7da 100644
--- a/packages/devui-vue/devui/cascader/components/cascader-list/index.tsx
+++ b/packages/devui-vue/devui/cascader/components/cascader-list/index.tsx
@@ -1,18 +1,26 @@
import { defineComponent } from 'vue'
-import { getRootClass } from './use-class'
-import './index.scss'
-import { cascaderulProps, CascaderulProps } from './cascader-list-types'
+import { useUlClassName } from '../../hooks/use-cascader-class'
+import { useDropdownStyle } from '../../hooks/use-cascader-style'
+import { cascaderulProps, CascaderulProps } from '../../src/cascader-types'
+
import { DCascaderItem } from '../cascader-item'
+import './index.scss'
export default defineComponent({
name: 'DCascaderList',
props: cascaderulProps,
setup(props: CascaderulProps) {
- const rootClasses = getRootClass()
+ const ulClass = useUlClassName(props)
+ const ulStyle = useDropdownStyle(props)
+ // console.log('props', props)
return () => (
-
- {props.cascaderlis.map((item, index) => {
- return
- })}
+
+ {
+ props?.cascaderItems?.length > 0
+ ? props.cascaderItems.map((item, index) => {
+ return
+ })
+ : 没有数据
+ }
)
}
diff --git a/packages/devui-vue/devui/cascader/components/cascader-list/use-class.ts b/packages/devui-vue/devui/cascader/components/cascader-list/use-class.ts
deleted file mode 100644
index eb0109c76ad6d5e3a8e51bf4052efb1a90a93512..0000000000000000000000000000000000000000
--- a/packages/devui-vue/devui/cascader/components/cascader-list/use-class.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * 定义组件class
- */
- import { computed, ComputedRef } from 'vue';
-
- export const getRootClass = (): ComputedRef => {
- return computed(() => ({
- 'devui-cascader-ul': true,
- }))
- }
-
\ No newline at end of file
diff --git a/packages/devui-vue/devui/cascader/components/cascader-multiple/index.scss b/packages/devui-vue/devui/cascader/components/cascader-multiple/index.scss
new file mode 100644
index 0000000000000000000000000000000000000000..848f54b3c6c28fc674ed812bcc8bc26c9723f6e4
--- /dev/null
+++ b/packages/devui-vue/devui/cascader/components/cascader-multiple/index.scss
@@ -0,0 +1,29 @@
+@import '../../../style/theme/color';
+@import '../../../style/theme/corner';
+@import '../../../style/core/font';
+.devui-tags {
+ &-input {
+ flex: 1;
+ padding: 1px 20px 1px 4px;
+ border: 1px solid $devui-form-control-line;
+ border-radius: $devui-border-radius;
+ outline: none;
+ background-color: $devui-base-bg;
+ transition: border-color 300ms cubic-bezier(0.645, 0.045, 0.355, 1);
+ }
+ &-box {
+ width: 100%;
+ overflow: auto;
+ min-height: 28px;
+ max-height: 56px;
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ }
+ &-placeholder {
+ font-size: $devui-font-size;
+ line-height: 22px;
+ margin-left: 6px;
+ color: $devui-placeholder;
+ }
+}
\ No newline at end of file
diff --git a/packages/devui-vue/devui/cascader/components/cascader-multiple/index.tsx b/packages/devui-vue/devui/cascader/components/cascader-multiple/index.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..932c4a3f3bac097c584af244187ab34d47c93704
--- /dev/null
+++ b/packages/devui-vue/devui/cascader/components/cascader-multiple/index.tsx
@@ -0,0 +1,20 @@
+/**
+ * 多选模式下的内容框
+ */
+import DTag from '../cascader-tag/'
+import { MultiplePropsType } from '../../src/cascader-types'
+import './index.scss'
+export default (props: MultiplePropsType) => {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/packages/devui-vue/devui/cascader/components/cascader-tag/index.scss b/packages/devui-vue/devui/cascader/components/cascader-tag/index.scss
new file mode 100644
index 0000000000000000000000000000000000000000..2e57f9bc2fd1f26e139bc29ccf575fa55866f105
--- /dev/null
+++ b/packages/devui-vue/devui/cascader/components/cascader-tag/index.scss
@@ -0,0 +1,39 @@
+@import '../../../style/theme/color';
+@import '../../../style/theme/corner';
+@import '../../../style/core/font';
+.devui-tag {
+ margin: 2px 4px 2px 0;
+ display: inline-block;
+ position: relative;
+ display: flex;
+ align-items: center;
+ padding: 0 8px 0 8px;
+ background-color: $devui-label-bg;
+ border-radius: $devui-border-radius;
+ border-color: inherit;
+ border: 0 solid;
+ span {
+ min-height: 20px;
+ line-height: 20px;
+ font-size: $devui-font-size;
+ color: $devui-text;
+ position: relative;
+ cursor: default;
+ }
+ &__close {
+ margin-left: 12px;
+ font-size: $devui-font-size;
+ cursor: pointer;
+ color: #fff;
+ width: 14px;
+ height: 14px;
+ line-height: 14px;
+ background-color: $devui-line;
+ border-radius: 50%;
+ display: inline-block;
+ text-align: center;
+ &:hover {
+ background-color: $devui-brand;
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/devui-vue/devui/cascader/components/cascader-tag/index.tsx b/packages/devui-vue/devui/cascader/components/cascader-tag/index.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8c4ff034af70e4eae0b492f26eaac911799f23e3
--- /dev/null
+++ b/packages/devui-vue/devui/cascader/components/cascader-tag/index.tsx
@@ -0,0 +1,26 @@
+/**
+ * 多选模式下的内容框中的选中tag
+ * tag组件还未开发完成,所以暂时使用自定义组件
+ */
+import { CascaderItem } from '../../src/cascader-types'
+import { multipleDeleteTag } from '../../hooks/use-cascader-multiple'
+import './index.scss'
+interface PropsType {
+ tag: CascaderItem
+ tagList: CascaderItem[]
+}
+export default (props: PropsType) => {
+ const { tagList, tag } = props
+ const deleteCurrentTag = (e: Event) => {
+ e.stopPropagation()
+ multipleDeleteTag(tagList, tag)
+ }
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/packages/devui-vue/devui/cascader/hooks/use-cascader-class.ts b/packages/devui-vue/devui/cascader/hooks/use-cascader-class.ts
index 706eca5edd019b251188470e8bf288d7e421d75f..576dc61c016d1fc694ecae6da945ca815bb8a5c4 100644
--- a/packages/devui-vue/devui/cascader/hooks/use-cascader-class.ts
+++ b/packages/devui-vue/devui/cascader/hooks/use-cascader-class.ts
@@ -2,15 +2,39 @@
* 定义组件class
*/
import { computed, ComputedRef, Ref } from 'vue';
-import { CascaderProps } from '../src/cascader-types'
-const TRIGGER_Map = {
- hover: 'hover',
- click: 'click',
-}
-export const getRootClass = (props: CascaderProps, menuShow: Ref ): ComputedRef => {
+import { CascaderProps, CascaderulProps, CascaderItemPropsType } from '../src/cascader-types'
+// import { UseClassNameType } from '../components/cascader-item/cascader-item-types'
+
+// 根节点class
+export const useRootClassName = (props: CascaderProps, menuShow: Ref ): ComputedRef => {
return computed(() => ({
'devui-cascader devui-dropdown devui-dropdown-animation': true,
'devui-dropdown__open': menuShow.value,
'devui-cascader__disbaled': props.disabled,
}))
}
+
+// 弹出层项 class
+export const useListClassName = (props: CascaderItemPropsType): ComputedRef => {
+ const itemProps = props?.cascaderItemNeedProps
+ const isActive = itemProps?.valueCache[props.ulIndex] === props.cascaderItem?.value
+ const isDisabled = props.cascaderItem?.disabled
+ return computed(() => ({
+ 'devui-cascader-li devui-dropdown-item': true,
+ 'devui-leaf-active': isActive,
+ 'disabled': isDisabled
+ }))
+}
+
+// 弹出层列 class
+export const useUlClassName = (props: CascaderulProps): ComputedRef => {
+ return computed(() => ({
+ 'devui-cascader-ul': true,
+ 'devui-drop-no-data': props?.cascaderItems?.length === 0
+ }))
+}
+
+// 为弹出层打开添加全局class
+export const dropdownOpenClass = (status: boolean): string => {
+ return status ? 'devui-drop-menu-wrapper' : ''
+}
\ No newline at end of file
diff --git a/packages/devui-vue/devui/cascader/hooks/use-cascader-item.ts b/packages/devui-vue/devui/cascader/hooks/use-cascader-item.ts
new file mode 100644
index 0000000000000000000000000000000000000000..603d7b4a09d84eae7c063b079186e591f9eed2dc
--- /dev/null
+++ b/packages/devui-vue/devui/cascader/hooks/use-cascader-item.ts
@@ -0,0 +1,28 @@
+/**
+ * 处理cascader-item中需要的参数
+ */
+import { cloneDeep } from 'lodash-es'
+import { ref, reactive, Ref } from 'vue'
+import { CascaderProps, UseCascaderItemCallback, CascaderItem } from '../src/cascader-types'
+
+export const useCascaderItem = (props?: CascaderProps, stopDefault?: Ref, tagList?: CascaderItem[]): UseCascaderItemCallback => {
+ /**
+ * 传递给cascader-item/index.ts组件的数据
+ */
+ const cascaderItemNeedProps = {
+ trigger: props.trigger,
+ inputValueCache: ref(''),
+ confirmInputValueFlg: ref(false), // 用于监听点击确定时输出选择内容
+ valueCache: reactive(cloneDeep(props.value)), // 操作时缓存选中的值
+ value: reactive(cloneDeep(props.value)), // 每级的value
+ multiple: props.multiple,
+ activeIndexs: reactive([]), // 维护用于视图更新的选中下标
+ tagList, // 多选模式下选中的值数组,用于生成tag
+ stopDefault,
+ }
+
+ return {
+ cascaderItemNeedProps,
+ // getInputValue
+ }
+}
\ No newline at end of file
diff --git a/packages/devui-vue/devui/cascader/hooks/use-cascader-multiple.ts b/packages/devui-vue/devui/cascader/hooks/use-cascader-multiple.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4a2caaf4f7401695dff61a1871cc77b84f50e210
--- /dev/null
+++ b/packages/devui-vue/devui/cascader/hooks/use-cascader-multiple.ts
@@ -0,0 +1,191 @@
+/**
+ * 多选模式
+ */
+import { CascaderItem, UpdateStatusCallback, CaascaderOptionsType, CheckedType, CascaderItemNeedType } from '../src/cascader-types'
+
+/**
+ * 初始化选中项,将选中的数组集合置为空
+ * @param tagList 前被选中的tagList集合
+ */
+export const initTagList = (tagList: CascaderItem[]): void => {
+ tagList.splice(0, tagList.length)
+}
+/**
+ * 添加选中项
+ * @param tagList 当前被选中的tagList集合
+ * @param singleItem 当前选中项
+ *
+ */
+export const multipleAddTag = (tagList: CascaderItem[], singleItem: CascaderItem): void => {
+ tagList.push(singleItem)
+}
+
+/**
+ * 删除选中项
+ * @param tagList 前被选中的tagList集合
+ * @param singleItem 当前选中项
+ *
+ */
+export const multipleDeleteTag = (tagList: CascaderItem[], singleItem: CascaderItem): void => {
+ // console.log(arr)
+ const i = tagList.findIndex(item => item.value === singleItem.value)
+ tagList.splice(i, 1)
+}
+
+/**
+ * 多选模式初始化选中的节点
+ * @param targetValues 多选模式下的value数组
+ * @param rootNode 选项的第一列
+ * @param tagList 选中的tag集合
+ */
+export const initMultipleCascaderItem = (targetValues: number[], rootColumn: CascaderItem[], tagList: CascaderItem[]): void => {
+ findNextColumn(targetValues, rootColumn, 0, tagList)
+}
+
+/**
+ * 根据values集合递归获取选中的节点
+ * @param targetValues 多选模式下的value数组
+ * @param rootNode 选项的第一列
+ * @param index 当前初始化的列下标
+ * @param tagList 选中的tag集合
+ */
+const findNextColumn = (targetValues: number[], options: CascaderItem[], index: number, tagList: CascaderItem[]): void => {
+ let targetNode = options.find(t => t.value === targetValues[index]) // 根据value获取当前选中的项
+ if (targetNode?.children?.length > 0) { // 递归的限制条件,是否还有子级
+ index += 1 // 进入下一级
+ targetNode = setChildrenParent(targetNode) // 为children设置parent,方便后续通过child使用parent
+ findNextColumn(targetValues, targetNode.children, index, tagList)
+ } else { // 没有子节点说明此时已经是最终结点了
+ multipleAddTag(tagList, targetNode) // 新增tag
+
+ targetNode['checked'] = true
+
+ // 从最终结点往上寻找父节点更新状态
+ // 通过父亲节点查询所有子节点状态从而更新父节点状态
+ findChildrenCheckedStatusToUpdateParent(targetNode?.parent)
+ }
+}
+ /**
+ *
+ * @param parentNode 父节点
+ * @returns parentNode 父节点
+ */
+ const setChildrenParent = (parentNode) => {
+ parentNode?.children.forEach(child => {
+ child.parent = parentNode
+ })
+ return parentNode
+ }
+
+export const updateCheckOptionStatus = (tagList: CascaderItem[]): UpdateStatusCallback => {
+ /**
+ * 更新当前选中状态
+ * @param node 当前结点
+ * @param options column的集合
+ * @param ulIndex 当前column的下标
+ */
+ const updateStatus = (node: CascaderItem, options: CaascaderOptionsType, ulIndex: number) => {
+ // 更新当前点击的node
+ updateCurNodeStatus(node, ulIndex)
+ ulIndex -= 1
+ // const parentNode = getParentNode(node.value, options, ulIndex)
+ const parentNode = node?.parent
+ updateParentNodeStatus(parentNode, options, ulIndex)
+ }
+ /**
+ * 更新当前选中的结点状态
+ * @param node 当前结点
+ * @param ulIndex 当前column的下标
+ */
+ const updateCurNodeStatus = (node: CascaderItem, ulIndex: number) => {
+ // 如果是半选状态,更新为false,其他状态则更新为与checked相反
+ if (node?.halfChecked) { // 更新半选状态
+ node['halfChecked'] = false
+ node['checked'] = false
+ updateCheckStatusLoop(node, 'halfChecked', ulIndex)
+ } else {
+ node['checked'] = !node.checked
+ // 更新是否选中状态
+ updateCheckStatusLoop(node, 'checked', ulIndex, node.checked)
+ }
+ }
+ /**
+ * 父节点改变子节点check状态
+ * @param node 节点
+ */
+ const updateCheckStatusLoop = (node: CascaderItem, type: CheckedType, ulIndex: number, status?: boolean) => {
+ if (node?.children?.length > 0) {
+ node.children.forEach(item => {
+ // 当需要改变checked时
+ // halfChecked一定是false
+ if (item.disabled) return // 禁用不可更改状态
+ if (type === 'checked') {
+ item[type] = status
+ item['halfChecked'] = false
+ updateCheckStatusLoop(item, type, ulIndex, status)
+ } else if (type === 'halfChecked') {
+ /**
+ * halfChecked为false时,取消子节点所有选中
+ */
+ item['halfChecked'] = false
+ item['checked'] = false
+ !status && updateCheckStatusLoop(item, type, ulIndex)
+ }
+ })
+ } else {
+ // 增加或者删除选中的项
+ !node.checked
+ ? multipleDeleteTag(tagList, node)
+ : multipleAddTag(tagList, node)
+ }
+ }
+ return {
+ updateStatus,
+ }
+}
+/**
+ * 子节点获取父节点
+ * 已在子节点创建父节点,此段代码不再使用
+ */
+// const getParentNode = (childValue: string | number, options: CaascaderOptionsType, ulIndex: number): CascaderItem => {
+// if (ulIndex < 0) return
+// const queue = [...options[ulIndex]]
+// let cur: CascaderItem
+// while(queue.length) {
+// cur = queue.shift()
+// if (cur.children && cur.children.find(t => t.value === childValue)) {
+// break
+// } else if (cur.children) {
+// queue.push(...cur.children)
+// }
+// }
+// return cur
+// }
+
+/**
+ * 根据当前节点的子节点更新当前节点状态
+ * @param node - 当前节点
+ */
+ const findChildrenCheckedStatusToUpdateParent = (node) => {
+ const checkedChild = node?.children?.find(t => t['checked'])
+ const halfcheckedChild = node?.children?.find(t => t['halfChecked'])
+ const uncheckedChild = node?.children?.find(t => !t['halfChecked'] && !t['checked'])
+ if (halfcheckedChild || (checkedChild && uncheckedChild)) {
+ node['checked'] = false
+ node['halfChecked'] = true
+ } else if (!checkedChild && !halfcheckedChild) {
+ node['checked'] = false
+ node['halfChecked'] = false
+ } else {
+ node['checked'] = true
+ node['halfChecked'] = false
+ }
+}
+const updateParentNodeStatus = (node: CascaderItem, options: CaascaderOptionsType, ulIndex: number) => {
+ if (ulIndex < 0) return
+ findChildrenCheckedStatusToUpdateParent(node)
+ ulIndex -= 1
+ // const parentNode = getParentNode(node.value, options, ulIndex)
+ const parentNode = node?.parent
+ updateParentNodeStatus(parentNode, options, ulIndex)
+}
\ No newline at end of file
diff --git a/packages/devui-vue/devui/cascader/hooks/use-cascader-options.ts b/packages/devui-vue/devui/cascader/hooks/use-cascader-options.ts
index e7a298708b09cd7276035cd0bcd79b08a568608d..1c3c897aa001f90d7b0022beb155fccd9c2124bd 100644
--- a/packages/devui-vue/devui/cascader/hooks/use-cascader-options.ts
+++ b/packages/devui-vue/devui/cascader/hooks/use-cascader-options.ts
@@ -1,15 +1,11 @@
/**
* 处理传入options数据
*/
-import { reactive } from 'vue';
-import { CascaderItem, OptionsCallback } from '../src/cascader-types'
-let cascaderOptions
-export const optionsHandles = (options?: CascaderItem[]): OptionsCallback => {
- if (options) {
- cascaderOptions = reactive<[CascaderItem[]]>([ options ])
- }
+import { CascaderItem, OptionsCallback, CaascaderOptionsType } from '../src/cascader-types'
+export const optionsHandles = (cascaderOptions?: CaascaderOptionsType): OptionsCallback => {
+
/**
- * hover时修改展示项
+ * change时修改展示项
* @param optionItem - 项
* @param ulIndex - 当前选中的第几级
*
@@ -23,6 +19,7 @@ export const optionsHandles = (options?: CascaderItem[]): OptionsCallback => {
cascaderOptions.splice(ulIndex + 1, cascaderOptions.length - 1 - ulIndex)
}
}
+
return {
cascaderOptions,
changeCascaderIndexs
diff --git a/packages/devui-vue/devui/cascader/hooks/use-cascader-popup.ts b/packages/devui-vue/devui/cascader/hooks/use-cascader-popup.ts
index 8e8f4bdff2ec8a5c7823c79af0fe426084888bc1..2f4bb5f1ad1e3518a8e504a5290b89adb6072b81 100644
--- a/packages/devui-vue/devui/cascader/hooks/use-cascader-popup.ts
+++ b/packages/devui-vue/devui/cascader/hooks/use-cascader-popup.ts
@@ -1,22 +1,33 @@
/**
* 控制窗口打开收起
*/
-import { ref, watch } from 'vue';
-import { PopupTypes } from '../src/cascader-types'
-
-export const popupHandles = (): PopupTypes => {
+import { ref, watch, computed } from 'vue';
+import { PopupTypes, CascaderProps } from '../src/cascader-types'
+import { dropdownOpenClass } from './use-cascader-class'
+export const popupHandles = (props: CascaderProps): PopupTypes => {
const menuShow = ref(false)
const menuOpenClass = ref('')
+ const disabled = computed(() => props.disabled) // select是否被禁用
+ const stopDefault = ref(false)
+ const updateStopDefaultType = () => {
+ stopDefault.value = !menuShow.value
+ }
+
const openPopup = () => {
+ if (disabled.value) return
menuShow.value = !menuShow.value
+ updateStopDefaultType()
}
+
watch(menuShow, (status) => {
- menuOpenClass.value = status ? 'devui-drop-menu-wrapper' : ''
+ menuOpenClass.value = dropdownOpenClass(status)
})
return {
menuShow,
+ stopDefault,
menuOpenClass,
+ updateStopDefaultType,
openPopup,
}
}
diff --git a/packages/devui-vue/devui/cascader/hooks/use-cascader-single.ts b/packages/devui-vue/devui/cascader/hooks/use-cascader-single.ts
new file mode 100644
index 0000000000000000000000000000000000000000..52409fa989bbee9bba68d0d32f0fd59905c3738f
--- /dev/null
+++ b/packages/devui-vue/devui/cascader/hooks/use-cascader-single.ts
@@ -0,0 +1,48 @@
+/**
+ * 单选模式
+ * */
+import { Ref } from 'vue'
+import { CascaderItem, CascaderValueType } from '../src/cascader-types'
+/**
+ * 初始化打开时的视图选中状态
+ * 通过value集合获取下标集合
+ * @param values 选中的value集合
+ * @param curColumn 当前列
+ * @param index values数组的起始项,最开始为0
+ * @param activeIndexs 当前渲染到视图列的下标集合
+ */
+export const initActiveIndexs = (values: CascaderValueType, curColumn: CascaderItem[], index: number, activeIndexs: number[]): void => {
+ let nextOption = null
+ for (let i = 0; i < curColumn.length; i++) {
+ if (curColumn[i]?.value === values[index]) {
+ nextOption = curColumn[i]?.children
+ activeIndexs[index] = i
+ break
+ }
+ }
+ if (index < values.length - 1 && nextOption) {
+ index += 1
+ initActiveIndexs(values, nextOption, index, activeIndexs)
+ }
+}
+
+/**
+ * 缓存输入框内容
+ * @param inputValueCache 缓存的输入框内容,当最终确定时输出内容
+ */
+export const initSingleIptValue = (inputValueCache: Ref): void => {
+ inputValueCache.value = ''
+}
+
+/**
+ * 单选选中
+ * @param ulIndex 当前操作的列
+ * @param valueCache 缓存的当前操作列的value集合
+ * @param cascaderItem 当前操作项
+ */
+export const singleChoose = (ulIndex: number, valueCache: CascaderValueType, cascaderItem: CascaderItem): void => {
+ // 删除当前联动级之后的所有级
+ valueCache.splice(ulIndex, valueCache.length - ulIndex)
+ // 更新当前active的value数组
+ valueCache[ulIndex] = cascaderItem?.value as number
+}
\ No newline at end of file
diff --git a/packages/devui-vue/devui/cascader/hooks/use-cascader-style.ts b/packages/devui-vue/devui/cascader/hooks/use-cascader-style.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e2374e98d5adc39a84eaca63dd5db9c317022f5b
--- /dev/null
+++ b/packages/devui-vue/devui/cascader/hooks/use-cascader-style.ts
@@ -0,0 +1,17 @@
+/**
+ * 定义组件 style
+ */
+import { CascaderProps, RootStyleFeedback, CascaderulProps, DropdownStyleFeedback } from '../src/cascader-types'
+// 根节点样式
+export const useRootStyle = (props: CascaderProps): RootStyleFeedback => {
+ return {
+ inputWidth: `width: ${props.width}px`,
+ }
+}
+
+// 弹出层样式
+export const useDropdownStyle = (props: CascaderulProps): DropdownStyleFeedback => {
+ return {
+ dropdownWidth: `width: ${props?.dropdownWidth}px`
+ }
+}
\ No newline at end of file
diff --git a/packages/devui-vue/devui/cascader/index.ts b/packages/devui-vue/devui/cascader/index.ts
index 0bb14975fbe22227e75154f17a366370611cd4ec..1e95f9128fa3bdddadc79ad4f2453cc7ae80e626 100644
--- a/packages/devui-vue/devui/cascader/index.ts
+++ b/packages/devui-vue/devui/cascader/index.ts
@@ -10,7 +10,7 @@ export { Cascader }
export default {
title: 'Cascader 级联菜单',
category: '数据录入',
- status: '10%',
+ status: '30%',
install(app: App): void {
app.use(Cascader as any)
}
diff --git a/packages/devui-vue/devui/cascader/src/cascader-types.ts b/packages/devui-vue/devui/cascader/src/cascader-types.ts
index 13abc1af5a1e869e2b9ec394dba7cbef6aa2263f..f88ae148ceb189736e5b475751ceeb614fc3e7e0 100644
--- a/packages/devui-vue/devui/cascader/src/cascader-types.ts
+++ b/packages/devui-vue/devui/cascader/src/cascader-types.ts
@@ -8,12 +8,18 @@ export interface CascaderItem {
value: number | string
isLeaf?: boolean
children?: CascaderItem[]
+ checked?: boolean
+ halfChecked?: boolean
disabled?: boolean
+ active?: boolean
+ _loading?: boolean
icon?: string
// 用户可以传入自定义属性,并在dropDownItemTemplate中使用
[prop: string]: any
}
+type CascaderModelValue = number[]
+export type CascaderValueType = CascaderModelValue | [CascaderModelValue]
export const cascaderProps = {
/**
* 可选,指定展开次级菜单方式
@@ -25,6 +31,24 @@ export const cascaderProps = {
type: String as PropType,
default: 'hover'
},
+ /**
+ * 可选,单位 px,用于控制组件输入框宽度和下拉的宽度
+ * @type { Number | String }
+ * @default 200
+ */
+ width: {
+ type: Number || String,
+ default: 200
+ },
+ /**
+ * 可选,单位 px,控制下拉列表的宽度,默认和组件输入框 width 相等
+ * @type { Number | String }
+ * @default 200
+ */
+ dropdownWidth: {
+ type: Number || String,
+ default: 200
+ },
/**
* 必选,级联器的菜单信息
* @type {CascaderItem[]}
@@ -35,6 +59,31 @@ export const cascaderProps = {
default: [],
required: true
},
+ /**
+ * 可选,级联器是否开启多选模式,开启后为 checkbox 选择
+ * @type {Boolean}
+ * @default false
+ */
+ multiple: {
+ type: Boolean,
+ default: false
+ },
+ /**
+ * 可选,级联器选中项是否显示路径,仅单选模式下生效
+ */
+ showPath: {
+ type: Boolean,
+ default: false
+ },
+ /**
+ * 可选,需要选中项的value集合
+ * @type {CascaderValueType}
+ * @default []
+ */
+ value: {
+ type: Array as PropType,
+ default: []
+ },
/**
* 可选,级联器是否禁用
* @type {boolean}
@@ -52,8 +101,11 @@ export const cascaderProps = {
placeholder: {
type: String,
default: ''
- }
-
+ },
+ change: {
+ type: Function as PropType<(v: CascaderValueType, k: CascaderItem[]) => void>,
+ default: undefined
+ },
} as const
export type CascaderProps = ExtractPropTypes
@@ -61,10 +113,106 @@ export type CascaderProps = ExtractPropTypes
export interface PopupTypes {
menuShow: Ref
menuOpenClass: Ref
+ stopDefault: Ref
openPopup: (e?: MouseEvent) => void
+ updateStopDefaultType: () => void
}
+export type CaascaderOptionsType = UnwrapNestedRefs<[CascaderItem[]]>
export interface OptionsCallback {
- cascaderOptions: never | UnwrapNestedRefs<[CascaderItem[]]>
+ cascaderOptions: never | CaascaderOptionsType
changeCascaderIndexs: (optionItem: CascaderItem, ulIndex: number) => void
+}
+
+// type cascaderItemExtendsProps = 'trigger'
+// export type PickCascader = Pick
+// export interface CascaderItemNeedType extends PickCascader {
+ export interface CascaderItemNeedType {
+ valueCache?: CascaderValueType
+ trigger?: TriggerTypes
+ value?: CascaderValueType
+ inputValueCache?: Ref
+ confirmInputValueFlg?: Ref
+ multiple?: boolean
+ stopDefault?: Ref
+ activeIndexs?: number[]
+ tagList?: UnwrapNestedRefs
+}
+export interface UseCascaderItemCallback {
+ cascaderItemNeedProps: CascaderItemNeedType
+ // getInputValue: (a: string, b?: CascaderItem[], c?: Ref) => void
+}
+
+export type CheckedType = 'checked' | 'halfChecked'
+
+export interface RootStyleFeedback {
+ inputWidth: string
+}
+
+export const cascaderulProps = {
+ /**
+ * 每个ul中的li
+ * @type {CascaderItem[]}
+ * @default []
+ */
+ cascaderItems: {
+ type: Array as PropType,
+ default: ():CascaderItem[] => ([{
+ label: '',
+ value: null
+ }]),
+ },
+ /**
+ * 可选,单位 px,控制下拉列表的宽度,默认和组件输入框 width 相等
+ * @type { Number | String }
+ * @default 200
+ */
+ dropdownWidth: {
+ type: Number || String,
+ default: 200
+ },
+ /**
+ * 当前选中的ul下标
+ * @type {Number}
+ * @default 0
+ */
+ ulIndex: {
+ type: Number,
+ default: 0
+ },
+ cascaderItemNeedProps: {
+ type: Object as PropType,
+ default: ():CascaderItemNeedType => ({})
+ },
+ stopDefault: {
+ type: Boolean,
+ default: false
+ },
+ cascaderOptions: {
+ type: Array as unknown as PropType<[CascaderItem[]]>,
+ default: ():[CascaderItem[]] => ([[{
+ label: '',
+ value: null
+ }]])
+ }
+}
+export type CascaderulProps = ExtractPropTypes
+
+export interface CascaderItemPropsType extends CascaderulProps {
+ cascaderItem: CascaderItem
+ liIndex: number
+ cascaderItemNeedProps: CascaderItemNeedType
+}
+
+export interface DropdownStyleFeedback {
+ dropdownWidth: string
+}
+
+export interface MultiplePropsType {
+ activeOptions: CascaderItem[]
+ placeholder: string
+}
+
+export interface UpdateStatusCallback {
+ updateStatus: (node: CascaderItem, options: CaascaderOptionsType, ulIndex: number) => void
}
\ No newline at end of file
diff --git a/packages/devui-vue/devui/cascader/src/cascader.scss b/packages/devui-vue/devui/cascader/src/cascader.scss
index cb49189eb72148a52d72dc3eaf1005a43c6a7817..5bed886b42124402706df168daac88843b575fbe 100644
--- a/packages/devui-vue/devui/cascader/src/cascader.scss
+++ b/packages/devui-vue/devui/cascader/src/cascader.scss
@@ -24,6 +24,12 @@
}
}
+ &__disbaled {
+ .icon {
+ color: $devui-disabled-text !important;
+ }
+ }
+
input {
width: 100%;
padding-right: 16px;
diff --git a/packages/devui-vue/devui/cascader/src/cascader.tsx b/packages/devui-vue/devui/cascader/src/cascader.tsx
index d0565e61ce12839982a0d1a88ec046e2c3617b28..2cc6e437e5d70ee5bde2504e5d5dfe27a2edf9b8 100644
--- a/packages/devui-vue/devui/cascader/src/cascader.tsx
+++ b/packages/devui-vue/devui/cascader/src/cascader.tsx
@@ -1,46 +1,168 @@
-import './cascader.scss'
+// 公共库
+import { cloneDeep } from 'lodash-es'
+import { defineComponent, ref, Ref, reactive, watch, toRef } from 'vue'
-import { defineComponent, ref, reactive } from 'vue'
-import { cascaderProps, CascaderProps } from './cascader-types'
-import { getRootClass } from '../hooks/use-cascader-class'
-import { popupHandles } from '../hooks/use-cascader-popup'
+// 组件
import DCascaderList from '../components/cascader-list'
-import { optionsHandles } from '../hooks/use-cascader-options'
-
+import DMultipleBox from '../components/cascader-multiple/index'
+// 事件
+import { cascaderProps, CascaderItem, CascaderProps, CascaderValueType } from './cascader-types'
+import { useCascaderItem } from '../hooks/use-cascader-item'
+import { useRootClassName } from '../hooks/use-cascader-class'
+import { useRootStyle } from '../hooks/use-cascader-style'
+import { popupHandles } from '../hooks/use-cascader-popup'
+import { initMultipleCascaderItem, initTagList } from '../hooks/use-cascader-multiple'
+import { initSingleIptValue, initActiveIndexs } from '../hooks/use-cascader-single'
+import './cascader.scss'
export default defineComponent({
name: 'DCascader',
props: cascaderProps,
setup(props: CascaderProps, ctx) {
const origin = ref(null)
+ const cascaderOptions = reactive<[CascaderItem[]]>(cloneDeep([ props?.options ]))
+ const multiple = toRef(props, 'multiple')
+ const inputValue = ref('')
+ const tagList = reactive([]) // 多选模式下选中的值数组,用于生成tag
+ const rootStyle = useRootStyle(props)
+ let initIptValue = props.value.length > 0 ? true : false // 有value默认值时,初始化输出内容
+
const position = reactive({
- originX: 'left',
- originY: 'bottom',
- overlayX: 'left',
+ originX: 'left',
+ originY: 'bottom',
+ overlayX: 'left',
overlayY: 'top'
} as const)
// popup弹出层
- const { menuShow, menuOpenClass, openPopup } = popupHandles()
+ const { menuShow, menuOpenClass, openPopup, stopDefault, updateStopDefaultType } = popupHandles(props)
// 配置class
- const rootClasses = getRootClass(props, menuShow)
- // 级联菜单操作,变换ul、li等
- const { cascaderOptions } = optionsHandles(props.options)
+ const rootClasses = useRootClassName(props, menuShow)
+ // 传递给cascaderItem的props
+ const { cascaderItemNeedProps } = useCascaderItem(props, stopDefault, tagList)
+ const getInputValue = (label: string, arr: CascaderItem[], inputValueCache: Ref, showPath?: boolean) => {
+ if (!showPath) {
+ inputValueCache.value = label
+ } else {
+ inputValueCache.value += (label + (arr?.length > 0 ? ' / ' : ''))
+ }
+ }
+ /**
+ * 控制视图更新
+ * 注意视图更新不区分单选或者多选
+ * @param activeIndexs 视图展示下标集合
+ * @param currentOption 选中的某项
+ * @param index value的下标,起始为0
+ */
+ const updateCascaderView = (value: CascaderValueType, currentOption: CascaderItem[], index: number) => {
+ if (index === value.length) return
+ const i = value[index] as number
+ // 当前的子级
+ const current = currentOption[i]
+ const children = current?.children
+ if (children?.length > 0) {
+ // 为下一级增添数据
+ cascaderOptions[index + 1] = children
+ // 递归添加
+ updateCascaderView(value, children, index + 1)
+ } else {
+ // 当最新的ul(级)没有下一级时删除之前选中ul的数据
+ cascaderOptions.splice(index + 1, cascaderOptions.length - 1)
+ }
+ }
+ /**
+ * 选中项输出
+ * 需要区分单选或多选模式
+ * @param value 选中值集合
+ * @param currentOption 激活的某项
+ * @param index value的下标,起始为0
+ */
+ const updateCascaderValue = (value: CascaderValueType, currentOption: CascaderItem[], index: number) => {
+ if (!multiple.value) {
+ // 单选模式
+ if (index === value.length) return
+ const i = value[index] as number
+ // 当前的子级
+ const current = getCurrentOption(currentOption, i)
+ const children = current?.children
+ getInputValue(current.label, children, cascaderItemNeedProps.inputValueCache, props.showPath)
+ if (children?.length > 0) {
+ updateCascaderValue(value, children, index + 1)
+ }
+ } else {
+ // 多选模式
+ const rootColumn = cascaderOptions[0] || [] // 第一列
+ value.forEach((targetValue) => {
+ initMultipleCascaderItem(targetValue, rootColumn, tagList)
+ })
+ }
+ }
+ /**
+ * 根据value筛选每列中选中item
+ */
+ const getCurrentOption = (currentOption: CascaderItem[], i: number) => {
+ return currentOption.filter(item => item?.value === i)[0]
+ }
+ /**
+ * 监听视图更新
+ */
+ watch(cascaderItemNeedProps.activeIndexs, val => {
+ // TODO 多选模式下优化切换选择后的视图切换
+ cascaderOptions.splice(val.length, cascaderOptions.length - 1)
+ updateCascaderView(val, cascaderOptions[0], 0)
+ })
+ /**
+ * 监听点击最终的节点输出内容
+ */
+ watch(() => cascaderItemNeedProps.confirmInputValueFlg.value, () => {
+ // 单选和多选模式初始化
+ multiple.value
+ ? initTagList(tagList)
+ : initSingleIptValue(cascaderItemNeedProps.inputValueCache)
+ // 输出确认的选中值
+ cascaderItemNeedProps.value = reactive(cloneDeep(cascaderItemNeedProps.valueCache))
+ menuShow.value = false
+ // 点击确定过后禁止再次选中
+ updateStopDefaultType()
+ // 更新值
+ updateCascaderValue(cascaderItemNeedProps.value, cascaderOptions[0], 0)
+ inputValue.value = cascaderItemNeedProps.inputValueCache.value
+ // 单选模式默认回显视图的选中态
+ // 多选模式不默认视图打开状态,因为选中了太多个,无法确定展示哪一种选中态
+ if (initIptValue && !multiple.value) {
+ initActiveIndexs(props.value, cascaderOptions[0], 0, cascaderItemNeedProps.activeIndexs)
+ initIptValue = false // 只需要初始化一次,之后不再执行
+ }
+ }, {
+ immediate: true
+ })
+
return () => (
<>
-
-
+
+ { multiple.value
+ ?
+ :
+ }
-
+
diff --git a/packages/devui-vue/devui/cascader/src/readme.md b/packages/devui-vue/devui/cascader/src/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..70b71288ac00912c79bc1f77230f8444cc047f2d
--- /dev/null
+++ b/packages/devui-vue/devui/cascader/src/readme.md
@@ -0,0 +1,13 @@
+# 组件组成
+cacader组件分为:
+- `cascader.tsx`主要文件,渲染主视图
+- `cascader-multiple.tsx`多选模式下的内容展示container,单选模式为input组件
+- `cascader-list.tsx`渲染列
+- `cascader-item.tsx`渲染列中的项
+
+# 组件设计
+以下属性在`hooks/use-cascader-item.ts`中
+- `activeIndexs`:每列中的选中下标,负责弹窗交互视图更新
+- `value`:选中的项的value值集合,也可以通过props方式传入
+
+**activeIndex** 负责视图更新,也就是hover或者click每项时的交互行为,**value**负责输出选中值。通过`watch`监听这两个值的改变驱动视图和值输出
diff --git a/packages/devui-vue/docs/components/cascader/index.md b/packages/devui-vue/docs/components/cascader/index.md
index 1c310e940526131358058a60a2b1f1b369876ce8..e124bc66dc7d559584a6fcc978898190af15dbed 100644
--- a/packages/devui-vue/docs/components/cascader/index.md
+++ b/packages/devui-vue/docs/components/cascader/index.md
@@ -8,11 +8,19 @@
2. 从一个较大的数据集合中进行选择时,用多级分类进行分隔,方便选择。
### 基本用法
+
:::demo
```vue
+ hover mode
+ click mode
+
+ data empty
+
+ disabled
+
+```
+
+:::
+
+
+
### API
| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置项 |
| :--------: | :------: | :-------: | :---------------------- | --------------------------------- | --------- |
+| trigger | ` 'hover'\|'click' ` | 'hover' | 可选,指定展开次级菜单的方式 | [基本用法](#基本用法) | |
| options | [`CascaderItem[]`](#CascaderItem) | [] | 必选,级联器的菜单信息 | [基本用法](#基本用法) | |
| placeholder | `string` | '' | 可选,没有选择时的输入框展示信息 | [基本用法](#基本用法) | |
+| disabled | `boolean` | false | 可选,级联器是否禁用 | [基本用法](#基本用法) | |
+| value | `number[] \| [number[]]` | [] | 可选,单选时为`number[]`,多选时为`[number[]]`,选中项的value值集合 | [基本用法](#基本用法) | |
+| multiple | `boolean` | false | 可选,级联器是否开启多选模式,开启后为 checkbox 选择 | [基本用法](#多选模式) | |
+| width | `number \| string` | 200 | 可选,单位 px,用于控制组件输入框宽度和下拉的宽度 | [基本用法](#多选模式) | |
+| dropdownWidth | `number \| string` | 200 | 可选,单位 px,控制下拉列表的宽度,默认和组件输入框 width 相等 | [基本用法](#多选模式) | |
+
### 接口 & 类型定义
@@ -141,7 +400,6 @@ export default defineComponent({
interface CascaderItem {
label: string;
value: number | string;
- isLeaf?: boolean;
children?: CascaderItem[];
disabled?: boolean;
icon?: string;