diff --git a/devui/cascader/components/cascader-item/index.scss b/devui/cascader/components/cascader-item/index.scss
new file mode 100644
index 0000000000000000000000000000000000000000..459a91366bdb41b186b4a21c989d59286dfb9404
--- /dev/null
+++ b/devui/cascader/components/cascader-item/index.scss
@@ -0,0 +1,22 @@
+@import '../../../style/mixins/flex';
+@import '../../../style/theme/color';
+@import '../../../style/core/font';
+
+.devui-cascader-li {
+ &.devui-dropdown-item {
+ height: 32px;
+ padding: 8px 12px;
+ color: $devui-text;
+ cursor: pointer;
+ @include flex(flex-start);
+ }
+
+ .dropdown-item-label {
+ display: inline-block;
+ flex: 1;
+ width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-size: $devui-font-size;
+ }
+}
diff --git a/devui/cascader/components/cascader-item/index.tsx b/devui/cascader/components/cascader-item/index.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5182dd221b1773743c2b7bf4a48cd6ff4f27aa9f
--- /dev/null
+++ b/devui/cascader/components/cascader-item/index.tsx
@@ -0,0 +1,21 @@
+import { getRootClass } from './use-class'
+import { optionsHandles } from '../../hooks/use-cascader-options'
+import './index.scss'
+export const DCascaderItem = (props) => {
+ const { cascaderli, ulIndex } = props
+ const { changeCascaderIndexs } = optionsHandles()
+ const rootClasses = getRootClass()
+ const mouseHover = () => {
+ changeCascaderIndexs(cascaderli, ulIndex)
+ }
+ return (
+
+
+ { cascaderli.label }
+
+ {
+ cascaderli?.children?.length > 0 &&
+ }
+
+ )
+}
diff --git a/devui/cascader/components/cascader-item/use-class.ts b/devui/cascader/components/cascader-item/use-class.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5a5ec3e21b6bd16e8fafc7da41ea55a592f0760b
--- /dev/null
+++ b/devui/cascader/components/cascader-item/use-class.ts
@@ -0,0 +1,11 @@
+/**
+ * 定义组件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/devui/cascader/components/cascader-list/cascader-list-types.ts b/devui/cascader/components/cascader-list/cascader-list-types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..85e2152df95d7ff7843a6db28e084074b8c38f24
--- /dev/null
+++ b/devui/cascader/components/cascader-list/cascader-list-types.ts
@@ -0,0 +1,23 @@
+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/devui/cascader/components/cascader-list/index.scss b/devui/cascader/components/cascader-list/index.scss
new file mode 100644
index 0000000000000000000000000000000000000000..a5ff2f80f31e199faf7c02531b3cbcaebf8d86d7
--- /dev/null
+++ b/devui/cascader/components/cascader-list/index.scss
@@ -0,0 +1,14 @@
+@import '../../../style/mixins/flex';
+@import '../../../style/theme/color';
+
+.devui-cascader-ul {
+ width: 200px;
+ height: 180px;
+ background: $devui-connected-overlay-bg;
+ display: block;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ overflow-y: auto;
+ border-left: 1px solid $devui-dividing-line;
+}
diff --git a/devui/cascader/components/cascader-list/index.tsx b/devui/cascader/components/cascader-list/index.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4fa19244988f5997d37de3f902b5ab75bf8b2ecf
--- /dev/null
+++ b/devui/cascader/components/cascader-list/index.tsx
@@ -0,0 +1,19 @@
+import { defineComponent } from 'vue'
+import { getRootClass } from './use-class'
+import './index.scss'
+import { cascaderulProps, CascaderulProps } from './cascader-list-types'
+import { DCascaderItem } from '../cascader-item'
+export default defineComponent({
+ name: 'DCascaderList',
+ props: cascaderulProps,
+ setup(props: CascaderulProps) {
+ const rootClasses = getRootClass()
+ return () => (
+
+ {props.cascaderlis.map((item, index) => {
+ return
+ })}
+
+ )
+ }
+})
diff --git a/devui/cascader/components/cascader-list/use-class.ts b/devui/cascader/components/cascader-list/use-class.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eb0109c76ad6d5e3a8e51bf4052efb1a90a93512
--- /dev/null
+++ b/devui/cascader/components/cascader-list/use-class.ts
@@ -0,0 +1,11 @@
+/**
+ * 定义组件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/devui/cascader/hooks/use-cascader-class.ts b/devui/cascader/hooks/use-cascader-class.ts
new file mode 100644
index 0000000000000000000000000000000000000000..706eca5edd019b251188470e8bf288d7e421d75f
--- /dev/null
+++ b/devui/cascader/hooks/use-cascader-class.ts
@@ -0,0 +1,16 @@
+/**
+ * 定义组件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 => {
+ return computed(() => ({
+ 'devui-cascader devui-dropdown devui-dropdown-animation': true,
+ 'devui-dropdown__open': menuShow.value,
+ 'devui-cascader__disbaled': props.disabled,
+ }))
+}
diff --git a/devui/cascader/hooks/use-cascader-options.ts b/devui/cascader/hooks/use-cascader-options.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e7a298708b09cd7276035cd0bcd79b08a568608d
--- /dev/null
+++ b/devui/cascader/hooks/use-cascader-options.ts
@@ -0,0 +1,30 @@
+/**
+ * 处理传入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 ])
+ }
+ /**
+ * hover时修改展示项
+ * @param optionItem - 项
+ * @param ulIndex - 当前选中的第几级
+ *
+ */
+ const changeCascaderIndexs = (optionItem: CascaderItem, ulIndex: number) => {
+ if (!cascaderOptions) return
+ if (optionItem?.children?.length > 0) {
+ cascaderOptions[ulIndex + 1] = optionItem.children
+ } else {
+ // 选择的项没有子项时清除多余的ul
+ cascaderOptions.splice(ulIndex + 1, cascaderOptions.length - 1 - ulIndex)
+ }
+ }
+ return {
+ cascaderOptions,
+ changeCascaderIndexs
+ }
+}
diff --git a/devui/cascader/hooks/use-cascader-popup.ts b/devui/cascader/hooks/use-cascader-popup.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8e8f4bdff2ec8a5c7823c79af0fe426084888bc1
--- /dev/null
+++ b/devui/cascader/hooks/use-cascader-popup.ts
@@ -0,0 +1,22 @@
+/**
+ * 控制窗口打开收起
+ */
+import { ref, watch } from 'vue';
+import { PopupTypes } from '../src/cascader-types'
+
+export const popupHandles = (): PopupTypes => {
+ const menuShow = ref(false)
+ const menuOpenClass = ref('')
+ const openPopup = () => {
+ menuShow.value = !menuShow.value
+ }
+ watch(menuShow, (status) => {
+ menuOpenClass.value = status ? 'devui-drop-menu-wrapper' : ''
+ })
+
+ return {
+ menuShow,
+ menuOpenClass,
+ openPopup,
+ }
+}
diff --git a/devui/cascader/index.ts b/devui/cascader/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..81e416df6174b05c0b8dd723eb079b8a1e461146
--- /dev/null
+++ b/devui/cascader/index.ts
@@ -0,0 +1,18 @@
+import type { App } from 'vue'
+import Cascader from './src/cascader'
+
+Cascader.install = function(app: App): void {
+ app.component(Cascader.name, Cascader)
+}
+
+export { Cascader }
+
+export default {
+ title: 'Cascader 级联菜单',
+ category: '数据录入',
+ status: undefined, // TODO: 组件若开发完成则填入"已完成",并删除该注释
+ install(app: App): void {
+
+ app.use(Cascader as any)
+ }
+}
diff --git a/devui/cascader/src/cascader-types.ts b/devui/cascader/src/cascader-types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..13abc1af5a1e869e2b9ec394dba7cbef6aa2263f
--- /dev/null
+++ b/devui/cascader/src/cascader-types.ts
@@ -0,0 +1,70 @@
+import type { PropType, ExtractPropTypes, Ref } from 'vue'
+import { UnwrapNestedRefs } from '@vue/reactivity'
+
+type TriggerTypes = 'hover'|'click'
+
+export interface CascaderItem {
+ label: string
+ value: number | string
+ isLeaf?: boolean
+ children?: CascaderItem[]
+ disabled?: boolean
+ icon?: string
+ // 用户可以传入自定义属性,并在dropDownItemTemplate中使用
+ [prop: string]: any
+}
+
+export const cascaderProps = {
+ /**
+ * 可选,指定展开次级菜单方式
+ * @description 可选择的值 'hover', 'click'
+ * @type {('hover'|'click')}
+ * @default 'hover'
+ */
+ trigger: {
+ type: String as PropType,
+ default: 'hover'
+ },
+ /**
+ * 必选,级联器的菜单信息
+ * @type {CascaderItem[]}
+ * @default []
+ */
+ options: {
+ type: Array as PropType,
+ default: [],
+ required: true
+ },
+ /**
+ * 可选,级联器是否禁用
+ * @type {boolean}
+ * @default false
+ */
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ /**
+ * 可选,没有选择时的输入框展示信息
+ * @type {string}
+ * @default '''
+ */
+ placeholder: {
+ type: String,
+ default: ''
+ }
+
+} as const
+
+export type CascaderProps = ExtractPropTypes
+
+export interface PopupTypes {
+ menuShow: Ref
+ menuOpenClass: Ref
+ openPopup: (e?: MouseEvent) => void
+}
+
+export interface OptionsCallback {
+ cascaderOptions: never | UnwrapNestedRefs<[CascaderItem[]]>
+ changeCascaderIndexs: (optionItem: CascaderItem, ulIndex: number) => void
+}
\ No newline at end of file
diff --git a/devui/cascader/src/cascader.scss b/devui/cascader/src/cascader.scss
new file mode 100644
index 0000000000000000000000000000000000000000..cb49189eb72148a52d72dc3eaf1005a43c6a7817
--- /dev/null
+++ b/devui/cascader/src/cascader.scss
@@ -0,0 +1,71 @@
+@import '../../style/mixins/size';
+@import '../../style/mixins/flex';
+@import '../../style/theme/color';
+
+.devui-cascader {
+ @include flex(flex-start);
+
+ position: relative;
+
+ >div:nth-child(1) {
+ width: 100%;
+ }
+
+ &__icon {
+ position: absolute;
+ right: 5px;
+ top: 0;
+ height: 100%;
+ @include flex;
+ @include flex-direction;
+
+ .icon {
+ margin: 0;
+ }
+ }
+
+ input {
+ width: 100%;
+ padding-right: 16px;
+ }
+
+ .devui-drop-menu-wrapper {
+ display: block;
+ margin: 4px 0;
+ font-size: 0;
+ white-space: nowrap;
+ padding: 0;
+ }
+}
+
+.devui-drop-icon-animation {
+ transition: transform 0.2s linear;
+}
+
+.devui-drop-menu-animation {
+ transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
+ // opacity: 0;
+ // position: absolute;
+ // float: left;
+ z-index: 1000;
+ // transform: scaleY(0.7) translateY(-5px);
+ margin-top: 1px;
+
+ .devui-dropdown-menu {
+ width: auto;
+ padding-bottom: 0;
+ @include flex('flex-start');
+ }
+}
+
+.devui-dropdown__open {
+ .devui-cascader__icon {
+ transform: rotate(180deg);
+ }
+
+ .devui-drop-menu-animation {
+ transform-origin: 0 0%;
+ transform: scaleY(0.9999) translateY(0);
+ opacity: 1;
+ }
+}
diff --git a/devui/cascader/src/cascader.tsx b/devui/cascader/src/cascader.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d0565e61ce12839982a0d1a88ec046e2c3617b28
--- /dev/null
+++ b/devui/cascader/src/cascader.tsx
@@ -0,0 +1,51 @@
+import './cascader.scss'
+
+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'
+
+
+export default defineComponent({
+ name: 'DCascader',
+ props: cascaderProps,
+ setup(props: CascaderProps, ctx) {
+ const origin = ref(null)
+ const position = reactive({
+ originX: 'left',
+ originY: 'bottom',
+ overlayX: 'left',
+ overlayY: 'top'
+ } as const)
+ // popup弹出层
+ const { menuShow, menuOpenClass, openPopup } = popupHandles()
+ // 配置class
+ const rootClasses = getRootClass(props, menuShow)
+ // 级联菜单操作,变换ul、li等
+ const { cascaderOptions } = optionsHandles(props.options)
+ return () => (
+ <>
+
+
+
+
+ >
+ )
+ },
+})
diff --git a/docs/components/cascader/index.md b/docs/components/cascader/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..88e98169a7e3e0f676a2c72975db7cad7db6ec89
--- /dev/null
+++ b/docs/components/cascader/index.md
@@ -0,0 +1,145 @@
+# 级联菜单
+下拉级联菜单。
+
+### 基本用法
+:::demo
+
+```vue
+
+
+
+
+```
+
+:::
+
+### API
+
+| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置项 |
+| :--------: | :------: | :-------: | :---------------------- | --------------------------------- | --------- |
+| options | [`CascaderItem[]`](#CascaderItem) | [] | 必选,级联器的菜单信息 | [基本用法](#基本用法) | |
+| placeholder | `string` | '' | 可选,没有选择时的输入框展示信息 | [基本用法](#基本用法) | |
+
+### 接口 & 类型定义
+
+-
+
+#### CascaderItem
+```ts
+interface CascaderItem {
+ label: string;
+ value: number | string;
+ isLeaf?: boolean;
+ children?: CascaderItem[];
+ disabled?: boolean;
+ icon?: string;
+ // 用户可以传入自定义属性,并在dropDownItemTemplate中使用
+ [prop: string]: any;
+}
+```
\ No newline at end of file