diff --git a/packages/api/src/migrations/init.ts b/packages/api/src/migrations/init.ts index cd1a377db..11c916c96 100644 --- a/packages/api/src/migrations/init.ts +++ b/packages/api/src/migrations/init.ts @@ -21,6 +21,7 @@ import * as uiSchema from './ui-schema'; const Collection = database.getModel('collections'); const collection = await Collection.create(config); await collection.updateAssociations(config); + await collection.migrate(); const Route = database.getModel('routes'); diff --git a/packages/client/package.json b/packages/client/package.json index c9f977b72..8faf1d0f0 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -29,6 +29,7 @@ "axios": "^0.21.1", "beautiful-react-hooks": "^0.35.0", "constate": "^3.3.0", + "flat": "^5.0.2", "html-react-parser": "^1.2.7", "lodash": "^4.17.21", "monaco-editor": "^0.25.2", diff --git a/packages/client/src/components/schema-renderer/index.tsx b/packages/client/src/components/schema-renderer/index.tsx index ba5d9ec32..e061e0c94 100644 --- a/packages/client/src/components/schema-renderer/index.tsx +++ b/packages/client/src/components/schema-renderer/index.tsx @@ -19,7 +19,7 @@ import { useMemo } from 'react'; import { CodeOutlined } from '@ant-design/icons'; import Editor from '@monaco-editor/react'; -import { ArrayCollapse, FormLayout, FormItem as FormilyFormItem } from '@formily/antd'; +import { ArrayItems, ArrayCollapse, FormLayout, FormItem as FormilyFormItem } from '@formily/antd'; import { Space, Card, Modal, Spin } from 'antd'; import { ArrayTable } from '../../schemas/array-table'; @@ -74,6 +74,7 @@ export const SchemaField = createSchemaField({ Page, Chart, + ArrayItems, ArrayCollapse, ArrayTable, FormLayout, @@ -365,6 +366,47 @@ export function useDesignable(path?: any) { refresh(); return target; }, + moveToBefore(path1, path2) { + const source = findPropertyByPath(schema, path1); + const property = source.toJSON(); + const target = findPropertyByPath(schema, path2); + if (!target) { + console.error('target schema does not exist.'); + return; + } + if (!property.name) { + property.name = uid(); + } + if (target['parentKey']) { + property['parentKey'] = target['parentKey']; + setKeys(property); + property['__insertBefore__'] = target['key']; + } + addPropertyBefore(target, property); + refresh(); + return target.parent.properties[property.name]; + }, + moveToAfter(path1, path2) { + const source = findPropertyByPath(schema, path1); + const property = source.toJSON(); + const target = findPropertyByPath(schema, path2); + if (!target) { + console.error('target schema does not exist.'); + return; + } + if (!property.name) { + property.name = uid(); + } + if (target['parentKey']) { + property['parentKey'] = target['parentKey']; + setKeys(property); + property['__insertAfter__'] = target['key']; + } + addPropertyAfter(target, property); + + refresh(); + return target.parent.properties[property.name]; + }, }; } diff --git a/packages/client/src/constate/Collections.ts b/packages/client/src/constate/Collections.ts index 86a3c3769..64c671373 100644 --- a/packages/client/src/constate/Collections.ts +++ b/packages/client/src/constate/Collections.ts @@ -14,31 +14,36 @@ export interface CollectionProviderProps { collectionName?: string; } -const [CollectionProvider, useCollectionContext] = constate( - (props: CollectionProviderProps) => { - const { collectionName } = props; - const { collections = [], loading, refresh } = useCollectionsContext(); - let collection: any; - let fields = []; - if (collectionName) { - collection = collections.find((item) => item.name === collectionName); - } - if (collection) { - fields = collection?.fields || []; - } - return { - collection, - fields, - loading, - refresh, - getField(name: string) { - if (!name) { - return null; - } - return fields.find((item) => item.name === name); - }, - }; - }, -); +export function useCollection(props: CollectionProviderProps) { + const { collectionName } = props; + const { collections = [], loading, refresh } = useCollectionsContext(); + let collection: any; + let fields = []; + if (collectionName) { + collection = collections.find((item) => item.name === collectionName); + } + if (collection) { + fields = collection?.fields || []; + } + let sortableField = collection.sortable; + if (collection.sortable && typeof collection.sortable === 'object') { + sortableField = collection.sortable?.name; + } + return { + sortableField, + collection, + fields, + loading, + refresh, + getField(name: string) { + if (!name) { + return null; + } + return fields.find((item) => item.name === name); + }, + }; +} + +const [CollectionProvider, useCollectionContext] = constate(useCollection); export { CollectionProvider, useCollectionContext }; diff --git a/packages/client/src/resource.ts b/packages/client/src/resource.ts index ed05b64f5..9a1ab5b2c 100644 --- a/packages/client/src/resource.ts +++ b/packages/client/src/resource.ts @@ -17,6 +17,8 @@ export interface SaveOptions { } export interface ListOptions { + defaultFilter?: any; + filter?: any; } export class Resource { @@ -31,9 +33,28 @@ export class Resource { } } - list(options: ListOptions = {}) { + sort(options) { const { resourceName } = this.options; - return request(`${resourceName}:list`); + const { resourceKey, target, field = 'sort' } = options; + return request(`${resourceName}:sort/${resourceKey}`, { + method: 'post', + data: { + target, + field, + }, + }); + } + + list(options: ListOptions = {}) { + const { defaultFilter, filter, ...others } = options; + const { resourceName } = this.options; + return request(`${resourceName}:list`, { + method: 'get', + params: { + filter: decodeURIComponent(JSON.stringify({ and: [defaultFilter, filter].filter(Boolean) })), + ...others, + }, + }); } get(options: GetOptions = {}) { diff --git a/packages/client/src/schemas/database-field/interfaces/createdAt.ts b/packages/client/src/schemas/database-field/interfaces/createdAt.ts index beea03a6a..524d4acf2 100644 --- a/packages/client/src/schemas/database-field/interfaces/createdAt.ts +++ b/packages/client/src/schemas/database-field/interfaces/createdAt.ts @@ -7,6 +7,7 @@ export const createdAt: ISchema = { group: 'systemInfo', order: 1, title: '创建时间', + sortable: true, default: { dataType: 'date', field: 'created_at', diff --git a/packages/client/src/schemas/database-field/interfaces/datetime.ts b/packages/client/src/schemas/database-field/interfaces/datetime.ts index d1218e9a5..90b6662fe 100644 --- a/packages/client/src/schemas/database-field/interfaces/datetime.ts +++ b/packages/client/src/schemas/database-field/interfaces/datetime.ts @@ -7,6 +7,7 @@ export const datetime: ISchema = { group: 'datetime', order: 1, title: '日期', + sortable: true, default: { dataType: 'date', // name, diff --git a/packages/client/src/schemas/database-field/interfaces/email.ts b/packages/client/src/schemas/database-field/interfaces/email.ts index 762907f05..9af5abf24 100644 --- a/packages/client/src/schemas/database-field/interfaces/email.ts +++ b/packages/client/src/schemas/database-field/interfaces/email.ts @@ -7,6 +7,7 @@ export const email: ISchema = { group: 'basic', order: 4, title: '电子邮箱', + sortable: true, default: { dataType: 'string', // name, diff --git a/packages/client/src/schemas/database-field/interfaces/number.ts b/packages/client/src/schemas/database-field/interfaces/number.ts index 6db0b9b44..778f217b3 100644 --- a/packages/client/src/schemas/database-field/interfaces/number.ts +++ b/packages/client/src/schemas/database-field/interfaces/number.ts @@ -7,6 +7,7 @@ export const number: ISchema = { group: 'basic', order: 5, title: '数字', + sortable: true, default: { dataType: 'float', // name, diff --git a/packages/client/src/schemas/database-field/interfaces/percent.ts b/packages/client/src/schemas/database-field/interfaces/percent.ts index bd82c22b8..1fd1b0722 100644 --- a/packages/client/src/schemas/database-field/interfaces/percent.ts +++ b/packages/client/src/schemas/database-field/interfaces/percent.ts @@ -7,6 +7,7 @@ export const percent: ISchema = { group: 'basic', order: 6, title: '百分比', + sortable: true, default: { dataType: 'float', // name, diff --git a/packages/client/src/schemas/database-field/interfaces/phone.ts b/packages/client/src/schemas/database-field/interfaces/phone.ts index 53f2c348a..4a245af3c 100644 --- a/packages/client/src/schemas/database-field/interfaces/phone.ts +++ b/packages/client/src/schemas/database-field/interfaces/phone.ts @@ -7,6 +7,7 @@ export const phone: ISchema = { group: 'basic', order: 3, title: '手机号码', + sortable: true, default: { dataType: 'string', // name, diff --git a/packages/client/src/schemas/database-field/interfaces/string.ts b/packages/client/src/schemas/database-field/interfaces/string.ts index 0350b5d72..524f7a69e 100644 --- a/packages/client/src/schemas/database-field/interfaces/string.ts +++ b/packages/client/src/schemas/database-field/interfaces/string.ts @@ -7,6 +7,7 @@ export const string: ISchema = { group: 'basic', order: 1, title: '单行文本', + sortable: true, default: { interface: 'string', dataType: 'string', diff --git a/packages/client/src/schemas/database-field/interfaces/time.ts b/packages/client/src/schemas/database-field/interfaces/time.ts index 7866bdd8b..7579e897d 100644 --- a/packages/client/src/schemas/database-field/interfaces/time.ts +++ b/packages/client/src/schemas/database-field/interfaces/time.ts @@ -7,6 +7,7 @@ export const time: ISchema = { group: 'datetime', order: 2, title: '时间', + sortable: true, default: { dataType: 'time', // name, diff --git a/packages/client/src/schemas/database-field/interfaces/updatedAt.ts b/packages/client/src/schemas/database-field/interfaces/updatedAt.ts index 909e803ce..7a48d2069 100644 --- a/packages/client/src/schemas/database-field/interfaces/updatedAt.ts +++ b/packages/client/src/schemas/database-field/interfaces/updatedAt.ts @@ -7,6 +7,7 @@ export const updatedAt: ISchema = { group: 'systemInfo', order: 2, title: '最后更新时间', + sortable: true, default: { dataType: 'date', field: 'updated_at', diff --git a/packages/client/src/schemas/filter/FilterItem.tsx b/packages/client/src/schemas/filter/FilterItem.tsx index fa864568c..6ab03483e 100644 --- a/packages/client/src/schemas/filter/FilterItem.tsx +++ b/packages/client/src/schemas/filter/FilterItem.tsx @@ -1,7 +1,7 @@ import React, { useContext, useMemo, useState } from 'react'; import { SchemaField } from '..'; -import { createForm, onFieldChange, onFieldReact } from '@formily/core'; -import { FormProvider, FormConsumer, useFieldSchema, Schema, SchemaOptionsContext, ISchema } from '@formily/react'; +import { createForm, onFieldChange, onFieldReact, onFormValuesChange } from '@formily/core'; +import { FormProvider, FormConsumer, useFieldSchema, Schema, SchemaOptionsContext, ISchema, SchemaKey } from '@formily/react'; import { Field } from '@formily/core/esm/models/Field'; import { Form } from '@formily/core/esm/models/Form'; import { @@ -14,26 +14,73 @@ import { } from '@formily/antd'; import { CloseCircleOutlined } from '@ant-design/icons'; import { get } from 'lodash'; +import { isValid } from '@formily/shared'; -function useFilterColumns(): Schema[] { +function useFilterColumns(): Map { const schema = useFieldSchema(); const columns = schema.reduceProperties((columns, current) => { if (current['x-component'] === 'Filter.Column') { - return [...columns, current]; + const fieldName = Object.keys(current.properties).shift(); + columns.set(fieldName, current); + return columns; } - return [...columns]; - }, []); + return columns; + }, new Map()); return columns; } export const FilterItem = (props) => { - const { initialValues = {}, onRemove } = props; - + const { value, initialValues = {}, onRemove, onChange } = props; const options = useContext(SchemaOptionsContext); - - const { key } = initialValues; const columns = useFilterColumns(); - console.log('useFilterColumns', columns); + console.log('FilterItem', value) + + const toValues = (value) => { + if (!value) { + return {}; + } + if (Object.keys(value).length === 0) { + return {}; + } + const fieldName = Object.keys(value).shift(); + const nested = value[fieldName]; + const column = columns.get(fieldName).toJSON(); + if (!nested) { + return { + column, + } + } + if (Object.keys(nested).length === 0) { + return { + column, + } + } + const operationValue = Object.keys(nested).shift(); + console.log('toValues', {operationValue}); + const operations = column?.['x-component-props']?.['operations']||[]; + const operation = operations.find(operation => operation.value === operationValue); + console.log('toValues', {operation}); + if (!operation) { + return { + column, + } + } + if (operation.noValue) { + return { + column, + operation, + } + } + return { + column, + operation, + value: nested[operationValue], + } + } + + const values = toValues(value); + + // console.log('toValues', values, value); const Remove = (props) => { return ( @@ -53,7 +100,7 @@ export const FilterItem = (props) => { const form = useMemo( () => createForm({ - initialValues, + initialValues: values, effects: (form) => { onFieldChange('column', (field: Field, form: Form) => { const column = (field.value || {}) as ISchema; @@ -76,12 +123,28 @@ export const FilterItem = (props) => { f.visible = !operation.noValue; }); }); + onFormValuesChange((form) => { + const { column, operation, value } = form.values; + if (!operation?.value) { + return; + } + const fieldName = Object.keys(column.properties).shift(); + if (operation?.noValue) { + onChange({[fieldName]: { + [operation.value]: true, + }}); + } else if (isValid(value)) { + onChange({[fieldName]: { + [operation.value]: value, + }}); + } + console.log('form.values', form.values); + }) }, }), [], ); - return ( @@ -89,7 +152,7 @@ export const FilterItem = (props) => { { value: 'name', } }} - enum={columns.map((column) => column.toJSON())} + enum={[...columns.values()].map((column) => column.toJSON())} /> { /> { return ( - + ); }; ``` diff --git a/packages/client/src/schemas/filter/index.tsx b/packages/client/src/schemas/filter/index.tsx index 04f7d3eca..b32048ce2 100644 --- a/packages/client/src/schemas/filter/index.tsx +++ b/packages/client/src/schemas/filter/index.tsx @@ -5,12 +5,39 @@ import { useDynamicList } from 'ahooks'; import { Select } from 'antd'; import { CloseCircleOutlined } from '@ant-design/icons'; import { FilterItem } from './FilterItem'; +import cls from 'classnames'; import './style.less'; +const toValue = (value) => { + if (!value) { + return { + logical: 'and', + list: [{}], + }; + } + if (value.and) { + return { + logical: 'and', + list: value.and, + }; + } + if (value.or) { + return { + logical: 'and', + list: value.or, + }; + } + return { + logical: 'and', + list: [{}], + }; +}; + export function FilterGroup(props) { - const { onRemove } = props; + const { bordered = true, onRemove, onChange } = props; + const value = toValue(props.value); return ( -
+
{onRemove && ( onRemove()}> @@ -20,8 +47,13 @@ export function FilterGroup(props) { 满足组内{' '} { + const s = Object.values(schema.properties).shift(); + s['x-component'] = value; + refresh(); + updateSchema(s); + // const f = field.query(getSchemaPath(s)).take() + // console.log('fffffff', { schema, f }); + }} > - 对话框 - 抽屉 - 浏览器窗口 + 对话框 + 抽屉 + 浏览器窗口 {' '} 内打开 @@ -1321,7 +1542,7 @@ Table.Action.DesignableBar = () => { setVisible(false); }} > - 删除 + 移除 } @@ -1389,7 +1610,8 @@ Table.Column.DesignableBar = () => { const { schema, remove, refresh, insertAfter } = useDesignable(); const [visible, setVisible] = useState(false); const displayed = useDisplayedMapContext(); - const ctx = useContext(ColDraggableContext); + const collectionField = useContext(CollectionFieldContext); + console.log('displayed.map', displayed.map); return (
{ { - const title = uid(); + const values = await FormDialog('修改列标题', () => { + return ( + + + + ); + }).open({ + initialValues: { + fieldName: collectionField?.uiSchema?.title, + title: schema['title'], + }, + }); + const title = values.title || null; field.title = title; schema.title = title; refresh(); @@ -1420,8 +1673,9 @@ Table.Column.DesignableBar = () => { setVisible(false); }} > - 编辑列 + 修改列标题 + { const s = remove(); @@ -1430,7 +1684,7 @@ Table.Column.DesignableBar = () => { await removeSchema(s); }} > - 删除列 + 移除 } @@ -1453,14 +1707,14 @@ Table.SortHandle = observer((props: any) => { Table.DesignableBar = observer((props) => { const field = useField(); - const { designable, schema, refresh, deepRemove } = useDesignable(); + const { schema, refresh, deepRemove } = useDesignable(); const [visible, setVisible] = useState(false); const { dragRef } = useContext(DraggableBlockContext); - if (!designable) { - return null; - } const defaultPageSize = - schema['x-component-props']?.['pagination']?.['defaultPageSize'] || 20; + field?.componentProps?.pagination?.defaultPageSize || 10; + const collectionName = field?.componentProps?.collectionName; + const { fields } = useCollection({ collectionName }); + console.log({ collectionName }); return (
{ const bool = !field.componentProps.showIndex; schema['x-component-props']['showIndex'] = bool; field.componentProps.showIndex = bool; + updateSchema(schema); }} > - {field.componentProps.showIndex ? '隐藏序号' : '显示序号'} +
+ 显示序号{' '} + +
{ : 'sort'; schema['x-component-props']['dragSort'] = dragSort; field.componentProps.dragSort = dragSort; + updateSchema(schema); }} > - {field.componentProps.dragSort - ? '禁用拖拽排序' - : '启用拖拽排序'} +
+ 启用拖拽排序{' '} + +
{!field.componentProps.dragSort && ( - 默认排序 + { + const defaultSort = + field.componentProps?.defaultSort?.map( + (item: string) => { + return item.startsWith('-') + ? { + field: item.substring(1), + direction: 'desc', + } + : { + field: item, + direction: 'asc', + }; + }, + ); + const values = await FormDialog('设置默认排序', () => { + return ( + + + + ); + }).open({ + initialValues: { + defaultSort, + }, + }); + const sort = values.defaultSort.map((item) => { + return item.direction === 'desc' + ? `-${item.field}` + : item.field; + }); + schema['x-component-props']['defaultSort'] = sort; + field.componentProps.defaultSort = sort; + await updateSchema(schema); + console.log('defaultSort', sort); + }} + > + 设置默认排序 + )} - 筛选范围 + { + const { defaultFilter } = await FormDialog( + '设置筛选范围', + () => { + return ( + + + + ); + }, + ).open({ + initialValues: { + defaultFilter: + field?.componentProps?.defaultFilter || {}, + }, + }); + schema['x-component-props']['defaultFilter'] = + defaultFilter; + field.componentProps.defaultFilter = defaultFilter; + await updateSchema(schema); + }} + > + 设置筛选范围 + 每页默认显示{' '}