mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 09:47:10 +00:00
fix: improve filters
This commit is contained in:
parent
e692d7bb5f
commit
519f8de40b
@ -157,14 +157,14 @@ const OP_MAP = {
|
|||||||
select: [
|
select: [
|
||||||
{label: '等于', value: 'eq', selected: true},
|
{label: '等于', value: 'eq', selected: true},
|
||||||
{label: '不等于', value: 'ne'},
|
{label: '不等于', value: 'ne'},
|
||||||
{label: '包含', value: '$anyOf'},
|
{label: '包含', value: 'in'},
|
||||||
{label: '不包含', value: '$noneOf'},
|
{label: '不包含', value: 'notIn'},
|
||||||
{label: '非空', value: '$notNull'},
|
{label: '非空', value: '$notNull'},
|
||||||
{label: '为空', value: '$null'},
|
{label: '为空', value: '$null'},
|
||||||
],
|
],
|
||||||
multipleSelect: [
|
multipleSelect: [
|
||||||
{label: '等于', value: 'eq', selected: true},
|
{label: '等于', value: '$match', selected: true},
|
||||||
{label: '不等于', value: 'ne'},
|
{label: '不等于', value: '$notMatch'},
|
||||||
{label: '包含', value: '$anyOf'},
|
{label: '包含', value: '$anyOf'},
|
||||||
{label: '不包含', value: '$noneOf'},
|
{label: '不包含', value: '$noneOf'},
|
||||||
{label: '非空', value: '$notNull'},
|
{label: '非空', value: '$notNull'},
|
||||||
@ -217,8 +217,8 @@ const op = {
|
|||||||
checkbox: OP_MAP.boolean,
|
checkbox: OP_MAP.boolean,
|
||||||
boolean: OP_MAP.boolean,
|
boolean: OP_MAP.boolean,
|
||||||
select: OP_MAP.select,
|
select: OP_MAP.select,
|
||||||
multipleSelect: OP_MAP.select,
|
multipleSelect: OP_MAP.multipleSelect,
|
||||||
checkboxes: OP_MAP.select,
|
checkboxes: OP_MAP.multipleSelect,
|
||||||
radio: OP_MAP.select,
|
radio: OP_MAP.select,
|
||||||
upload: OP_MAP.file,
|
upload: OP_MAP.file,
|
||||||
attachment: OP_MAP.file,
|
attachment: OP_MAP.file,
|
||||||
@ -237,7 +237,13 @@ const controls = {
|
|||||||
string: StringInput,
|
string: StringInput,
|
||||||
textarea: StringInput,
|
textarea: StringInput,
|
||||||
number: InputNumber,
|
number: InputNumber,
|
||||||
percent: InputNumber,
|
percent: (props) => (
|
||||||
|
<InputNumber
|
||||||
|
formatter={value => value ? `${value}%` : ''}
|
||||||
|
parser={value => value.replace('%', '')}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
boolean: BooleanControl,
|
boolean: BooleanControl,
|
||||||
checkbox: BooleanControl,
|
checkbox: BooleanControl,
|
||||||
select: OptionControl,
|
select: OptionControl,
|
||||||
@ -302,13 +308,13 @@ export function FilterItem(props: FilterItemProps) {
|
|||||||
const field = fields.find(field => field.name === props.dataSource.column);
|
const field = fields.find(field => field.name === props.dataSource.column);
|
||||||
if (field) {
|
if (field) {
|
||||||
setField(field);
|
setField(field);
|
||||||
setType(field.component.type);
|
let componentType = field.component.type;
|
||||||
// console.log(dataSource);
|
if (field.component.type === 'select' && field.multiple) {
|
||||||
|
componentType = 'multipleSelect';
|
||||||
|
}
|
||||||
|
setType(componentType);
|
||||||
}
|
}
|
||||||
setDataSource({...props.dataSource});
|
setDataSource({...props.dataSource});
|
||||||
// if (['boolean', 'checkbox'].indexOf(type) !== -1) {
|
|
||||||
// onChange({...dataSource, op: undefined});
|
|
||||||
// }
|
|
||||||
}, [
|
}, [
|
||||||
props.dataSource, type,
|
props.dataSource, type,
|
||||||
]);
|
]);
|
||||||
@ -328,10 +334,12 @@ export function FilterItem(props: FilterItemProps) {
|
|||||||
<Select value={dataSource.column}
|
<Select value={dataSource.column}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
const field = fields.find(field => field.name === value);
|
const field = fields.find(field => field.name === value);
|
||||||
if (field) {
|
let componentType = field.component.type;
|
||||||
setType(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 }}
|
style={{ width: 120 }}
|
||||||
placeholder={'选择字段'}>
|
placeholder={'选择字段'}>
|
||||||
|
@ -284,4 +284,9 @@ export default class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getFieldByPath(fieldPath: string) {
|
||||||
|
const [tableName, fieldName] = fieldPath.split('.');
|
||||||
|
return this.getTable(tableName).getField(fieldName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -312,7 +312,7 @@ export class ARRAY extends Column {
|
|||||||
public readonly options: Options.ArrayOptions;
|
public readonly options: Options.ArrayOptions;
|
||||||
|
|
||||||
public getDataType() {
|
public getDataType() {
|
||||||
return DataTypes.JSON;
|
return DataTypes.JSONB;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAttributeOptions() {
|
public getAttributeOptions() {
|
||||||
@ -325,6 +325,9 @@ export class ARRAY extends Column {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class JSON extends Column {
|
export class JSON extends Column {
|
||||||
|
public getDataType() {
|
||||||
|
return DataTypes.JSONB;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class JSONB extends Column {
|
export class JSONB extends Column {
|
||||||
|
@ -20,7 +20,11 @@ for (const key in Op) {
|
|||||||
// 通用
|
// 通用
|
||||||
|
|
||||||
// 是否为空:数据库意义的 null
|
// 是否为空:数据库意义的 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('$notNull', () => ({ [Op.not]: null }));
|
||||||
|
|
||||||
op.set('$isTruly', () => ({
|
op.set('$isTruly', () => ({
|
||||||
@ -55,9 +59,20 @@ op.set('$notEndsWith', (value: string) => ({ [Op.notILike]: `%${value}` }));
|
|||||||
// 多选(JSON)类型
|
// 多选(JSON)类型
|
||||||
|
|
||||||
// 包含组中任意值(命名来源:`Array.prototype.some`)
|
// 包含组中任意值(命名来源:`Array.prototype.some`)
|
||||||
op.set('$anyOf', (values: any[]) => ({
|
op.set('$anyOf', (values: any[], options) => {
|
||||||
[Op.or]: toArray(values).map(value => ({ [Op.contains]: value }))
|
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) }));
|
op.set('$allOf', (values: any) => ({ [Op.contains]: toArray(values) }));
|
||||||
// TODO(bug): 不包含组中任意值
|
// TODO(bug): 不包含组中任意值
|
||||||
@ -66,6 +81,9 @@ op.set('$noneOf', (values: any[], options) => {
|
|||||||
return Sequelize.literal('');
|
return Sequelize.literal('');
|
||||||
}
|
}
|
||||||
values = Array.isArray(values) ? values : [values];
|
values = Array.isArray(values) ? values : [values];
|
||||||
|
if (values.length === 0) {
|
||||||
|
return Sequelize.literal('');
|
||||||
|
}
|
||||||
const { field, fieldPath } = options;
|
const { field, fieldPath } = options;
|
||||||
const column = fieldPath.split('.').map(name => `"${name}"`).join('.');
|
const column = fieldPath.split('.').map(name => `"${name}"`).join('.');
|
||||||
const sql = values.map(value => `(${column})::jsonb @> '${JSON.stringify(value)}'`).join(' OR ');
|
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})`);
|
return Sequelize.literal(`not (${sql})`);
|
||||||
});
|
});
|
||||||
// 与组中值匹配
|
// 与组中值匹配
|
||||||
op.set('$match', (values: any[]) => {
|
op.set('$match', (values: any[], options) => {
|
||||||
const array = toArray(values);
|
const array = toArray(values);
|
||||||
return {
|
if (values.length === 0) {
|
||||||
[Op.contains]: array,
|
return Sequelize.literal('');
|
||||||
[Op.contained]: array
|
}
|
||||||
};
|
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;
|
export default op;
|
||||||
|
@ -20,7 +20,7 @@ export function toWhere(options: any, context: ToWhereContext = {}) {
|
|||||||
if (Array.isArray(options)) {
|
if (Array.isArray(options)) {
|
||||||
return options.map((item) => toWhere(item, context));
|
return options.map((item) => toWhere(item, context));
|
||||||
}
|
}
|
||||||
const { prefix, model, associations = {}, ctx, dialect } = context;
|
const { prefix, model, associations = {}, ctx, dialect, database } = context;
|
||||||
const items = {};
|
const items = {};
|
||||||
// 先处理「点号」的问题
|
// 先处理「点号」的问题
|
||||||
for (const key in options) {
|
for (const key in options) {
|
||||||
@ -54,7 +54,11 @@ export function toWhere(options: any, context: ToWhereContext = {}) {
|
|||||||
switch (typeof opKey) {
|
switch (typeof opKey) {
|
||||||
case 'function':
|
case 'function':
|
||||||
const name = model ? model.options.name.plural : '';
|
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') {
|
if (result.constructor.name === 'Literal') {
|
||||||
values['$__literals'] = values['$__literals'] || [];
|
values['$__literals'] = values['$__literals'] || [];
|
||||||
values['$__literals'].push(result);
|
values['$__literals'].push(result);
|
||||||
@ -184,7 +188,7 @@ export function toInclude(options: any, context: ToIncludeContext = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { fields = [], filter } = options;
|
const { fields = [], filter } = options;
|
||||||
const { model, sourceAlias, associations = {}, ctx, dialect } = context;
|
const { model, sourceAlias, associations = {}, ctx, database, dialect } = context;
|
||||||
|
|
||||||
let where = options.where || {};
|
let where = options.where || {};
|
||||||
|
|
||||||
@ -193,6 +197,7 @@ export function toInclude(options: any, context: ToIncludeContext = {}) {
|
|||||||
model,
|
model,
|
||||||
associations,
|
associations,
|
||||||
ctx,
|
ctx,
|
||||||
|
database,
|
||||||
}) || {};
|
}) || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,6 +157,7 @@ export const multipleSelect = {
|
|||||||
type: 'json', // json 过滤
|
type: 'json', // json 过滤
|
||||||
filterable: true,
|
filterable: true,
|
||||||
dataSource: [],
|
dataSource: [],
|
||||||
|
defaultValue: [],
|
||||||
multiple: true, // 需要重点考虑
|
multiple: true, // 需要重点考虑
|
||||||
component: {
|
component: {
|
||||||
type: 'select',
|
type: 'select',
|
||||||
@ -184,6 +185,7 @@ export const checkboxes = {
|
|||||||
type: 'json',
|
type: 'json',
|
||||||
filterable: true,
|
filterable: true,
|
||||||
dataSource: [],
|
dataSource: [],
|
||||||
|
defaultValue: [],
|
||||||
component: {
|
component: {
|
||||||
type: 'checkboxes',
|
type: 'checkboxes',
|
||||||
},
|
},
|
||||||
@ -387,7 +389,7 @@ export const createdBy = {
|
|||||||
interface: 'createdBy',
|
interface: 'createdBy',
|
||||||
type: 'createdBy',
|
type: 'createdBy',
|
||||||
// name: 'createdBy',
|
// name: 'createdBy',
|
||||||
filterable: true,
|
// filterable: true,
|
||||||
target: 'users',
|
target: 'users',
|
||||||
labelField: 'nickname',
|
labelField: 'nickname',
|
||||||
foreignKey: 'created_by_id',
|
foreignKey: 'created_by_id',
|
||||||
@ -405,7 +407,7 @@ export const updatedBy = {
|
|||||||
interface: 'updatedBy',
|
interface: 'updatedBy',
|
||||||
// name: 'updatedBy',
|
// name: 'updatedBy',
|
||||||
type: 'updatedBy',
|
type: 'updatedBy',
|
||||||
filterable: true,
|
// filterable: true,
|
||||||
target: 'users',
|
target: 'users',
|
||||||
labelField: 'nickname',
|
labelField: 'nickname',
|
||||||
foreignKey: 'created_by_id',
|
foreignKey: 'created_by_id',
|
||||||
|
Loading…
Reference in New Issue
Block a user