diff --git a/packages/devui-vue/devui/editable-select/__tests__/editable-select.spec.ts b/packages/devui-vue/devui/editable-select/__tests__/editable-select.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..aa694a308b703f06ced6875e435057fdc97350f4 --- /dev/null +++ b/packages/devui-vue/devui/editable-select/__tests__/editable-select.spec.ts @@ -0,0 +1,8 @@ +import { mount } from '@vue/test-utils'; +import { EditableSelect } from '../index'; + +describe('editable-select test', () => { + it('editable-select init render', async () => { + // todo + }) +}) diff --git a/packages/devui-vue/devui/editable-select/index.ts b/packages/devui-vue/devui/editable-select/index.ts index bd343fc7d8250ac80bec524a8ac1ae4cd307c4fc..411728313716c4f53e85b39ecb87f7dc765ab8bf 100644 --- a/packages/devui-vue/devui/editable-select/index.ts +++ b/packages/devui-vue/devui/editable-select/index.ts @@ -1,20 +1,17 @@ import type { App } from 'vue' import EditableSelect from './src/editable-select' -import EditableSelectOption from './src/components/option' -EditableSelect.install = function (app: App): void { +EditableSelect.install = function(app: App): void { app.component(EditableSelect.name, EditableSelect) - app.component(EditableSelectOption.name, EditableSelectOption) - } -export { EditableSelect, EditableSelectOption } +export { EditableSelect } export default { title: 'EditableSelect 可输入下拉选择框', category: '数据录入', - status: '10%', + 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/option/index.tsx b/packages/devui-vue/devui/editable-select/src/components/option/index.tsx deleted file mode 100644 index 1dfd5c5c9dcf4814d07c5772d37d4ff5c4cce0f3..0000000000000000000000000000000000000000 --- a/packages/devui-vue/devui/editable-select/src/components/option/index.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { defineComponent, renderSlot, getCurrentInstance, inject } from 'vue' -import { className } from '../../utils/index' -import { selectKey } from '../../editable-select-types' -export default defineComponent({ - name: 'DEditableSelectOption', - props: { - label: { - type: [String, Number], - }, - disabled: { - type: Boolean, - default: false, - }, - }, - setup(props, ctx) { - const optionsClassName = className('devui-dropdown-item', { - disabled: props.disabled, - }) - const instance = getCurrentInstance() - - const select = inject(selectKey) - - const selectOptionClick = () => { - if (!props.disabled) { - select.handleOptionSelect(instance) - } - } - return () => { - return ( -
  • - {props.label ? props.label : renderSlot(ctx.slots, 'default')} -
  • - ) - } - }, -}) 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 cf83a584267d36446362cd7471f8bbf4aba4cdf1..4da866665772dc30d0ab009d275ba650367315d8 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,56 +1,49 @@ -import type { ExtractPropTypes, Ref, PropType, InjectionKey } from 'vue' -export type ModelValue = number | string | Array -// porps +import type { PropType, ExtractPropTypes } from 'vue' +export interface OptionItem { + name: string + [key: string]: any +} +export type Options = Array export const editableSelectProps = { /* test: { type: Object as PropType<{ xxx: xxx }> } */ - width: { - type: Number, - default: 450 + modelValue: { + type: [String, Number] as PropType }, - appendToBody: { - type: Boolean, - default: true, + options: { + type: Array as PropType, + default: () => [] + }, + width: { + type: Number }, maxHeight: { - type: Number, - default: 300 + type: Number }, disabled: { type: Boolean, default: false }, - modelValue: { - type: [String, Number, Array] as PropType + disabledKey: { + type: String, + }, + remote: { + type: Boolean, + default: false }, - 'onUpdate:modelValue': { - type: Function as PropType<(val: ModelValue) => void>, + loading: { + type: Boolean }, + remoteMethod: { + type: Function as PropType<(inputValue: string) => Array> + }, + filterMethod: { + type: Function as PropType<(inputValue: string) => Array> + }, + searchFn: { + type: Function as PropType<(term: string) => Array>, + } } as const export type EditableSelectProps = ExtractPropTypes -type HorizontalConnectionPos = 'left' | 'center' | 'right'; -type VerticalConnectionPos = 'top' | 'center' | 'bottom'; - -interface ConnectionPosition { - originX: HorizontalConnectionPos - originY: VerticalConnectionPos - overlayX: HorizontalConnectionPos - overlayY: VerticalConnectionPos -} - -export interface SelectStatesReturnType { - visible: boolean - origin: Ref - position: ConnectionPosition -} -export interface SelectReturnType { - toggleMenu: () => void - handleOptionSelect: (vm: unknown) => void -} - -export const selectKey = 'DSelect' as unknown as InjectionKey -export interface SelectContext { - handleOptionSelect(vm: unknown): void -} 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 94567edcb2db2f0608d459a8db2d5e94ee04e624..78714927a24c4c9db4db8cc193bcd93b0118baae 100644 --- a/packages/devui-vue/devui/editable-select/src/editable-select.scss +++ b/packages/devui-vue/devui/editable-select/src/editable-select.scss @@ -32,7 +32,7 @@ transform: rotate(180deg); svg path { - fill: $devui-text-weak; // TODO: Color-Question + fill: $devui-text-weak; } } } 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 f76b6e430d5eda57392572636429a9d1c7d3c077..671e6eb9d8f50f288a301937926bc30d9728323b 100644 --- a/packages/devui-vue/devui/editable-select/src/editable-select.tsx +++ b/packages/devui-vue/devui/editable-select/src/editable-select.tsx @@ -1,80 +1,169 @@ -import './editable-select.scss' -import { - defineComponent, - reactive, - toRefs, - renderSlot, - provide, - SetupContext, -} from 'vue' +import { defineComponent, ref, renderSlot, computed, Transition } from "vue" import { + OptionItem, editableSelectProps, EditableSelectProps, - selectKey, -} from './editable-select-types' -import { Icon } from '../../icon' -import { FlexibleOverlay } from '../../overlay' -import { useSelectStates, useSelect } from './hooks/use-select' -import { className } from './utils/index' +} from "./editable-select-types" +import "./editable-select.scss" +import { Icon } from "../../icon" +import ClickOutside from "../../shared/devui-directive/clickoutside" +import { className } from "./utils" +import { debounce } from "lodash" export default defineComponent({ - name: 'DEditableSelect', + name: "DEditableSelect", + directives: { ClickOutside }, props: editableSelectProps, - emits: ['update:modelValue'], + emits: ["update:modelValue"], + setup(props: EditableSelectProps, ctx) { + const inputCls = className( + "devui-form-control devui-dropdown-origin devui-dropdown-origin-open", + { + disabled: props.disabled, + } + ) - setup(props: EditableSelectProps, ctx: SetupContext) { - const states = useSelectStates() - const { origin, visible } = toRefs(states) + const getLiCls = (item) => { + const { disabledKey } = props + return className("devui-dropdown-item", { + disabled: disabledKey ? !!item[disabledKey] : false, + }) + } - const inputCls = className('devui-form-control devui-dropdown-origin', { - disabled: props.disabled, - }) - const { toggleMenu, handleOptionSelect } = useSelect(props, ctx, states) + const visible = ref(false) + const inputValue = ref("") + const query = ref(props.modelValue) - provide( - selectKey, - reactive({ - handleOptionSelect, + const wait = computed(() => (props.remote ? 300 : 0)) + + const emptyText = computed(() => { + const options = filteredOptions.value + if (!props.remote && inputValue.value && options.length === 0) { + return "没有相关记录" + } + if (options.length === 0) { + return "没有数据" + } + return null + }) + const normalizeOptions = computed(() => { + let options: OptionItem + const { disabledKey } = props + disabledKey ? disabledKey : "disabled" + return props.options.map((item) => { + if (typeof item !== "object") { + options = { + name: item, + } + return options + } + return item }) - ) + }) + + const filteredOptions = computed(() => { + const isValidOption = (o: OptionItem): boolean => { + const query = inputValue.value + const containsQueryString = query ? o.name.includes(query) : true + return containsQueryString + } + return normalizeOptions.value + .map((item) => { + if (props.remote || isValidOption(item)) { + return item + } + return null + }) + .filter((item) => item !== null) + }) + + const handleClose = () => { + visible.value = false + } + const toggleMenu = () => { + if (!props.disabled) { + visible.value = !visible.value + } + } + const onInputChange = () => { + if (props.filterMethod) { + props.filterMethod(inputValue.value) + } else if (props.remote) { + props.remoteMethod(inputValue.value) + } + } + const debouncedOnInputChange = debounce(onInputChange, wait.value) + + const handleInput = (event) => { + const value = event.target.value + inputValue.value = value + query.value = value + if (props.remote) { + debouncedOnInputChange() + } else { + onInputChange() + } + } + const selectOptionClick = (e, item) => { + const { disabledKey } = props + if (disabledKey && item[disabledKey]) { + e.stopPropagation() + } else { + query.value = item.name + ctx.emit("update:modelValue", item.name) + } + } return () => { return ( - <> -
    - -