lots of updates
This commit is contained in:
parent
c23d2c5c3c
commit
5b014bc67e
@ -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');
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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 };
|
||||
|
@ -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 = {}) {
|
||||
|
@ -7,6 +7,7 @@ export const createdAt: ISchema = {
|
||||
group: 'systemInfo',
|
||||
order: 1,
|
||||
title: '创建时间',
|
||||
sortable: true,
|
||||
default: {
|
||||
dataType: 'date',
|
||||
field: 'created_at',
|
||||
|
@ -7,6 +7,7 @@ export const datetime: ISchema = {
|
||||
group: 'datetime',
|
||||
order: 1,
|
||||
title: '日期',
|
||||
sortable: true,
|
||||
default: {
|
||||
dataType: 'date',
|
||||
// name,
|
||||
|
@ -7,6 +7,7 @@ export const email: ISchema = {
|
||||
group: 'basic',
|
||||
order: 4,
|
||||
title: '电子邮箱',
|
||||
sortable: true,
|
||||
default: {
|
||||
dataType: 'string',
|
||||
// name,
|
||||
|
@ -7,6 +7,7 @@ export const number: ISchema = {
|
||||
group: 'basic',
|
||||
order: 5,
|
||||
title: '数字',
|
||||
sortable: true,
|
||||
default: {
|
||||
dataType: 'float',
|
||||
// name,
|
||||
|
@ -7,6 +7,7 @@ export const percent: ISchema = {
|
||||
group: 'basic',
|
||||
order: 6,
|
||||
title: '百分比',
|
||||
sortable: true,
|
||||
default: {
|
||||
dataType: 'float',
|
||||
// name,
|
||||
|
@ -7,6 +7,7 @@ export const phone: ISchema = {
|
||||
group: 'basic',
|
||||
order: 3,
|
||||
title: '手机号码',
|
||||
sortable: true,
|
||||
default: {
|
||||
dataType: 'string',
|
||||
// name,
|
||||
|
@ -7,6 +7,7 @@ export const string: ISchema = {
|
||||
group: 'basic',
|
||||
order: 1,
|
||||
title: '单行文本',
|
||||
sortable: true,
|
||||
default: {
|
||||
interface: 'string',
|
||||
dataType: 'string',
|
||||
|
@ -7,6 +7,7 @@ export const time: ISchema = {
|
||||
group: 'datetime',
|
||||
order: 2,
|
||||
title: '时间',
|
||||
sortable: true,
|
||||
default: {
|
||||
dataType: 'time',
|
||||
// name,
|
||||
|
@ -7,6 +7,7 @@ export const updatedAt: ISchema = {
|
||||
group: 'systemInfo',
|
||||
order: 2,
|
||||
title: '最后更新时间',
|
||||
sortable: true,
|
||||
default: {
|
||||
dataType: 'date',
|
||||
field: 'updated_at',
|
||||
|
@ -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<SchemaKey, Schema> {
|
||||
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<SchemaKey, Schema>());
|
||||
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 (
|
||||
<FormProvider form={form}>
|
||||
<FormLayout layout={'inline'}>
|
||||
@ -89,7 +152,7 @@ export const FilterItem = (props) => {
|
||||
<SchemaField.Void x-component="Space">
|
||||
<SchemaField.String
|
||||
name="column"
|
||||
x-decorator="FormItem"
|
||||
x-decorator="FormilyFormItem"
|
||||
x-decorator-props={{
|
||||
asterisk: true,
|
||||
feedbackLayout: 'none',
|
||||
@ -105,11 +168,11 @@ export const FilterItem = (props) => {
|
||||
value: 'name',
|
||||
}
|
||||
}}
|
||||
enum={columns.map((column) => column.toJSON())}
|
||||
enum={[...columns.values()].map((column) => column.toJSON())}
|
||||
/>
|
||||
<SchemaField.String
|
||||
name="operation"
|
||||
x-decorator="FormItem"
|
||||
x-decorator="FormilyFormItem"
|
||||
x-decorator-props={{
|
||||
asterisk: true,
|
||||
feedbackLayout: 'none',
|
||||
@ -124,7 +187,7 @@ export const FilterItem = (props) => {
|
||||
/>
|
||||
<SchemaField.String
|
||||
name="value"
|
||||
x-decorator="FormItem"
|
||||
x-decorator="FormilyFormItem"
|
||||
x-decorator-props={{
|
||||
asterisk: true,
|
||||
feedbackLayout: 'none',
|
||||
|
@ -38,6 +38,20 @@ const schema = {
|
||||
name: 'filter',
|
||||
type: 'object',
|
||||
'x-component': 'Filter',
|
||||
default: {
|
||||
and: [
|
||||
{
|
||||
field1: {
|
||||
eq: 'aa',
|
||||
}
|
||||
},
|
||||
{
|
||||
field1: {
|
||||
eq: 'bbb',
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
properties: {
|
||||
column1: {
|
||||
type: 'void',
|
||||
@ -68,7 +82,7 @@ const schema = {
|
||||
],
|
||||
},
|
||||
properties: {
|
||||
field1: {
|
||||
field2: {
|
||||
type: 'number',
|
||||
'x-component': 'InputNumber',
|
||||
},
|
||||
@ -79,7 +93,7 @@ const schema = {
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaRenderer schema={schema} />
|
||||
<SchemaRenderer debug={true} schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
@ -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 (
|
||||
<div className={'nb-filter-group'}>
|
||||
<div className={cls('nb-filter-group', { bordered })}>
|
||||
{onRemove && (
|
||||
<a className={'nb-filter-group-close'} onClick={() => onRemove()}>
|
||||
<CloseCircleOutlined />
|
||||
@ -20,8 +47,13 @@ export function FilterGroup(props) {
|
||||
满足组内{' '}
|
||||
<Select
|
||||
style={{ width: 80 }}
|
||||
onChange={(value) => {}}
|
||||
defaultValue={'and'}
|
||||
onChange={(logical) => {
|
||||
onChange &&
|
||||
onChange({
|
||||
[logical]: value.list,
|
||||
});
|
||||
}}
|
||||
defaultValue={value.logical}
|
||||
>
|
||||
<Select.Option value={'and'}>全部</Select.Option>
|
||||
<Select.Option value={'or'}>任意</Select.Option>
|
||||
@ -29,9 +61,12 @@ export function FilterGroup(props) {
|
||||
条件
|
||||
</div>
|
||||
<FilterList
|
||||
initialValue={[{}]}
|
||||
onChange={(list) => {
|
||||
console.log({ list });
|
||||
initialValue={value.list}
|
||||
onChange={(list: any[]) => {
|
||||
onChange &&
|
||||
onChange({
|
||||
[value.logical]: list.filter((item) => Object.keys(item).length),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@ -40,7 +75,9 @@ export function FilterGroup(props) {
|
||||
|
||||
export function FilterList(props) {
|
||||
const { initialValue } = props;
|
||||
const { list, push, remove } = useDynamicList<any>(initialValue || []);
|
||||
const { list, push, remove, replace } = useDynamicList<any>(
|
||||
initialValue || [],
|
||||
);
|
||||
useEffect(() => {
|
||||
props.onChange && props.onChange(list);
|
||||
}, [list]);
|
||||
@ -48,18 +85,29 @@ export function FilterList(props) {
|
||||
<div className={'nb-filter-list'}>
|
||||
<div>
|
||||
{list.map((item, index) => {
|
||||
if (item.type === 'group') {
|
||||
return <FilterGroup key={index} onRemove={() => remove(index)} />;
|
||||
if (item.and || item.or) {
|
||||
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>
|
||||
<a
|
||||
onClick={() => {
|
||||
push({
|
||||
type: 'item',
|
||||
key: new Date().toTimeString(),
|
||||
});
|
||||
push({});
|
||||
}}
|
||||
>
|
||||
添加条件
|
||||
@ -67,8 +115,7 @@ export function FilterList(props) {
|
||||
<a
|
||||
onClick={() => {
|
||||
push({
|
||||
type: 'group',
|
||||
key: new Date().toTimeString(),
|
||||
and: [{}],
|
||||
});
|
||||
}}
|
||||
>
|
||||
@ -80,9 +127,10 @@ export function FilterList(props) {
|
||||
|
||||
export const Filter = connect(
|
||||
(props) => {
|
||||
// console.log('Filter.props', { props });
|
||||
return (
|
||||
<div>
|
||||
<FilterGroup />
|
||||
<FilterGroup bordered={false} {...props} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
@ -1,10 +1,18 @@
|
||||
.nb-filter-group {
|
||||
position: relative;
|
||||
margin-bottom: 14px;
|
||||
padding: 14px;
|
||||
border: 1px dashed rgb(222, 222, 222);
|
||||
min-width: 400px;
|
||||
&-close {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
&.bordered {
|
||||
border: 1px dashed rgb(222, 222, 222);
|
||||
padding: 14px;
|
||||
}
|
||||
}
|
||||
.nb-filter-list {
|
||||
.ant-form-inline {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
@ -123,7 +123,6 @@ export function SortableBodyRow(props: any) {
|
||||
<SortableContext
|
||||
strategy={horizontalListSortingStrategy}
|
||||
items={React.Children.map(props.children, (child) => {
|
||||
console.log(child.key, 'child.key');
|
||||
return `td${child.key}`;
|
||||
})}
|
||||
>
|
||||
|
@ -17,7 +17,7 @@ import { uid } from '@formily/shared';
|
||||
import useRequest from '@ahooksjs/use-request';
|
||||
import { BaseResult } from '@ahooksjs/use-request/lib/types';
|
||||
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 {
|
||||
SortableContext,
|
||||
@ -42,6 +42,7 @@ import { Resource } from '../../resource';
|
||||
import {
|
||||
CollectionProvider,
|
||||
DisplayedMapProvider,
|
||||
useCollection,
|
||||
useCollectionContext,
|
||||
useDisplayedMapContext,
|
||||
} from '../../constate';
|
||||
@ -59,7 +60,10 @@ import {
|
||||
SortableRowHandle,
|
||||
} from './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 {
|
||||
props: any;
|
||||
@ -79,12 +83,12 @@ export interface ITableRowContext {
|
||||
record: any;
|
||||
}
|
||||
|
||||
const TableConetxt = createContext<ITableContext>(null);
|
||||
const TableContext = createContext<ITableContext>({} as any);
|
||||
const TableRowContext = createContext<ITableRowContext>(null);
|
||||
const CollectionFieldContext = createContext(null);
|
||||
|
||||
const useTable = () => {
|
||||
return useContext(TableConetxt);
|
||||
return useContext(TableContext);
|
||||
};
|
||||
|
||||
const useTableRow = () => {
|
||||
@ -98,10 +102,15 @@ function useTableFilterAction() {
|
||||
refresh,
|
||||
props: { refreshRequestOnChange },
|
||||
} = useTable();
|
||||
const form = useForm();
|
||||
return {
|
||||
async run() {
|
||||
console.log('useTableFilterAction', form.values);
|
||||
if (refreshRequestOnChange) {
|
||||
return service.refresh();
|
||||
return service.run({
|
||||
...service.params[0],
|
||||
// filter,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -204,7 +213,7 @@ const useTableIndex = () => {
|
||||
const { pagination, props } = useTable();
|
||||
const ctx = useContext(TableRowContext);
|
||||
if (pagination && !props.clientSidePagination) {
|
||||
const { pageSize, page } = pagination;
|
||||
const { pageSize, page = 1 } = pagination;
|
||||
return ctx.index + (page - 1) * pageSize;
|
||||
}
|
||||
return ctx.index;
|
||||
@ -433,11 +442,13 @@ const useDataSource = () => {
|
||||
|
||||
const TableMain = () => {
|
||||
const {
|
||||
resource,
|
||||
selectedRowKeys,
|
||||
setSelectedRowKeys,
|
||||
service,
|
||||
field,
|
||||
props: { rowKey, dragSort, showIndex },
|
||||
refresh,
|
||||
} = useTable();
|
||||
const columns = useTableColumns();
|
||||
const dataSource = useDataSource();
|
||||
@ -446,8 +457,29 @@ const TableMain = () => {
|
||||
return (
|
||||
<div className={'nb-table'}>
|
||||
<DndContext
|
||||
onDragEnd={(event) => {
|
||||
console.log({ event });
|
||||
onDragEnd={async (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) => (
|
||||
@ -541,13 +573,35 @@ const TableMain = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const usePagination = (paginationProps?: any) => {
|
||||
return useState(() => {
|
||||
if (!paginationProps) {
|
||||
return false;
|
||||
}
|
||||
return { page: 1, pageSize: 10, ...paginationProps };
|
||||
});
|
||||
const usePagination = () => {
|
||||
const field = useField<Formily.Core.Models.ArrayField>();
|
||||
const paginationProps = field.componentProps.pagination;
|
||||
|
||||
let pagination = 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) => {
|
||||
@ -559,10 +613,25 @@ const TableProvider = (props: any) => {
|
||||
} = props;
|
||||
const { schema } = useDesignable();
|
||||
const field = useField<Formily.Core.Models.ArrayField>();
|
||||
const [pagination, setPagination] = usePagination(props.pagination);
|
||||
const [pagination, setPagination] = usePagination();
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<any>([]);
|
||||
const [, refresh] = useState(uid());
|
||||
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(
|
||||
(params?: any) => {
|
||||
if (!resource) {
|
||||
@ -582,12 +651,20 @@ const TableProvider = (props: any) => {
|
||||
onSuccess(data: any) {
|
||||
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 (
|
||||
<TableConetxt.Provider
|
||||
<TableContext.Provider
|
||||
value={{
|
||||
resource,
|
||||
refresh: () => {
|
||||
@ -597,7 +674,7 @@ const TableProvider = (props: any) => {
|
||||
: service?.data?.total;
|
||||
const maxPage = Math.ceil(total / pageSize);
|
||||
if (page > maxPage) {
|
||||
setPagination((prev) => ({ ...prev, page: maxPage }));
|
||||
setPagination({ page: maxPage });
|
||||
} else {
|
||||
refresh(uid());
|
||||
}
|
||||
@ -613,7 +690,7 @@ const TableProvider = (props: any) => {
|
||||
}}
|
||||
>
|
||||
<TableMain />
|
||||
</TableConetxt.Provider>
|
||||
</TableContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -654,19 +731,18 @@ Table.Pagination = observer(() => {
|
||||
total={total}
|
||||
onChange={(current, pageSize) => {
|
||||
const page = pagination.pageSize !== pageSize ? 1 : current;
|
||||
setPagination((prev) => ({
|
||||
...prev,
|
||||
page,
|
||||
pageSize,
|
||||
}));
|
||||
if (clientSidePagination) {
|
||||
return;
|
||||
}
|
||||
service.run({
|
||||
...service.params,
|
||||
setPagination({
|
||||
page,
|
||||
pageSize,
|
||||
});
|
||||
// if (clientSidePagination) {
|
||||
// return;
|
||||
// }
|
||||
// service.run({
|
||||
// ...service.params[0],
|
||||
// page,
|
||||
// pageSize,
|
||||
// });
|
||||
}}
|
||||
/>
|
||||
</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) => {
|
||||
const { service } = useTable();
|
||||
const { fieldNames = [] } = props;
|
||||
const { schema, DesignableBar } = useDesignable();
|
||||
const form = useMemo(() => createForm(), []);
|
||||
const { fields = [] } = useCollectionContext();
|
||||
const fields2properties = (fields: any[]) => {
|
||||
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,
|
||||
title: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
return properties;
|
||||
};
|
||||
const [visible, setVisible] = useState(false);
|
||||
const obj = flatten(form.values.filter || {});
|
||||
console.log('flatten', obj, Object.values(obj));
|
||||
const count = Object.values(obj).filter((i) =>
|
||||
Array.isArray(i) ? i.length : i,
|
||||
).length;
|
||||
|
||||
const icon = props.icon || 'FilterOutlined';
|
||||
|
||||
return (
|
||||
<Popover
|
||||
trigger={['click']}
|
||||
placement={'bottomLeft'}
|
||||
visible={visible}
|
||||
onVisibleChange={setVisible}
|
||||
content={
|
||||
<div>
|
||||
<FormProvider form={form}>
|
||||
@ -1077,17 +1185,32 @@ Table.Filter = observer((props: any) => {
|
||||
filter: {
|
||||
type: 'object',
|
||||
'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>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Button>
|
||||
{schema.title}
|
||||
<Button icon={<IconPicker type={icon} />}>
|
||||
{count > 0 ? `${count} 个筛选项` : schema.title}
|
||||
<DesignableBar />
|
||||
</Button>
|
||||
</Popover>
|
||||
@ -1099,7 +1222,11 @@ Table.Filter.DesignableBar = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const displayed = useDisplayedMapContext();
|
||||
const { fields } = useCollectionContext();
|
||||
|
||||
const field = useField();
|
||||
let fieldNames = field.componentProps.fieldNames || [];
|
||||
if (fieldNames.length === 0) {
|
||||
fieldNames = fields.map((field) => field.name);
|
||||
}
|
||||
return (
|
||||
<div className={cls('designable-bar', { active: visible })}>
|
||||
<span
|
||||
@ -1119,22 +1246,72 @@ Table.Filter.DesignableBar = () => {
|
||||
<Menu>
|
||||
<Menu.ItemGroup title={'筛选字段'}>
|
||||
{fields
|
||||
.filter((field) => {
|
||||
const option = interfaces.get(field.interface);
|
||||
.filter((collectionField) => {
|
||||
const option = interfaces.get(collectionField.interface);
|
||||
return option.operations?.length;
|
||||
})
|
||||
.map((field) => (
|
||||
.map((collectionField) => (
|
||||
<SwitchMenuItem
|
||||
title={field?.uiSchema?.title}
|
||||
checked={true}
|
||||
onChange={async (checked) => {}}
|
||||
title={collectionField?.uiSchema?.title}
|
||||
checked={fieldNames.includes(collectionField.name)}
|
||||
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.Divider />
|
||||
<Menu.Item
|
||||
onClick={(e) => {
|
||||
schema.title = uid();
|
||||
onClick={async (e) => {
|
||||
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();
|
||||
}}
|
||||
>
|
||||
@ -1153,7 +1330,7 @@ Table.Filter.DesignableBar = () => {
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
删除
|
||||
移除
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
@ -1262,6 +1439,7 @@ Table.Action.DesignableBar = () => {
|
||||
const isPopup = Object.keys(schema.properties || {}).length > 0;
|
||||
const inActionBar = schema.parent['x-component'] === 'Table.ActionBar';
|
||||
const displayed = useDisplayedMapContext();
|
||||
const field = useField();
|
||||
return (
|
||||
<div className={cls('designable-bar', { active: visible })}>
|
||||
<span
|
||||
@ -1280,8 +1458,43 @@ Table.Action.DesignableBar = () => {
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
onClick={(e) => {
|
||||
schema.title = uid();
|
||||
onClick={async (e) => {
|
||||
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();
|
||||
}}
|
||||
>
|
||||
@ -1293,11 +1506,19 @@ Table.Action.DesignableBar = () => {
|
||||
<Select
|
||||
bordered={false}
|
||||
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={'drawer'}>抽屉</Select.Option>
|
||||
<Select.Option value={'window'}>浏览器窗口</Select.Option>
|
||||
<Select.Option value={'Action.Modal'}>对话框</Select.Option>
|
||||
<Select.Option value={'Action.Drawer'}>抽屉</Select.Option>
|
||||
<Select.Option value={'Action.Window'}>浏览器窗口</Select.Option>
|
||||
</Select>{' '}
|
||||
内打开
|
||||
</Menu.Item>
|
||||
@ -1321,7 +1542,7 @@ Table.Action.DesignableBar = () => {
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
删除
|
||||
移除
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
@ -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 (
|
||||
<div className={cls('designable-bar', { active: visible })}>
|
||||
<span
|
||||
@ -1409,7 +1631,38 @@ Table.Column.DesignableBar = () => {
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
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;
|
||||
schema.title = title;
|
||||
refresh();
|
||||
@ -1420,8 +1673,9 @@ Table.Column.DesignableBar = () => {
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
编辑列
|
||||
修改列标题
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item
|
||||
onClick={async () => {
|
||||
const s = remove();
|
||||
@ -1430,7 +1684,7 @@ Table.Column.DesignableBar = () => {
|
||||
await removeSchema(s);
|
||||
}}
|
||||
>
|
||||
删除列
|
||||
移除
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
@ -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 (
|
||||
<div className={cls('designable-bar', { active: visible })}>
|
||||
<span
|
||||
@ -1486,9 +1740,16 @@ Table.DesignableBar = observer((props) => {
|
||||
const bool = !field.componentProps.showIndex;
|
||||
schema['x-component-props']['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
|
||||
key={'dragSort'}
|
||||
@ -1498,16 +1759,166 @@ Table.DesignableBar = observer((props) => {
|
||||
: 'sort';
|
||||
schema['x-component-props']['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>
|
||||
{!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'}>
|
||||
每页默认显示{' '}
|
||||
<Select
|
||||
@ -1516,11 +1927,16 @@ Table.DesignableBar = observer((props) => {
|
||||
onChange={(value) => {
|
||||
const componentProps = schema['x-component-props'] || {};
|
||||
set(componentProps, 'pagination.defaultPageSize', value);
|
||||
set(componentProps, 'pagination.pageSize', value);
|
||||
schema['x-component-props'] = componentProps;
|
||||
field.componentProps.pagination.pageSize = value;
|
||||
field.componentProps.pagination.defaultPageSize = value;
|
||||
refresh();
|
||||
updateSchema(schema);
|
||||
}}
|
||||
defaultValue={defaultPageSize}
|
||||
>
|
||||
<Select.Option value={10}>10</Select.Option>
|
||||
<Select.Option value={20}>20</Select.Option>
|
||||
<Select.Option value={50}>50</Select.Option>
|
||||
<Select.Option value={100}>100</Select.Option>
|
||||
@ -1539,7 +1955,7 @@ Table.DesignableBar = observer((props) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
删除当前区块
|
||||
移除
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
|
@ -224,4 +224,10 @@ td.nb-table-operation {
|
||||
.action-bar-align-left.isOver {
|
||||
min-height: 32px;
|
||||
background-color: #e6f7ff;
|
||||
}
|
||||
}
|
||||
|
||||
.nb-space-between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { observer, connect, useField, RecursionField } from '@formily/react';
|
||||
import React from 'react';
|
||||
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 { PlusOutlined, MenuOutlined } from '@ant-design/icons';
|
||||
import { useState } from 'react';
|
||||
@ -11,6 +16,8 @@ import './style.less';
|
||||
import { uid } from '@formily/shared';
|
||||
import { DragHandle, SortableItem } from '../../components/Sortable';
|
||||
import { DndContext, DragOverlay } from '@dnd-kit/core';
|
||||
import { FormDialog, FormLayout } from '@formily/antd';
|
||||
import IconPicker from '../../components/icon-picker';
|
||||
|
||||
const useTabs = ({ singleton }) => {
|
||||
const tabsField = useField();
|
||||
@ -38,7 +45,8 @@ const useTabs = ({ singleton }) => {
|
||||
|
||||
export const Tabs: any = observer((props: any) => {
|
||||
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 [dragOverlayContent, setDragOverlayContent] = useState('');
|
||||
|
||||
@ -93,14 +101,45 @@ export const Tabs: any = observer((props: any) => {
|
||||
type={'dashed'}
|
||||
icon={<PlusOutlined />}
|
||||
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({
|
||||
type: 'void',
|
||||
name: uid(),
|
||||
title: uid(),
|
||||
title: values.title,
|
||||
'x-component': 'Tabs.TabPane',
|
||||
'x-designable-bar': 'Tabs.TabPane.DesignableBar',
|
||||
'x-component-props': {
|
||||
tab: uid(),
|
||||
icon: values.icon,
|
||||
},
|
||||
properties: {
|
||||
[uid()]: {
|
||||
@ -151,6 +190,7 @@ Tabs.TabPane = observer((props: any) => {
|
||||
}}
|
||||
>
|
||||
<div className={'nb-tab-pane'}>
|
||||
<IconPicker type={props.icon} />
|
||||
{schema.title} <DesignableBar />
|
||||
</div>
|
||||
</SortableItem>
|
||||
@ -185,10 +225,14 @@ Tabs.DesignableBar = () => {
|
||||
schema['x-component-props'] || {};
|
||||
schema['x-component-props'].singleton = singleton;
|
||||
field.componentProps.singleton = singleton;
|
||||
updateSchema(schema);
|
||||
}}
|
||||
>
|
||||
禁用标签页 <span style={{ marginRight: 24 }}></span>{' '}
|
||||
<Switch size={'small'} checked={!!field.componentProps.singleton}/>
|
||||
<Switch
|
||||
size={'small'}
|
||||
checked={!!field.componentProps.singleton}
|
||||
/>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
@ -203,7 +247,7 @@ Tabs.DesignableBar = () => {
|
||||
Tabs.TabPane.DesignableBar = () => {
|
||||
const { schema, remove, refresh, insertAfter } = useDesignable();
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const field = useField();
|
||||
return (
|
||||
<div className={cls('designable-bar', { active: visible })}>
|
||||
<span
|
||||
@ -222,12 +266,45 @@ Tabs.TabPane.DesignableBar = () => {
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
schema.title = uid();
|
||||
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'],
|
||||
},
|
||||
});
|
||||
field.componentProps.icon = values.icon;
|
||||
schema.title = values.title;
|
||||
schema['x-component-props'] =
|
||||
schema['x-component-props'] || {};
|
||||
schema['x-component-props']['tab'] = uid();
|
||||
schema['x-component-props']['icon'] = values.icon;
|
||||
refresh();
|
||||
updateSchema(schema);
|
||||
}}
|
||||
>
|
||||
修改名称和图标
|
||||
@ -240,7 +317,7 @@ Tabs.TabPane.DesignableBar = () => {
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
删除
|
||||
移除
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
|
@ -144,6 +144,7 @@ export class Table {
|
||||
model,
|
||||
fields = [],
|
||||
indexes = [],
|
||||
sortable,
|
||||
} = options;
|
||||
this.options = options;
|
||||
this.database = database;
|
||||
@ -155,9 +156,28 @@ export class Table {
|
||||
this.addIndexes(indexes, 'modelOnly');
|
||||
// this.modelInit('modelOnly');
|
||||
this.setFields(fields);
|
||||
this.initSortable();
|
||||
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) {
|
||||
if (reinitialize || !this.Model) {
|
||||
this.Model = this.defaultModel || class extends Model { };
|
||||
@ -389,6 +409,7 @@ export class Table {
|
||||
for (const key in fields) {
|
||||
this.addField(fields[key], false);
|
||||
}
|
||||
this.initSortable();
|
||||
// @ts-ignore
|
||||
this.addIndexes(indexes, false);
|
||||
this.modelInit(true);
|
||||
|
@ -4,11 +4,12 @@ export default {
|
||||
name: 'collections',
|
||||
title: '数据表配置',
|
||||
model: 'Collection',
|
||||
sortable: 'sort',
|
||||
fields: [
|
||||
{
|
||||
type: 'sort',
|
||||
name: 'sort',
|
||||
},
|
||||
// {
|
||||
// type: 'sort',
|
||||
// name: 'sort',
|
||||
// },
|
||||
{
|
||||
type: 'uid',
|
||||
name: 'name',
|
||||
@ -24,6 +25,11 @@ export default {
|
||||
type: 'string',
|
||||
name: 'privilege',
|
||||
},
|
||||
{
|
||||
type: 'json',
|
||||
name: 'sortable',
|
||||
defaultValue: 'sort',
|
||||
},
|
||||
{
|
||||
type: 'json',
|
||||
name: 'options',
|
||||
|
@ -5,12 +5,17 @@ export default {
|
||||
name: 'fields',
|
||||
title: '字段配置',
|
||||
model: 'Field',
|
||||
sortable: {
|
||||
type: 'sort',
|
||||
name: 'sort',
|
||||
scope: ['parentKey'],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'sort',
|
||||
name: 'sort',
|
||||
scope: ['parentKey'],
|
||||
},
|
||||
// {
|
||||
// type: 'sort',
|
||||
// name: 'sort',
|
||||
// scope: ['parentKey'],
|
||||
// },
|
||||
{
|
||||
type: 'uid',
|
||||
name: 'key',
|
||||
|
@ -4,12 +4,17 @@ export default {
|
||||
name: 'routes',
|
||||
title: '路由表',
|
||||
model: 'Route',
|
||||
sortable: {
|
||||
type: 'sort',
|
||||
name: 'sort',
|
||||
scope: ['parentKey'],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'sort',
|
||||
name: 'sort',
|
||||
scope: ['parentKey'],
|
||||
},
|
||||
// {
|
||||
// type: 'sort',
|
||||
// name: 'sort',
|
||||
// scope: ['parentKey'],
|
||||
// },
|
||||
{
|
||||
type: 'uid',
|
||||
name: 'key',
|
||||
|
@ -4,17 +4,22 @@ export default {
|
||||
name: 'ui_schemas',
|
||||
title: '字段配置',
|
||||
model: 'UISchema',
|
||||
sortable: {
|
||||
type: 'sort',
|
||||
name: 'sort',
|
||||
scope: ['parentKey'],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'uid',
|
||||
name: 'key',
|
||||
primaryKey: true,
|
||||
},
|
||||
{
|
||||
type: 'sort',
|
||||
name: 'sort',
|
||||
scope: ['parentKey'],
|
||||
},
|
||||
// {
|
||||
// type: 'sort',
|
||||
// name: 'sort',
|
||||
// scope: ['parentKey'],
|
||||
// },
|
||||
{
|
||||
type: 'string',
|
||||
name: 'name',
|
||||
|
@ -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"
|
||||
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:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469"
|
||||
|
Loading…
Reference in New Issue
Block a user