From 69e1fb9fa456dd226ff6cb277e2e9a9a819a19af Mon Sep 17 00:00:00 2001 From: chenxi_24 Date: Sun, 28 Nov 2021 09:46:32 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(editable-select):=E7=BB=99=E5=8F=AF?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E4=B8=8B=E6=8B=89=E6=A1=86=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?appendToBody?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../devui-vue/devui/editable-select/index.ts | 7 +- .../src/components/dropdown.tsx | 53 +++++++ .../src/editable-select-types.ts | 29 +++- .../editable-select/src/editable-select.scss | 142 ++++++++++++----- .../editable-select/src/editable-select.tsx | 143 +++++++++++------- .../docs/components/editable-select/index.md | 98 +++++------- 6 files changed, 317 insertions(+), 155 deletions(-) create mode 100644 packages/devui-vue/devui/editable-select/src/components/dropdown.tsx diff --git a/packages/devui-vue/devui/editable-select/index.ts b/packages/devui-vue/devui/editable-select/index.ts index 41172831..01d59699 100644 --- a/packages/devui-vue/devui/editable-select/index.ts +++ b/packages/devui-vue/devui/editable-select/index.ts @@ -1,7 +1,7 @@ import type { App } from 'vue' import EditableSelect from './src/editable-select' - -EditableSelect.install = function(app: App): void { +import EselectDropdown from './src/components/dropdown' +EditableSelect.install = function (app: App): void { app.component(EditableSelect.name, EditableSelect) } @@ -12,6 +12,7 @@ export default { category: '数据录入', status: undefined, // TODO: 组件若开发完成则填入"已完成",并删除该注释 install(app: App): void { - app.use(EditableSelect as any) + app.use(EditableSelect as any) + app.use(EselectDropdown 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 00000000..711f94ee --- /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 ee9cb2fd..2fb55f95 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 78714927..ae391973 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 c6f095bc..2f812a07 100644 --- a/packages/devui-vue/devui/editable-select/src/editable-select.tsx +++ b/packages/devui-vue/devui/editable-select/src/editable-select.tsx @@ -1,7 +1,23 @@ -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 { FlexibleOverlay } from '../../overlay' import ClickOutside from '../../shared/devui-directive/clickoutside' import { className } from './utils' import { debounce } from 'lodash' @@ -11,12 +27,53 @@ export default defineComponent({ 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(() => { @@ -63,7 +120,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 +129,7 @@ export default defineComponent({ const handleClose = () => { visible.value = false } + const toggleMenu = () => { if (!props.disabled) { visible.value = !visible.value @@ -105,7 +163,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 +173,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 +200,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 ( -
- -
- + ) } } + const renderDefaultSlots = (item) => { return ctx.slots.default ? renderSlot(ctx.slots, 'default', { item }) : item.name } @@ -119,7 +118,6 @@ export default defineComponent({ }) .filter((item) => item !== null) }) - const findIndex = (o: OptionItem) => { return normalizeOptions.value.findIndex((item) => { return item.name === o.name @@ -129,7 +127,7 @@ export default defineComponent({ const handleClose = () => { visible.value = false } - + const toggleMenu = () => { if (!props.disabled) { visible.value = !visible.value @@ -206,7 +204,7 @@ export default defineComponent({ {renderDropdown(props.appendToBody, 0)} diff --git a/packages/devui-vue/docs/components/editable-select/index.md b/packages/devui-vue/docs/components/editable-select/index.md index 186e73b2..acb6ad98 100644 --- a/packages/devui-vue/docs/components/editable-select/index.md +++ b/packages/devui-vue/docs/components/editable-select/index.md @@ -230,29 +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 | | 自定义筛选函数 | | -<<<<<<< HEAD | remoteMethod | | 远程搜索对应的函数 | [异步获取数据并设置匹配方法](异步获取数据并设置匹配方法) | | loadMore | | 懒加载 | [懒加载](懒加载) | -======= -| remoteMethod | | 远程搜索对应的函数 | [异步获取数据并设置匹配方法](#异步获取数据并设置匹配方法) | ->>>>>>> 93f7035399b1efbc707938bffce4bf956b1da77a d-editable-select 插槽 -- Gitee From fc20ab589a07dd762536eae4c6efa38bb81b2c72 Mon Sep 17 00:00:00 2001 From: chenxi24 Date: Tue, 30 Nov 2021 17:48:52 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix=20:=E7=A7=BB=E9=99=A4EditableSelect?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E5=86=85dropdown=E7=9A=84install=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/devui-vue/devui/editable-select/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/devui-vue/devui/editable-select/index.ts b/packages/devui-vue/devui/editable-select/index.ts index 01d59699..4fa0d405 100644 --- a/packages/devui-vue/devui/editable-select/index.ts +++ b/packages/devui-vue/devui/editable-select/index.ts @@ -1,6 +1,5 @@ import type { App } from 'vue' import EditableSelect from './src/editable-select' -import EselectDropdown from './src/components/dropdown' EditableSelect.install = function (app: App): void { app.component(EditableSelect.name, EditableSelect) } @@ -13,6 +12,5 @@ export default { status: undefined, // TODO: 组件若开发完成则填入"已完成",并删除该注释 install(app: App): void { app.use(EditableSelect as any) - app.use(EselectDropdown as any) } } -- Gitee