diff --git a/packages/devui-vue/devui/auto-complete/__tests__/auto-complete.spec.ts b/packages/devui-vue/devui/auto-complete/__tests__/auto-complete.spec.ts index 3df59b01c4..bfc33cdfa7 100644 --- a/packages/devui-vue/devui/auto-complete/__tests__/auto-complete.spec.ts +++ b/packages/devui-vue/devui/auto-complete/__tests__/auto-complete.spec.ts @@ -1,7 +1,6 @@ import { mount } from '@vue/test-utils'; import { nextTick, ref } from 'vue'; import DAutoComplete from '../src/auto-complete'; - // delay api const wait = (delay = 300) => new Promise(resolve => setTimeout(() => resolve(true), delay)); @@ -13,7 +12,7 @@ describe('auto-complete', () => { `, setup() { @@ -71,7 +70,7 @@ describe('auto-complete', () => { :source="source" v-model="value" :disabled="isDisabled" - /> + /> {{ isDisabled ? 'Enable AutoComplete' : 'Disable AutoComplete' }} `, @@ -128,8 +127,8 @@ describe('auto-complete', () => { disabledKey="disabled" isSearching :formatter="formatter" - > - + > + {{slotProps}} @@ -234,13 +233,13 @@ describe('auto-complete', () => { - + > + 第{{slotProps.index}}项: {{slotProps.item}} - + {{slotProps}} @@ -345,13 +344,16 @@ describe('auto-complete', () => { expect(selectValueCB).toHaveBeenCalledTimes(1); }); it('allowEmptyValueSearch ', async () => { + const div = document.createElement('div'); + div.id="app"; + document.body.appendChild(div); const wrapper = mount({ components: {'d-auto-complete': DAutoComplete }, template: ` `, setup() { @@ -377,23 +379,25 @@ describe('auto-complete', () => { expect(input.element.value).toBe(''); await input.trigger('focus'); await nextTick(); + await input.setValue(''); + await wait(300); + await nextTick(); expect(wrapper.find('ul').element.childElementCount).toBe(5); }); - - it('appendToBody & appendToBodyDirections', async () => { + it('appendToBody & position', async () => { const wrapper = mount({ components: {'d-auto-complete': DAutoComplete }, template: ` `, setup() { const value = ref(''); - const allowEmptyValueSearch = ref(true); + const appendToBody = ref(true); const source = [ 'CC#', 'C', @@ -401,17 +405,12 @@ describe('auto-complete', () => { 'CPython', 'CoffeeScript', ]; - const appendToBodyDirections = ref({ - originX: 'left', - originY: 'bottom', - overlayX: 'left', - overlayY: 'top', - }); + const position = ref(['bottom']); return { value, source, - allowEmptyValueSearch, - appendToBodyDirections + appendToBody, + position }; } }); @@ -424,10 +423,20 @@ describe('auto-complete', () => { await nextTick(); await wait(300); await nextTick(); - expect(wrapper.find('ul').element.childElementCount).toBe(5); - expect(wrapper.find('.selected').element.innerHTML).toBe('CC#'); + expect(wrapper.find('.devui-select-open').exists()).toBe(true); + const ul = document.querySelector('.devui-list-unstyled'); + let lis = 0; + if(ul&&ul.getElementsByTagName('li')){ + lis=ul.getElementsByTagName('li').length; + } + expect(lis).toBe(5); + const li_ed = document.querySelector('.selected'); + let li_text = ''; + if(li_ed&&li_ed.getElementsByTagName('li')){ + li_text=li_ed.innerHTML; + } + expect(li_text).toBe('CC#'); }); - it('latestSource',async () => { const wrapper = mount({ components: {'d-auto-complete': DAutoComplete }, @@ -530,6 +539,7 @@ describe('auto-complete', () => { expect(wrapper.find('.devui-auto-complete').exists()).toBe(true); const input = wrapper.find('input'); expect(input.element.value).toBe(''); + await input.trigger('click'); await input.setValue('c'); await nextTick(); expect(wrapper.find('.devui-dropdown-menu').exists()).toBe(true); diff --git a/packages/devui-vue/devui/auto-complete/index.ts b/packages/devui-vue/devui/auto-complete/index.ts index 3924ccab54..037667ea91 100644 --- a/packages/devui-vue/devui/auto-complete/index.ts +++ b/packages/devui-vue/devui/auto-complete/index.ts @@ -1,9 +1,7 @@ import type { App } from 'vue'; import AutoComplete from './src/auto-complete'; -AutoComplete.install = function(app: App): void { - app.component(AutoComplete.name, AutoComplete); -}; +export * from './src/auto-complete-types'; export { AutoComplete }; @@ -12,6 +10,6 @@ export default { category: '数据录入', status: '100%', install(app: App): void { - app.use(AutoComplete as any); + app.component(AutoComplete.name, AutoComplete); } }; diff --git a/packages/devui-vue/devui/auto-complete/src/auto-complete-types.ts b/packages/devui-vue/devui/auto-complete/src/auto-complete-types.ts index 038f8f85c6..b5144b9d42 100644 --- a/packages/devui-vue/devui/auto-complete/src/auto-complete-types.ts +++ b/packages/devui-vue/devui/auto-complete/src/auto-complete-types.ts @@ -1,6 +1,16 @@ import type { PropType, ExtractPropTypes, InjectionKey, SetupContext, Ref } from 'vue'; -const defaultFormatter = (item: any) => (item ? item.label || item.toString() : ''); -const defaultValueParse = (item: any) => item; +export interface SourceItemObj { + label: string; + disabled: boolean; + [propName: string]: unknown; +} +const defaultFormatter = (item: string | SourceItemObj) => { + if(typeof item === 'string'){ + return item; + } + return item!==null ? item.label || item.toString() : ''; +}; +const defaultValueParse = (item: string | SourceItemObj) => item; export type Placement = | 'top' | 'right' @@ -14,122 +24,132 @@ export type Placement = | 'bottom-end' | 'left-start' | 'left-end'; + + +export type SourceType = Array| Array; + export const autoCompleteProps = { modelValue: { type: String, - default:'' + default: '' }, - source:{ - type :Array, - default:null + source: { + type : Array as PropType, + default: null }, - allowEmptyValueSearch:{ - type:Boolean, - default:false + allowEmptyValueSearch: { + type: Boolean, + default: false }, - position :{ + appendToBody:{ + type: Boolean, + default: false + }, + position : { type: Array as PropType>, default: ['bottom-end'], }, - disabled:{ - type:Boolean, - default:false + disabled: { + type: Boolean, + default: false }, - delay:{ - type:Number, - default:300 + delay: { + type: Number, + default: 300 }, - disabledKey:{ - type:String, - default:null + disabledKey: { + type: String, + default: null }, formatter: { - type:Function as PropType<(item: any) => string>, - default:defaultFormatter + type: Function as PropType<(item: string | SourceItemObj) => string>, + default: defaultFormatter }, isSearching: { - type:Boolean, - default:false + type: Boolean, + default: false }, - sceneType:{ - type:String, - default:null + sceneType: { + type: String, + default: null }, - searchFn:{ - type:Function as PropType<(term: string) => Array>, - default:null + searchFn: { + type: Function as PropType<(term: string) => SourceType>, + default: null }, - tipsText:{ - type:String, + tipsText: { + type: String, default:'最近输入' }, - latestSource:{ - type:Array, - default:null + latestSource: { + type: Array, + default: null }, - valueParser:{ - type:Function as PropType<(item: any) => any>, - default:defaultValueParse + valueParser: { + type: Function as PropType<(item: string | SourceItemObj) => string>, + default: defaultValueParse }, enableLazyLoad: { - type:Boolean, - default:false + type: Boolean, + default: false }, - width:{ + width: { type: Number, - default:400 + default: 400 }, - showAnimation:{ - type:Boolean, - default:true + showAnimation: { + type: Boolean, + default: true }, - maxHeight:{ - type:Number, - default:300 + maxHeight: { + type: Number, + default: 300 }, - transInputFocusEmit:{ - type:Function as PropType<() => void>, - default:null + transInputFocusEmit: { + type: Function as PropType<() => void>, + default: null }, selectValue:{ - type:Function as PropType<(val: any) => any>, - default:null + type: Function as PropType<(val: string) => string>, + default: null }, - loadMore:{ - type:Function as PropType<() => void>, - default:null + loadMore: { + type: Function as PropType<() => void>, + default: null } } as const; export type AutoCompleteProps = ExtractPropTypes; export interface AutoCompleteRootType { - ctx: SetupContext; + ctx: SetupContext; props: AutoCompleteProps; } -export type SearchFnType = (term: string) => Array; -export type FormatterType = (item: any) => string; -export type DefaultFuncType = (arg?: any) => any; -export type HandleSearch = (term?: string | string,enableLazyLoad?: boolean) => void; -export type RecentlyFocus = (latestSource: Array) => void; -export type InputDebounceCb = (...rest: any) => Promise; -export type TransInputFocusEmit = (any?: any) => void; -export type SelectOptionClick = (any?: any) => void; +export type SearchFnType = (term: string) => SourceType; +export type FormatterType = (item: string | SourceItemObj) => string; +export type DefaultFuncType = () => void; +export type HandleSearch = (term: string,enableLazyLoad?: boolean) => void; +export type RecentlyFocus = (latestSource: SourceType) => void; +export type InputDebounceCb = (value: string) => void; +export type TransInputFocusEmit = () => unknown; +export type SelectOptionClick = (item: string | SourceItemObj) => void; +export type SelectValueType = (value: string) => unknown; // 弹出选择框参数 export type DropdownProps = { props: AutoCompleteProps; - searchList: Ref; + searchList: Ref; searchStatus?: Ref; showNoResultItemTemplate: Ref; term?: string; visible: Ref; selectedIndex: Ref; - selectOptionClick: HandleSearch; - dropDownRef: Ref; + selectOptionClick: SelectOptionClick; + dropDownRef: Ref; showLoading: Ref; - loadMore: (arg?: any) => void; - latestSource: Ref; + loadMore: () => void; + latestSource: Ref>; modelValue: Ref; hoverIndex: Ref; + valueParser: () => void; }; export const DropdownPropsKey: InjectionKey=Symbol('DropdownPropsKey'); diff --git a/packages/devui-vue/devui/auto-complete/src/auto-complete.scss b/packages/devui-vue/devui/auto-complete/src/auto-complete.scss index 88eba5ad52..22bfb62f67 100644 --- a/packages/devui-vue/devui/auto-complete/src/auto-complete.scss +++ b/packages/devui-vue/devui/auto-complete/src/auto-complete.scss @@ -52,9 +52,10 @@ .devui-dropdown-menu { .devui-dropdown-item:not(.disabled) { - &.selected { - color: $devui-list-item-active-text; - background-color: $devui-list-item-active-bg; + &.selected, + &.selected &:hover { + color: $devui-list-item-active-text !important; + background-color: $devui-list-item-active-bg !important; } } } diff --git a/packages/devui-vue/devui/auto-complete/src/auto-complete.tsx b/packages/devui-vue/devui/auto-complete/src/auto-complete.tsx index feea055dac..a62dc6b4a8 100644 --- a/packages/devui-vue/devui/auto-complete/src/auto-complete.tsx +++ b/packages/devui-vue/devui/auto-complete/src/auto-complete.tsx @@ -10,7 +10,6 @@ import './auto-complete.scss'; import DAutoCompleteDropdown from './components/dropdown'; import ClickOutside from '../../shared/devui-directive/clickoutside'; import { FlexibleOverlay } from '../../overlay/src/flexible-overlay'; - export default defineComponent({ name: 'DAutoComplete', directives: { ClickOutside }, @@ -23,6 +22,7 @@ export default defineComponent({ width, delay, allowEmptyValueSearch, + appendToBody, formatter, transInputFocusEmit, selectValue, @@ -31,6 +31,7 @@ export default defineComponent({ position, latestSource, showAnimation, + valueParser } = toRefs(props); const { handleSearch, searchList, showNoResultItemTemplate, recentlyFocus } = useSearchFn( @@ -80,14 +81,33 @@ export default defineComponent({ modelValue, showNoResultItemTemplate: showNoResultItemTemplate, hoverIndex: hoverIndex, + valueParser }); const origin = ref(); const renderDropdown = () => { - return ( - + if(appendToBody.value){ + return ( + + + + + {customRenderSolts()} + + + + + ); + }else{ + return ( - + - - ); + ); + } + }; return () => { return ( diff --git a/packages/devui-vue/devui/auto-complete/src/components/dropdown.tsx b/packages/devui-vue/devui/auto-complete/src/components/dropdown.tsx index 9aa123a637..fbe952842e 100644 --- a/packages/devui-vue/devui/auto-complete/src/components/dropdown.tsx +++ b/packages/devui-vue/devui/auto-complete/src/components/dropdown.tsx @@ -1,5 +1,5 @@ import { defineComponent, inject } from 'vue'; -import { DropdownProps, DropdownPropsKey } from '../auto-complete-types'; +import { DropdownProps, DropdownPropsKey, SourceItemObj } from '../auto-complete-types'; import dLoading from '../../../loading/src/directive'; export default defineComponent({ name: 'DAutoCompleteDropdown', @@ -18,7 +18,8 @@ export default defineComponent({ showNoResultItemTemplate, latestSource, modelValue, - hoverIndex + hoverIndex, + valueParser } = propsData; const { disabled, @@ -28,8 +29,11 @@ export default defineComponent({ isSearching, } = propsData.props; - const onSelect =(item: any)=>{ - if(item[disabledKey]){return;} + const onSelect =(item: string|SourceItemObj)=>{ + item = valueParser.value(item); + if(typeof item === 'object'&&item[disabledKey]){ + return; + } selectOptionClick(item); }; return () => { @@ -79,7 +83,7 @@ export default defineComponent({ &&!searchStatus?.value &&searchList!=null &&searchList.value.length>0 - &&searchList.value.map((item: { [x: string]: any },index: number)=>{ + &&searchList.value.map((item,index)=>{ return ( onSelect(item)} @@ -87,7 +91,7 @@ export default defineComponent({ 'devui-dropdown-item', selectedIndex.value===index &&'selected', - {'disabled': disabledKey && item[disabledKey]}, + {'disabled': disabledKey &&typeof item=== 'object' && item[disabledKey]}, {'devui-dropdown-bg': hoverIndex.value=== index}, ]} title={formatter(item)} diff --git a/packages/devui-vue/devui/auto-complete/src/composables/use-custom-template.ts b/packages/devui-vue/devui/auto-complete/src/composables/use-custom-template.ts index 0b83da0f5f..65a13dc6f8 100644 --- a/packages/devui-vue/devui/auto-complete/src/composables/use-custom-template.ts +++ b/packages/devui-vue/devui/auto-complete/src/composables/use-custom-template.ts @@ -1,11 +1,14 @@ import { Ref, SetupContext } from 'vue'; +import { SourceItemObj } from '../auto-complete-types'; type CustomRenderSoltsType = { - itemTemplate?: (item: any, index: number) => any; - noResultItemTemplate?: () => any; - searchingTemplate?: () => any; + itemTemplate?: (item: string | SourceItemObj, index: number) => unknown; + noResultItemTemplate?: () => unknown; + searchingTemplate?: () => unknown; }; -export default function useCustomTemplate(ctx: SetupContext,modelValue: Ref): any { - const itemTemplate = (item: any, index: number) => { +export default function useCustomTemplate(ctx: SetupContext,modelValue: Ref): { + customRenderSolts: () => CustomRenderSoltsType; +} { + const itemTemplate = (item: string | SourceItemObj, index: number) =>{ const arr = { item, index }; if (ctx.slots.item) { return ctx.slots.item(arr); diff --git a/packages/devui-vue/devui/auto-complete/src/composables/use-input-handle.ts b/packages/devui-vue/devui/auto-complete/src/composables/use-input-handle.ts index 1cecc8918a..4f08eb733e 100644 --- a/packages/devui-vue/devui/auto-complete/src/composables/use-input-handle.ts +++ b/packages/devui-vue/devui/auto-complete/src/composables/use-input-handle.ts @@ -1,8 +1,8 @@ import { ref, Ref, SetupContext } from 'vue'; -import {HandleSearch,RecentlyFocus,InputDebounceCb,TransInputFocusEmit} from '../auto-complete-types'; +import {HandleSearch,RecentlyFocus,InputDebounceCb,TransInputFocusEmit, SourceType,SourceItemObj} from '../auto-complete-types'; export default function useInputHandle( ctx: SetupContext, - searchList: Ref, + searchList: Ref, showNoResultItemTemplate: Ref, modelValue: Ref, disabled: Ref, @@ -10,25 +10,33 @@ export default function useInputHandle( handleSearch: HandleSearch, transInputFocusEmit: Ref, recentlyFocus: RecentlyFocus, - latestSource: Ref> -): any { + latestSource: Ref> +): { + handleClose: () => void; + toggleMenu: () => void; + onInput: (e: Event) => void; + onFocus: () => void; + inputRef: Ref; + visible: Ref; + searchStatus: Ref; + } { const visible = ref(false); const inputRef = ref(); const searchStatus = ref(false); const debounce =(cb: InputDebounceCb,time: number) =>{ let timer: NodeJS.Timeout | null; - return (...args: any)=>{ + return (arg: string)=>{ if(timer){ clearTimeout(timer); } timer = setTimeout(async ()=>{ searchStatus.value=true; - await cb(...args); + await cb(arg); searchStatus.value=false; },time); }; }; - const onInputCb = async(value: string)=>{ + const onInputCb =async (value: string)=>{ await handleSearch(value); visible.value = true; }; @@ -42,7 +50,7 @@ export default function useInputHandle( }; const onFocus =() => { handleSearch(modelValue.value); - recentlyFocus(latestSource.value); + recentlyFocus(latestSource?.value); transInputFocusEmit.value && transInputFocusEmit.value(); }; const handleClose = ()=>{ diff --git a/packages/devui-vue/devui/auto-complete/src/composables/use-keyboard-select.ts b/packages/devui-vue/devui/auto-complete/src/composables/use-keyboard-select.ts index 3ddc8abd0a..af71be6d9f 100644 --- a/packages/devui-vue/devui/auto-complete/src/composables/use-keyboard-select.ts +++ b/packages/devui-vue/devui/auto-complete/src/composables/use-keyboard-select.ts @@ -1,7 +1,19 @@ import { nextTick, ref, Ref } from 'vue'; -import { DefaultFuncType, SelectOptionClick } from '../auto-complete-types'; +import { DefaultFuncType, SelectOptionClick,SourceType } from '../auto-complete-types'; -export default function useKeyBoardHandle(dropDownRef: Ref, visible: Ref, searchList: Ref>, selectedIndex: Ref, searchStatus: Ref, showNoResultItemTemplate: Ref, selectOptionClick: SelectOptionClick, handleClose: DefaultFuncType): any { +export default function useKeyBoardHandle( + dropDownRef: Ref, + visible: Ref, + searchList: Ref, + selectedIndex: Ref, + searchStatus: Ref, + showNoResultItemTemplate: Ref, + selectOptionClick: SelectOptionClick, + handleClose: DefaultFuncType +): { + hoverIndex: Ref; + handlekeyDown: (e: KeyboardEvent) => void; + } { const hoverIndex = ref(selectedIndex.value??0); const scrollToActive = (index: number) => { const ul = dropDownRef.value; diff --git a/packages/devui-vue/devui/auto-complete/src/composables/use-lazy-handle.ts b/packages/devui-vue/devui/auto-complete/src/composables/use-lazy-handle.ts index 445f3a66a8..c6d3a0bfa5 100644 --- a/packages/devui-vue/devui/auto-complete/src/composables/use-lazy-handle.ts +++ b/packages/devui-vue/devui/auto-complete/src/composables/use-lazy-handle.ts @@ -1,6 +1,11 @@ -import { ref,SetupContext } from 'vue'; +import { Ref, ref,SetupContext } from 'vue'; import { AutoCompleteProps,HandleSearch } from '../auto-complete-types'; -export default function useLazyHandle(props: AutoCompleteProps,ctx: SetupContext,handleSearch: HandleSearch): any { + +export default function useLazyHandle(props: AutoCompleteProps,ctx: SetupContext,handleSearch: HandleSearch): { + showLoading: Ref; + dropDownRef: Ref; + loadMore: () => void; +} { const showLoading = ref(false); const dropDownRef = ref(); const loadMore = () => { @@ -14,12 +19,12 @@ export default function useLazyHandle(props: AutoCompleteProps,ctx: SetupContext showLoading.value = true; } }; - ctx.expose({loadFinish}); async function loadFinish (){ await handleSearch(props.modelValue,props.enableLazyLoad); showLoading.value = false; } + ctx.expose({loadFinish}); return { showLoading, dropDownRef, diff --git a/packages/devui-vue/devui/auto-complete/src/composables/use-searchfn.ts b/packages/devui-vue/devui/auto-complete/src/composables/use-searchfn.ts index 86d2a58bdc..1f718dad52 100644 --- a/packages/devui-vue/devui/auto-complete/src/composables/use-searchfn.ts +++ b/packages/devui-vue/devui/auto-complete/src/composables/use-searchfn.ts @@ -1,32 +1,43 @@ import { ref, Ref, SetupContext } from 'vue'; -import { FormatterType, SearchFnType } from '../auto-complete-types'; +import { FormatterType, SearchFnType, SourceType,SourceItemObj } from '../auto-complete-types'; export default function useSearchFn( ctx: SetupContext, allowEmptyValueSearch: Ref, - source: Ref>, + source: Ref, searchFn: Ref, formatter: Ref -): any { - const searchList = ref(); +): { + handleSearch: (term: string, enableLazyLoad?: boolean) => Promise; + recentlyFocus: (latestSource: Array) => void; + searchList: Ref; + showNoResultItemTemplate: Ref; + } { + const searchList = ref([]); const showNoResultItemTemplate = ref(false); - const handleSearch = async (term: string,enableLazyLoad: boolean) => { + const defaultSearchFn = (term: string) => { + type ItemType = typeof source.value[0]; + const arr: ItemType[] = []; + source.value.forEach((item) => { + let cur = formatter.value((item as ItemType)); + cur = cur.toLowerCase(); + if (cur.startsWith(term)) { + arr.push(item); + } + }); + return arr as SourceType; + }; + const handleSearch = async (term: string,enableLazyLoad=false) => { if (term === ''&&!allowEmptyValueSearch.value) { searchList.value = []; showNoResultItemTemplate.value=false; return; } - let arr = []; + let arr: SourceType = []; term = term.toLowerCase(); if(enableLazyLoad) { arr = source.value; }else if (!searchFn.value) { - source.value.forEach((item) => { - let cur = formatter.value(item); - cur = cur.toLowerCase(); - if (cur.startsWith(term)) { - arr.push(item); - } - }); + arr = defaultSearchFn(term); } else { arr = await searchFn.value(term); } @@ -37,7 +48,7 @@ export default function useSearchFn( showNoResultItemTemplate.value=false; } }; - const recentlyFocus = (latestSource: Array) => { + const recentlyFocus = (latestSource: Array) => { if(latestSource) { searchList.value = latestSource; } diff --git a/packages/devui-vue/devui/auto-complete/src/composables/use-select-handle.ts b/packages/devui-vue/devui/auto-complete/src/composables/use-select-handle.ts index 504995db86..32fdfd8af7 100644 --- a/packages/devui-vue/devui/auto-complete/src/composables/use-select-handle.ts +++ b/packages/devui-vue/devui/auto-complete/src/composables/use-select-handle.ts @@ -1,23 +1,37 @@ import { ref, Ref, SetupContext } from 'vue'; -import { DefaultFuncType,FormatterType,HandleSearch } from '../auto-complete-types'; +import { DefaultFuncType,FormatterType,HandleSearch,SelectValueType,SourceItemObj, SourceType } from '../auto-complete-types'; export default function useSelectHandle( ctx: SetupContext, - searchList: Ref>, - selectValue: Ref, + searchList: Ref, + selectValue: Ref, handleSearch: HandleSearch, formatter: Ref, handleClose: DefaultFuncType -): any { +): { + selectedIndex: Ref; + selectOptionClick: (item: string | SourceItemObj) => Promise; + } { const selectedIndex = ref(0); - const getListIndex = (item: string) => { + const getListIndex = (cur: string) => { if (searchList.value.length === 0) { return 0; } - const ind = searchList.value.indexOf(item); + let ind = 0; + searchList.value.forEach((item,index)=>{ + if(typeof item ==='string'){ + if(item === cur){ + ind = index; + } + }else{ + if(String(item.label) === cur){ + ind = index; + } + } + }); return ind === -1 ? 0 : ind; }; - const selectOptionClick = async(item: any) => { + const selectOptionClick = async (item: string | SourceItemObj) => { const cur = formatter.value(item); ctx.emit('update:modelValue', cur); handleClose(); diff --git a/packages/devui-vue/docs/.vitepress/devui-theme/components/PageContributorConfig.ts b/packages/devui-vue/docs/.vitepress/devui-theme/components/PageContributorConfig.ts index 123f7c614b..79745ceda4 100644 --- a/packages/devui-vue/docs/.vitepress/devui-theme/components/PageContributorConfig.ts +++ b/packages/devui-vue/docs/.vitepress/devui-theme/components/PageContributorConfig.ts @@ -220,18 +220,6 @@ export const CONTRIBUTORS_MAP: IContributingMap = { avatar: 'https://avatars.githubusercontent.com/u/66343210?v=4', homepage: 'https://github.com/Denver-Ning' }, - { - avatar: 'https://avatars.githubusercontent.com/u/47918504?v=4', - homepage: 'https://github.com/banlify' - }, - { - avatar: 'https://avatars.githubusercontent.com/u/23261843?v=4', - homepage: 'https://github.com/to0simple' - }, - { - avatar: 'https://avatars.githubusercontent.com/u/61737780?v=4', - homepage: 'https://github.com/asdlml6' - }, ], cascader: [ { diff --git a/packages/devui-vue/docs/components/auto-complete/index.md b/packages/devui-vue/docs/components/auto-complete/index.md index 7e6a9fc856..30bae8db34 100644 --- a/packages/devui-vue/docs/components/auto-complete/index.md +++ b/packages/devui-vue/docs/components/auto-complete/index.md @@ -22,6 +22,7 @@ :trans-input-focus-emit="transInputFocusEmit" :position="position" :width="420" + :append-to-body="false" > @@ -375,6 +376,7 @@ enable-lazy-load 开启懒加载 enable-lazy-load :load-more="loadMore" scene-type="select" + :value-parser="valueParser" > @@ -415,10 +417,14 @@ export default defineComponent({ autoCompleteRef.value?.loadFinish() },3000) } + const valueParser = (e) => { + return e + '123' + } return { value, source, loadMore, + valueParser, autoCompleteRef } } @@ -437,9 +443,10 @@ export default defineComponent({ | 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置项 | | :----------------------: | :----------------------------: | :---------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------- | ---------- | -| source | `Array` | -- | 必选,有 searchFn 的情况下可以不必选 | [基本用法](#基本用法) | +| source | [`SourceType`](#sourcetype) | -- | 必选,有 searchFn 的情况下可以不必选 | [基本用法](#基本用法) | | allow-empty-value-search | `boolean` | false | 可选,在绑定的输入框 value 为空时,是否进行搜索提示操作 | [基本用法](#基本用法) | -| position | `Placement[]` | ['bottom'](#placement) | 可选,指定下拉框与输入框的相对位置 | [基本用法](#基本用法) | +| append-to-body | `boolean` | false | 可选,下拉弹出是否 append to body | [基本用法](#基本用法) | +| position | [`Placement`](#placement) | `['bottom']` | 可选,指定下拉框与输入框的相对位置 | [基本用法](#基本用法) | | disabled | `boolean` | false | 可选,是否禁用指令 | [设置禁用](#设置禁用) | | delay | `number` | 300 | 可选,只有在 delay 时间经过后并且未输入新值,才做搜索查询(`ms`) | [基本用法](#基本用法) | | disabled-key | `string` | -- | 可选,禁用单个选项,当传入资源 source 选项类型为对象,比如设置为'disabled',则当对象的 disable 属性为 true 时,比如{ label: xxx, disabled: true },该选项将禁用 | [自定义数据匹配方法](#自定义数据匹配方法) | @@ -456,11 +463,11 @@ export default defineComponent({ ### AutoComplete 事件 -| 参数 | 类型 | 说明 | 跳转 Demo | -| :--------------------: | :---------------: | :---------------------------------------------------------------------------------------------------------------------------------: | :-------------------- | -| loadMore | `function` | 懒加载触发事件,配合`enable-lazy-load`使用,使用`$event.loadFinish()`关闭 loading 状态,其中\$event 为 AutoCompleteComponent 的实例 | [懒加载](#懒加载) | -| select-value | `function(value)` | 可选,选择选项之后的回调函数 | [基本用法](#基本用法) | -| trans-input-focus-emit | `function` | 可选,Input focus 时回调函数 | [基本用法](#基本用法) | +| 参数 | 类型 | 说明 | 跳转 Demo | +| :--------------------: | :---------------: | :---------------------------------------------------------------------------------------------------------------------------------: | :------------------------ | +| load-more | `function` | 懒加载触发事件,配合`enable-lazy-load`使用,使用`$event.loadFinish()`关闭 loading 状态,其中\$event 为 AutoCompleteComponent 的实例 | [启用懒加载](#启用懒加载) | +| select-value | `function(value)` | 可选,选择选项之后的回调函数 | [基本用法](#基本用法) | +| trans-input-focus-emit | `function` | 可选,Input focus 时回调函数 | [基本用法](#基本用法) | ### AutoComplete 插槽 @@ -472,6 +479,15 @@ export default defineComponent({ # 接口 & 类型定义 +### SourceType +```ts +interface SourceItemObj { + label: string; + disabled: boolean; + [propName: string]: unknown; +} +type SourceType = Array| Array +``` ### Placement ```ts type Placement = @@ -492,15 +508,18 @@ type Placement = ### defaultSearchFn ```ts -defaultSearchFn = (term) => { - return source.forEach((item)=>{ - let cur = formatter(item) - cur = cur.toLowerCase() - if(cur.startsWith(term)){ - arr.push(item) - } - }) - }; +defaultSearchFn = (term: string) => { + type ItemType = typeof source.value[0]; + const arr: ItemType[] = []; + source.value.forEach((item) => { + let cur = formatter.value((item as ItemType)); + cur = cur.toLowerCase(); + if (cur.startsWith(term)) { + arr.push(item); + } + }); + return arr as SourceType; +}; ``` term 为输入的关键字。 @@ -508,7 +527,12 @@ term 为输入的关键字。 ### defaultFormatter ```ts -defaultFormatter = (item) => (item ? item.label || item.toString() : ''); +defaultFormatter = (item: string | SourceItemObj) => { + if(typeof item === 'string'){ + return item; + } + return item!==null ? item.label || item.toString() : ''; +}; ``` item 为数据项。 diff --git a/packages/devui-vue/docs/en-US/components/auto-complete/api-en.md b/packages/devui-vue/docs/en-US/components/auto-complete/api-en.md index f31dd883a9..d66d0043de 100644 --- a/packages/devui-vue/docs/en-US/components/auto-complete/api-en.md +++ b/packages/devui-vue/docs/en-US/components/auto-complete/api-en.md @@ -22,6 +22,7 @@ Set source to the data source that is automatically completed. :trans-input-focus-emit="transInputFocusEmit" :position="position" :width="420" + :append-to-body="true" > @@ -374,6 +375,7 @@ enable-lazy-load: enables lazy loading. enable-lazy-load :load-more="loadMore" scene-type="select" + :value-parser="valueParser" > @@ -414,10 +416,14 @@ export default defineComponent({ autoCompleteRef.value?.loadFinish() },3000) } + const valueParser = (e) => { + return e + '123' + } return { value, source, loadMore, + valueParser, autoCompleteRef } } @@ -437,9 +443,10 @@ export default defineComponent({ | Parameter | Type | Default | Description | Jump to Demo | Global Config | | :----------------------: | :----------------------------: | :-------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------ | ------------- | -| source | `Array` | -- | Required. This parameter is optional if searchFn is specified. | [Basic usage](#Basic-usage) | +| source | [`SourceType`](#sourcetype) | -- | Required. This parameter is optional if searchFn is specified. | [Basic usage](#Basic-usage) | | allow-empty-value-search | `boolean` | false | Optional. indicates whether to display a search message when the bound text box value is empty. | [Basic usage](#Basic-usage) | -| position | `Placement[]` | ['bottom'](#placement) | Optional. Specify the relative position of the drop-down box and the input box . | [Basic usage](#Basic-usage) | +| append-to-body | `boolean` | false | Optional. Whether to append to body is displayed in the drop-down list box. | [Basic usage](#Basic-usage) | +| position | [`Placement`](#placement) | `['bottom']` | Optional. Specify the relative position of the drop-down box and the input box . | [Basic usage](#Basic-usage) | | disabled | `boolean` | false | Optional. Indicating whether to disable commands. | [Disabled](#Disabled) | | delay | `number` | 300 | Optional. The search is performed only after the delay time elapses and a new value is entered. (ms) | [Basic usage](#Basic-usage) | | disabled-key | `string` | -- | Optional. Disable a single option. If the input resource source option type is an object, for example, disabled, and the disable attribute of the object is true, for example, {label: xxx, disabled: true}, this option will be disabled | [Customized data matching method](#Customized-data-matching-method) | @@ -472,6 +479,15 @@ export default defineComponent({ # Interface & Type Definition +### SourceType +```ts +interface SourceItemObj { + label: string; + disabled: boolean; + [propName: string]: unknown; +} +type SourceType = Array| Array +``` ### Placement ```ts type Placement = @@ -492,15 +508,18 @@ type Placement = ### defaultSearchFn ```ts -defaultSearchFn = (term) => { - return source.forEach((item)=>{ - let cur = formatter(item) - cur = cur.toLowerCase() - if(cur.startsWith(term)){ - arr.push(item) - } - }) - }; +defaultSearchFn = (term: string) => { + type ItemType = typeof source.value[0]; + const arr: ItemType[] = []; + source.value.forEach((item) => { + let cur = formatter.value((item as ItemType)); + cur = cur.toLowerCase(); + if (cur.startsWith(term)) { + arr.push(item); + } + }); + return arr as SourceType; +}; ``` term indicates the entered keyword. @@ -508,7 +527,12 @@ term indicates the entered keyword. ### defaultFormatter ```ts -defaultFormatter = (item) => (item ? item.label || item.toString() : ''); +defaultFormatter = (item: string | SourceItemObj) => { + if(typeof item === 'string'){ + return item; + } + return item!==null ? item.label || item.toString() : ''; +}; ``` item indicates a data item. diff --git a/packages/devui-vue/tsconfig.json b/packages/devui-vue/tsconfig.json index 8687857c12..88e35e8105 100644 --- a/packages/devui-vue/tsconfig.json +++ b/packages/devui-vue/tsconfig.json @@ -7,7 +7,7 @@ "jsx": "preserve", "sourceMap": true, "lib": ["esnext", "dom"], - "types": ["vite/client", "jest"], + "types": ["vite/client", "jest","node"], "esModuleInterop": true, "plugins": [{ "name": "@vuedx/typescript-plugin-vue" }], "paths": {