improve filter & sort...

This commit is contained in:
chenos 2020-12-13 00:09:25 +08:00
parent 787d2b611f
commit 5fd8139767
15 changed files with 134 additions and 67 deletions

View File

@ -1,3 +1,4 @@
import { values } from 'lodash';
import { stringify } from 'querystring'; import { stringify } from 'querystring';
import { request } from 'umi'; import { request } from 'umi';
@ -28,7 +29,7 @@ class ApiClient {
const proxy: any = new Proxy({}, { const proxy: any = new Proxy({}, {
get(target, method, receiver) { get(target, method, receiver) {
return (params: ActionParams = {}) => { return (params: ActionParams = {}) => {
let { associatedKey, resourceKey, filter, sorter, sort = [], ...restParams } = params; let { associatedKey, resourceKey, filter, sorter, sort = [], values, ...restParams } = params;
let url = `/${name}`; let url = `/${name}`;
sort = sort || []; sort = sort || [];
let options: any = { let options: any = {
@ -39,7 +40,7 @@ class ApiClient {
options.params = restParams; options.params = restParams;
} else { } else {
options.method = 'post'; options.method = 'post';
options.data = restParams; options.data = values;
} }
if (associatedKey) { if (associatedKey) {
url = `/${name.split('.').join(`/${associatedKey}/`)}`; url = `/${name.split('.').join(`/${associatedKey}/`)}`;

View File

@ -30,7 +30,7 @@ export function FilterGroup(props: any) {
{' '} {' '}
<Select style={{width: 80}} onChange={(value) => { <Select style={{width: 80}} onChange={(value) => {
onChange({...dataSource, andor: value}); onChange({type: 'group', list, andor: value});
}} defaultValue={'and'}> }} defaultValue={'and'}>
<Select.Option value={'and'}></Select.Option> <Select.Option value={'and'}></Select.Option>
<Select.Option value={'or'}></Select.Option> <Select.Option value={'or'}></Select.Option>
@ -283,6 +283,24 @@ function toFilter(values: any) {
return filter; return filter;
} }
function toValues(filter: any = {}) {
let values: any = {};
Object.keys(filter).forEach(key => {
const value = filter[key];
if (Array.isArray(value)) {
values['andor'] = key;
values['type'] = 'group';
values['list'] = value.map(v => toValues(v));
} else if (typeof value === 'object') {
values['type'] = 'item';
values['column'] = key;
values['op'] = Object.keys(value).shift();
values['value'] = Object.values(value).shift();
}
});
return values;
}
export const Filter = connect({ export const Filter = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
})((props) => { })((props) => {
@ -295,7 +313,9 @@ export const Filter = connect({
], ],
}; };
const { value, onChange, ...restProps } = props; const { value, onChange, ...restProps } = props;
return <FilterGroup dataSource={dataSource} onChange={(values) => { console.log('valuevaluevaluevaluevaluevalue', value);
return <FilterGroup dataSource={value ? toValues(value) : dataSource} onChange={(values) => {
console.log(values);
onChange(toFilter(values)); onChange(toFilter(values));
}} {...restProps}/> }} {...restProps}/>
}); });

View File

@ -20,8 +20,9 @@ export const setup = () => {
time: TimePicker, time: TimePicker,
timerange: TimePicker.RangePicker, timerange: TimePicker.RangePicker,
transfer: Transfer, transfer: Transfer,
boolean: Switch, boolean: Checkbox,
checkbox: Switch, switch: Switch,
checkbox: Checkbox,
array: ArrayCards, array: ArrayCards,
cards: ArrayCards, cards: ArrayCards,
table: ArrayTable, table: ArrayTable,

View File

@ -67,12 +67,12 @@ export const DrawerForm = forwardRef((props: any, ref) => {
await api.resource(name).update({ await api.resource(name).update({
resourceKey, resourceKey,
associatedKey, associatedKey,
...values, values,
}); });
} else { } else {
await api.resource(name).create({ await api.resource(name).create({
associatedKey, associatedKey,
...values, values,
}); });
} }
setVisible(false); setVisible(false);

View File

@ -25,17 +25,18 @@ export function SimpleTable(props: SimpleTableProps) {
associatedName, associatedName,
associatedKey, associatedKey,
} = props; } = props;
const { rowKey = 'id', fields = [], rowViewName, actions = [], paginated = true, defaultPageSize = 10 } = schema; const { rowKey = 'id', fields = [], rowViewName, actions = [], paginated = true, defaultPerPage = 10 } = schema;
const { sourceKey = 'id' } = activeTab.field||{}; const { sourceKey = 'id' } = activeTab.field||{};
const drawerRef = useRef<any>(); const drawerRef = useRef<any>();
const name = associatedName ? `${associatedName}.${resourceName}` : resourceName; const name = associatedName ? `${associatedName}.${resourceName}` : resourceName;
const { data, loading, pagination, mutate, refresh, params, run } = useRequest((params = {}) => { const { data, loading, pagination, mutate, refresh, params, run } = useRequest((params = {}) => {
const { current, pageSize, sorter, ...restParams } = params; const { current, pageSize, sorter, filter, ...restParams } = params;
return api.resource(name).list({ return api.resource(name).list({
associatedKey, associatedKey,
page: paginated ? current : 1, page: paginated ? current : 1,
perPage: paginated ? pageSize : -1, perPage: paginated ? pageSize : -1,
sorter, sorter,
filter,
}) })
.then(({data = [], meta = {}}) => { .then(({data = [], meta = {}}) => {
return { return {
@ -47,7 +48,7 @@ export function SimpleTable(props: SimpleTableProps) {
}); });
}, { }, {
paginated, paginated,
defaultPageSize, defaultPageSize: defaultPerPage,
}); });
console.log(schema, data); console.log(schema, data);
const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [selectedRowKeys, setSelectedRowKeys] = useState([]);
@ -110,15 +111,17 @@ export function SimpleTable(props: SimpleTableProps) {
data, data,
mutate, mutate,
rowKey, rowKey,
onMoved: async ({resourceKey, offset}) => { onMoved: async ({resourceKey, target}) => {
await api.resource(name).sort({ await api.resource(name).sort({
associatedKey, associatedKey,
resourceKey, resourceKey,
values: {
field: 'sort', field: 'sort',
offset, target,
},
}); });
await refresh(); await refresh();
console.log({resourceKey, offset}); console.log({resourceKey, target});
} }
})} })}
onRow={(record) => ({ onRow={(record) => ({

View File

@ -30,6 +30,7 @@ export const components = ({data = {}, rowKey, mutate, onMoved}: Props) => {
helperClass="row-dragging" helperClass="row-dragging"
onSortEnd={async ({ oldIndex, newIndex, ...restProps }) => { onSortEnd={async ({ oldIndex, newIndex, ...restProps }) => {
if (oldIndex !== newIndex) { if (oldIndex !== newIndex) {
const targetIndex = get(data.list, [newIndex, rowKey]);
const list = arrayMove([].concat(data.list), oldIndex, newIndex).filter(el => !!el); const list = arrayMove([].concat(data.list), oldIndex, newIndex).filter(el => !!el);
console.log({oldIndex, newIndex, list}); console.log({oldIndex, newIndex, list});
mutate({ mutate({
@ -37,7 +38,7 @@ export const components = ({data = {}, rowKey, mutate, onMoved}: Props) => {
list, list,
}); });
const resourceKey = get(list, [newIndex, rowKey]); const resourceKey = get(list, [newIndex, rowKey]);
await onMoved({resourceKey, offset: newIndex - oldIndex}); await onMoved({resourceKey, target: {[rowKey]: targetIndex}});
} }
}} }}
{...props} {...props}

View File

@ -41,7 +41,7 @@ export function Table(props: TableProps) {
associatedName, associatedName,
associatedKey, associatedKey,
} = props; } = props;
const { fields, defaultTabName, rowKey = 'id', actions = [], paginated = true, defaultPageSize = 10 } = schema; const { fields, defaultTabName, rowKey = 'id', actions = [], paginated = true, defaultPerPage = 10 } = schema;
// const { data, mutate } = useRequest(() => api.resource(name).list({ // const { data, mutate } = useRequest(() => api.resource(name).list({
// associatedKey, // associatedKey,
// })); // }));
@ -67,7 +67,7 @@ export function Table(props: TableProps) {
}); });
}, { }, {
paginated, paginated,
defaultPageSize, defaultPageSize: defaultPerPage,
}); });
const { sourceKey = 'id' } = activeTab.field||{}; const { sourceKey = 'id' } = activeTab.field||{};
console.log(props); console.log(props);
@ -120,15 +120,17 @@ export function Table(props: TableProps) {
data, data,
mutate, mutate,
rowKey, rowKey,
onMoved: async ({resourceKey, offset}) => { onMoved: async ({resourceKey, target}) => {
await api.resource(name).sort({ await api.resource(name).sort({
associatedKey, associatedKey,
resourceKey, resourceKey,
values: {
field: 'sort', field: 'sort',
offset, target,
},
}); });
await refresh(); await refresh();
console.log({resourceKey, offset}); console.log({resourceKey, target});
} }
})} })}
onRow={(data) => ({ onRow={(data) => ({

View File

@ -49,11 +49,15 @@ export default function ViewFactory(props: ViewProps) {
mode, mode,
reference, reference,
} = props; } = props;
console.log('propspropspropspropspropspropsprops', props);
const { data = {}, loading } = useRequest(() => { const { data = {}, loading } = useRequest(() => {
const params = { const params = {
resourceKey: viewName, resourceKey: viewName,
values: {
associatedKey: associatedKey,
associatedName: associatedName, associatedName: associatedName,
mode, mode
},
}; };
return api.resource(resourceName).getView(params); return api.resource(resourceName).getView(params);
}, { }, {

View File

@ -10,8 +10,9 @@ export default {
fields: [ fields: [
{ {
interface: 'sort', interface: 'sort',
type: 'integer', type: 'sort',
name: 'sort', name: 'sort',
scope: ['collection'],
title: '排序', title: '排序',
component: { component: {
type: 'sort', type: 'sort',
@ -149,13 +150,7 @@ export default {
detailsViewName: 'details', detailsViewName: 'details',
updateViewName: 'form', updateViewName: 'form',
paginated: false, paginated: false,
}, draggable: true,
{
type: 'table',
name: 'table',
title: '列表',
template: 'Table',
actionNames: ['create', 'destroy'],
}, },
], ],
} as TableOptions; } as TableOptions;

View File

@ -11,7 +11,7 @@ export default {
fields: [ fields: [
{ {
interface: 'sort', interface: 'sort',
type: 'integer', type: 'sort',
name: 'sort', name: 'sort',
title: '排序', title: '排序',
component: { component: {
@ -295,6 +295,7 @@ export default {
template: 'Table', template: 'Table',
actionNames: ['create', 'destroy'], actionNames: ['create', 'destroy'],
default: true, default: true,
draggable: true,
}, },
], ],
tabs: [ tabs: [

View File

@ -11,8 +11,9 @@ export default {
fields: [ fields: [
{ {
interface: 'sort', interface: 'sort',
type: 'integer', type: 'sort',
name: 'sort', name: 'sort',
scope: ['collection'],
title: '排序', title: '排序',
defaultValue: 1, defaultValue: 1,
component: { component: {
@ -27,6 +28,7 @@ export default {
type: 'string', type: 'string',
name: 'title', name: 'title',
title: '字段名称', title: '字段名称',
required: true,
component: { component: {
type: 'string', type: 'string',
className: 'drag-visible', className: 'drag-visible',
@ -42,6 +44,7 @@ export default {
title: '标识', title: '标识',
required: true, required: true,
createOnly: true, createOnly: true,
developerMode: true,
component: { component: {
type: 'string', type: 'string',
showInTable: true, showInTable: true,
@ -383,13 +386,7 @@ export default {
detailsViewName: 'details', detailsViewName: 'details',
updateViewName: 'form', updateViewName: 'form',
paginated: false, paginated: false,
}, draggable: true,
{
type: 'table',
name: 'table',
title: '列表',
template: 'Table',
actionNames: ['create', 'destroy'],
}, },
], ],
} as TableOptions; } as TableOptions;

View File

@ -10,8 +10,9 @@ export default {
fields: [ fields: [
{ {
interface: 'sort', interface: 'sort',
type: 'integer', type: 'sort',
name: 'sort', name: 'sort',
scope: ['collection'],
title: '排序', title: '排序',
component: { component: {
type: 'sort', type: 'sort',
@ -51,6 +52,7 @@ export default {
type: 'string', type: 'string',
name: 'type', name: 'type',
title: '类型', title: '类型',
required: true,
dataSource: [ dataSource: [
{ label: '详情数据', value: 'details' }, { label: '详情数据', value: 'details' },
{ label: '相关数据', value: 'association', disabled: true }, { label: '相关数据', value: 'association', disabled: true },
@ -189,13 +191,7 @@ export default {
detailsViewName: 'details', detailsViewName: 'details',
updateViewName: 'form', updateViewName: 'form',
paginated: false, paginated: false,
}, draggable: true,
{
type: 'table',
name: 'table',
title: '列表',
template: 'Table',
actionNames: ['create', 'destroy'],
}, },
], ],
} as TableOptions; } as TableOptions;

View File

@ -10,8 +10,9 @@ export default {
fields: [ fields: [
{ {
interface: 'sort', interface: 'sort',
type: 'integer', type: 'sort',
name: 'sort', name: 'sort',
scope: ['collection'],
title: '排序', title: '排序',
component: { component: {
type: 'sort', type: 'sort',
@ -62,6 +63,25 @@ export default {
showInTable: true, showInTable: true,
showInDetail: true, showInDetail: true,
showInForm: true, showInForm: true,
"x-linkages": [
{
"type": "value:visible",
"target": "filter",
"condition": "{{ $self.value !== 'form' }}"
},
],
},
},
{
interface: 'json',
type: 'json',
name: 'filter',
title: '筛选数据',
developerMode: false,
mode: 'replace',
component: {
type: 'filter',
showInForm: true,
}, },
}, },
{ {
@ -234,13 +254,7 @@ export default {
detailsViewName: 'details', detailsViewName: 'details',
updateViewName: 'form', updateViewName: 'form',
paginated: false, paginated: false,
}, draggable: true,
{
type: 'table',
name: 'table',
title: '列表',
template: 'Table',
actionNames: ['create', 'destroy'],
}, },
], ],
} as TableOptions; } as TableOptions;

View File

@ -82,9 +82,13 @@ export class BaseModel extends Model {
} }
// 如果是 object 数据merge 处理 // 如果是 object 数据merge 处理
if (_.isPlainObject(value)) { if (_.isPlainObject(value)) {
// TODO 需要改进 JSON 字段的内部处理逻辑,暂时这里跳过了特殊的 filter 字段
if (key !== 'filter') {
// console.log(key, value);
// @ts-ignore // @ts-ignore
value = merge(this.get(key)||{}, value); value = merge(this.get(key)||{}, value);
} }
}
const [column, ...path] = key.split('.'); const [column, ...path] = key.split('.');
if (!options.raw) { if (!options.raw) {
this.changed(column, true); this.changed(column, true);

View File

@ -1,6 +1,6 @@
import { ResourceOptions } from '@nocobase/resourcer';
import { Model, ModelCtor } from '@nocobase/database'; import { Model, ModelCtor } from '@nocobase/database';
import { get, set } from 'lodash'; import { get, set } from 'lodash';
import { Op } from 'sequelize';
const transforms = { const transforms = {
table: async (fields: Model[], context?: any) => { table: async (fields: Model[], context?: any) => {
@ -18,6 +18,7 @@ const transforms = {
return arr; return arr;
}, },
form: async (fields: Model[], ctx?: any) => { form: async (fields: Model[], ctx?: any) => {
const [Field] = ctx.db.getModels(['fields']) as ModelCtor<Model>[];
const mode = get(ctx.action.params, ['values', 'mode'], ctx.action.params.mode); const mode = get(ctx.action.params, ['values', 'mode'], ctx.action.params.mode);
const schema = {}; const schema = {};
for (const field of fields) { for (const field of fields) {
@ -31,9 +32,26 @@ const transforms = {
title: field.title||field.name, title: field.title||field.name,
...(field.component||{}), ...(field.component||{}),
} }
if (field.get('name') === 'filter' && field.get('collection_name') === 'views') {
const { values } = ctx.action.params;
const all = await Field.findAll({
where: {
collection_name: get(values, 'associatedKey'),
developerMode: ctx.state.developerMode,
},
order: [['sort', 'asc']],
});
set(prop, 'x-component-props.fields', all.filter(f => f.get('filterable')));
}
if (type === 'select') { if (type === 'select') {
prop.type = 'string' prop.type = 'string'
} }
if (field.get('component.tooltip')) {
prop.description = field.get('component.tooltip');
}
if (field.get('required')) {
prop.required = true;
}
if (mode === 'update' && field.get('createOnly')) { if (mode === 'update' && field.get('createOnly')) {
set(prop, 'x-component-props.disabled', true); set(prop, 'x-component-props.disabled', true);
} }
@ -44,6 +62,10 @@ const transforms = {
if (defaultValue) { if (defaultValue) {
prop.default = defaultValue; prop.default = defaultValue;
} }
if (interfaceType === 'boolean') {
set(prop, 'x-component-props.children', prop.title);
delete prop.title;
}
if (['radio', 'select', 'checkboxes'].includes(interfaceType)) { if (['radio', 'select', 'checkboxes'].includes(interfaceType)) {
prop.enum = get(field.options, 'dataSource', []); prop.enum = get(field.options, 'dataSource', []);
} }
@ -71,9 +93,9 @@ const transforms = {
filter: { filter: {
type: 'filter', type: 'filter',
'x-component-props': { 'x-component-props': {
fields, fields: fields.filter(field => field.get('filterable')),
},
}, },
}
} }
return properties; return properties;
}, },
@ -100,10 +122,16 @@ export default async (ctx, next) => {
name: resourceName, name: resourceName,
}, },
}); });
const fields = await collection.getFields({ const where: any = {
where: {
developerMode: ctx.state.developerMode, developerMode: ctx.state.developerMode,
}, }
if (!view.get('draggable')) {
where.type = {
[Op.not]: 'sort',
};
}
const fields = await collection.getFields({
where,
order: [ order: [
['sort', 'asc'], ['sort', 'asc'],
] ]
@ -128,7 +156,7 @@ export default async (ctx, next) => {
if (view.get('updateViewName')) { if (view.get('updateViewName')) {
view.setDataValue('rowViewName', view.get('updateViewName')); view.setDataValue('rowViewName', view.get('updateViewName'));
} }
view.setDataValue('viewCollectionName', view.collection_name); // view.setDataValue('viewCollectionName', view.collection_name);
let title = collection.get('title'); let title = collection.get('title');
const mode = get(ctx.action.params, ['values', 'mode'], ctx.action.params.mode); const mode = get(ctx.action.params, ['values', 'mode'], ctx.action.params.mode);
if (mode === 'update') { if (mode === 'update') {
@ -144,7 +172,7 @@ export default async (ctx, next) => {
actions: actions.filter(action => actionNames.includes(action.name)).map(action => ({ actions: actions.filter(action => actionNames.includes(action.name)).map(action => ({
...action.toJSON(), ...action.toJSON(),
...action.options, ...action.options,
viewCollectionName: action.collection_name, // viewCollectionName: action.collection_name,
})), })),
}; };
await next(); await next();