diff --git a/packages/devui-vue/devui/editable-select/index.ts b/packages/devui-vue/devui/editable-select/index.ts
index 411728313716c4f53e85b39ecb87f7dc765ab8bf..4fa0d405db937c0f83bea53931df8164a5246e1c 100644
--- a/packages/devui-vue/devui/editable-select/index.ts
+++ b/packages/devui-vue/devui/editable-select/index.ts
@@ -1,7 +1,6 @@
import type { App } from 'vue'
import EditableSelect from './src/editable-select'
-
-EditableSelect.install = function(app: App): void {
+EditableSelect.install = function (app: App): void {
app.component(EditableSelect.name, EditableSelect)
}
@@ -12,6 +11,6 @@ export default {
category: '数据录入',
status: undefined, // TODO: 组件若开发完成则填入"已完成",并删除该注释
install(app: App): void {
- app.use(EditableSelect as any)
+ app.use(EditableSelect as any)
}
}
diff --git a/packages/devui-vue/devui/editable-select/src/components/dropdown.tsx b/packages/devui-vue/devui/editable-select/src/components/dropdown.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..711f94ee2ac44c43bff206a3bd0fec436120e1f7
--- /dev/null
+++ b/packages/devui-vue/devui/editable-select/src/components/dropdown.tsx
@@ -0,0 +1,53 @@
+import { defineComponent, inject } from 'vue'
+import { OptionItem, selectDropdownProps } from '../editable-select-types'
+import { className } from '../utils'
+export default defineComponent({
+ name: 'DSelectDropdown',
+ props: selectDropdownProps,
+ setup(props) {
+ const select = inject('InjectionKey') as any
+ const {
+ props: selectProps,
+ dropdownRef,
+ visible,
+ selectOptionClick,
+ renderDefaultSlots,
+ renderEmptySlots,
+ selectedIndex,
+ loadMore
+ } = select
+ const { maxHeight } = selectProps
+ return () => {
+ const getLiCls = (item: OptionItem, index: number) => {
+ const { disabledKey } = selectProps
+ return className('devui-dropdown-item', {
+ disabled: disabledKey ? !!item[disabledKey] : false,
+ selected: selectedIndex.value === index
+ })
+ }
+ return (
+
+ )
+ }
+ }
+})
diff --git a/packages/devui-vue/devui/editable-select/src/editable-select-types.ts b/packages/devui-vue/devui/editable-select/src/editable-select-types.ts
index ee9cb2fdf1a6e593cfd4e501f46e96b56bb15034..2fb55f95255922d3d3dcb96d02fcc9d7fbd129fa 100644
--- a/packages/devui-vue/devui/editable-select/src/editable-select-types.ts
+++ b/packages/devui-vue/devui/editable-select/src/editable-select-types.ts
@@ -1,13 +1,23 @@
import type { PropType, ExtractPropTypes } from 'vue'
+type HorizontalConnectionPos = 'left' | 'center' | 'right';
+type VerticalConnectionPos = 'top' | 'center' | 'bottom';
+
+export interface ConnectionPosition {
+ originX: HorizontalConnectionPos
+ originY: VerticalConnectionPos
+ overlayX: HorizontalConnectionPos
+ overlayY: VerticalConnectionPos
+}
export interface OptionItem {
name: string
[key: string]: any
}
export type Options = Array
export const editableSelectProps = {
- /* test: {
- type: Object as PropType<{ xxx: xxx }>
- } */
+ appendToBody: {
+ type: Boolean,
+ default: false
+ },
modelValue: {
type: [String, Number] as PropType
},
@@ -16,7 +26,8 @@ export const editableSelectProps = {
default: () => []
},
width: {
- type: Number
+ type: Number,
+ default: 450
},
maxHeight: {
type: Number
@@ -47,7 +58,17 @@ export const editableSelectProps = {
},
searchFn: {
type: Function as PropType<(term: string) => Array>,
+ },
+ loadMore: {
+ type: Function as PropType<() => Array>
}
} as const
+export const selectDropdownProps = {
+ options: {
+ type: Array as PropType,
+ default: () => []
+ }
+} as const
export type EditableSelectProps = ExtractPropTypes
+export type SelectDropdownProps = ExtractPropTypes
\ No newline at end of file
diff --git a/packages/devui-vue/devui/editable-select/src/editable-select.scss b/packages/devui-vue/devui/editable-select/src/editable-select.scss
index 78714927a24c4c9db4db8cc193bcd93b0118baae..ae3919734b62fea44062b5b74fbed1b665ad9f9c 100644
--- a/packages/devui-vue/devui/editable-select/src/editable-select.scss
+++ b/packages/devui-vue/devui/editable-select/src/editable-select.scss
@@ -1,64 +1,134 @@
@import '../../style/theme/color';
@import '../../style/core/animation';
+.devui-editable-select {
+ .devui-form-group {
+ input::-ms-clear {
+ display: none;
+ }
+
+ ul.devui-list-unstyled {
+ margin: 0;
+ overflow-y: auto;
+ padding: 0;
+ }
+
+ .devui-dropdown-bg {
+ background: $devui-list-item-hover-bg;
+ }
-.devui-form-group {
- input::-ms-clear {
- display: none;
+ .devui-popup-tips {
+ color: $devui-text-weak;
+ padding: 4px 12px;
+ }
+
+ .devui-form-control {
+ outline: none;
+ padding-right: 24px;
+ }
}
- ul.devui-list-unstyled {
- margin: 0;
- overflow-y: auto;
- padding: 0;
+ .devui-select-open {
+ .devui-select-chevron-icon {
+ transform: rotate(180deg);
+
+ svg path {
+ fill: $devui-text-weak;
+ }
+ }
}
- .devui-dropdown-bg {
- background: $devui-list-item-hover-bg;
+ .devui-form-control-feedback {
+ .devui-select-chevron-icon {
+ display: inline-flex;
+ vertical-align: middle;
+ transition: transform $devui-animation-duration-slow $devui-animation-ease-in-out-smooth;
+ }
}
- .devui-popup-tips {
- color: $devui-text-weak;
- padding: 4px 12px;
+ .devui-has-feedback > .devui-form-control-feedback {
+ line-height: 26px;
}
- .devui-form-control {
- outline: none;
- padding-right: 24px;
+ .devui-dropdown-bg.devui-dropdown-bg {
+ background-color: inherit;
+ }
+ // 下拉部分
+ .devui-dropdown-menu {
+ width: 100%;
+ display: block;
+ }
+
+ .devui-dropdown-item {
+ cursor: pointer;
+ display: block;
+ width: 100%;
+ padding: 8px 12px;
+ clear: both;
+ border: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ line-height: 14px;
+ }
+
+ .devui-dropdown-menu {
+ .devui-dropdown-item:not(.disabled) {
+ &.selected {
+ color: $devui-list-item-active-text;
+ background-color: $devui-list-item-active-bg;
+ }
+ }
}
-}
-.devui-select-open {
- .devui-select-chevron-icon {
- transform: rotate(180deg);
+ .devui-no-result-template,
+ .devui-is-searching-template {
+ display: block;
+ width: 100%;
+ padding: 8px 12px;
+ clear: both;
+ border: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ cursor: not-allowed;
+ background-color: $devui-disabled-bg;
+ color: $devui-disabled-text;
+ line-height: 14px;
- svg path {
- fill: $devui-text-weak;
+ &:hover,
+ &:active,
+ &:hover:active {
+ background-color: $devui-unavailable;
}
}
-}
+ // 选项disabled
+ .devui-dropdown-item.disabled,
+ .devui-dropdown-item.disabled:hover {
+ cursor: not-allowed;
+ color: $devui-disabled-text;
+ }
-.devui-form-control-feedback {
- .devui-select-chevron-icon {
- display: inline-flex;
- vertical-align: middle;
- transition: transform $devui-animation-duration-slow $devui-animation-ease-in-out-smooth;
+ ul.devui-list-unstyled {
+ margin: 0;
+ overflow-y: auto;
+ padding: 0;
}
-}
-.devui-has-feedback > .devui-form-control-feedback {
- line-height: 26px;
-}
+ .devui-dropdown-bg {
+ background: $devui-list-item-hover-bg;
+ }
-.devui-dropdown-bg.devui-dropdown-bg {
- background-color: inherit;
+ .devui-popup-tips {
+ color: $devui-text-weak;
+ padding: 4px 12px;
+ }
}
-// 下拉部分
-.devui-editable-select {
+
+.devui-dropdown {
.devui-dropdown-menu {
width: 100%;
display: block;
}
-
.devui-dropdown-item {
cursor: pointer;
display: block;
diff --git a/packages/devui-vue/devui/editable-select/src/editable-select.tsx b/packages/devui-vue/devui/editable-select/src/editable-select.tsx
index c6f095bcaffe92d4fda44dca6a7b3cc66393a181..851816c24a7d2461167380d2fa6181bc317ef063 100644
--- a/packages/devui-vue/devui/editable-select/src/editable-select.tsx
+++ b/packages/devui-vue/devui/editable-select/src/editable-select.tsx
@@ -1,22 +1,78 @@
-import { defineComponent, ref, renderSlot, computed, Transition, watch } from 'vue'
-import { OptionItem, editableSelectProps, EditableSelectProps } from './editable-select-types'
+import {
+ defineComponent,
+ Transition,
+ ref,
+ computed,
+ reactive,
+ toRefs,
+ provide,
+ renderSlot
+} from 'vue'
+import {
+ OptionItem,
+ editableSelectProps,
+ EditableSelectProps,
+ ConnectionPosition
+} from './editable-select-types'
+import SelectDropdown from './components/dropdown'
import './editable-select.scss'
-import { Icon } from '../../icon'
import ClickOutside from '../../shared/devui-directive/clickoutside'
-import { className } from './utils'
import { debounce } from 'lodash'
+import { className } from './utils'
export default defineComponent({
name: 'DEditableSelect',
directives: { ClickOutside },
props: editableSelectProps,
emits: ['update:modelValue'],
setup(props: EditableSelectProps, ctx) {
- const dropdownRef = ref(null)
+ const renderDropdown = (condition: boolean, type: number) => {
+ if (!condition && type === 0) {
+ return (
+
+
+
+ )
+ } else if (condition && type === 1) {
+ return (
+
+
+
+
+
+ )
+ }
+ }
+
+ const renderDefaultSlots = (item) => {
+ return ctx.slots.default ? renderSlot(ctx.slots, 'default', { item }) : item.name
+ }
+
+ const renderEmptySlots = () => {
+ return ctx.slots.empty ? renderSlot(ctx.slots, 'empty') : emptyText.value
+ }
+
+ const origin = ref()
+ const dropdownRef = ref()
const visible = ref(false)
const inputValue = ref('')
- const activeIndex = ref(0)
+ const selectedIndex = ref(0)
const query = ref(props.modelValue)
-
+ const position = reactive({
+ originX: 'left',
+ originY: 'bottom',
+ overlayX: 'left',
+ overlayY: 'top'
+ })
const wait = computed(() => (props.remote ? 300 : 0))
const emptyText = computed(() => {
@@ -62,8 +118,7 @@ export default defineComponent({
})
.filter((item) => item !== null)
})
-
- const findIndex = (o) => {
+ const findIndex = (o: OptionItem) => {
return normalizeOptions.value.findIndex((item) => {
return item.name === o.name
})
@@ -72,6 +127,7 @@ export default defineComponent({
const handleClose = () => {
visible.value = false
}
+
const toggleMenu = () => {
if (!props.disabled) {
visible.value = !visible.value
@@ -105,7 +161,7 @@ export default defineComponent({
e.stopPropagation()
} else {
query.value = item.name
- activeIndex.value = findIndex(item)
+ selectedIndex.value = findIndex(item)
inputValue.value = ''
ctx.emit('update:modelValue', item.name)
}
@@ -115,12 +171,24 @@ export default defineComponent({
if (!props.enableLazyLoad) return
const dropdownVal = dropdownRef.value
if (dropdownVal.clientHeight + dropdownVal.scrollTop >= dropdownVal.scrollHeight) {
- props.remoteMethod(inputValue.value)
+ props.loadMore()
}
}
-
+ provide('InjectionKey', {
+ dropdownRef,
+ props: reactive({
+ ...toRefs(props)
+ }),
+ visible,
+ emptyText,
+ selectedIndex,
+ loadMore,
+ selectOptionClick,
+ renderDefaultSlots,
+ renderEmptySlots
+ })
return () => {
- const selectCls = className('devui-form-group devui-has-feedback', {
+ const selectCls = className('devui-editable-select devui-form-group devui-has-feedback', {
'devui-select-open': visible.value
})
const inputCls = className(
@@ -130,52 +198,19 @@ export default defineComponent({
}
)
- const getLiCls = (item, index) => {
- const { disabledKey } = props
- return className('devui-dropdown-item', {
- disabled: disabledKey ? !!item[disabledKey] : false,
- selected: activeIndex.value === index
- })
- }
-
return (
-
-
-
-
-
+ <>
+
+
+
+
+
+
-
-
-
-
-
+ {renderDropdown(props.appendToBody, 0)}
-
+ {renderDropdown(props.appendToBody, 1)}
+ >
)
}
}
diff --git a/packages/devui-vue/docs/components/editable-select/index.md b/packages/devui-vue/docs/components/editable-select/index.md
index 7dc7001d8d5da15d2c31512aa210dd81fccc30ef..acb6ad98af762a2aee9e4e6ef8aff89bffd1bf02 100644
--- a/packages/devui-vue/docs/components/editable-select/index.md
+++ b/packages/devui-vue/docs/components/editable-select/index.md
@@ -14,9 +14,12 @@
```vue
-
-
-
+
@@ -251,24 +230,25 @@ export default defineComponent({
d-editable-select 参数
-| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置 |
-| -------------- | ------------- | ----- | -------------------------------------------------- | ---------------------------- | -------- |
-| appendToBody | boolean | false | 可选,下拉是否 appendToBody | [基本用法](#基本用法) | |
-| width | number | -- | 可选,控制下拉框宽度,搭配 appendToBody 使用(px) | [基本用法](#基本用法) | |
-| v-model | string/number | -- | 绑定值 | [基本用法](#基本用法) | |
-| options | Array | -- | 必选,数据列表 | [基本用法](#基本用法) | |
+| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置 |
+| -------------- | ------------- | ----- | -------------------------------------------------- | ----------------------------- | -------- |
+| appendToBody | boolean | false | 可选,下拉是否 appendToBody | [基本用法](#基本用法) | |
+| width | number | -- | 可选,控制下拉框宽度,搭配 appendToBody 使用(px) | [基本用法](#基本用法) | |
+| v-model | string/number | -- | 绑定值 | [基本用法](#基本用法) | |
+| options | Array | -- | 必选,数据列表 | [基本用法](#基本用法) | |
| disabled | boolean | false | 可选,值为 true 禁用下拉框 | [设置禁用选项](#设置禁用选项) | |
| disabledKey | string | -- | 可选,设置禁用选项的 Key 值 | [设置禁用选项](#设置禁用选项) | |
-| maxHeight | number | -- | 可选,下拉菜单的最大高度(px) | [基本用法](#基本用法) | |
-| remote | boolean | false | 可选,远程搜索 | | |
-| enableLazyLoad | boolean | false | 可选,是否允许懒加载 | [懒加载](#懒加载) | |
+| maxHeight | number | -- | 可选,下拉菜单的最大高度(px) | [基本用法](#基本用法) | |
+| remote | boolean | false | 可选,远程搜索 | | |
+| enableLazyLoad | boolean | false | 可选,是否允许懒加载 | [懒加载](#懒加载) | |
d-editable-select 事件
| 事件 | 类型 | 说明 | 跳转 Demo |
| ------------ | ---- | ------------------ | -------------------------------------------------------- |
| filterMethod | | 自定义筛选函数 | |
-| remoteMethod | | 远程搜索对应的函数 | [异步获取数据并设置匹配方法](#异步获取数据并设置匹配方法) |
+| remoteMethod | | 远程搜索对应的函数 | [异步获取数据并设置匹配方法](异步获取数据并设置匹配方法) |
+| loadMore | | 懒加载 | [懒加载](懒加载) |
d-editable-select 插槽