Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { computed } from 'vue';
import { SelectProps, allowCreateOption, useAllowCreateReturn } from '../select-types';

export default function useAllowCreate(props: SelectProps, option: allowCreateOption): useAllowCreateReturn {
const { filterQuery, injectOptionsArray } = option;

// allow-create
const isShowCreateOption = computed(() => {
const hasCommonOption = injectOptionsArray.value.filter((item) => !item.create).some((item) => item.name === filterQuery.value);
return props.filter === true && props.allowCreate && !!filterQuery.value && !hasCommonOption;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

判断条件较为复杂,建议添加注释。

});

return {
isShowCreateOption,
};
}
31 changes: 31 additions & 0 deletions packages/devui-vue/devui/select/src/composables/use-filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ref, computed } from 'vue';
import { SelectProps, useFilterReturn } from '../select-types';
import { isFunction, debounce } from 'lodash';

export default function useFilter(props: SelectProps): useFilterReturn {
const filterQuery = ref('');
const debounceTime = computed(() => (props.remote ? 300 : 0));
const isSupportFilter = computed(() => isFunction(props.filter) || props.filter === true);

const queryChange = (query: string) => {
filterQuery.value = query;
};

const handlerQueryFunc = (query: string) => {
if (isFunction(props.filter)) {
props.filter(query);
} else {
queryChange(query);
}
};

const debounceQueryFilter = debounce((query: string) => {
handlerQueryFunc(query);
}, debounceTime.value);

return {
filterQuery,
isSupportFilter,
debounceQueryFilter,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { SetupContext } from 'vue';
import { SelectProps, OptionObjectItem, useMultipleOption, useMultipleReturn } from '../select-types';

export default function useMultipleSelect(props: SelectProps, ctx: SetupContext, multipleOption: useMultipleOption): useMultipleReturn {
const { filterQuery, isSupportFilter, isObjectOption, mergeOptions, injectOptions, getValuesOption, getInjectOptions } = multipleOption;

const getMultipleSelected = (items: (string | number)[]) => {
if (mergeOptions.value.length) {
ctx.emit(
'value-change',
getValuesOption(items).filter((item) => (item ? true : false))
);
} else if (isObjectOption.value) {
const selectItems = getInjectOptions(items).filter((item) => (item ? true : false));
ctx.emit('value-change', selectItems);
} else {
ctx.emit('value-change', items);
}
};
const multipleValueChange = (item: OptionObjectItem) => {
let { modelValue } = props;

const checkedItems = Array.isArray(modelValue) ? modelValue.slice() : [];
const index = checkedItems.indexOf(item.value);
const option = getInjectOptions([item.value])[0];
if (option) {
option._checked = !option._checked;
}
const mergeOption = getValuesOption([item.value])[0];
if (mergeOption) {
mergeOption._checked = !mergeOption._checked;
}
if (index > -1) {
checkedItems.splice(index, 1);
} else {
checkedItems.push(item.value);
}
modelValue = checkedItems;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

props的数据流应该是单向流动的,在子组件中修改父元素props的值,会报warning吧?

ctx.emit('update:modelValue', modelValue);
if (item.create) {
filterQuery.value = '';
}
if (isSupportFilter.value) {
focus();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

focus方法在当前文件没有找到声明的位置,此处是如何工作的?

}
getMultipleSelected(checkedItems);
};

const tagDelete = (data: OptionObjectItem) => {
const checkedItems = [];
for (const child of injectOptions.value.values()) {
if (data.value === child.value) {
child._checked = false;
}
if (child._checked) {
checkedItems.push(child.value);
}
}
ctx.emit('update:modelValue', checkedItems);
ctx.emit('remove-tag', data.value);
getMultipleSelected(checkedItems);
};

return {
multipleValueChange,
tagDelete,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { computed } from 'vue';
import { SelectProps, useNoDataOption, useNoDataReturn } from '../select-types';

export default function useNoDataText(props: SelectProps, option: useNoDataOption): useNoDataReturn {
const { filterQuery, isSupportFilter, injectOptionsArray, t } = option;

// no-data-text
const isLoading = computed(() => typeof props.loading === 'boolean' && props.loading);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里建议不对props.loading进行是否为boolean的检查,可以消除isLoading中间变量,直接使用props.loading。可以讨论一下,当用户传入的loading值为一个非空的字符串或者数字的时候,我们是否希望loading是工作的?

const emptyText = computed(() => {
const visibleOptionsCount = injectOptionsArray.value.filter((item) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

visibleOptionsCount主要是用来判断是否存在visibleOptions,所以是否可以将名字改为hasVisibleOptions,同时后面的filter方法替换为some方法。

const label = item.name || item.value;
return label.toString().toLocaleLowerCase().includes(filterQuery.value.toLocaleLowerCase());
}).length;
if (isLoading.value) {
return props.loadingText || (t('loadingText') as string);
}
if (isSupportFilter.value && filterQuery.value && injectOptionsArray.value.length > 0 && visibleOptionsCount === 0) {
return props.noMatchText || (t('noMatchText') as string);
}
if (injectOptionsArray.value.length === 0) {
return props.noDataText || (t('noDataText') as string);
}
return '';
});

const isShowEmptyText = computed(() => {
return !!emptyText.value && (!props.allowCreate || isLoading.value || (props.allowCreate && injectOptionsArray.value.length === 0));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里的判断条件有些复杂,存在&&与||的嵌套,建议填加注释,不然比较难读懂。

});

return {
isLoading,
emptyText,
isShowEmptyText,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { OptionProps, UseOptionReturnType } from '../select-types';
import { SELECT_TOKEN, OPTION_GROUP_TOKEN } from '../const';
import { className } from '../utils';
import { useNamespace } from '../../../shared/hooks/use-namespace';

export default function useOption(props: OptionProps): UseOptionReturnType {
const ns = useNamespace('select');
const select = inject(SELECT_TOKEN, null);
Expand Down Expand Up @@ -43,7 +44,12 @@ export default function useOption(props: OptionProps): UseOptionReturnType {
});

const optionSelect = (): void => {
if (!isDisabled.value) {
if (isDisabled.value) {
return;
}
if (select?.multiple) {
select?.multipleValueChange(optionItem.value);
} else {
select?.valueChange(optionItem.value);
}
};
Expand Down
60 changes: 52 additions & 8 deletions packages/devui-vue/devui/select/src/select-types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PropType, ComputedRef, ExtractPropTypes, Ref } from 'vue';

import { KeyType } from './utils';
export interface OptionObjectItem {
name: string;
value: string | number;
Expand Down Expand Up @@ -116,23 +116,23 @@ export interface UseSelectReturnType {
dropdownRef: Ref<HTMLElement | undefined>;
isOpen: Ref<boolean>;
selectCls: ComputedRef<string>;
isObjectOption: Ref<boolean>;
mergeOptions: Ref<OptionObjectItem[]>;
injectOptions: Ref<Map<string | number, Record<string, unknown>>>;
injectOptionsArray: ComputedRef<OptionObjectItem[]>;
selectedOptions: ComputedRef<OptionObjectItem[]>;
filterQuery: Ref<string>;
emptyText: ComputedRef<string>;
isLoading: Ref<boolean>;
isShowEmptyText: ComputedRef<boolean>;
dropdownWidth: ComputedRef<string>;
onClick: (e: MouseEvent) => void;
handleClear: (e: MouseEvent) => void;
valueChange: (item: OptionObjectItem) => void;
handleClose: () => void;
updateInjectOptions: (item: Record<string, unknown>, operation: string, isObject: boolean) => void;
tagDelete: (data: OptionObjectItem) => void;
onFocus: (e: FocusEvent) => void;
onBlur: (e: FocusEvent) => void;
isDisabled: (item: OptionObjectItem) => boolean;
toggleChange: (bool: boolean) => void;
debounceQueryFilter: (query: string) => void;
isShowCreateOption: ComputedRef<boolean>;
getValuesOption: (values: KeyType<OptionObjectItem, 'value'>[]) => unknown[];
getInjectOptions: (values: KeyType<OptionObjectItem, 'value'>[]) => unknown[];
}

export interface SelectContext extends SelectProps {
Expand All @@ -142,6 +142,7 @@ export interface SelectContext extends SelectProps {
selectedOptions: OptionObjectItem[];
filterQuery: string;
valueChange: (item: OptionObjectItem) => void;
multipleValueChange: (item: OptionObjectItem) => void;
handleClear: () => void;
updateInjectOptions: (item: Record<string, unknown>, operation: string, isObject: boolean) => void;
tagDelete: (data: OptionObjectItem) => void;
Expand Down Expand Up @@ -217,3 +218,46 @@ export const optionGroupProps = {
export type OptionGroupProps = ExtractPropTypes<typeof optionGroupProps>;

export type OptionGroupContext = OptionGroupProps;

export interface allowCreateOption {
filterQuery: Ref<string>;
injectOptionsArray: ComputedRef<OptionObjectItem[]>;
}

export interface useAllowCreateReturn {
isShowCreateOption: ComputedRef<boolean>;
}

export interface useFilterReturn {
filterQuery: Ref<string>;
isSupportFilter: ComputedRef<boolean>;
debounceQueryFilter: (query: string) => void;
}

export interface useMultipleOption {
filterQuery: Ref<string>;
isSupportFilter: ComputedRef<boolean>;
isObjectOption: Ref<boolean>;
mergeOptions: Ref<OptionObjectItem[]>;
injectOptions: Ref<Map<string | number, Record<string, unknown>>>;
getValuesOption: (values: KeyType<OptionObjectItem, 'value'>[]) => unknown[];
getInjectOptions: (values: KeyType<OptionObjectItem, 'value'>[]) => unknown[];
}

export interface useMultipleReturn {
multipleValueChange: (item: OptionObjectItem) => void;
tagDelete: (data: OptionObjectItem) => void;
}

export interface useNoDataOption {
filterQuery: Ref<string>;
isSupportFilter: ComputedRef<boolean>;
injectOptionsArray: ComputedRef<OptionObjectItem[]>;
t: (path: string) => unknown;
}

export interface useNoDataReturn {
isLoading: Ref<boolean>;
emptyText: ComputedRef<string>;
isShowEmptyText: ComputedRef<boolean>;
}
41 changes: 33 additions & 8 deletions packages/devui-vue/devui/select/src/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import Option from './components/option';
import { useNamespace } from '../../shared/hooks/use-namespace';
import SelectContent from './components/select-content';
import useSelectFunction from './composables/use-select-function';
import useFilter from './composables/use-filter';
import useMultipleSelect from './composables/use-multiple-select';
import useNoDataText from './composables/use-no-data-text';
import useAllowCreate from './composables/use-allow-create';
import './select.scss';
import { createI18nTranslate } from '../../locale/create';
import { FlexibleOverlay, Placement } from '../../overlay';
Expand All @@ -35,30 +39,50 @@ export default defineComponent({

const selectRef = ref();
const { isSelectFocus, focus, blur } = useSelectFunction(props, selectRef);
const { filterQuery, isSupportFilter, debounceQueryFilter } = useFilter(props);
const {
selectDisabled,
selectSize,
originRef,
dropdownRef,
isOpen,
selectCls,
isObjectOption,
mergeOptions,
injectOptions,
injectOptionsArray,
selectedOptions,
filterQuery,
emptyText,
isLoading,
isShowEmptyText,
dropdownWidth,
onClick,
valueChange,
handleClear,
updateInjectOptions,
tagDelete,
onFocus,
onBlur,
debounceQueryFilter,
isDisabled,
toggleChange,
isShowCreateOption,
} = useSelect(props, selectRef, ctx, focus, blur, isSelectFocus, t);
getValuesOption,
getInjectOptions,
} = useSelect(props, ctx, focus, blur, isSelectFocus);

const { multipleValueChange, tagDelete } = useMultipleSelect(props, ctx, {
filterQuery,
isSupportFilter,
isObjectOption,
mergeOptions,
injectOptions,
getValuesOption,
getInjectOptions,
});

const { isShowCreateOption } = useAllowCreate(props, { filterQuery, injectOptionsArray });

const { isLoading, emptyText, isShowEmptyText } = useNoDataText(props, {
filterQuery,
isSupportFilter,
injectOptionsArray,
t,
});

const scrollbarNs = useNamespace('scrollbar');
const ns = useNamespace('select');
Expand Down Expand Up @@ -104,6 +128,7 @@ export default defineComponent({
selectedOptions,
filterQuery,
valueChange,
multipleValueChange,
handleClear,
updateInjectOptions,
tagDelete,
Expand Down
Loading