补充搜索功能

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

View File

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

View File

@ -6,7 +6,13 @@ import Checkbox from '../../components/Checkbox';
import PopOver from '../../components/PopOver';
import {RootCloseWrapper} from 'react-overlays';
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 {OptionsControl, OptionsControlProps} from '../Form/Options';
import {Option, Options} from '../../components/Select';
@ -357,7 +363,7 @@ export default class NestedSelectControl extends React.Component<
const inputValue = evt.currentTarget.value;
const {options, labelField, valueField} = this.props;
const regexp = new RegExp(`${inputValue}`, 'i');
const regexp = string2regExp(inputValue);
let filtedOptions =
inputValue && this.state.isOpened

View File

@ -2,13 +2,22 @@ import {OptionsControlProps, OptionsControl} from './Options';
import React from 'react';
import Transfer from '../../components/Transfer';
import {Option} from './Options';
import {autobind} from '../../utils/helper';
import {
autobind,
filterTree,
string2regExp,
createObject
} from '../../utils/helper';
import {Api} from '../../types';
import Spinner from '../../components/Spinner';
import find from 'lodash/find';
import {optionValueCompare} from '../../components/Select';
export interface TransferProps extends OptionsControlProps {
sortable?: boolean;
selectMode?: 'table' | 'list' | 'tree';
columns?: Array<any>;
searchable?: boolean;
searchApi?: Api; // todo 通过传递进去 onSearch 实现。
}
@ -23,20 +32,38 @@ export class TransferRenderer extends React.Component<TransferProps> {
joinValues,
delimiter,
valueField,
extractValue
extractValue,
options,
setOptions
} = this.props;
let newValue: any = value;
let newOptions = options.concat();
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) {
newValue = value
.map(item => item[valueField || 'value'])
.join(delimiter || ',');
} else if (extractValue) {
newValue = value.map(item => item[valueField || 'value']);
newValue = newValue.join(delimiter || ',');
}
}
newOptions.length > options.length && setOptions(newOptions);
onChange(newValue);
}
@ -45,6 +72,56 @@ export class TransferRenderer extends React.Component<TransferProps> {
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() {
const {
classnames: cx,
@ -52,7 +129,10 @@ export class TransferRenderer extends React.Component<TransferProps> {
selectedOptions,
sortable,
selectMode,
columns
columns,
loading,
searchable,
searchApi
} = this.props;
return (
@ -65,7 +145,10 @@ export class TransferRenderer extends React.Component<TransferProps> {
sortable={sortable}
selectMode={selectMode}
columns={columns}
onSearch={searchable ? this.handleSearch : undefined}
/>
<Spinner overlay key="info" show={loading} />
</div>
);
}

View File

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