补充搜索功能

This commit is contained in:
2betop 2020-05-14 10:40:44 +08:00
parent 1442357975
commit 2f22d7621b
6 changed files with 127 additions and 20 deletions

View File

@ -72,3 +72,7 @@
} }
} }
} }
.#{$ns}TransferControl {
position: relative;
}

View File

@ -35,7 +35,7 @@ export interface TransferPorps extends ThemeProps, CheckboxesProps {
onSearch?: ( onSearch?: (
term: string, term: string,
setCancel: (cancel: () => void) => void setCancel: (cancel: () => void) => void
) => Promise<Options>; ) => Promise<Options | void>;
// 自定义选择框相关 // 自定义选择框相关
selectRender?: (props: TransferPorps) => JSX.Element; selectRender?: (props: TransferPorps) => JSX.Element;
@ -216,6 +216,7 @@ export class Transfer extends React.Component<TransferPorps, TransferState> {
renderSearchResult() { renderSearchResult() {
const { const {
searchResultMode, searchResultMode,
selectMode,
noResultsText, noResultsText,
searchResultColumns, searchResultColumns,
classnames: cx, classnames: cx,
@ -224,8 +225,9 @@ export class Transfer extends React.Component<TransferPorps, TransferState> {
option2value option2value
} = this.props; } = this.props;
const options = this.state.searchResult || []; const options = this.state.searchResult || [];
const mode = searchResultMode || selectMode;
return searchResultMode === 'table' ? ( return mode === 'table' ? (
<TableCheckboxes <TableCheckboxes
placeholder={noResultsText} placeholder={noResultsText}
className={cx('Transfer-checkboxes')} className={cx('Transfer-checkboxes')}
@ -235,7 +237,7 @@ export class Transfer extends React.Component<TransferPorps, TransferState> {
onChange={onChange} onChange={onChange}
option2value={option2value} option2value={option2value}
/> />
) : searchResultMode === 'tree' ? ( ) : mode === 'tree' ? (
<TreeCheckboxes <TreeCheckboxes
placeholder={noResultsText} placeholder={noResultsText}
className={cx('Transfer-checkboxes')} className={cx('Transfer-checkboxes')}

View File

@ -45,7 +45,7 @@ export interface FormItemProps extends RendererProps {
formHorizontal: FormHorizontal; formHorizontal: FormHorizontal;
defaultSize?: 'xs' | 'sm' | 'md' | 'lg' | 'full'; defaultSize?: 'xs' | 'sm' | 'md' | 'lg' | 'full';
size?: 'xs' | 'sm' | 'md' | 'lg' | 'full'; size?: 'xs' | 'sm' | 'md' | 'lg' | 'full';
disabled: boolean; disabled?: boolean;
btnDisabled: boolean; btnDisabled: boolean;
defaultValue: any; defaultValue: any;
value: any; value: any;

View File

@ -6,7 +6,13 @@ import Checkbox from '../../components/Checkbox';
import PopOver from '../../components/PopOver'; import PopOver from '../../components/PopOver';
import {RootCloseWrapper} from 'react-overlays'; import {RootCloseWrapper} from 'react-overlays';
import {Icon} from '../../components/icons'; import {Icon} from '../../components/icons';
import {autobind, flattenTree, isEmpty, filterTree} from '../../utils/helper'; import {
autobind,
flattenTree,
isEmpty,
filterTree,
string2regExp
} from '../../utils/helper';
import {dataMapping} from '../../utils/tpl-builtin'; import {dataMapping} from '../../utils/tpl-builtin';
import {OptionsControl, OptionsControlProps} from '../Form/Options'; import {OptionsControl, OptionsControlProps} from '../Form/Options';
import {Option, Options} from '../../components/Select'; import {Option, Options} from '../../components/Select';
@ -357,7 +363,7 @@ export default class NestedSelectControl extends React.Component<
const inputValue = evt.currentTarget.value; const inputValue = evt.currentTarget.value;
const {options, labelField, valueField} = this.props; const {options, labelField, valueField} = this.props;
const regexp = new RegExp(`${inputValue}`, 'i'); const regexp = string2regExp(inputValue);
let filtedOptions = let filtedOptions =
inputValue && this.state.isOpened inputValue && this.state.isOpened

View File

@ -2,13 +2,22 @@ import {OptionsControlProps, OptionsControl} from './Options';
import React from 'react'; import React from 'react';
import Transfer from '../../components/Transfer'; import Transfer from '../../components/Transfer';
import {Option} from './Options'; import {Option} from './Options';
import {autobind} from '../../utils/helper'; import {
autobind,
filterTree,
string2regExp,
createObject
} from '../../utils/helper';
import {Api} from '../../types'; import {Api} from '../../types';
import Spinner from '../../components/Spinner';
import find from 'lodash/find';
import {optionValueCompare} from '../../components/Select';
export interface TransferProps extends OptionsControlProps { export interface TransferProps extends OptionsControlProps {
sortable?: boolean; sortable?: boolean;
selectMode?: 'table' | 'list' | 'tree'; selectMode?: 'table' | 'list' | 'tree';
columns?: Array<any>; columns?: Array<any>;
searchable?: boolean;
searchApi?: Api; // todo 通过传递进去 onSearch 实现。 searchApi?: Api; // todo 通过传递进去 onSearch 实现。
} }
@ -23,20 +32,38 @@ export class TransferRenderer extends React.Component<TransferProps> {
joinValues, joinValues,
delimiter, delimiter,
valueField, valueField,
extractValue extractValue,
options,
setOptions
} = this.props; } = this.props;
let newValue: any = value; let newValue: any = value;
let newOptions = options.concat();
if (Array.isArray(value)) { if (Array.isArray(value)) {
if (joinValues || extractValue) {
newValue = value.map(item => {
const resolved = find(
options,
optionValueCompare(
item[valueField || 'value'],
valueField || 'value'
)
);
if (!resolved) {
newOptions.push(item);
}
return item[valueField || 'value'];
});
}
if (joinValues) { if (joinValues) {
newValue = value newValue = newValue.join(delimiter || ',');
.map(item => item[valueField || 'value'])
.join(delimiter || ',');
} else if (extractValue) {
newValue = value.map(item => item[valueField || 'value']);
} }
} }
newOptions.length > options.length && setOptions(newOptions);
onChange(newValue); onChange(newValue);
} }
@ -45,6 +72,56 @@ export class TransferRenderer extends React.Component<TransferProps> {
return option; return option;
} }
@autobind
async handleSearch(term: string) {
const {searchApi, options, labelField, valueField, env, data} = this.props;
if (searchApi) {
const payload = await env.fetcher(searchApi, createObject(data, {term}));
if (!payload.ok) {
env.notify('error', payload.msg || '搜索请求异常');
return [];
}
const result = payload.data.options || payload.data.items || payload.data;
if (!Array.isArray(result)) {
env.notify('error', '期望接口返回数组信息');
return [];
}
return result.map(item => {
let resolved: any = null;
if (Array.isArray(options)) {
resolved = find(
options,
optionValueCompare(item[valueField || 'value'], valueField)
);
}
return resolved || item;
});
} else if (term) {
const regexp = string2regExp(term);
return filterTree(
options,
(option: Option) => {
return !!(
(Array.isArray(option.children) && option.children.length) ||
regexp.test(option[labelField || 'label']) ||
regexp.test(option[valueField || 'value'])
);
},
0,
true
);
} else {
return options;
}
}
render() { render() {
const { const {
classnames: cx, classnames: cx,
@ -52,7 +129,10 @@ export class TransferRenderer extends React.Component<TransferProps> {
selectedOptions, selectedOptions,
sortable, sortable,
selectMode, selectMode,
columns columns,
loading,
searchable,
searchApi
} = this.props; } = this.props;
return ( return (
@ -65,7 +145,10 @@ export class TransferRenderer extends React.Component<TransferProps> {
sortable={sortable} sortable={sortable}
selectMode={selectMode} selectMode={selectMode}
columns={columns} columns={columns}
onSearch={searchable ? this.handleSearch : undefined}
/> />
<Spinner overlay key="info" show={loading} />
</div> </div>
); );
} }

View File

@ -251,9 +251,10 @@ export function anyChanged(
to: {[propName: string]: any}, to: {[propName: string]: any},
strictMode: boolean = true strictMode: boolean = true
): boolean { ): boolean {
return (typeof attrs === 'string' ? attrs.split(/\s*,\s*/) : attrs).some( return (typeof attrs === 'string'
key => (strictMode ? from[key] !== to[key] : from[key] != to[key]) ? attrs.split(/\s*,\s*/)
); : attrs
).some(key => (strictMode ? from[key] !== to[key] : from[key] != to[key]));
} }
export function rmUndefined(obj: PlainObject) { export function rmUndefined(obj: PlainObject) {
@ -362,7 +363,7 @@ export function makeColumnClassBuild(
let count = 12; let count = 12;
let step = Math.floor(count / steps); let step = Math.floor(count / steps);
return function(schema: Schema) { return function (schema: Schema) {
if ( if (
schema.columnClassName && schema.columnClassName &&
/\bcol-(?:xs|sm|md|lg)-(\d+)\b/.test(schema.columnClassName) /\bcol-(?:xs|sm|md|lg)-(\d+)\b/.test(schema.columnClassName)
@ -470,7 +471,7 @@ export function promisify<T extends Function>(
) => Promise<any> & { ) => Promise<any> & {
raw: T; raw: T;
} { } {
let promisified = function() { let promisified = function () {
try { try {
const ret = fn.apply(null, arguments); const ret = fn.apply(null, arguments);
if (ret && ret.then) { if (ret && ret.then) {
@ -1029,6 +1030,17 @@ export function getLevelFromClassName(
return defaultValue; return defaultValue;
} }
export function string2regExp(value: string, caseSensitive = false) {
if (typeof value !== 'string') {
throw new TypeError('Expected a string');
}
return new RegExp(
value.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/-/g, '\\x2d'),
!caseSensitive ? 'i' : ''
);
}
export function pickEventsProps(props: any) { export function pickEventsProps(props: any) {
const ret: any = {}; const ret: any = {};
props && props &&
@ -1040,7 +1052,7 @@ export function pickEventsProps(props: any) {
export const autobind = boundMethod; export const autobind = boundMethod;
export const bulkBindFunctions = function< export const bulkBindFunctions = function <
T extends { T extends {
[propName: string]: any; [propName: string]: any;
} }