mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 07:25:15 +00:00
feat: add support for filter action
This commit is contained in:
parent
a44bab62fc
commit
0d3d30e0c2
59
packages/app/src/components/actions/Filter.tsx
Normal file
59
packages/app/src/components/actions/Filter.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import React, { useRef, useState } from 'react';
|
||||||
|
import { Button, Popover } from 'antd';
|
||||||
|
import ViewFactory from '@/components/views';
|
||||||
|
|
||||||
|
export function Filter(props) {
|
||||||
|
console.log(props);
|
||||||
|
const drawerRef = useRef<any>();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const { title, viewName, collection_name } = props.schema;
|
||||||
|
const { activeTab = {}, item = {}, associatedName, associatedKey } = props;
|
||||||
|
const { association } = activeTab;
|
||||||
|
|
||||||
|
const params = {};
|
||||||
|
|
||||||
|
if (association) {
|
||||||
|
params['resourceName'] = association;
|
||||||
|
params['associatedName'] = associatedName;
|
||||||
|
params['associatedKey'] = associatedKey;
|
||||||
|
} else {
|
||||||
|
params['resourceName'] = collection_name;
|
||||||
|
params['resourceKey'] = item.itemId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Popover
|
||||||
|
title="设置筛选"
|
||||||
|
trigger="click"
|
||||||
|
visible={visible}
|
||||||
|
placement={'bottomLeft'}
|
||||||
|
onVisibleChange={(visible) => {
|
||||||
|
setVisible(visible);
|
||||||
|
}}
|
||||||
|
className={'filters-popover'}
|
||||||
|
style={{
|
||||||
|
}}
|
||||||
|
overlayStyle={{
|
||||||
|
minWidth: 500
|
||||||
|
}}
|
||||||
|
content={(
|
||||||
|
<>
|
||||||
|
<div className={'popover-button-mask'} onClick={() => setVisible(false)}></div>
|
||||||
|
<ViewFactory
|
||||||
|
{...props}
|
||||||
|
viewName={'filter'}
|
||||||
|
{...params}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Button type={'primary'} onClick={() => {
|
||||||
|
setVisible(true);
|
||||||
|
}}>{title}</Button>
|
||||||
|
</Popover>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Filter;
|
@ -3,6 +3,7 @@ import React from 'react';
|
|||||||
import Create from './Create';
|
import Create from './Create';
|
||||||
import Update from './Update';
|
import Update from './Update';
|
||||||
import Destroy from './Destroy';
|
import Destroy from './Destroy';
|
||||||
|
import Filter from './Filter';
|
||||||
import { Space } from 'antd';
|
import { Space } from 'antd';
|
||||||
|
|
||||||
const ACTIONS = new Map<string, any>();
|
const ACTIONS = new Map<string, any>();
|
||||||
@ -14,6 +15,7 @@ export function registerAction(type: string, Action: any) {
|
|||||||
registerAction('update', Update);
|
registerAction('update', Update);
|
||||||
registerAction('create', Create);
|
registerAction('create', Create);
|
||||||
registerAction('destroy', Destroy);
|
registerAction('destroy', Destroy);
|
||||||
|
registerAction('filter', Filter);
|
||||||
|
|
||||||
export function getAction(type: string) {
|
export function getAction(type: string) {
|
||||||
return ACTIONS.get(type);
|
return ACTIONS.get(type);
|
||||||
|
263
packages/app/src/components/form.fields/filter/index.tsx
Normal file
263
packages/app/src/components/form.fields/filter/index.tsx
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Button, Select, Input, Space, Form, InputNumber, DatePicker } from 'antd';
|
||||||
|
import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
|
||||||
|
import useDynamicList from './useDynamicList';
|
||||||
|
import { connect } from '@formily/react-schema-renderer'
|
||||||
|
import { mapStyledProps } from '../shared'
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
export function FilterGroup(props: any) {
|
||||||
|
const { showDeleteButton = false, fields = [], onDelete, onChange, onAdd, dataSource = {} } = props;
|
||||||
|
const { list, getKey, push, remove, replace } = useDynamicList<any>(dataSource.list || [
|
||||||
|
{
|
||||||
|
type: 'item',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
return (
|
||||||
|
<div style={{marginBottom: 14, padding: 14, border: '1px dashed #dedede'}}>
|
||||||
|
<div style={{marginBottom: 14}}>
|
||||||
|
满足组内
|
||||||
|
{' '}
|
||||||
|
<Select style={{width: 80}} onChange={(value) => {
|
||||||
|
onChange({...dataSource, andor: value});
|
||||||
|
}} defaultValue={'and'}>
|
||||||
|
<Select.Option value={'and'}>全部</Select.Option>
|
||||||
|
<Select.Option value={'or'}>任意</Select.Option>
|
||||||
|
</Select>
|
||||||
|
{' '}
|
||||||
|
条件
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{list.map((item, index) => {
|
||||||
|
// console.log(item);
|
||||||
|
const Component = item.type === 'group' ? FilterGroup : FilterItem;
|
||||||
|
return (
|
||||||
|
<div style={{marginBottom: 14}}>
|
||||||
|
{<Component
|
||||||
|
fields={fields}
|
||||||
|
dataSource={item}
|
||||||
|
showDeleteButton={list.length > 1}
|
||||||
|
onChange={(value) => {
|
||||||
|
replace(index, value);
|
||||||
|
const newList = [...list];
|
||||||
|
newList[index] = value;
|
||||||
|
onChange({...dataSource, list: newList});
|
||||||
|
// console.log(list, value, index);
|
||||||
|
}}
|
||||||
|
onDelete={() => {
|
||||||
|
remove(index);
|
||||||
|
const newList = [...list];
|
||||||
|
newList.splice(index, 1);
|
||||||
|
onChange({...dataSource, list: newList});
|
||||||
|
// console.log(list, index);
|
||||||
|
}}
|
||||||
|
/>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Space>
|
||||||
|
<Button onClick={() => {
|
||||||
|
const data = {
|
||||||
|
type: 'item'
|
||||||
|
};
|
||||||
|
push(data);
|
||||||
|
const newList = [...list];
|
||||||
|
newList.push(data);
|
||||||
|
onChange({...dataSource, list: newList});
|
||||||
|
}}>
|
||||||
|
添加条件
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => {
|
||||||
|
const data = {
|
||||||
|
type: 'group',
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
type: 'item',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
push(data);
|
||||||
|
const newList = [...list];
|
||||||
|
newList.push(data);
|
||||||
|
onChange({...dataSource, list: newList});
|
||||||
|
}}>
|
||||||
|
添加条件组
|
||||||
|
</Button>
|
||||||
|
{showDeleteButton && <Button onClick={(e) => {
|
||||||
|
onDelete && onDelete(e);
|
||||||
|
}}>
|
||||||
|
删除组
|
||||||
|
</Button>}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FieldOptions {
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
interface: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FilterItemProps {
|
||||||
|
fields: FieldOptions[];
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OP_MAP = {
|
||||||
|
string: [
|
||||||
|
{label: '等于', value: 'eq'},
|
||||||
|
{label: '不等于', value: 'neq'},
|
||||||
|
{label: '包含', value: 'cont'},
|
||||||
|
{label: '不包含', value: 'ncont'},
|
||||||
|
{label: '非空', value: 'notnull'},
|
||||||
|
{label: '为空', value: 'null'},
|
||||||
|
],
|
||||||
|
number: [
|
||||||
|
{label: '等于', value: 'eq'},
|
||||||
|
{label: '不等于', value: 'neq'},
|
||||||
|
{label: '大于', value: 'gt'},
|
||||||
|
{label: '大于等于', value: 'gte'},
|
||||||
|
{label: '小于', value: 'lt'},
|
||||||
|
{label: '小于等于', value: 'lte'},
|
||||||
|
{label: '介于', value: 'between'},
|
||||||
|
{label: '非空', value: 'notnull'},
|
||||||
|
{label: '为空', value: 'null'},
|
||||||
|
],
|
||||||
|
file: [
|
||||||
|
{label: '非空', value: 'notnull'},
|
||||||
|
{label: '为空', value: 'null'},
|
||||||
|
],
|
||||||
|
boolean: [
|
||||||
|
{label: '等于', value: 'eq'},
|
||||||
|
],
|
||||||
|
choices: [
|
||||||
|
{label: '等于', value: 'eq'},
|
||||||
|
{label: '不等于', value: 'neq'},
|
||||||
|
{label: '包含', value: 'cont'},
|
||||||
|
{label: '不包含', value: 'ncont'},
|
||||||
|
{label: '非空', value: 'notnull'},
|
||||||
|
{label: '为空', value: 'null'},
|
||||||
|
],
|
||||||
|
datetime: [
|
||||||
|
{label: '等于', value: 'eq'},
|
||||||
|
{label: '不等于', value: 'neq'},
|
||||||
|
{label: '大于', value: 'gt'},
|
||||||
|
{label: '大于等于', value: 'gte'},
|
||||||
|
{label: '小于', value: 'lt'},
|
||||||
|
{label: '小于等于', value: 'lte'},
|
||||||
|
{label: '介于', value: 'between'},
|
||||||
|
{label: '非空', value: 'notnull'},
|
||||||
|
{label: '为空', value: 'null'},
|
||||||
|
{label: '是今天', value: 'now'},
|
||||||
|
{label: '在今天之前', value: 'before_today'},
|
||||||
|
{label: '在今天之后', value: 'after_today'},
|
||||||
|
],
|
||||||
|
linkTo: [
|
||||||
|
{label: '包含', value: 'cont'},
|
||||||
|
{label: '不包含', value: 'ncont'},
|
||||||
|
{label: '非空', value: 'notnull'},
|
||||||
|
{label: '为空', value: 'null'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const op = {
|
||||||
|
string: OP_MAP.string,
|
||||||
|
textarea: OP_MAP.string,
|
||||||
|
number: OP_MAP.number,
|
||||||
|
datetime: OP_MAP.datetime,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StringInput = (props) => {
|
||||||
|
const { onChange, ...restProps } = props;
|
||||||
|
return (
|
||||||
|
<Input {...restProps} onChange={(e) => {
|
||||||
|
onChange(e.target.value);
|
||||||
|
}}/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const controls = {
|
||||||
|
string: StringInput,
|
||||||
|
textarea: StringInput,
|
||||||
|
number: InputNumber,
|
||||||
|
// datetime: DatePicker,
|
||||||
|
datetime: (props) => {
|
||||||
|
const { value, onChange, ...restProps } = props;
|
||||||
|
const m = moment(value, 'YYYY-MM-DD HH:mm:ss');
|
||||||
|
return (
|
||||||
|
<DatePicker value={m.isValid() ? m : null} onChange={(value) => {
|
||||||
|
onChange(value ? value.format('YYYY-MM-DD HH:mm:ss') : null)
|
||||||
|
console.log(value.format('YYYY-MM-DD HH:mm:ss'));
|
||||||
|
}}/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function FilterItem(props: FilterItemProps) {
|
||||||
|
const { index, fields = [], showDeleteButton = false, onDelete, onChange, dataSource = {} } = props;
|
||||||
|
const [type, setType] = useState('string');
|
||||||
|
useEffect(() => {
|
||||||
|
const field = fields.find(field => field.name === dataSource.column);
|
||||||
|
if (field) {
|
||||||
|
setType(field.interface);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
dataSource,
|
||||||
|
]);
|
||||||
|
const ValueControl = controls[type]||controls.string;
|
||||||
|
return (
|
||||||
|
<Input.Group compact>
|
||||||
|
<Select value={dataSource.column}
|
||||||
|
onChange={(value) => {
|
||||||
|
const field = fields.find(field => field.name === value);
|
||||||
|
if (field) {
|
||||||
|
setType(field.interface);
|
||||||
|
}
|
||||||
|
onChange({...dataSource, column: value});
|
||||||
|
}}
|
||||||
|
style={{ width: '30%' }} placeholder={'选择字段'}>
|
||||||
|
{fields.map(field => (
|
||||||
|
<Select.Option value={field.name}>{field.title}</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<Select value={dataSource.op} style={{ minWidth: 100 }}
|
||||||
|
onChange={(value) => {
|
||||||
|
onChange({...dataSource, op: value});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(op[type]||op.string).map(option => (
|
||||||
|
<Select.Option value={option.value}>{option.label}</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<ValueControl value={dataSource.value} onChange={(value) => {
|
||||||
|
onChange({...dataSource, value: value});
|
||||||
|
}} style={{ width: '30%' }}/>
|
||||||
|
{showDeleteButton && (
|
||||||
|
<Button onClick={(e) => {
|
||||||
|
onDelete && onDelete(e);
|
||||||
|
}}>删除</Button>
|
||||||
|
)}
|
||||||
|
</Input.Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Filter = connect({
|
||||||
|
getProps: mapStyledProps,
|
||||||
|
})((props) => {
|
||||||
|
const dataSource = {
|
||||||
|
type: 'group',
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
type: 'item',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
};
|
||||||
|
return <FilterGroup dataSource={dataSource} {...props}/>
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Filter;
|
160
packages/app/src/components/form.fields/filter/useDynamicList.ts
Normal file
160
packages/app/src/components/form.fields/filter/useDynamicList.ts
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import { useCallback, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
export default <T>(initialValue: T[]) => {
|
||||||
|
const counterRef = useRef(-1);
|
||||||
|
// key 存储器
|
||||||
|
const keyList = useRef<number[]>([]);
|
||||||
|
|
||||||
|
// 内部方法
|
||||||
|
const setKey = useCallback((index: number) => {
|
||||||
|
counterRef.current += 1;
|
||||||
|
keyList.current.splice(index, 0, counterRef.current);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [list, setList] = useState(() => {
|
||||||
|
(initialValue || []).forEach((_, index) => {
|
||||||
|
setKey(index);
|
||||||
|
});
|
||||||
|
return initialValue || [];
|
||||||
|
});
|
||||||
|
|
||||||
|
const resetList = (newList: T[] = []) => {
|
||||||
|
keyList.current = [];
|
||||||
|
counterRef.current = -1;
|
||||||
|
setList(() => {
|
||||||
|
(newList || []).forEach((_, index) => {
|
||||||
|
setKey(index);
|
||||||
|
});
|
||||||
|
return newList || [];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const insert = (index: number, obj: T) => {
|
||||||
|
setList((l) => {
|
||||||
|
const temp = [...l];
|
||||||
|
temp.splice(index, 0, obj);
|
||||||
|
setKey(index);
|
||||||
|
return temp;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAll = () => list;
|
||||||
|
const getKey = (index: number) => keyList.current[index];
|
||||||
|
const getIndex = (index: number) => keyList.current.findIndex((ele) => ele === index);
|
||||||
|
|
||||||
|
const merge = (index: number, obj: T[]) => {
|
||||||
|
setList((l) => {
|
||||||
|
const temp = [...l];
|
||||||
|
obj.forEach((_, i) => {
|
||||||
|
setKey(index + i);
|
||||||
|
});
|
||||||
|
temp.splice(index, 0, ...obj);
|
||||||
|
return temp;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const replace = (index: number, obj: T) => {
|
||||||
|
setList((l) => {
|
||||||
|
const temp = [...l];
|
||||||
|
temp[index] = obj;
|
||||||
|
return temp;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const remove = (index: number) => {
|
||||||
|
setList((l) => {
|
||||||
|
const temp = [...l];
|
||||||
|
temp.splice(index, 1);
|
||||||
|
|
||||||
|
// remove keys if necessary
|
||||||
|
try {
|
||||||
|
keyList.current.splice(index, 1);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
return temp;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const move = (oldIndex: number, newIndex: number) => {
|
||||||
|
if (oldIndex === newIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setList((l) => {
|
||||||
|
const newList = [...l];
|
||||||
|
const temp = newList.filter((_: {}, index: number) => index !== oldIndex);
|
||||||
|
temp.splice(newIndex, 0, newList[oldIndex]);
|
||||||
|
|
||||||
|
// move keys if necessary
|
||||||
|
try {
|
||||||
|
const keyTemp = keyList.current.filter((_: {}, index: number) => index !== oldIndex);
|
||||||
|
keyTemp.splice(newIndex, 0, keyList.current[oldIndex]);
|
||||||
|
keyList.current = keyTemp;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return temp;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const push = (obj: T) => {
|
||||||
|
setList((l) => {
|
||||||
|
setKey(l.length);
|
||||||
|
return l.concat([obj]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const pop = () => {
|
||||||
|
// remove keys if necessary
|
||||||
|
try {
|
||||||
|
keyList.current = keyList.current.slice(0, keyList.current.length - 1);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
setList((l) => l.slice(0, l.length - 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
const unshift = (obj: T) => {
|
||||||
|
setList((l) => {
|
||||||
|
setKey(0);
|
||||||
|
return [obj].concat(l);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortForm = (result: unknown[]) =>
|
||||||
|
result
|
||||||
|
.map((item, index) => ({ key: index, item })) // add index into obj
|
||||||
|
.sort((a, b) => getIndex(a.key) - getIndex(b.key)) // sort based on the index of table
|
||||||
|
.filter((item) => !!item.item) // remove undefined(s)
|
||||||
|
.map((item) => item.item); // retrive the data
|
||||||
|
|
||||||
|
const shift = () => {
|
||||||
|
// remove keys if necessary
|
||||||
|
try {
|
||||||
|
keyList.current = keyList.current.slice(1, keyList.current.length);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
setList((l) => l.slice(1, l.length));
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
list,
|
||||||
|
insert,
|
||||||
|
merge,
|
||||||
|
replace,
|
||||||
|
remove,
|
||||||
|
getAll,
|
||||||
|
getKey,
|
||||||
|
getIndex,
|
||||||
|
move,
|
||||||
|
push,
|
||||||
|
pop,
|
||||||
|
unshift,
|
||||||
|
shift,
|
||||||
|
sortForm,
|
||||||
|
resetList,
|
||||||
|
};
|
||||||
|
};
|
@ -13,6 +13,7 @@ import { Radio } from './radio'
|
|||||||
import { Range } from './range'
|
import { Range } from './range'
|
||||||
import { Rating } from './rating'
|
import { Rating } from './rating'
|
||||||
import { Upload } from './upload'
|
import { Upload } from './upload'
|
||||||
|
import { Filter } from './filter'
|
||||||
|
|
||||||
export const setup = () => {
|
export const setup = () => {
|
||||||
registerFormFields({
|
registerFormFields({
|
||||||
@ -37,6 +38,7 @@ export const setup = () => {
|
|||||||
radio: Radio.Group,
|
radio: Radio.Group,
|
||||||
range: Range,
|
range: Range,
|
||||||
rating: Rating,
|
rating: Rating,
|
||||||
upload: Upload
|
upload: Upload,
|
||||||
|
filter: Filter,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
54
packages/app/src/components/views/Form/FilterForm.tsx
Normal file
54
packages/app/src/components/views/Form/FilterForm.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Tooltip, Card } from 'antd';
|
||||||
|
import {
|
||||||
|
SchemaForm,
|
||||||
|
SchemaMarkupField as Field,
|
||||||
|
createFormActions,
|
||||||
|
createAsyncFormActions,
|
||||||
|
Submit,
|
||||||
|
Reset,
|
||||||
|
FormButtonGroup,
|
||||||
|
registerFormFields,
|
||||||
|
FormValidator,
|
||||||
|
setValidationLanguage,
|
||||||
|
} from '@formily/antd';
|
||||||
|
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
export function FilterForm(props: any) {
|
||||||
|
const actions = createAsyncFormActions();
|
||||||
|
const { title, fields: properties ={} } = props.schema||{};
|
||||||
|
return (
|
||||||
|
<SchemaForm
|
||||||
|
colon={true}
|
||||||
|
layout={'vertical'}
|
||||||
|
initialValues={{}}
|
||||||
|
actions={actions}
|
||||||
|
schema={{
|
||||||
|
type: 'object',
|
||||||
|
properties,
|
||||||
|
}}
|
||||||
|
expressionScope={{
|
||||||
|
text(...args: any[]) {
|
||||||
|
return React.createElement('span', {}, ...args)
|
||||||
|
},
|
||||||
|
tooltip(title: string, offset = 3) {
|
||||||
|
return (
|
||||||
|
<Tooltip title={title}>
|
||||||
|
<QuestionCircleOutlined
|
||||||
|
style={{ margin: '0 3px', cursor: 'default', marginLeft: offset }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormButtonGroup>
|
||||||
|
<Reset>取消</Reset>
|
||||||
|
<Submit onClick={async () => {
|
||||||
|
const { values = {} } = await actions.submit();
|
||||||
|
console.log(values);
|
||||||
|
}}>确定</Submit>
|
||||||
|
</FormButtonGroup>
|
||||||
|
</SchemaForm>
|
||||||
|
);
|
||||||
|
}
|
@ -19,3 +19,4 @@ setValidationLanguage('zh-CN');
|
|||||||
|
|
||||||
export { Form } from './Form';
|
export { Form } from './Form';
|
||||||
export { DrawerForm } from './DrawerForm';
|
export { DrawerForm } from './DrawerForm';
|
||||||
|
export { FilterForm } from './FilterForm';
|
||||||
|
@ -5,7 +5,7 @@ import { useRequest } from 'umi';
|
|||||||
import { Spin } from '@nocobase/client';
|
import { Spin } from '@nocobase/client';
|
||||||
import { SimpleTable } from './SimpleTable';
|
import { SimpleTable } from './SimpleTable';
|
||||||
import { Table } from './Table';
|
import { Table } from './Table';
|
||||||
import { Form, DrawerForm } from './Form/index';
|
import { Form, DrawerForm, FilterForm } from './Form/index';
|
||||||
import { Details } from './Details';
|
import { Details } from './Details';
|
||||||
import './style.less';
|
import './style.less';
|
||||||
import { Login } from './Form/Login';
|
import { Login } from './Form/Login';
|
||||||
@ -21,6 +21,7 @@ export function getViewTemplate(template: string) {
|
|||||||
return TEMPLATES.get(template);
|
return TEMPLATES.get(template);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerView('FilterForm', FilterForm)
|
||||||
registerView('DrawerForm', DrawerForm);
|
registerView('DrawerForm', DrawerForm);
|
||||||
registerView('PermissionForm', DrawerForm);
|
registerView('PermissionForm', DrawerForm);
|
||||||
registerView('Form', Form);
|
registerView('Form', Form);
|
||||||
|
@ -21,10 +21,11 @@ const transforms = {
|
|||||||
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) {
|
||||||
if (!get(field.component, 'showInForm')) {
|
if (!field.get('component.showInForm')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const type = get(field.component, 'type', 'string');
|
const interfaceType = field.get('interface');
|
||||||
|
const type = field.get('component.type') || 'string';
|
||||||
const prop: any = {
|
const prop: any = {
|
||||||
type,
|
type,
|
||||||
title: field.title||field.name,
|
title: field.title||field.name,
|
||||||
@ -40,7 +41,7 @@ const transforms = {
|
|||||||
if (defaultValue) {
|
if (defaultValue) {
|
||||||
prop.default = defaultValue;
|
prop.default = defaultValue;
|
||||||
}
|
}
|
||||||
if (['radio', 'select', 'checkboxes'].includes(type)) {
|
if (['radio', 'select', 'checkboxes'].includes(interfaceType)) {
|
||||||
prop.enum = get(field.options, 'dataSource', []);
|
prop.enum = get(field.options, 'dataSource', []);
|
||||||
}
|
}
|
||||||
schema[field.name] = {
|
schema[field.name] = {
|
||||||
@ -62,12 +63,23 @@ const transforms = {
|
|||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
},
|
},
|
||||||
|
filter: async (fields: Model[], ctx?: any) => {
|
||||||
|
const properties = {
|
||||||
|
filter: {
|
||||||
|
type: 'filter',
|
||||||
|
'x-component-props': {
|
||||||
|
fields,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async (ctx, next) => {
|
export default async (ctx, next) => {
|
||||||
const { resourceName, resourceKey } = ctx.action.params;
|
const { resourceName, resourceKey } = ctx.action.params;
|
||||||
const [View, Field, Action] = ctx.db.getModels(['views', 'fields', 'actions']) as ModelCtor<Model>[];
|
const [View, Collection, Field, Action] = ctx.db.getModels(['views', 'collections', 'fields', 'actions']) as ModelCtor<Model>[];
|
||||||
const view = await View.findOne(View.parseApiJson({
|
let view = await View.findOne(View.parseApiJson({
|
||||||
filter: {
|
filter: {
|
||||||
collection_name: resourceName,
|
collection_name: resourceName,
|
||||||
name: resourceKey,
|
name: resourceKey,
|
||||||
@ -76,8 +88,15 @@ export default async (ctx, next) => {
|
|||||||
// appends: ['actions', 'fields'],
|
// appends: ['actions', 'fields'],
|
||||||
// },
|
// },
|
||||||
}));
|
}));
|
||||||
// console.log('getView', ctx.action.params, mode);
|
if (!view) {
|
||||||
const collection = await view.getCollection();
|
// 如果不存在 view,新建一个
|
||||||
|
view = new View({type: resourceKey, template: 'FilterForm'});
|
||||||
|
}
|
||||||
|
const collection = await Collection.findOne({
|
||||||
|
where: {
|
||||||
|
name: resourceName,
|
||||||
|
},
|
||||||
|
});
|
||||||
const fields = await collection.getFields({
|
const fields = await collection.getFields({
|
||||||
where: {
|
where: {
|
||||||
developerMode: ctx.state.developerMode,
|
developerMode: ctx.state.developerMode,
|
||||||
@ -94,9 +113,8 @@ export default async (ctx, next) => {
|
|||||||
['sort', 'asc'],
|
['sort', 'asc'],
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
const actionNames = view.options.actionNames||[];
|
const actionNames = view.get('actionNames') || [];
|
||||||
console.log(view.options);
|
if (view.get('type') === 'table') {
|
||||||
if (view.type === 'table') {
|
|
||||||
const defaultTabs = await collection.getTabs({
|
const defaultTabs = await collection.getTabs({
|
||||||
where: {
|
where: {
|
||||||
default: true,
|
default: true,
|
||||||
@ -104,14 +122,21 @@ export default async (ctx, next) => {
|
|||||||
});
|
});
|
||||||
view.setDataValue('defaultTabName', get(defaultTabs, [0, 'name']));
|
view.setDataValue('defaultTabName', get(defaultTabs, [0, 'name']));
|
||||||
}
|
}
|
||||||
if (view.options.updateViewName) {
|
if (view.get('updateViewName')) {
|
||||||
view.setDataValue('rowViewName', view.options.updateViewName);
|
view.setDataValue('rowViewName', view.get('updateViewName'));
|
||||||
}
|
}
|
||||||
view.setDataValue('viewCollectionName', view.collection_name);
|
view.setDataValue('viewCollectionName', view.collection_name);
|
||||||
|
let title = collection.get('title');
|
||||||
|
const mode = get(ctx.action.params, ['values', 'mode'], ctx.action.params.mode);
|
||||||
|
if (mode === 'update') {
|
||||||
|
title = `编辑${title}`;
|
||||||
|
} else {
|
||||||
|
title = `创建${title}`;
|
||||||
|
}
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
...view.toJSON(),
|
...view.get(),
|
||||||
...(view.options||{}),
|
title,
|
||||||
ofs: fields,
|
original: fields,
|
||||||
fields: await (transforms[view.type]||transforms.table)(fields, ctx),
|
fields: await (transforms[view.type]||transforms.table)(fields, ctx),
|
||||||
actions: actions.filter(action => actionNames.includes(action.name)).map(action => ({
|
actions: actions.filter(action => actionNames.includes(action.name)).map(action => ({
|
||||||
...action.toJSON(),
|
...action.toJSON(),
|
||||||
|
Loading…
Reference in New Issue
Block a user