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 ( -
- -