lots of updates

This commit is contained in:
chenos 2021-08-04 12:38:08 +08:00
parent c23d2c5c3c
commit 5b014bc67e
28 changed files with 951 additions and 194 deletions

View File

@ -21,6 +21,7 @@ import * as uiSchema from './ui-schema';
const Collection = database.getModel('collections'); const Collection = database.getModel('collections');
const collection = await Collection.create(config); const collection = await Collection.create(config);
await collection.updateAssociations(config); await collection.updateAssociations(config);
await collection.migrate();
const Route = database.getModel('routes'); const Route = database.getModel('routes');

View File

@ -29,6 +29,7 @@
"axios": "^0.21.1", "axios": "^0.21.1",
"beautiful-react-hooks": "^0.35.0", "beautiful-react-hooks": "^0.35.0",
"constate": "^3.3.0", "constate": "^3.3.0",
"flat": "^5.0.2",
"html-react-parser": "^1.2.7", "html-react-parser": "^1.2.7",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"monaco-editor": "^0.25.2", "monaco-editor": "^0.25.2",

View File

@ -19,7 +19,7 @@ import { useMemo } from 'react';
import { CodeOutlined } from '@ant-design/icons'; import { CodeOutlined } from '@ant-design/icons';
import Editor from '@monaco-editor/react'; 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 { Space, Card, Modal, Spin } from 'antd';
import { ArrayTable } from '../../schemas/array-table'; import { ArrayTable } from '../../schemas/array-table';
@ -74,6 +74,7 @@ export const SchemaField = createSchemaField({
Page, Page,
Chart, Chart,
ArrayItems,
ArrayCollapse, ArrayCollapse,
ArrayTable, ArrayTable,
FormLayout, FormLayout,
@ -365,6 +366,47 @@ export function useDesignable(path?: any) {
refresh(); refresh();
return target; 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];
},
}; };
} }

View File

@ -14,31 +14,36 @@ export interface CollectionProviderProps {
collectionName?: string; collectionName?: string;
} }
const [CollectionProvider, useCollectionContext] = constate( export function useCollection(props: CollectionProviderProps) {
(props: CollectionProviderProps) => { const { collectionName } = props;
const { collectionName } = props; const { collections = [], loading, refresh } = useCollectionsContext();
const { collections = [], loading, refresh } = useCollectionsContext(); let collection: any;
let collection: any; let fields = [];
let fields = []; if (collectionName) {
if (collectionName) { collection = collections.find((item) => item.name === collectionName);
collection = collections.find((item) => item.name === collectionName); }
} if (collection) {
if (collection) { fields = collection?.fields || [];
fields = collection?.fields || []; }
} let sortableField = collection.sortable;
return { if (collection.sortable && typeof collection.sortable === 'object') {
collection, sortableField = collection.sortable?.name;
fields, }
loading, return {
refresh, sortableField,
getField(name: string) { collection,
if (!name) { fields,
return null; loading,
} refresh,
return fields.find((item) => item.name === name); getField(name: string) {
}, if (!name) {
}; return null;
}, }
); return fields.find((item) => item.name === name);
},
};
}
const [CollectionProvider, useCollectionContext] = constate(useCollection);
export { CollectionProvider, useCollectionContext }; export { CollectionProvider, useCollectionContext };

View File

@ -17,6 +17,8 @@ export interface SaveOptions {
} }
export interface ListOptions { export interface ListOptions {
defaultFilter?: any;
filter?: any;
} }
export class Resource { export class Resource {
@ -31,9 +33,28 @@ export class Resource {
} }
} }
list(options: ListOptions = {}) { sort(options) {
const { resourceName } = this.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 = {}) { get(options: GetOptions = {}) {

View File

@ -7,6 +7,7 @@ export const createdAt: ISchema = {
group: 'systemInfo', group: 'systemInfo',
order: 1, order: 1,
title: '创建时间', title: '创建时间',
sortable: true,
default: { default: {
dataType: 'date', dataType: 'date',
field: 'created_at', field: 'created_at',

View File

@ -7,6 +7,7 @@ export const datetime: ISchema = {
group: 'datetime', group: 'datetime',
order: 1, order: 1,
title: '日期', title: '日期',
sortable: true,
default: { default: {
dataType: 'date', dataType: 'date',
// name, // name,

View File

@ -7,6 +7,7 @@ export const email: ISchema = {
group: 'basic', group: 'basic',
order: 4, order: 4,
title: '电子邮箱', title: '电子邮箱',
sortable: true,
default: { default: {
dataType: 'string', dataType: 'string',
// name, // name,

View File

@ -7,6 +7,7 @@ export const number: ISchema = {
group: 'basic', group: 'basic',
order: 5, order: 5,
title: '数字', title: '数字',
sortable: true,
default: { default: {
dataType: 'float', dataType: 'float',
// name, // name,

View File

@ -7,6 +7,7 @@ export const percent: ISchema = {
group: 'basic', group: 'basic',
order: 6, order: 6,
title: '百分比', title: '百分比',
sortable: true,
default: { default: {
dataType: 'float', dataType: 'float',
// name, // name,

View File

@ -7,6 +7,7 @@ export const phone: ISchema = {
group: 'basic', group: 'basic',
order: 3, order: 3,
title: '手机号码', title: '手机号码',
sortable: true,
default: { default: {
dataType: 'string', dataType: 'string',
// name, // name,

View File

@ -7,6 +7,7 @@ export const string: ISchema = {
group: 'basic', group: 'basic',
order: 1, order: 1,
title: '单行文本', title: '单行文本',
sortable: true,
default: { default: {
interface: 'string', interface: 'string',
dataType: 'string', dataType: 'string',

View File

@ -7,6 +7,7 @@ export const time: ISchema = {
group: 'datetime', group: 'datetime',
order: 2, order: 2,
title: '时间', title: '时间',
sortable: true,
default: { default: {
dataType: 'time', dataType: 'time',
// name, // name,

View File

@ -7,6 +7,7 @@ export const updatedAt: ISchema = {
group: 'systemInfo', group: 'systemInfo',
order: 2, order: 2,
title: '最后更新时间', title: '最后更新时间',
sortable: true,
default: { default: {
dataType: 'date', dataType: 'date',
field: 'updated_at', field: 'updated_at',

View File

@ -1,7 +1,7 @@
import React, { useContext, useMemo, useState } from 'react'; import React, { useContext, useMemo, useState } from 'react';
import { SchemaField } from '..'; import { SchemaField } from '..';
import { createForm, onFieldChange, onFieldReact } from '@formily/core'; import { createForm, onFieldChange, onFieldReact, onFormValuesChange } from '@formily/core';
import { FormProvider, FormConsumer, useFieldSchema, Schema, SchemaOptionsContext, ISchema } from '@formily/react'; import { FormProvider, FormConsumer, useFieldSchema, Schema, SchemaOptionsContext, ISchema, SchemaKey } from '@formily/react';
import { Field } from '@formily/core/esm/models/Field'; import { Field } from '@formily/core/esm/models/Field';
import { Form } from '@formily/core/esm/models/Form'; import { Form } from '@formily/core/esm/models/Form';
import { import {
@ -14,26 +14,73 @@ import {
} from '@formily/antd'; } from '@formily/antd';
import { CloseCircleOutlined } from '@ant-design/icons'; import { CloseCircleOutlined } from '@ant-design/icons';
import { get } from 'lodash'; import { get } from 'lodash';
import { isValid } from '@formily/shared';
function useFilterColumns(): Schema[] { function useFilterColumns(): Map<SchemaKey, Schema> {
const schema = useFieldSchema(); const schema = useFieldSchema();
const columns = schema.reduceProperties((columns, current) => { const columns = schema.reduceProperties((columns, current) => {
if (current['x-component'] === 'Filter.Column') { 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<SchemaKey, Schema>());
return columns; return columns;
} }
export const FilterItem = (props) => { export const FilterItem = (props) => {
const { initialValues = {}, onRemove } = props; const { value, initialValues = {}, onRemove, onChange } = props;
const options = useContext(SchemaOptionsContext); const options = useContext(SchemaOptionsContext);
const { key } = initialValues;
const columns = useFilterColumns(); 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) => { const Remove = (props) => {
return ( return (
@ -53,7 +100,7 @@ export const FilterItem = (props) => {
const form = useMemo( const form = useMemo(
() => () =>
createForm({ createForm({
initialValues, initialValues: values,
effects: (form) => { effects: (form) => {
onFieldChange('column', (field: Field, form: Form) => { onFieldChange('column', (field: Field, form: Form) => {
const column = (field.value || {}) as ISchema; const column = (field.value || {}) as ISchema;
@ -76,12 +123,28 @@ export const FilterItem = (props) => {
f.visible = !operation.noValue; 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 ( return (
<FormProvider form={form}> <FormProvider form={form}>
<FormLayout layout={'inline'}> <FormLayout layout={'inline'}>
@ -89,7 +152,7 @@ export const FilterItem = (props) => {
<SchemaField.Void x-component="Space"> <SchemaField.Void x-component="Space">
<SchemaField.String <SchemaField.String
name="column" name="column"
x-decorator="FormItem" x-decorator="FormilyFormItem"
x-decorator-props={{ x-decorator-props={{
asterisk: true, asterisk: true,
feedbackLayout: 'none', feedbackLayout: 'none',
@ -105,11 +168,11 @@ export const FilterItem = (props) => {
value: 'name', value: 'name',
} }
}} }}
enum={columns.map((column) => column.toJSON())} enum={[...columns.values()].map((column) => column.toJSON())}
/> />
<SchemaField.String <SchemaField.String
name="operation" name="operation"
x-decorator="FormItem" x-decorator="FormilyFormItem"
x-decorator-props={{ x-decorator-props={{
asterisk: true, asterisk: true,
feedbackLayout: 'none', feedbackLayout: 'none',
@ -124,7 +187,7 @@ export const FilterItem = (props) => {
/> />
<SchemaField.String <SchemaField.String
name="value" name="value"
x-decorator="FormItem" x-decorator="FormilyFormItem"
x-decorator-props={{ x-decorator-props={{
asterisk: true, asterisk: true,
feedbackLayout: 'none', feedbackLayout: 'none',

View File

@ -38,6 +38,20 @@ const schema = {
name: 'filter', name: 'filter',
type: 'object', type: 'object',
'x-component': 'Filter', 'x-component': 'Filter',
default: {
and: [
{
field1: {
eq: 'aa',
}
},
{
field1: {
eq: 'bbb',
}
}
],
},
properties: { properties: {
column1: { column1: {
type: 'void', type: 'void',
@ -68,7 +82,7 @@ const schema = {
], ],
}, },
properties: { properties: {
field1: { field2: {
type: 'number', type: 'number',
'x-component': 'InputNumber', 'x-component': 'InputNumber',
}, },
@ -79,7 +93,7 @@ const schema = {
export default () => { export default () => {
return ( return (
<SchemaRenderer schema={schema} /> <SchemaRenderer debug={true} schema={schema} />
); );
}; };
``` ```

View File

@ -5,12 +5,39 @@ import { useDynamicList } from 'ahooks';
import { Select } from 'antd'; import { Select } from 'antd';
import { CloseCircleOutlined } from '@ant-design/icons'; import { CloseCircleOutlined } from '@ant-design/icons';
import { FilterItem } from './FilterItem'; import { FilterItem } from './FilterItem';
import cls from 'classnames';
import './style.less'; 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) { export function FilterGroup(props) {
const { onRemove } = props; const { bordered = true, onRemove, onChange } = props;
const value = toValue(props.value);
return ( return (
<div className={'nb-filter-group'}> <div className={cls('nb-filter-group', { bordered })}>
{onRemove && ( {onRemove && (
<a className={'nb-filter-group-close'} onClick={() => onRemove()}> <a className={'nb-filter-group-close'} onClick={() => onRemove()}>
<CloseCircleOutlined /> <CloseCircleOutlined />
@ -20,8 +47,13 @@ export function FilterGroup(props) {
{' '} {' '}
<Select <Select
style={{ width: 80 }} style={{ width: 80 }}
onChange={(value) => {}} onChange={(logical) => {
defaultValue={'and'} onChange &&
onChange({
[logical]: value.list,
});
}}
defaultValue={value.logical}
> >
<Select.Option value={'and'}></Select.Option> <Select.Option value={'and'}></Select.Option>
<Select.Option value={'or'}></Select.Option> <Select.Option value={'or'}></Select.Option>
@ -29,9 +61,12 @@ export function FilterGroup(props) {
</div> </div>
<FilterList <FilterList
initialValue={[{}]} initialValue={value.list}
onChange={(list) => { onChange={(list: any[]) => {
console.log({ list }); onChange &&
onChange({
[value.logical]: list.filter((item) => Object.keys(item).length),
});
}} }}
/> />
</div> </div>
@ -40,7 +75,9 @@ export function FilterGroup(props) {
export function FilterList(props) { export function FilterList(props) {
const { initialValue } = props; const { initialValue } = props;
const { list, push, remove } = useDynamicList<any>(initialValue || []); const { list, push, remove, replace } = useDynamicList<any>(
initialValue || [],
);
useEffect(() => { useEffect(() => {
props.onChange && props.onChange(list); props.onChange && props.onChange(list);
}, [list]); }, [list]);
@ -48,18 +85,29 @@ export function FilterList(props) {
<div className={'nb-filter-list'}> <div className={'nb-filter-list'}>
<div> <div>
{list.map((item, index) => { {list.map((item, index) => {
if (item.type === 'group') { if (item.and || item.or) {
return <FilterGroup key={index} onRemove={() => remove(index)} />; return (
<FilterGroup
key={index}
value={item}
onChange={(value: any) => replace(index, value)}
onRemove={() => remove(index)}
/>
);
} }
return <FilterItem key={index} onRemove={() => remove(index)} />; return (
<FilterItem
key={index}
value={item}
onChange={(value: any) => replace(index, value)}
onRemove={() => remove(index)}
/>
);
})} })}
</div> </div>
<a <a
onClick={() => { onClick={() => {
push({ push({});
type: 'item',
key: new Date().toTimeString(),
});
}} }}
> >
@ -67,8 +115,7 @@ export function FilterList(props) {
<a <a
onClick={() => { onClick={() => {
push({ push({
type: 'group', and: [{}],
key: new Date().toTimeString(),
}); });
}} }}
> >
@ -80,9 +127,10 @@ export function FilterList(props) {
export const Filter = connect( export const Filter = connect(
(props) => { (props) => {
// console.log('Filter.props', { props });
return ( return (
<div> <div>
<FilterGroup /> <FilterGroup bordered={false} {...props} />
</div> </div>
); );
}, },

View File

@ -1,10 +1,18 @@
.nb-filter-group { .nb-filter-group {
position: relative; position: relative;
margin-bottom: 14px; margin-bottom: 14px;
padding: 14px; min-width: 400px;
border: 1px dashed rgb(222, 222, 222);
&-close { &-close {
position: absolute; position: absolute;
right: 10px; right: 10px;
} }
} &.bordered {
border: 1px dashed rgb(222, 222, 222);
padding: 14px;
}
}
.nb-filter-list {
.ant-form-inline {
margin-bottom: 8px;
}
}

View File

@ -123,7 +123,6 @@ export function SortableBodyRow(props: any) {
<SortableContext <SortableContext
strategy={horizontalListSortingStrategy} strategy={horizontalListSortingStrategy}
items={React.Children.map(props.children, (child) => { items={React.Children.map(props.children, (child) => {
console.log(child.key, 'child.key');
return `td${child.key}`; return `td${child.key}`;
})} })}
> >

View File

@ -17,7 +17,7 @@ import { uid } from '@formily/shared';
import useRequest from '@ahooksjs/use-request'; import useRequest from '@ahooksjs/use-request';
import { BaseResult } from '@ahooksjs/use-request/lib/types'; import { BaseResult } from '@ahooksjs/use-request/lib/types';
import cls from 'classnames'; import cls from 'classnames';
import { MenuOutlined, DragOutlined } from '@ant-design/icons'; import { MenuOutlined, DragOutlined, FilterOutlined } from '@ant-design/icons';
import { DndContext, DragOverlay } from '@dnd-kit/core'; import { DndContext, DragOverlay } from '@dnd-kit/core';
import { import {
SortableContext, SortableContext,
@ -42,6 +42,7 @@ import { Resource } from '../../resource';
import { import {
CollectionProvider, CollectionProvider,
DisplayedMapProvider, DisplayedMapProvider,
useCollection,
useCollectionContext, useCollectionContext,
useDisplayedMapContext, useDisplayedMapContext,
} from '../../constate'; } from '../../constate';
@ -59,7 +60,10 @@ import {
SortableRowHandle, SortableRowHandle,
} from './Sortable'; } from './Sortable';
import { DragHandle, Droppable, SortableItem } from '../../components/Sortable'; import { DragHandle, Droppable, SortableItem } from '../../components/Sortable';
import { VisibleContext } from '../../context'; import { isValid } from '@formily/shared';
import { FormButtonGroup, FormDialog, FormLayout, Submit } from '@formily/antd';
import flatten from 'flat';
import IconPicker from '../../components/icon-picker';
export interface ITableContext { export interface ITableContext {
props: any; props: any;
@ -79,12 +83,12 @@ export interface ITableRowContext {
record: any; record: any;
} }
const TableConetxt = createContext<ITableContext>(null); const TableContext = createContext<ITableContext>({} as any);
const TableRowContext = createContext<ITableRowContext>(null); const TableRowContext = createContext<ITableRowContext>(null);
const CollectionFieldContext = createContext(null); const CollectionFieldContext = createContext(null);
const useTable = () => { const useTable = () => {
return useContext(TableConetxt); return useContext(TableContext);
}; };
const useTableRow = () => { const useTableRow = () => {
@ -98,10 +102,15 @@ function useTableFilterAction() {
refresh, refresh,
props: { refreshRequestOnChange }, props: { refreshRequestOnChange },
} = useTable(); } = useTable();
const form = useForm();
return { return {
async run() { async run() {
console.log('useTableFilterAction', form.values);
if (refreshRequestOnChange) { if (refreshRequestOnChange) {
return service.refresh(); return service.run({
...service.params[0],
// filter,
});
} }
}, },
}; };
@ -204,7 +213,7 @@ const useTableIndex = () => {
const { pagination, props } = useTable(); const { pagination, props } = useTable();
const ctx = useContext(TableRowContext); const ctx = useContext(TableRowContext);
if (pagination && !props.clientSidePagination) { if (pagination && !props.clientSidePagination) {
const { pageSize, page } = pagination; const { pageSize, page = 1 } = pagination;
return ctx.index + (page - 1) * pageSize; return ctx.index + (page - 1) * pageSize;
} }
return ctx.index; return ctx.index;
@ -433,11 +442,13 @@ const useDataSource = () => {
const TableMain = () => { const TableMain = () => {
const { const {
resource,
selectedRowKeys, selectedRowKeys,
setSelectedRowKeys, setSelectedRowKeys,
service, service,
field, field,
props: { rowKey, dragSort, showIndex }, props: { rowKey, dragSort, showIndex },
refresh,
} = useTable(); } = useTable();
const columns = useTableColumns(); const columns = useTableColumns();
const dataSource = useDataSource(); const dataSource = useDataSource();
@ -446,8 +457,29 @@ const TableMain = () => {
return ( return (
<div className={'nb-table'}> <div className={'nb-table'}>
<DndContext <DndContext
onDragEnd={(event) => { onDragEnd={async (event) => {
console.log({ event }); const fromId = event.active?.id as any;
const toId = event.over?.id as any;
if (isValid(fromId) && isValid(toId)) {
const fromIndex = findIndex(
field.value,
(item) => item[rowKey] === fromId,
);
const toIndex = findIndex(
field.value,
(item) => item[rowKey] === toId,
);
console.log({ fromId, toId, fromIndex, toIndex });
field.move(fromIndex, toIndex);
refresh();
await resource.sort({
resourceKey: fromId,
target: {
[rowKey]: toId,
},
});
await service.refresh();
}
}} }}
> >
{actionBars.map((actionBar) => ( {actionBars.map((actionBar) => (
@ -541,13 +573,35 @@ const TableMain = () => {
); );
}; };
const usePagination = (paginationProps?: any) => { const usePagination = () => {
return useState(() => { const field = useField<Formily.Core.Models.ArrayField>();
if (!paginationProps) { const paginationProps = field.componentProps.pagination;
return false;
} let pagination = paginationProps;
return { page: 1, pageSize: 10, ...paginationProps };
}); // const [pagination, setPagination] = useState(() => {
// if (!paginationProps) {
// return false;
// }
// const { defaultPageSize = 10, ...others } = paginationProps;
// return { page: 1, pageSize: defaultPageSize, ...others };
// });
// useEffect(() => {
// if (!paginationProps) {
// return setPagination(false);
// }
// const { defaultPageSize = 10, ...others } = paginationProps;
// setPagination({ page: 1, pageSize: defaultPageSize, ...others });
// }, [paginationProps]);
return [
pagination,
(params) => {
const defaults = field.componentProps.pagination;
field.componentProps.pagination = { ...defaults, ...params };
},
];
}; };
const TableProvider = (props: any) => { const TableProvider = (props: any) => {
@ -559,10 +613,25 @@ const TableProvider = (props: any) => {
} = props; } = props;
const { schema } = useDesignable(); const { schema } = useDesignable();
const field = useField<Formily.Core.Models.ArrayField>(); const field = useField<Formily.Core.Models.ArrayField>();
const [pagination, setPagination] = usePagination(props.pagination); const [pagination, setPagination] = usePagination();
const [selectedRowKeys, setSelectedRowKeys] = useState<any>([]); const [selectedRowKeys, setSelectedRowKeys] = useState<any>([]);
const [, refresh] = useState(uid()); const [, refresh] = useState(uid());
const { resource } = useResource(); const { resource } = useResource();
const { sortableField } = useCollectionContext();
const dragSort = props.dragSort;
const getDefaultParams = () => {
const defaultParams = { ...pagination };
if (dragSort) {
defaultParams['sort'] = [sortableField || 'sort'];
} else {
defaultParams['sort'] = (props.defaultSort || []).join(',');
}
if (props.defaultFilter) {
defaultParams['defaultFilter'] = props.defaultFilter;
}
console.log({ defaultParams });
return defaultParams;
};
const service = useRequest( const service = useRequest(
(params?: any) => { (params?: any) => {
if (!resource) { if (!resource) {
@ -582,12 +651,20 @@ const TableProvider = (props: any) => {
onSuccess(data: any) { onSuccess(data: any) {
field.setValue(data?.list || []); field.setValue(data?.list || []);
}, },
defaultParams: [{ ...pagination }], manual: true,
// defaultParams: [getDefaultParams()],
}, },
); );
console.log('refresh', { pagination }); useEffect(() => {
service.run(getDefaultParams());
}, [
pagination.pageSize,
props.dragSort,
props.defaultSort,
props.defaultFilter,
]);
return ( return (
<TableConetxt.Provider <TableContext.Provider
value={{ value={{
resource, resource,
refresh: () => { refresh: () => {
@ -597,7 +674,7 @@ const TableProvider = (props: any) => {
: service?.data?.total; : service?.data?.total;
const maxPage = Math.ceil(total / pageSize); const maxPage = Math.ceil(total / pageSize);
if (page > maxPage) { if (page > maxPage) {
setPagination((prev) => ({ ...prev, page: maxPage })); setPagination({ page: maxPage });
} else { } else {
refresh(uid()); refresh(uid());
} }
@ -613,7 +690,7 @@ const TableProvider = (props: any) => {
}} }}
> >
<TableMain /> <TableMain />
</TableConetxt.Provider> </TableContext.Provider>
); );
}; };
@ -654,19 +731,18 @@ Table.Pagination = observer(() => {
total={total} total={total}
onChange={(current, pageSize) => { onChange={(current, pageSize) => {
const page = pagination.pageSize !== pageSize ? 1 : current; const page = pagination.pageSize !== pageSize ? 1 : current;
setPagination((prev) => ({ setPagination({
...prev,
page,
pageSize,
}));
if (clientSidePagination) {
return;
}
service.run({
...service.params,
page, page,
pageSize, pageSize,
}); });
// if (clientSidePagination) {
// return;
// }
// service.run({
// ...service.params[0],
// page,
// pageSize,
// });
}} }}
/> />
</div> </div>
@ -1031,42 +1107,74 @@ Table.ActionBar = observer((props: any) => {
); );
}); });
const fieldsToFilterColumns = (fields: any[], options: any = {}) => {
const { fieldNames = [] } = options;
const properties = {};
fields.forEach((field, index) => {
if (fieldNames?.length && !fieldNames.includes(field.name)) {
return;
}
const fieldOption = interfaces.get(field.interface);
if (!fieldOption.operations) {
return;
}
properties[`column${index}`] = {
type: 'void',
title: field?.uiSchema?.title,
'x-component': 'Filter.Column',
'x-component-props': {
operations: fieldOption.operations,
},
properties: {
[field.name]: {
...field.uiSchema,
'x-decorator': 'FormilyFormItem',
title: null,
},
},
};
});
return properties;
};
const fieldsToSortColumns = (fields: any[]) => {
const dataSource = [];
fields.forEach((field) => {
const fieldOption = interfaces.get(field.interface);
if (!fieldOption.sortable) {
return;
}
dataSource.push({
value: field.name,
label: field?.uiSchema?.title,
});
});
return dataSource;
};
Table.Filter = observer((props: any) => { Table.Filter = observer((props: any) => {
const { service } = useTable();
const { fieldNames = [] } = props; const { fieldNames = [] } = props;
const { schema, DesignableBar } = useDesignable(); const { schema, DesignableBar } = useDesignable();
const form = useMemo(() => createForm(), []); const form = useMemo(() => createForm(), []);
const { fields = [] } = useCollectionContext(); const { fields = [] } = useCollectionContext();
const fields2properties = (fields: any[]) => { const [visible, setVisible] = useState(false);
const properties = {}; const obj = flatten(form.values.filter || {});
fields.forEach((field, index) => { console.log('flatten', obj, Object.values(obj));
if (fieldNames?.length && !fieldNames.includes(field.name)) { const count = Object.values(obj).filter((i) =>
return; Array.isArray(i) ? i.length : i,
} ).length;
const fieldOption = interfaces.get(field.interface);
if (!fieldOption.operations) { const icon = props.icon || 'FilterOutlined';
return;
}
properties[`column${index}`] = {
type: 'void',
title: field?.uiSchema?.title,
'x-component': 'Filter.Column',
'x-component-props': {
operations: fieldOption.operations,
},
properties: {
[field.name]: {
...field.uiSchema,
title: null,
},
},
};
});
return properties;
};
return ( return (
<Popover <Popover
trigger={['click']} trigger={['click']}
placement={'bottomLeft'} placement={'bottomLeft'}
visible={visible}
onVisibleChange={setVisible}
content={ content={
<div> <div>
<FormProvider form={form}> <FormProvider form={form}>
@ -1077,17 +1185,32 @@ Table.Filter = observer((props: any) => {
filter: { filter: {
type: 'object', type: 'object',
'x-component': 'Filter', 'x-component': 'Filter',
properties: fields2properties(fields), properties: fieldsToFilterColumns(fields, { fieldNames }),
}, },
}, },
}} }}
/> />
<FormButtonGroup align={'right'}>
<Submit
onSubmit={() => {
const { filter } = form.values;
console.log('Table.Filter', form.values);
setVisible(false);
return service.run({
...service.params[0],
filter,
});
}}
>
</Submit>
</FormButtonGroup>
</FormProvider> </FormProvider>
</div> </div>
} }
> >
<Button> <Button icon={<IconPicker type={icon} />}>
{schema.title} {count > 0 ? `${count} 个筛选项` : schema.title}
<DesignableBar /> <DesignableBar />
</Button> </Button>
</Popover> </Popover>
@ -1099,7 +1222,11 @@ Table.Filter.DesignableBar = () => {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const displayed = useDisplayedMapContext(); const displayed = useDisplayedMapContext();
const { fields } = useCollectionContext(); const { fields } = useCollectionContext();
const field = useField();
let fieldNames = field.componentProps.fieldNames || [];
if (fieldNames.length === 0) {
fieldNames = fields.map((field) => field.name);
}
return ( return (
<div className={cls('designable-bar', { active: visible })}> <div className={cls('designable-bar', { active: visible })}>
<span <span
@ -1119,22 +1246,72 @@ Table.Filter.DesignableBar = () => {
<Menu> <Menu>
<Menu.ItemGroup title={'筛选字段'}> <Menu.ItemGroup title={'筛选字段'}>
{fields {fields
.filter((field) => { .filter((collectionField) => {
const option = interfaces.get(field.interface); const option = interfaces.get(collectionField.interface);
return option.operations?.length; return option.operations?.length;
}) })
.map((field) => ( .map((collectionField) => (
<SwitchMenuItem <SwitchMenuItem
title={field?.uiSchema?.title} title={collectionField?.uiSchema?.title}
checked={true} checked={fieldNames.includes(collectionField.name)}
onChange={async (checked) => {}} onChange={async (checked) => {
if (checked) {
fieldNames.push(collectionField.name);
} else {
const index = fieldNames.indexOf(
collectionField.name,
);
if (index > -1) {
fieldNames.splice(index, 1);
}
}
console.log({ fieldNames, field });
schema['x-component-props']['fieldNames'] = fieldNames;
field.componentProps.fieldNames = fieldNames;
updateSchema(schema);
}}
/> />
))} ))}
</Menu.ItemGroup> </Menu.ItemGroup>
<Menu.Divider /> <Menu.Divider />
<Menu.Item <Menu.Item
onClick={(e) => { onClick={async (e) => {
schema.title = uid(); const values = await FormDialog('修改名称和图标', () => {
return (
<FormLayout layout={'vertical'}>
<SchemaField
schema={{
type: 'object',
properties: {
title: {
type: 'string',
title: '按钮名称',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
icon: {
type: 'string',
title: '按钮图标',
'x-decorator': 'FormItem',
'x-component': 'IconPicker',
},
},
}}
/>
</FormLayout>
);
}).open({
initialValues: {
title: schema['title'],
icon: schema['x-component-props']?.['icon'],
},
});
schema['title'] = values.title;
schema['x-component-props']['icon'] = values.icon;
field.componentProps.icon = values.icon;
field.title = values.title;
updateSchema(schema);
refresh(); refresh();
}} }}
> >
@ -1153,7 +1330,7 @@ Table.Filter.DesignableBar = () => {
setVisible(false); setVisible(false);
}} }}
> >
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }
@ -1262,6 +1439,7 @@ Table.Action.DesignableBar = () => {
const isPopup = Object.keys(schema.properties || {}).length > 0; const isPopup = Object.keys(schema.properties || {}).length > 0;
const inActionBar = schema.parent['x-component'] === 'Table.ActionBar'; const inActionBar = schema.parent['x-component'] === 'Table.ActionBar';
const displayed = useDisplayedMapContext(); const displayed = useDisplayedMapContext();
const field = useField();
return ( return (
<div className={cls('designable-bar', { active: visible })}> <div className={cls('designable-bar', { active: visible })}>
<span <span
@ -1280,8 +1458,43 @@ Table.Action.DesignableBar = () => {
overlay={ overlay={
<Menu> <Menu>
<Menu.Item <Menu.Item
onClick={(e) => { onClick={async (e) => {
schema.title = uid(); const values = await FormDialog('修改名称和图标', () => {
return (
<FormLayout layout={'vertical'}>
<SchemaField
schema={{
type: 'object',
properties: {
title: {
type: 'string',
title: '按钮名称',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
icon: {
type: 'string',
title: '按钮图标',
'x-decorator': 'FormItem',
'x-component': 'IconPicker',
},
},
}}
/>
</FormLayout>
);
}).open({
initialValues: {
title: schema['title'],
icon: schema['x-component-props']?.['icon'],
},
});
schema['title'] = values.title;
schema['x-component-props']['icon'] = values.icon;
field.componentProps.icon = values.icon;
field.title = values.title;
updateSchema(schema);
refresh(); refresh();
}} }}
> >
@ -1293,11 +1506,19 @@ Table.Action.DesignableBar = () => {
<Select <Select
bordered={false} bordered={false}
size={'small'} size={'small'}
defaultValue={'modal'} defaultValue={'Action.Modal'}
onChange={(value) => {
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 });
}}
> >
<Select.Option value={'modal'}></Select.Option> <Select.Option value={'Action.Modal'}></Select.Option>
<Select.Option value={'drawer'}></Select.Option> <Select.Option value={'Action.Drawer'}></Select.Option>
<Select.Option value={'window'}></Select.Option> <Select.Option value={'Action.Window'}></Select.Option>
</Select>{' '} </Select>{' '}
</Menu.Item> </Menu.Item>
@ -1321,7 +1542,7 @@ Table.Action.DesignableBar = () => {
setVisible(false); setVisible(false);
}} }}
> >
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }
@ -1389,7 +1610,8 @@ Table.Column.DesignableBar = () => {
const { schema, remove, refresh, insertAfter } = useDesignable(); const { schema, remove, refresh, insertAfter } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const displayed = useDisplayedMapContext(); const displayed = useDisplayedMapContext();
const ctx = useContext(ColDraggableContext); const collectionField = useContext(CollectionFieldContext);
console.log('displayed.map', displayed.map);
return ( return (
<div className={cls('designable-bar', { active: visible })}> <div className={cls('designable-bar', { active: visible })}>
<span <span
@ -1409,7 +1631,38 @@ Table.Column.DesignableBar = () => {
<Menu> <Menu>
<Menu.Item <Menu.Item
onClick={async (e) => { onClick={async (e) => {
const title = uid(); const values = await FormDialog('修改列标题', () => {
return (
<FormLayout layout={'vertical'}>
<SchemaField
schema={{
type: 'object',
properties: {
fieldName: {
type: 'string',
title: '原字段名称',
'x-read-pretty': true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
title: {
type: 'string',
title: '自定义列标题',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
}}
/>
</FormLayout>
);
}).open({
initialValues: {
fieldName: collectionField?.uiSchema?.title,
title: schema['title'],
},
});
const title = values.title || null;
field.title = title; field.title = title;
schema.title = title; schema.title = title;
refresh(); refresh();
@ -1420,8 +1673,9 @@ Table.Column.DesignableBar = () => {
setVisible(false); setVisible(false);
}} }}
> >
</Menu.Item> </Menu.Item>
<Menu.Divider />
<Menu.Item <Menu.Item
onClick={async () => { onClick={async () => {
const s = remove(); const s = remove();
@ -1430,7 +1684,7 @@ Table.Column.DesignableBar = () => {
await removeSchema(s); await removeSchema(s);
}} }}
> >
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }
@ -1453,14 +1707,14 @@ Table.SortHandle = observer((props: any) => {
Table.DesignableBar = observer((props) => { Table.DesignableBar = observer((props) => {
const field = useField(); const field = useField();
const { designable, schema, refresh, deepRemove } = useDesignable(); const { schema, refresh, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const { dragRef } = useContext(DraggableBlockContext); const { dragRef } = useContext(DraggableBlockContext);
if (!designable) {
return null;
}
const defaultPageSize = 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 ( return (
<div className={cls('designable-bar', { active: visible })}> <div className={cls('designable-bar', { active: visible })}>
<span <span
@ -1486,9 +1740,16 @@ Table.DesignableBar = observer((props) => {
const bool = !field.componentProps.showIndex; const bool = !field.componentProps.showIndex;
schema['x-component-props']['showIndex'] = bool; schema['x-component-props']['showIndex'] = bool;
field.componentProps.showIndex = bool; field.componentProps.showIndex = bool;
updateSchema(schema);
}} }}
> >
{field.componentProps.showIndex ? '隐藏序号' : '显示序号'} <div className={'nb-space-between'}>
{' '}
<Switch
size={'small'}
checked={field.componentProps.showIndex}
/>
</div>
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
key={'dragSort'} key={'dragSort'}
@ -1498,16 +1759,166 @@ Table.DesignableBar = observer((props) => {
: 'sort'; : 'sort';
schema['x-component-props']['dragSort'] = dragSort; schema['x-component-props']['dragSort'] = dragSort;
field.componentProps.dragSort = dragSort; field.componentProps.dragSort = dragSort;
updateSchema(schema);
}} }}
> >
{field.componentProps.dragSort <div className={'nb-space-between'}>
? '禁用拖拽排序' {' '}
: '启用拖拽排序'} <Switch
size={'small'}
checked={field.componentProps.dragSort}
/>
</div>
</Menu.Item> </Menu.Item>
{!field.componentProps.dragSort && ( {!field.componentProps.dragSort && (
<Menu.Item key={'defaultSort'}></Menu.Item> <Menu.Item
key={'defaultSort'}
onClick={async () => {
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 (
<FormLayout layout={'vertical'}>
<SchemaField
schema={{
type: 'object',
properties: {
defaultSort: {
type: 'array',
'x-component': 'ArrayItems',
'x-decorator': 'FormItem',
items: {
type: 'object',
properties: {
space: {
type: 'void',
'x-component': 'Space',
properties: {
sort: {
type: 'void',
'x-decorator': 'FormItem',
'x-component':
'ArrayItems.SortHandle',
},
field: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
enum: fieldsToSortColumns(fields),
'x-component-props': {
style: {
width: 260,
},
},
},
direction: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
style: {
width: 100,
},
},
enum: [
{ label: '正序', value: 'asc' },
{
label: '倒序',
value: 'desc',
},
],
},
remove: {
type: 'void',
'x-decorator': 'FormItem',
'x-component':
'ArrayItems.Remove',
},
},
},
},
},
properties: {
add: {
type: 'void',
title: '新增',
'x-component': 'ArrayItems.Addition',
},
},
},
},
}}
/>
</FormLayout>
);
}).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);
}}
>
</Menu.Item>
)} )}
<Menu.Item key={'defaultFilter'}></Menu.Item> <Menu.Item
key={'defaultFilter'}
onClick={async () => {
const { defaultFilter } = await FormDialog(
'设置筛选范围',
() => {
return (
<FormLayout layout={'vertical'}>
<SchemaField
schema={{
type: 'object',
properties: {
defaultFilter: {
type: 'object',
'x-component': 'Filter',
properties: fieldsToFilterColumns(fields),
},
},
}}
/>
</FormLayout>
);
},
).open({
initialValues: {
defaultFilter:
field?.componentProps?.defaultFilter || {},
},
});
schema['x-component-props']['defaultFilter'] =
defaultFilter;
field.componentProps.defaultFilter = defaultFilter;
await updateSchema(schema);
}}
>
</Menu.Item>
<Menu.Item key={'defaultPageSize'}> <Menu.Item key={'defaultPageSize'}>
{' '} {' '}
<Select <Select
@ -1516,11 +1927,16 @@ Table.DesignableBar = observer((props) => {
onChange={(value) => { onChange={(value) => {
const componentProps = schema['x-component-props'] || {}; const componentProps = schema['x-component-props'] || {};
set(componentProps, 'pagination.defaultPageSize', value); set(componentProps, 'pagination.defaultPageSize', value);
set(componentProps, 'pagination.pageSize', value);
schema['x-component-props'] = componentProps; schema['x-component-props'] = componentProps;
field.componentProps.pagination.pageSize = value;
field.componentProps.pagination.defaultPageSize = value;
refresh(); refresh();
updateSchema(schema);
}} }}
defaultValue={defaultPageSize} defaultValue={defaultPageSize}
> >
<Select.Option value={10}>10</Select.Option>
<Select.Option value={20}>20</Select.Option> <Select.Option value={20}>20</Select.Option>
<Select.Option value={50}>50</Select.Option> <Select.Option value={50}>50</Select.Option>
<Select.Option value={100}>100</Select.Option> <Select.Option value={100}>100</Select.Option>
@ -1539,7 +1955,7 @@ Table.DesignableBar = observer((props) => {
} }
}} }}
> >
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }

View File

@ -224,4 +224,10 @@ td.nb-table-operation {
.action-bar-align-left.isOver { .action-bar-align-left.isOver {
min-height: 32px; min-height: 32px;
background-color: #e6f7ff; background-color: #e6f7ff;
} }
.nb-space-between {
display: flex;
align-items: center;
justify-content: space-between;
}

View File

@ -1,7 +1,12 @@
import { observer, connect, useField, RecursionField } from '@formily/react'; import { observer, connect, useField, RecursionField } from '@formily/react';
import React from 'react'; import React from 'react';
import { Button, Tabs as AntdTabs, Dropdown, Menu, Switch } from 'antd'; import { Button, Tabs as AntdTabs, Dropdown, Menu, Switch } from 'antd';
import { findPropertyByPath, getSchemaPath, useDesignable } from '../../components/schema-renderer'; import {
findPropertyByPath,
getSchemaPath,
SchemaField,
useDesignable,
} from '../../components/schema-renderer';
import { Schema, SchemaKey } from '@formily/react'; import { Schema, SchemaKey } from '@formily/react';
import { PlusOutlined, MenuOutlined } from '@ant-design/icons'; import { PlusOutlined, MenuOutlined } from '@ant-design/icons';
import { useState } from 'react'; import { useState } from 'react';
@ -11,6 +16,8 @@ import './style.less';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import { DragHandle, SortableItem } from '../../components/Sortable'; import { DragHandle, SortableItem } from '../../components/Sortable';
import { DndContext, DragOverlay } from '@dnd-kit/core'; import { DndContext, DragOverlay } from '@dnd-kit/core';
import { FormDialog, FormLayout } from '@formily/antd';
import IconPicker from '../../components/icon-picker';
const useTabs = ({ singleton }) => { const useTabs = ({ singleton }) => {
const tabsField = useField(); const tabsField = useField();
@ -38,7 +45,8 @@ const useTabs = ({ singleton }) => {
export const Tabs: any = observer((props: any) => { export const Tabs: any = observer((props: any) => {
const { singleton, ...others } = props; const { singleton, ...others } = props;
const { schema, DesignableBar, appendChild, root, remove, insertAfter } = useDesignable(); const { schema, DesignableBar, appendChild, root, remove, insertAfter } =
useDesignable();
const tabs = useTabs({ singleton }); const tabs = useTabs({ singleton });
const [dragOverlayContent, setDragOverlayContent] = useState(''); const [dragOverlayContent, setDragOverlayContent] = useState('');
@ -93,14 +101,45 @@ export const Tabs: any = observer((props: any) => {
type={'dashed'} type={'dashed'}
icon={<PlusOutlined />} icon={<PlusOutlined />}
onClick={async () => { onClick={async () => {
const values = await FormDialog('新增标签页', () => {
return (
<FormLayout layout={'vertical'}>
<SchemaField
schema={{
type: 'object',
properties: {
title: {
type: 'string',
title: '标签名称',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
icon: {
type: 'string',
title: '标签图标',
'x-decorator': 'FormItem',
'x-component': 'IconPicker',
},
},
}}
/>
</FormLayout>
);
}).open({
initialValues: {
title: schema['title'],
icon: schema['x-component-props']?.['icon'],
},
});
const data = appendChild({ const data = appendChild({
type: 'void', type: 'void',
name: uid(), name: uid(),
title: uid(), title: values.title,
'x-component': 'Tabs.TabPane', 'x-component': 'Tabs.TabPane',
'x-designable-bar': 'Tabs.TabPane.DesignableBar', 'x-designable-bar': 'Tabs.TabPane.DesignableBar',
'x-component-props': { 'x-component-props': {
tab: uid(), icon: values.icon,
}, },
properties: { properties: {
[uid()]: { [uid()]: {
@ -151,6 +190,7 @@ Tabs.TabPane = observer((props: any) => {
}} }}
> >
<div className={'nb-tab-pane'}> <div className={'nb-tab-pane'}>
<IconPicker type={props.icon} />
{schema.title} <DesignableBar /> {schema.title} <DesignableBar />
</div> </div>
</SortableItem> </SortableItem>
@ -185,10 +225,14 @@ Tabs.DesignableBar = () => {
schema['x-component-props'] || {}; schema['x-component-props'] || {};
schema['x-component-props'].singleton = singleton; schema['x-component-props'].singleton = singleton;
field.componentProps.singleton = singleton; field.componentProps.singleton = singleton;
updateSchema(schema);
}} }}
> >
<span style={{ marginRight: 24 }}></span>{' '} <span style={{ marginRight: 24 }}></span>{' '}
<Switch size={'small'} checked={!!field.componentProps.singleton}/> <Switch
size={'small'}
checked={!!field.componentProps.singleton}
/>
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }
@ -203,7 +247,7 @@ Tabs.DesignableBar = () => {
Tabs.TabPane.DesignableBar = () => { Tabs.TabPane.DesignableBar = () => {
const { schema, remove, refresh, insertAfter } = useDesignable(); const { schema, remove, refresh, insertAfter } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const field = useField();
return ( return (
<div className={cls('designable-bar', { active: visible })}> <div className={cls('designable-bar', { active: visible })}>
<span <span
@ -222,12 +266,45 @@ Tabs.TabPane.DesignableBar = () => {
overlay={ overlay={
<Menu> <Menu>
<Menu.Item <Menu.Item
onClick={() => { onClick={async () => {
schema.title = uid(); const values = await FormDialog('修改名称和图标', () => {
return (
<FormLayout layout={'vertical'}>
<SchemaField
schema={{
type: 'object',
properties: {
title: {
type: 'string',
title: '标签名称',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
icon: {
type: 'string',
title: '标签图标',
'x-decorator': 'FormItem',
'x-component': 'IconPicker',
},
},
}}
/>
</FormLayout>
);
}).open({
initialValues: {
title: schema['title'],
icon: schema['x-component-props']?.['icon'],
},
});
field.componentProps.icon = values.icon;
schema.title = values.title;
schema['x-component-props'] = schema['x-component-props'] =
schema['x-component-props'] || {}; schema['x-component-props'] || {};
schema['x-component-props']['tab'] = uid(); schema['x-component-props']['icon'] = values.icon;
refresh(); refresh();
updateSchema(schema);
}} }}
> >
@ -240,7 +317,7 @@ Tabs.TabPane.DesignableBar = () => {
setVisible(false); setVisible(false);
}} }}
> >
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }

View File

@ -144,6 +144,7 @@ export class Table {
model, model,
fields = [], fields = [],
indexes = [], indexes = [],
sortable,
} = options; } = options;
this.options = options; this.options = options;
this.database = database; this.database = database;
@ -155,9 +156,28 @@ export class Table {
this.addIndexes(indexes, 'modelOnly'); this.addIndexes(indexes, 'modelOnly');
// this.modelInit('modelOnly'); // this.modelInit('modelOnly');
this.setFields(fields); this.setFields(fields);
this.initSortable();
database.runHooks('afterTableInit', this); database.runHooks('afterTableInit', this);
} }
public initSortable() {
const { sortable } = this.options;
if (!sortable) {
return;
}
if (typeof sortable === 'string') {
this.addField({
type: 'sort',
name: sortable,
});
} else if (typeof sortable === 'object') {
this.addField({
...sortable,
type: 'sort',
});
}
}
public modelInit(reinitialize: Reinitialize = false) { public modelInit(reinitialize: Reinitialize = false) {
if (reinitialize || !this.Model) { if (reinitialize || !this.Model) {
this.Model = this.defaultModel || class extends Model { }; this.Model = this.defaultModel || class extends Model { };
@ -389,6 +409,7 @@ export class Table {
for (const key in fields) { for (const key in fields) {
this.addField(fields[key], false); this.addField(fields[key], false);
} }
this.initSortable();
// @ts-ignore // @ts-ignore
this.addIndexes(indexes, false); this.addIndexes(indexes, false);
this.modelInit(true); this.modelInit(true);

View File

@ -4,11 +4,12 @@ export default {
name: 'collections', name: 'collections',
title: '数据表配置', title: '数据表配置',
model: 'Collection', model: 'Collection',
sortable: 'sort',
fields: [ fields: [
{ // {
type: 'sort', // type: 'sort',
name: 'sort', // name: 'sort',
}, // },
{ {
type: 'uid', type: 'uid',
name: 'name', name: 'name',
@ -24,6 +25,11 @@ export default {
type: 'string', type: 'string',
name: 'privilege', name: 'privilege',
}, },
{
type: 'json',
name: 'sortable',
defaultValue: 'sort',
},
{ {
type: 'json', type: 'json',
name: 'options', name: 'options',

View File

@ -5,12 +5,17 @@ export default {
name: 'fields', name: 'fields',
title: '字段配置', title: '字段配置',
model: 'Field', model: 'Field',
sortable: {
type: 'sort',
name: 'sort',
scope: ['parentKey'],
},
fields: [ fields: [
{ // {
type: 'sort', // type: 'sort',
name: 'sort', // name: 'sort',
scope: ['parentKey'], // scope: ['parentKey'],
}, // },
{ {
type: 'uid', type: 'uid',
name: 'key', name: 'key',

View File

@ -4,12 +4,17 @@ export default {
name: 'routes', name: 'routes',
title: '路由表', title: '路由表',
model: 'Route', model: 'Route',
sortable: {
type: 'sort',
name: 'sort',
scope: ['parentKey'],
},
fields: [ fields: [
{ // {
type: 'sort', // type: 'sort',
name: 'sort', // name: 'sort',
scope: ['parentKey'], // scope: ['parentKey'],
}, // },
{ {
type: 'uid', type: 'uid',
name: 'key', name: 'key',

View File

@ -4,17 +4,22 @@ export default {
name: 'ui_schemas', name: 'ui_schemas',
title: '字段配置', title: '字段配置',
model: 'UISchema', model: 'UISchema',
sortable: {
type: 'sort',
name: 'sort',
scope: ['parentKey'],
},
fields: [ fields: [
{ {
type: 'uid', type: 'uid',
name: 'key', name: 'key',
primaryKey: true, primaryKey: true,
}, },
{ // {
type: 'sort', // type: 'sort',
name: 'sort', // name: 'sort',
scope: ['parentKey'], // scope: ['parentKey'],
}, // },
{ {
type: 'string', type: 'string',
name: 'name', name: 'name',

View File

@ -8710,6 +8710,11 @@ flat-to-nested@^1.1.1:
resolved "https://registry.npmjs.org/flat-to-nested/-/flat-to-nested-1.1.1.tgz#ec183cd9a72f6bfbf8ca21acb0fc8235e509f581" resolved "https://registry.npmjs.org/flat-to-nested/-/flat-to-nested-1.1.1.tgz#ec183cd9a72f6bfbf8ca21acb0fc8235e509f581"
integrity sha512-Sym5oik6BO9JnsDEjv9Q9hPTCexG2ttk0UiM2mgLEiCiiUOQr8acBd33r8ixnoSGR0HAxPoP8WtLAL5oV46IhQ== integrity sha512-Sym5oik6BO9JnsDEjv9Q9hPTCexG2ttk0UiM2mgLEiCiiUOQr8acBd33r8ixnoSGR0HAxPoP8WtLAL5oV46IhQ==
flat@^5.0.2:
version "5.0.2"
resolved "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
flatted@^3.1.0: flatted@^3.1.0:
version "3.1.1" version "3.1.1"
resolved "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" resolved "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469"