fix: improve filters

This commit is contained in:
chenos 2020-12-29 14:53:39 +08:00
parent e692d7bb5f
commit 519f8de40b
6 changed files with 85 additions and 32 deletions

View File

@ -157,14 +157,14 @@ const OP_MAP = {
select: [
{label: '等于', value: 'eq', selected: true},
{label: '不等于', value: 'ne'},
{label: '包含', value: '$anyOf'},
{label: '不包含', value: '$noneOf'},
{label: '包含', value: 'in'},
{label: '不包含', value: 'notIn'},
{label: '非空', value: '$notNull'},
{label: '为空', value: '$null'},
],
multipleSelect: [
{label: '等于', value: 'eq', selected: true},
{label: '不等于', value: 'ne'},
{label: '等于', value: '$match', selected: true},
{label: '不等于', value: '$notMatch'},
{label: '包含', value: '$anyOf'},
{label: '不包含', value: '$noneOf'},
{label: '非空', value: '$notNull'},
@ -217,8 +217,8 @@ const op = {
checkbox: OP_MAP.boolean,
boolean: OP_MAP.boolean,
select: OP_MAP.select,
multipleSelect: OP_MAP.select,
checkboxes: OP_MAP.select,
multipleSelect: OP_MAP.multipleSelect,
checkboxes: OP_MAP.multipleSelect,
radio: OP_MAP.select,
upload: OP_MAP.file,
attachment: OP_MAP.file,
@ -237,7 +237,13 @@ const controls = {
string: StringInput,
textarea: StringInput,
number: InputNumber,
percent: InputNumber,
percent: (props) => (
<InputNumber
formatter={value => value ? `${value}%` : ''}
parser={value => value.replace('%', '')}
{...props}
/>
),
boolean: BooleanControl,
checkbox: BooleanControl,
select: OptionControl,
@ -302,13 +308,13 @@ export function FilterItem(props: FilterItemProps) {
const field = fields.find(field => field.name === props.dataSource.column);
if (field) {
setField(field);
setType(field.component.type);
// console.log(dataSource);
let componentType = field.component.type;
if (field.component.type === 'select' && field.multiple) {
componentType = 'multipleSelect';
}
setType(componentType);
}
setDataSource({...props.dataSource});
// if (['boolean', 'checkbox'].indexOf(type) !== -1) {
// onChange({...dataSource, op: undefined});
// }
}, [
props.dataSource, type,
]);
@ -328,10 +334,12 @@ export function FilterItem(props: FilterItemProps) {
<Select value={dataSource.column}
onChange={(value) => {
const field = fields.find(field => field.name === value);
if (field) {
setType(field.component.type);
let componentType = field.component.type;
if (field.component.type === 'select' && field.multiple) {
componentType = 'multipleSelect';
}
onChange({...dataSource, column: value, op: get(op, [field.component.type, 0, 'value']), value: undefined});
setType(componentType);
onChange({...dataSource, column: value, op: get(op, [componentType, 0, 'value']), value: undefined});
}}
style={{ width: 120 }}
placeholder={'选择字段'}>

View File

@ -284,4 +284,9 @@ export default class Database {
}
}
}
public getFieldByPath(fieldPath: string) {
const [tableName, fieldName] = fieldPath.split('.');
return this.getTable(tableName).getField(fieldName);
}
}

View File

@ -312,7 +312,7 @@ export class ARRAY extends Column {
public readonly options: Options.ArrayOptions;
public getDataType() {
return DataTypes.JSON;
return DataTypes.JSONB;
}
public getAttributeOptions() {
@ -325,6 +325,9 @@ export class ARRAY extends Column {
}
export class JSON extends Column {
public getDataType() {
return DataTypes.JSONB;
}
}
export class JSONB extends Column {

View File

@ -20,7 +20,11 @@ for (const key in Op) {
// 通用
// 是否为空:数据库意义的 null
op.set('$null', () => ({ [Op.is]: null }));
op.set('$null', (value, {fieldPath, database}) => {
// const field = database.getFieldByPath(fieldPath);
// console.log({field});
return { [Op.is]: null };
});
op.set('$notNull', () => ({ [Op.not]: null }));
op.set('$isTruly', () => ({
@ -55,9 +59,20 @@ op.set('$notEndsWith', (value: string) => ({ [Op.notILike]: `%${value}` }));
// 多选JSON类型
// 包含组中任意值(命名来源:`Array.prototype.some`
op.set('$anyOf', (values: any[]) => ({
[Op.or]: toArray(values).map(value => ({ [Op.contains]: value }))
}));
op.set('$anyOf', (values: any[], options) => {
if (!values) {
return Sequelize.literal('');
}
values = Array.isArray(values) ? values : [values];
if (values.length === 0) {
return Sequelize.literal('');
}
const { field, fieldPath } = options;
const column = fieldPath.split('.').map(name => `"${name}"`).join('.');
const sql = values.map(value => `(${column})::jsonb @> '${JSON.stringify(value)}'`).join(' OR ');
console.log(sql);
return Sequelize.literal(sql);
});
// 包含组中所有值
op.set('$allOf', (values: any) => ({ [Op.contains]: toArray(values) }));
// TODO(bug): 不包含组中任意值
@ -66,6 +81,9 @@ op.set('$noneOf', (values: any[], options) => {
return Sequelize.literal('');
}
values = Array.isArray(values) ? values : [values];
if (values.length === 0) {
return Sequelize.literal('');
}
const { field, fieldPath } = options;
const column = fieldPath.split('.').map(name => `"${name}"`).join('.');
const sql = values.map(value => `(${column})::jsonb @> '${JSON.stringify(value)}'`).join(' OR ');
@ -73,14 +91,26 @@ op.set('$noneOf', (values: any[], options) => {
return Sequelize.literal(`not (${sql})`);
});
// 与组中值匹配
op.set('$match', (values: any[]) => {
op.set('$match', (values: any[], options) => {
const array = toArray(values);
return {
[Op.contains]: array,
[Op.contained]: array
};
if (values.length === 0) {
return Sequelize.literal('');
}
const { field, fieldPath } = options;
const column = fieldPath.split('.').map(name => `"${name}"`).join('.');
const sql = `(${column})::jsonb @> '${JSON.stringify(array)}' AND (${column})::jsonb <@ '${JSON.stringify(array)}'`
return Sequelize.literal(sql);
});
op.set('$notMatch', (values: any[], options) => {
const array = toArray(values);
if (values.length === 0) {
return Sequelize.literal('');
}
const { field, fieldPath } = options;
const column = fieldPath.split('.').map(name => `"${name}"`).join('.');
const sql = `(${column})::jsonb @> '${JSON.stringify(array)}' AND (${column})::jsonb <@ '${JSON.stringify(array)}'`
return Sequelize.literal(`not (${sql})`);
// return Sequelize.literal(`(not (${sql})) AND ${column} IS NULL`);
});
export default op;

View File

@ -20,7 +20,7 @@ export function toWhere(options: any, context: ToWhereContext = {}) {
if (Array.isArray(options)) {
return options.map((item) => toWhere(item, context));
}
const { prefix, model, associations = {}, ctx, dialect } = context;
const { prefix, model, associations = {}, ctx, dialect, database } = context;
const items = {};
// 先处理「点号」的问题
for (const key in options) {
@ -54,7 +54,11 @@ export function toWhere(options: any, context: ToWhereContext = {}) {
switch (typeof opKey) {
case 'function':
const name = model ? model.options.name.plural : '';
const result = opKey(items[key], { model, fieldPath: name ? `${name}.${prefix}` : prefix });
const result = opKey(items[key], {
model,
database,
fieldPath: name ? `${name}.${prefix}` : prefix,
});
if (result.constructor.name === 'Literal') {
values['$__literals'] = values['$__literals'] || [];
values['$__literals'].push(result);
@ -184,7 +188,7 @@ export function toInclude(options: any, context: ToIncludeContext = {}) {
}
const { fields = [], filter } = options;
const { model, sourceAlias, associations = {}, ctx, dialect } = context;
const { model, sourceAlias, associations = {}, ctx, database, dialect } = context;
let where = options.where || {};
@ -193,6 +197,7 @@ export function toInclude(options: any, context: ToIncludeContext = {}) {
model,
associations,
ctx,
database,
}) || {};
}

View File

@ -157,6 +157,7 @@ export const multipleSelect = {
type: 'json', // json 过滤
filterable: true,
dataSource: [],
defaultValue: [],
multiple: true, // 需要重点考虑
component: {
type: 'select',
@ -184,6 +185,7 @@ export const checkboxes = {
type: 'json',
filterable: true,
dataSource: [],
defaultValue: [],
component: {
type: 'checkboxes',
},
@ -387,7 +389,7 @@ export const createdBy = {
interface: 'createdBy',
type: 'createdBy',
// name: 'createdBy',
filterable: true,
// filterable: true,
target: 'users',
labelField: 'nickname',
foreignKey: 'created_by_id',
@ -405,7 +407,7 @@ export const updatedBy = {
interface: 'updatedBy',
// name: 'updatedBy',
type: 'updatedBy',
filterable: true,
// filterable: true,
target: 'users',
labelField: 'nickname',
foreignKey: 'created_by_id',