refactor: code splitting of the table component (#120)
This commit is contained in:
		
							parent
							
								
									82ad5de8cb
								
							
						
					
					
						commit
						0f9eb4e7f2
					
				@ -1,7 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
  "singleQuote": true,
 | 
			
		||||
  "trailingComma": "all",
 | 
			
		||||
  "printWidth": 80,
 | 
			
		||||
  "printWidth": 120,
 | 
			
		||||
  "overrides": [
 | 
			
		||||
    {
 | 
			
		||||
      "files": ".prettierrc",
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,8 @@ import { PlusOutlined, DownOutlined } from '@ant-design/icons';
 | 
			
		||||
import cls from 'classnames';
 | 
			
		||||
import { uid, isValid, clone } from '@formily/shared';
 | 
			
		||||
import { Resource } from '../../../resource';
 | 
			
		||||
import { TableRowContext, useTable } from '../../../schemas/table';
 | 
			
		||||
import { TableRowContext } from '../../../schemas/table/context';
 | 
			
		||||
import { useTable } from '../../../schemas/table/hooks/useTable';
 | 
			
		||||
import { useRequest } from 'ahooks';
 | 
			
		||||
import { VisibleContext } from '../../../context';
 | 
			
		||||
import { connect, FormProvider, observer, useForm } from '@formily/react';
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,8 @@ import { LockOutlined } from '@ant-design/icons';
 | 
			
		||||
import cls from 'classnames';
 | 
			
		||||
import { uid } from '@formily/shared';
 | 
			
		||||
import { Resource } from '../../../resource';
 | 
			
		||||
import { TableRowContext, useTable } from '../../../schemas/table';
 | 
			
		||||
import { TableRowContext } from '../../../schemas/table/context';
 | 
			
		||||
import { useTable } from '../../../schemas/table/hooks/useTable';
 | 
			
		||||
import { useRequest } from 'ahooks';
 | 
			
		||||
import { VisibleContext } from '../../../context';
 | 
			
		||||
import { connect, ISchema, observer, useField } from '@formily/react';
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,8 @@ import { LockOutlined } from '@ant-design/icons';
 | 
			
		||||
import cls from 'classnames';
 | 
			
		||||
import { uid, isValid } from '@formily/shared';
 | 
			
		||||
import { Resource } from '../../../resource';
 | 
			
		||||
import { TableRowContext, useTable } from '../../../schemas/table';
 | 
			
		||||
import { TableRowContext } from '../../../schemas/table/context';
 | 
			
		||||
import { useTable } from '../../../schemas/table/hooks/useTable';
 | 
			
		||||
import { useRequest } from 'ahooks';
 | 
			
		||||
import { VisibleContext } from '../../../context';
 | 
			
		||||
import { connect, observer, useForm } from '@formily/react';
 | 
			
		||||
 | 
			
		||||
@ -52,11 +52,8 @@ import { Menu } from '../../schemas/menu';
 | 
			
		||||
import { Password } from '../../schemas/password';
 | 
			
		||||
import { Radio } from '../../schemas/radio';
 | 
			
		||||
import { Select } from '../../schemas/select';
 | 
			
		||||
import {
 | 
			
		||||
  CollectionFieldContext,
 | 
			
		||||
  Table,
 | 
			
		||||
  TableRowContext,
 | 
			
		||||
} from '../../schemas/table';
 | 
			
		||||
import { Table } from '../../schemas/table';
 | 
			
		||||
import { CollectionFieldContext, TableRowContext } from '../../schemas/table/context';
 | 
			
		||||
import { Tabs } from '../../schemas/tabs';
 | 
			
		||||
import { TimePicker } from '../../schemas/time-picker';
 | 
			
		||||
import { Upload } from '../../schemas/upload';
 | 
			
		||||
 | 
			
		||||
@ -38,7 +38,7 @@ import { useEffect } from 'react';
 | 
			
		||||
import { uid } from '@formily/shared';
 | 
			
		||||
import { getSchemaPath } from '../../components/schema-renderer';
 | 
			
		||||
import { useCollection, useCollectionContext } from '../../constate';
 | 
			
		||||
import { useTable } from '../table';
 | 
			
		||||
import { useTable } from '../table/hooks/useTable';
 | 
			
		||||
import { fieldsToFilterColumns } from './';
 | 
			
		||||
import { set } from 'lodash';
 | 
			
		||||
import { DragHandle } from '../../components/Sortable';
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ import { useEffect } from 'react';
 | 
			
		||||
import { uid } from '@formily/shared';
 | 
			
		||||
import { getSchemaPath } from '../../components/schema-renderer';
 | 
			
		||||
import { useCollection, useCollectionContext } from '../../constate';
 | 
			
		||||
import { useTable } from '../table';
 | 
			
		||||
import { useTable } from '../table/hooks/useTable';
 | 
			
		||||
import { BlockSchemaContext } from '../../context';
 | 
			
		||||
import { DragHandle } from '../../components/Sortable';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
 | 
			
		||||
@ -44,7 +44,7 @@ import {
 | 
			
		||||
import { useResource as useGeneralResource } from '../../hooks/useResource';
 | 
			
		||||
import { Resource } from '../../resource';
 | 
			
		||||
import { BaseResult } from '@ahooksjs/use-request/lib/types';
 | 
			
		||||
import { CollectionFieldContext } from '../table';
 | 
			
		||||
import { CollectionFieldContext } from '../table/context';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
 | 
			
		||||
export interface DescriptionsContextProps {
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ import { useEffect } from 'react';
 | 
			
		||||
import { uid } from '@formily/shared';
 | 
			
		||||
import { getSchemaPath } from '../../components/schema-renderer';
 | 
			
		||||
import { useCollection, useCollectionContext } from '../../constate';
 | 
			
		||||
import { useTable } from '../table';
 | 
			
		||||
import { useTable } from '../table/hooks/useTable';
 | 
			
		||||
import { DragHandle } from '../../components/Sortable';
 | 
			
		||||
import { useCompile } from '../../hooks/useCompile';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@ import { useEffect } from 'react';
 | 
			
		||||
import { uid } from '@formily/shared';
 | 
			
		||||
import { getSchemaPath } from '../../components/schema-renderer';
 | 
			
		||||
import { useCollection, useCollectionContext } from '../../constate';
 | 
			
		||||
import { useTable } from '../table';
 | 
			
		||||
import { useTable } from '../table/hooks/useTable';
 | 
			
		||||
import { DragHandle } from '../../components/Sortable';
 | 
			
		||||
import { fieldsToFilterColumns } from '../calendar';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@ import { Action } from '../action';
 | 
			
		||||
import { BlockSchemaContext, VisibleContext } from '../../context';
 | 
			
		||||
import { SchemaRenderer } from '../../components/schema-renderer';
 | 
			
		||||
import { uid } from '@formily/shared';
 | 
			
		||||
import { CollectionFieldContext } from '../table';
 | 
			
		||||
import { CollectionFieldContext } from '../table/context';
 | 
			
		||||
import {
 | 
			
		||||
  CollectionProvider,
 | 
			
		||||
  useCollectionContext,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										40
									
								
								packages/client/src/schemas/table/Actions.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								packages/client/src/schemas/table/Actions.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { Space } from 'antd';
 | 
			
		||||
import { RecursionField } from '@formily/react';
 | 
			
		||||
import { useDesignable } from '..';
 | 
			
		||||
import { Droppable, SortableItem } from '../../components/Sortable';
 | 
			
		||||
import { getSchemaPath } from '../../components/schema-renderer';
 | 
			
		||||
 | 
			
		||||
export const Actions = (props: any) => {
 | 
			
		||||
  const { align = 'left' } = props;
 | 
			
		||||
  const { schema, designable } = useDesignable();
 | 
			
		||||
  return (
 | 
			
		||||
    <Droppable
 | 
			
		||||
      id={`${schema.name}-${align}`}
 | 
			
		||||
      className={`action-bar-align-${align}`}
 | 
			
		||||
      data={{ align, path: getSchemaPath(schema) }}
 | 
			
		||||
    >
 | 
			
		||||
      <Space>
 | 
			
		||||
        {schema.mapProperties((s) => {
 | 
			
		||||
          const currentAlign = s['x-align'] || 'left';
 | 
			
		||||
          if (currentAlign !== align) {
 | 
			
		||||
            return null;
 | 
			
		||||
          }
 | 
			
		||||
          return (
 | 
			
		||||
            <SortableItem
 | 
			
		||||
              id={s.name}
 | 
			
		||||
              data={{
 | 
			
		||||
                align,
 | 
			
		||||
                draggable: true,
 | 
			
		||||
                title: s.title,
 | 
			
		||||
                path: getSchemaPath(s),
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              <RecursionField name={s.name} schema={s} />
 | 
			
		||||
            </SortableItem>
 | 
			
		||||
          );
 | 
			
		||||
        })}
 | 
			
		||||
      </Space>
 | 
			
		||||
    </Droppable>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										77
									
								
								packages/client/src/schemas/table/AddActionButton.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								packages/client/src/schemas/table/AddActionButton.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,77 @@
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import { Dropdown, Menu, Button } from 'antd';
 | 
			
		||||
import { SettingOutlined } from '@ant-design/icons';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { Schema } from '@formily/react';
 | 
			
		||||
import { useDesignable, ISchema } from '..';
 | 
			
		||||
import { useDisplayedMapContext, useClient } from '../../constate';
 | 
			
		||||
import SwitchMenuItem from '../../components/SwitchMenuItem';
 | 
			
		||||
import { getSchemaPath } from '../../components/schema-renderer';
 | 
			
		||||
import {generateActionSchema} from './utils';
 | 
			
		||||
 | 
			
		||||
export const AddActionButton = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const [visible, setVisible] = useState(false);
 | 
			
		||||
  const displayed = useDisplayedMapContext();
 | 
			
		||||
  const { appendChild, remove } = useDesignable();
 | 
			
		||||
  const { schema, designable } = useDesignable();
 | 
			
		||||
  const { createSchema, removeSchema, updateSchema } = useClient();
 | 
			
		||||
 | 
			
		||||
  if (!designable || !schema['x-designable-bar']) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
  return (
 | 
			
		||||
    <Dropdown
 | 
			
		||||
      trigger={['hover']}
 | 
			
		||||
      visible={visible}
 | 
			
		||||
      onVisibleChange={setVisible}
 | 
			
		||||
      overlay={
 | 
			
		||||
        <Menu>
 | 
			
		||||
          <Menu.ItemGroup title={t('Enable actions')}>
 | 
			
		||||
            {[
 | 
			
		||||
              { title: t('Filter'), name: 'filter' },
 | 
			
		||||
              { title: t('Export'), name: 'export' },
 | 
			
		||||
              { title: t('Add new'), name: 'create' },
 | 
			
		||||
              { title: t('Delete'), name: 'destroy' },
 | 
			
		||||
            ].map((item) => (
 | 
			
		||||
              <SwitchMenuItem
 | 
			
		||||
                key={item.name}
 | 
			
		||||
                checked={displayed.has(item.name)}
 | 
			
		||||
                title={item.title}
 | 
			
		||||
                onChange={async (checked) => {
 | 
			
		||||
                  if (!checked) {
 | 
			
		||||
                    const s = displayed.get(item.name) as Schema;
 | 
			
		||||
                    const path = getSchemaPath(s);
 | 
			
		||||
                    displayed.remove(item.name);
 | 
			
		||||
                    const removed = remove(path);
 | 
			
		||||
                    await removeSchema(removed);
 | 
			
		||||
                  } else {
 | 
			
		||||
                    const s = generateActionSchema(item.name);
 | 
			
		||||
                    const data = appendChild(s);
 | 
			
		||||
                    await createSchema(data);
 | 
			
		||||
                  }
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
            ))}
 | 
			
		||||
          </Menu.ItemGroup>
 | 
			
		||||
          <Menu.Divider />
 | 
			
		||||
          <Menu.SubMenu disabled title={t('Customize')}>
 | 
			
		||||
            <Menu.Item style={{ minWidth: 120 }}>{t('Function')}</Menu.Item>
 | 
			
		||||
            <Menu.Item>{t('Popup form')}</Menu.Item>
 | 
			
		||||
            <Menu.Item>{t('Flexible popup')}</Menu.Item>
 | 
			
		||||
          </Menu.SubMenu>
 | 
			
		||||
        </Menu>
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <Button
 | 
			
		||||
        className={'designable-btn designable-btn-dash'}
 | 
			
		||||
        style={{ marginLeft: 8 }}
 | 
			
		||||
        type={'dashed'}
 | 
			
		||||
        icon={<SettingOutlined />}
 | 
			
		||||
      >
 | 
			
		||||
        {t('Configure actions')}
 | 
			
		||||
      </Button>
 | 
			
		||||
    </Dropdown>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										232
									
								
								packages/client/src/schemas/table/AddColumn.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										232
									
								
								packages/client/src/schemas/table/AddColumn.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,232 @@
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import { Dropdown, Menu, Button } from 'antd';
 | 
			
		||||
import { SettingOutlined } from '@ant-design/icons';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { uid } from '@formily/shared';
 | 
			
		||||
import { FormDialog, FormLayout } from '@formily/antd';
 | 
			
		||||
import { useDesignable, createCollectionField, ISchema } from '..';
 | 
			
		||||
import { useCollectionContext, useCollectionsContext, useDisplayedMapContext, useClient } from '../../constate';
 | 
			
		||||
import { useTable } from './hooks/useTable';
 | 
			
		||||
import SwitchMenuItem from '../../components/SwitchMenuItem';
 | 
			
		||||
import { isAssociation, options } from '../database-field/interfaces';
 | 
			
		||||
import { getSchemaPath, SchemaField } from '../../components/schema-renderer';
 | 
			
		||||
 | 
			
		||||
export function AddColumn() {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const [visible, setVisible] = useState(false);
 | 
			
		||||
  const { appendChild, remove } = useDesignable();
 | 
			
		||||
  const { loadCollections } = useCollectionsContext();
 | 
			
		||||
  const { collection, fields, refresh } = useCollectionContext();
 | 
			
		||||
  const displayed = useDisplayedMapContext();
 | 
			
		||||
  const { service } = useTable();
 | 
			
		||||
  const { createSchema, removeSchema, updateSchema } = useClient();
 | 
			
		||||
  return (
 | 
			
		||||
    <Dropdown
 | 
			
		||||
      trigger={['hover']}
 | 
			
		||||
      visible={visible}
 | 
			
		||||
      onVisibleChange={setVisible}
 | 
			
		||||
      overlay={
 | 
			
		||||
        <Menu>
 | 
			
		||||
          <Menu.ItemGroup className={'display-fields'} title={t('Display fields')}>
 | 
			
		||||
            {fields.map((field) => (
 | 
			
		||||
              <SwitchMenuItem
 | 
			
		||||
                title={field?.uiSchema?.title}
 | 
			
		||||
                checked={displayed.has(field.name)}
 | 
			
		||||
                onChange={async (checked) => {
 | 
			
		||||
                  if (checked) {
 | 
			
		||||
                    console.log('SwitchMenuItem.field.name', field.dataType, service.params[0]);
 | 
			
		||||
                    const columnSchema: ISchema = {
 | 
			
		||||
                      type: 'void',
 | 
			
		||||
                      'x-component': 'Table.Column',
 | 
			
		||||
                      'x-component-props': {
 | 
			
		||||
                        fieldName: field.name,
 | 
			
		||||
                      },
 | 
			
		||||
                      'x-designable-bar': 'Table.Column.DesignableBar',
 | 
			
		||||
                    };
 | 
			
		||||
                    if (field.interface === 'linkTo') {
 | 
			
		||||
                      columnSchema.properties = {
 | 
			
		||||
                        options: {
 | 
			
		||||
                          type: 'void',
 | 
			
		||||
                          'x-decorator': 'Form',
 | 
			
		||||
                          'x-component': 'Select.Options.Drawer',
 | 
			
		||||
                          'x-component-props': {
 | 
			
		||||
                            useOkAction: '{{ Select.useOkAction }}',
 | 
			
		||||
                          },
 | 
			
		||||
                          title: "{{t('Select record')}}",
 | 
			
		||||
                          properties: {
 | 
			
		||||
                            table: {
 | 
			
		||||
                              type: 'array',
 | 
			
		||||
                              'x-designable-bar': 'Table.DesignableBar',
 | 
			
		||||
                              'x-decorator': 'BlockItem',
 | 
			
		||||
                              'x-decorator-props': {
 | 
			
		||||
                                draggable: false,
 | 
			
		||||
                              },
 | 
			
		||||
                              'x-component': 'Table',
 | 
			
		||||
                              default: [],
 | 
			
		||||
                              'x-component-props': {
 | 
			
		||||
                                rowKey: 'id',
 | 
			
		||||
                                useRowSelection: '{{ Select.useRowSelection }}',
 | 
			
		||||
                                useSelectedRowKeys: '{{ Select.useSelectedRowKeys }}',
 | 
			
		||||
                                onSelect: '{{ Select.useSelect() }}',
 | 
			
		||||
                                collectionName: field.target,
 | 
			
		||||
                                // dragSort: true,
 | 
			
		||||
                                // showIndex: true,
 | 
			
		||||
                                refreshRequestOnChange: true,
 | 
			
		||||
                                pagination: {
 | 
			
		||||
                                  pageSize: 10,
 | 
			
		||||
                                },
 | 
			
		||||
                              },
 | 
			
		||||
                              properties: {
 | 
			
		||||
                                [uid()]: {
 | 
			
		||||
                                  type: 'void',
 | 
			
		||||
                                  'x-component': 'Table.ActionBar',
 | 
			
		||||
                                  'x-designable-bar': 'Table.ActionBar.DesignableBar',
 | 
			
		||||
                                  properties: {
 | 
			
		||||
                                    [uid()]: {
 | 
			
		||||
                                      type: 'void',
 | 
			
		||||
                                      title: "{{t('Filter')}}",
 | 
			
		||||
                                      'x-decorator': 'AddNew.Displayed',
 | 
			
		||||
                                      'x-decorator-props': {
 | 
			
		||||
                                        displayName: 'filter',
 | 
			
		||||
                                      },
 | 
			
		||||
                                      'x-align': 'left',
 | 
			
		||||
                                      'x-component': 'Table.Filter',
 | 
			
		||||
                                      'x-designable-bar': 'Table.Filter.DesignableBar',
 | 
			
		||||
                                      'x-component-props': {
 | 
			
		||||
                                        fieldNames: [],
 | 
			
		||||
                                      },
 | 
			
		||||
                                    },
 | 
			
		||||
                                  },
 | 
			
		||||
                                },
 | 
			
		||||
                              },
 | 
			
		||||
                            },
 | 
			
		||||
                          },
 | 
			
		||||
                        },
 | 
			
		||||
                        option: {
 | 
			
		||||
                          type: 'void',
 | 
			
		||||
                          'x-component': 'Select.OptionTag',
 | 
			
		||||
                          properties: {
 | 
			
		||||
                            [uid()]: {
 | 
			
		||||
                              type: 'void',
 | 
			
		||||
                              title: "{{t('View record')}}",
 | 
			
		||||
                              'x-component': 'Action.Drawer',
 | 
			
		||||
                              'x-component-props': {
 | 
			
		||||
                                bodyStyle: {
 | 
			
		||||
                                  background: '#f0f2f5',
 | 
			
		||||
                                },
 | 
			
		||||
                              },
 | 
			
		||||
                              properties: {
 | 
			
		||||
                                [uid()]: {
 | 
			
		||||
                                  type: 'void',
 | 
			
		||||
                                  'x-component': 'Tabs',
 | 
			
		||||
                                  'x-designable-bar': 'Tabs.DesignableBar',
 | 
			
		||||
                                  properties: {
 | 
			
		||||
                                    [uid()]: {
 | 
			
		||||
                                      type: 'void',
 | 
			
		||||
                                      title: "{{t('Details')}}",
 | 
			
		||||
                                      'x-designable-bar': 'Tabs.TabPane.DesignableBar',
 | 
			
		||||
                                      'x-component': 'Tabs.TabPane',
 | 
			
		||||
                                      'x-component-props': {},
 | 
			
		||||
                                      properties: {
 | 
			
		||||
                                        [uid()]: {
 | 
			
		||||
                                          type: 'void',
 | 
			
		||||
                                          'x-component': 'Grid',
 | 
			
		||||
                                          'x-component-props': {
 | 
			
		||||
                                            addNewComponent: 'AddNew.PaneItem',
 | 
			
		||||
                                          },
 | 
			
		||||
                                        },
 | 
			
		||||
                                      },
 | 
			
		||||
                                    },
 | 
			
		||||
                                  },
 | 
			
		||||
                                },
 | 
			
		||||
                              },
 | 
			
		||||
                            },
 | 
			
		||||
                          },
 | 
			
		||||
                        },
 | 
			
		||||
                      };
 | 
			
		||||
                    }
 | 
			
		||||
                    const data = appendChild(columnSchema);
 | 
			
		||||
                    await createSchema(data);
 | 
			
		||||
                    if (isAssociation(field)) {
 | 
			
		||||
                      const defaultAppends = service.params[0]?.defaultAppends || [];
 | 
			
		||||
                      defaultAppends.push(field.name);
 | 
			
		||||
                      await service.run({
 | 
			
		||||
                        ...service.params[0],
 | 
			
		||||
                        defaultAppends,
 | 
			
		||||
                      });
 | 
			
		||||
                    }
 | 
			
		||||
                  } else {
 | 
			
		||||
                    const s: any = displayed.get(field.name);
 | 
			
		||||
                    const p = getSchemaPath(s);
 | 
			
		||||
                    const removed = remove(p);
 | 
			
		||||
                    await removeSchema(removed);
 | 
			
		||||
                    displayed.remove(field.name);
 | 
			
		||||
                    if (isAssociation(field)) {
 | 
			
		||||
                      const defaultAppends = service.params[0]?.defaultAppends || [];
 | 
			
		||||
                      const index = defaultAppends.indexOf(field.name);
 | 
			
		||||
                      if (index > -1) {
 | 
			
		||||
                        defaultAppends.splice(index, 1);
 | 
			
		||||
                      }
 | 
			
		||||
                      await service.run({
 | 
			
		||||
                        ...service.params[0],
 | 
			
		||||
                        defaultAppends,
 | 
			
		||||
                      });
 | 
			
		||||
                    }
 | 
			
		||||
                  }
 | 
			
		||||
                  // service.refresh();
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
            ))}
 | 
			
		||||
          </Menu.ItemGroup>
 | 
			
		||||
          <Menu.Divider />
 | 
			
		||||
          <Menu.SubMenu disabled popupClassName={'add-new-fields-popup'} title={t('Add field')}>
 | 
			
		||||
            {options.map((option) => (
 | 
			
		||||
              <Menu.ItemGroup title={option.label}>
 | 
			
		||||
                {option.children.map((item) => (
 | 
			
		||||
                  <Menu.Item
 | 
			
		||||
                    style={{ minWidth: 150 }}
 | 
			
		||||
                    key={item.name}
 | 
			
		||||
                    onClick={async () => {
 | 
			
		||||
                      setVisible(false);
 | 
			
		||||
                      const values = await FormDialog(t('Add field'), () => {
 | 
			
		||||
                        return (
 | 
			
		||||
                          <FormLayout layout={'vertical'}>
 | 
			
		||||
                            <SchemaField scope={{ loadCollections }} schema={item} />
 | 
			
		||||
                          </FormLayout>
 | 
			
		||||
                        );
 | 
			
		||||
                      }).open({
 | 
			
		||||
                        initialValues: {
 | 
			
		||||
                          interface: item.name,
 | 
			
		||||
                          ...item.default,
 | 
			
		||||
                          key: uid(),
 | 
			
		||||
                          name: `f_${uid()}`,
 | 
			
		||||
                        },
 | 
			
		||||
                      });
 | 
			
		||||
                      await createCollectionField(collection?.name, values);
 | 
			
		||||
                      const data = appendChild({
 | 
			
		||||
                        type: 'void',
 | 
			
		||||
                        'x-component': 'Table.Column',
 | 
			
		||||
                        'x-component-props': {
 | 
			
		||||
                          fieldName: values.name,
 | 
			
		||||
                        },
 | 
			
		||||
                        'x-designable-bar': 'Table.Column.DesignableBar',
 | 
			
		||||
                      });
 | 
			
		||||
                      await createSchema(data);
 | 
			
		||||
                      await refresh();
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                    {item.title}
 | 
			
		||||
                  </Menu.Item>
 | 
			
		||||
                ))}
 | 
			
		||||
              </Menu.ItemGroup>
 | 
			
		||||
            ))}
 | 
			
		||||
          </Menu.SubMenu>
 | 
			
		||||
        </Menu>
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <Button type={'dashed'} className={'designable-btn designable-btn-dash'} icon={<SettingOutlined />}>
 | 
			
		||||
        {t('Configure fields')}
 | 
			
		||||
      </Button>
 | 
			
		||||
    </Dropdown>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -1,42 +1,15 @@
 | 
			
		||||
import React, { forwardRef, useState } from 'react';
 | 
			
		||||
import {
 | 
			
		||||
  SortableContext,
 | 
			
		||||
  useSortable,
 | 
			
		||||
  horizontalListSortingStrategy,
 | 
			
		||||
  verticalListSortingStrategy,
 | 
			
		||||
  sortableKeyboardCoordinates,
 | 
			
		||||
} from '@dnd-kit/sortable';
 | 
			
		||||
import React, { createContext, useContext, forwardRef, useState } from 'react';
 | 
			
		||||
import { SortableContext, useSortable, horizontalListSortingStrategy } from '@dnd-kit/sortable';
 | 
			
		||||
import { CSS } from '@dnd-kit/utilities';
 | 
			
		||||
import { Table } from 'antd';
 | 
			
		||||
import { createContext } from 'react';
 | 
			
		||||
import { useContext } from 'react';
 | 
			
		||||
import { range } from 'lodash';
 | 
			
		||||
import parse from 'html-react-parser';
 | 
			
		||||
import cls from 'classnames';
 | 
			
		||||
import { DndContext, DragOverlay, useDroppable, useDraggable } from '@dnd-kit/core';
 | 
			
		||||
import { MenuOutlined } from '@ant-design/icons';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  DndContext,
 | 
			
		||||
  DragOverlay,
 | 
			
		||||
  closestCenter,
 | 
			
		||||
  KeyboardSensor,
 | 
			
		||||
  PointerSensor,
 | 
			
		||||
  useSensor,
 | 
			
		||||
  useSensors,
 | 
			
		||||
  useDroppable,
 | 
			
		||||
  useDraggable,
 | 
			
		||||
} from '@dnd-kit/core';
 | 
			
		||||
import { createPortal } from 'react-dom';
 | 
			
		||||
import { useRef } from 'react';
 | 
			
		||||
import { SortableItem } from '../../components/Sortable';
 | 
			
		||||
import {
 | 
			
		||||
  findPropertyByPath,
 | 
			
		||||
  getSchemaPath,
 | 
			
		||||
  useDesignable,
 | 
			
		||||
} from '../../components/schema-renderer';
 | 
			
		||||
import { findPropertyByPath, getSchemaPath, useDesignable } from '../../components/schema-renderer';
 | 
			
		||||
import { updateSchema } from '..';
 | 
			
		||||
import { Schema } from '@formily/react';
 | 
			
		||||
import { isColumn, isColumnComponent } from '.';
 | 
			
		||||
import { isColumn } from './utils';
 | 
			
		||||
 | 
			
		||||
export const RowDraggableContext = createContext({});
 | 
			
		||||
export const ColDraggableContext = createContext(null);
 | 
			
		||||
@ -68,9 +41,7 @@ export function SortableColumn(props) {
 | 
			
		||||
      ref={setDroppableNodeRef}
 | 
			
		||||
      // {...attributes}
 | 
			
		||||
    >
 | 
			
		||||
      <ColDraggableContext.Provider
 | 
			
		||||
        value={{ setDraggableNodeRef, attributes, listeners }}
 | 
			
		||||
      >
 | 
			
		||||
      <ColDraggableContext.Provider value={{ setDraggableNodeRef, attributes, listeners }}>
 | 
			
		||||
        {props.children}
 | 
			
		||||
      </ColDraggableContext.Provider>
 | 
			
		||||
      {/* <span ref={setDraggableNodeRef} {...attributes} {...listeners}>
 | 
			
		||||
@ -81,22 +52,9 @@ export function SortableColumn(props) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function SortableBodyRow(props: any) {
 | 
			
		||||
  const {
 | 
			
		||||
    className,
 | 
			
		||||
    style: prevStyle,
 | 
			
		||||
    ['data-row-key']: dataRowKey,
 | 
			
		||||
    ...others
 | 
			
		||||
  } = props;
 | 
			
		||||
  const {
 | 
			
		||||
    isDragging,
 | 
			
		||||
    attributes,
 | 
			
		||||
    listeners,
 | 
			
		||||
    setNodeRef,
 | 
			
		||||
    setDraggableNodeRef,
 | 
			
		||||
    overIndex,
 | 
			
		||||
    transform,
 | 
			
		||||
    transition,
 | 
			
		||||
  } = useSortable({ id: dataRowKey });
 | 
			
		||||
  const { className, style: prevStyle, ['data-row-key']: dataRowKey, ...others } = props;
 | 
			
		||||
  const { isDragging, attributes, listeners, setNodeRef, setDraggableNodeRef, overIndex, transform, transition } =
 | 
			
		||||
    useSortable({ id: dataRowKey });
 | 
			
		||||
 | 
			
		||||
  const style = {
 | 
			
		||||
    ...prevStyle,
 | 
			
		||||
@ -105,15 +63,9 @@ export function SortableBodyRow(props: any) {
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <RowDraggableContext.Provider
 | 
			
		||||
      value={{ listeners, setDraggableNodeRef, attributes }}
 | 
			
		||||
    >
 | 
			
		||||
    <RowDraggableContext.Provider value={{ listeners, setDraggableNodeRef, attributes }}>
 | 
			
		||||
      <tr
 | 
			
		||||
        className={cls(
 | 
			
		||||
          { isDragging },
 | 
			
		||||
          props.className,
 | 
			
		||||
          `droppable-${props['data-row-key']}`,
 | 
			
		||||
        )}
 | 
			
		||||
        className={cls({ isDragging }, props.className, `droppable-${props['data-row-key']}`)}
 | 
			
		||||
        // className={cls(className)}
 | 
			
		||||
        ref={setNodeRef}
 | 
			
		||||
        {...others}
 | 
			
		||||
@ -127,9 +79,7 @@ export function SortableBodyRow(props: any) {
 | 
			
		||||
          })}
 | 
			
		||||
        >
 | 
			
		||||
          {React.Children.map(props.children, (child, index) => (
 | 
			
		||||
            <CellContext.Provider value={`td${child.key}`}>
 | 
			
		||||
              {child}
 | 
			
		||||
            </CellContext.Provider>
 | 
			
		||||
            <CellContext.Provider value={`td${child.key}`}>{child}</CellContext.Provider>
 | 
			
		||||
          ))}
 | 
			
		||||
        </SortableContext>
 | 
			
		||||
      </tr>
 | 
			
		||||
@ -264,8 +214,7 @@ export function SortableBodyCell(props) {
 | 
			
		||||
 | 
			
		||||
export function SortableRowHandle(props) {
 | 
			
		||||
  const { className, ...others } = props;
 | 
			
		||||
  const { setDraggableNodeRef, attributes, listeners } =
 | 
			
		||||
    useContext<any>(RowDraggableContext);
 | 
			
		||||
  const { setDraggableNodeRef, attributes, listeners } = useContext<any>(RowDraggableContext);
 | 
			
		||||
  return setDraggableNodeRef ? (
 | 
			
		||||
    <MenuOutlined
 | 
			
		||||
      {...others}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										106
									
								
								packages/client/src/schemas/table/TableActionBar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								packages/client/src/schemas/table/TableActionBar.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,106 @@
 | 
			
		||||
import React, { useContext, useState } from 'react';
 | 
			
		||||
import { observer } from '@formily/react';
 | 
			
		||||
import cls from 'classnames';
 | 
			
		||||
import { DndContext, DragOverlay } from '@dnd-kit/core';
 | 
			
		||||
import { useDesignable } from '..';
 | 
			
		||||
import { DisplayedMapProvider, useClient } from '../../constate';
 | 
			
		||||
import { AddActionButton } from './AddActionButton';
 | 
			
		||||
import { Actions } from './Actions';
 | 
			
		||||
import { findPropertyByPath } from '../../components/schema-renderer';
 | 
			
		||||
 | 
			
		||||
export const TableActionBar = observer((props: any) => {
 | 
			
		||||
  const { align = 'top' } = props;
 | 
			
		||||
  // const { schema, designable } = useDesignable();
 | 
			
		||||
  const { root, schema, insertAfter, remove, appendChild } = useDesignable();
 | 
			
		||||
  const moveToAfter = (path1, path2, extra = {}) => {
 | 
			
		||||
    if (!path1 || !path2) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (path1.join('.') === path2.join('.')) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const data = findPropertyByPath(root, path1);
 | 
			
		||||
    if (!data) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    remove(path1);
 | 
			
		||||
    return insertAfter(
 | 
			
		||||
      {
 | 
			
		||||
        ...data.toJSON(),
 | 
			
		||||
        ...extra,
 | 
			
		||||
      },
 | 
			
		||||
      path2,
 | 
			
		||||
    );
 | 
			
		||||
  };
 | 
			
		||||
  const { createSchema, removeSchema, updateSchema } = useClient();
 | 
			
		||||
 | 
			
		||||
  const [dragOverlayContent, setDragOverlayContent] = useState('');
 | 
			
		||||
  return (
 | 
			
		||||
    <DndContext
 | 
			
		||||
      onDragStart={(event) => {
 | 
			
		||||
        setDragOverlayContent(event.active.data?.current?.title || '');
 | 
			
		||||
        // const previewRef = event.active.data?.current?.previewRef;
 | 
			
		||||
        // if (previewRef) {
 | 
			
		||||
        //   setDragOverlayContent(previewRef?.current?.innerHTML);
 | 
			
		||||
        // } else {
 | 
			
		||||
        //   setDragOverlayContent('');
 | 
			
		||||
        // }
 | 
			
		||||
      }}
 | 
			
		||||
      onDragEnd={async (event) => {
 | 
			
		||||
        const path1 = event.active?.data?.current?.path;
 | 
			
		||||
        const path2 = event.over?.data?.current?.path;
 | 
			
		||||
        const align = event.over?.data?.current?.align;
 | 
			
		||||
        const draggable = event.over?.data?.current?.draggable;
 | 
			
		||||
        if (!path1 || !path2) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        if (path1.join('.') === path2.join('.')) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        if (!draggable) {
 | 
			
		||||
          console.log('alignalignalignalign', align);
 | 
			
		||||
          const p = findPropertyByPath(root, path1);
 | 
			
		||||
          if (!p) {
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          remove(path1);
 | 
			
		||||
          const data = appendChild(
 | 
			
		||||
            {
 | 
			
		||||
              ...p.toJSON(),
 | 
			
		||||
              'x-align': align,
 | 
			
		||||
            },
 | 
			
		||||
            path2,
 | 
			
		||||
          );
 | 
			
		||||
          await updateSchema(data);
 | 
			
		||||
        } else {
 | 
			
		||||
          const data = moveToAfter(path1, path2, {
 | 
			
		||||
            'x-align': align,
 | 
			
		||||
          });
 | 
			
		||||
          await updateSchema(data);
 | 
			
		||||
        }
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <DragOverlay
 | 
			
		||||
        dropAnimation={{
 | 
			
		||||
          duration: 10,
 | 
			
		||||
          easing: 'cubic-bezier(0.18, 0.67, 0.6, 1.22)',
 | 
			
		||||
        }}
 | 
			
		||||
        style={{ pointerEvents: 'none', whiteSpace: 'nowrap' }}
 | 
			
		||||
      >
 | 
			
		||||
        {dragOverlayContent}
 | 
			
		||||
        {/* <div style={{ transform: 'translateX(-100%)' }} dangerouslySetInnerHTML={{__html: dragOverlayContent}}></div> */}
 | 
			
		||||
      </DragOverlay>
 | 
			
		||||
      <DisplayedMapProvider>
 | 
			
		||||
        <div className={cls('nb-action-bar', `align-${align}`)}>
 | 
			
		||||
          <div style={{ width: '50%' }}>
 | 
			
		||||
            <Actions align={'left'} />
 | 
			
		||||
          </div>
 | 
			
		||||
          <div style={{ marginLeft: 'auto', width: '50%', textAlign: 'right' }}>
 | 
			
		||||
            <Actions align={'right'} />
 | 
			
		||||
          </div>
 | 
			
		||||
          <AddActionButton />
 | 
			
		||||
        </div>
 | 
			
		||||
      </DisplayedMapProvider>
 | 
			
		||||
    </DndContext>
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										143
									
								
								packages/client/src/schemas/table/TableActionDesignableBar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								packages/client/src/schemas/table/TableActionDesignableBar.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,143 @@
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import { Select, Dropdown, Menu, Switch, Space } from 'antd';
 | 
			
		||||
import { MenuOutlined } from '@ant-design/icons';
 | 
			
		||||
import { useField } from '@formily/react';
 | 
			
		||||
import { FormDialog, FormLayout } from '@formily/antd';
 | 
			
		||||
import cls from 'classnames';
 | 
			
		||||
import { Trans, useTranslation } from 'react-i18next';
 | 
			
		||||
import { useDesignable } from '..';
 | 
			
		||||
import { useDisplayedMapContext, useClient } from '../../constate';
 | 
			
		||||
import { DragHandle } from '../../components/Sortable';
 | 
			
		||||
import { SchemaField } from '../../components/schema-renderer';
 | 
			
		||||
 | 
			
		||||
export const TableActionDesignableBar = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const { schema, remove, refresh, insertAfter } = useDesignable();
 | 
			
		||||
  const [visible, setVisible] = useState(false);
 | 
			
		||||
  const isPopup = Object.keys(schema.properties || {}).length > 0;
 | 
			
		||||
  const popupSchema = Object.values(schema.properties || {}).shift();
 | 
			
		||||
  const inActionBar = schema.parent['x-component'] === 'Table.ActionBar';
 | 
			
		||||
  const displayed = useDisplayedMapContext();
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const { createSchema, removeSchema, updateSchema } = useClient();
 | 
			
		||||
  const popupComponent = popupSchema?.['x-component'] || 'Action.Drawer';
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={cls('designable-bar', { active: visible })}>
 | 
			
		||||
      <span
 | 
			
		||||
        onClick={(e) => {
 | 
			
		||||
          e.stopPropagation();
 | 
			
		||||
        }}
 | 
			
		||||
        className={cls('designable-bar-actions', { active: visible })}
 | 
			
		||||
      >
 | 
			
		||||
        <Space>
 | 
			
		||||
          <DragHandle />
 | 
			
		||||
          <Dropdown
 | 
			
		||||
            trigger={['hover']}
 | 
			
		||||
            visible={visible}
 | 
			
		||||
            onVisibleChange={(visible) => {
 | 
			
		||||
              setVisible(visible);
 | 
			
		||||
            }}
 | 
			
		||||
            overlay={
 | 
			
		||||
              <Menu>
 | 
			
		||||
                <Menu.Item
 | 
			
		||||
                  onClick={async (e) => {
 | 
			
		||||
                    setVisible(false);
 | 
			
		||||
                    const values = await FormDialog(t('Edit button'), () => {
 | 
			
		||||
                      return (
 | 
			
		||||
                        <FormLayout layout={'vertical'}>
 | 
			
		||||
                          <SchemaField
 | 
			
		||||
                            schema={{
 | 
			
		||||
                              type: 'object',
 | 
			
		||||
                              properties: {
 | 
			
		||||
                                title: {
 | 
			
		||||
                                  type: 'string',
 | 
			
		||||
                                  title: t('Display name'),
 | 
			
		||||
                                  required: true,
 | 
			
		||||
                                  'x-decorator': 'FormItem',
 | 
			
		||||
                                  'x-component': 'Input',
 | 
			
		||||
                                },
 | 
			
		||||
                                icon: {
 | 
			
		||||
                                  type: 'string',
 | 
			
		||||
                                  title: t('Icon'),
 | 
			
		||||
                                  '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();
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  {t('Edit button')}
 | 
			
		||||
                </Menu.Item>
 | 
			
		||||
                {isPopup && (
 | 
			
		||||
                  <Menu.Item>
 | 
			
		||||
                    <Trans t={t}>
 | 
			
		||||
                      Open in
 | 
			
		||||
                      <Select
 | 
			
		||||
                        bordered={false}
 | 
			
		||||
                        size={'small'}
 | 
			
		||||
                        defaultValue={popupComponent}
 | 
			
		||||
                        style={{ width: 100 }}
 | 
			
		||||
                        onChange={async (value) => {
 | 
			
		||||
                          const s = Object.values(schema.properties).shift();
 | 
			
		||||
                          s['x-component'] = value;
 | 
			
		||||
                          refresh();
 | 
			
		||||
                          await updateSchema(s);
 | 
			
		||||
                          window.location.reload();
 | 
			
		||||
                          // const f = field.query(getSchemaPath(s)).take()
 | 
			
		||||
                          // console.log('fffffff', { schema, f });
 | 
			
		||||
                        }}
 | 
			
		||||
                      >
 | 
			
		||||
                        <Select.Option value={'Action.Modal'}>Modal</Select.Option>
 | 
			
		||||
                        <Select.Option value={'Action.Drawer'}>Drawer</Select.Option>
 | 
			
		||||
                        <Select.Option disabled value={'Action.Window'}>
 | 
			
		||||
                          Window
 | 
			
		||||
                        </Select.Option>
 | 
			
		||||
                      </Select>
 | 
			
		||||
                    </Trans>
 | 
			
		||||
                  </Menu.Item>
 | 
			
		||||
                )}
 | 
			
		||||
                {!inActionBar && (
 | 
			
		||||
                  <Menu.Item>
 | 
			
		||||
                    {t('Triggered when the row is clicked')}   
 | 
			
		||||
                    <Switch size={'small'} defaultChecked />
 | 
			
		||||
                  </Menu.Item>
 | 
			
		||||
                )}
 | 
			
		||||
                <Menu.Divider />
 | 
			
		||||
                <Menu.Item
 | 
			
		||||
                  onClick={async () => {
 | 
			
		||||
                    const displayName = schema?.['x-decorator-props']?.['displayName'];
 | 
			
		||||
                    const data = remove();
 | 
			
		||||
                    await removeSchema(data);
 | 
			
		||||
                    if (displayName) {
 | 
			
		||||
                      displayed.remove(displayName);
 | 
			
		||||
                    }
 | 
			
		||||
                    setVisible(false);
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  {t('Hide')}
 | 
			
		||||
                </Menu.Item>
 | 
			
		||||
              </Menu>
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <MenuOutlined />
 | 
			
		||||
          </Dropdown>
 | 
			
		||||
        </Space>
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										54
									
								
								packages/client/src/schemas/table/TableCell.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								packages/client/src/schemas/table/TableCell.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
			
		||||
import React, { useContext } from 'react';
 | 
			
		||||
import { observer, RecursionField, Schema } from '@formily/react';
 | 
			
		||||
import { cloneDeepWith, set } from 'lodash';
 | 
			
		||||
import { uid, merge } from '@formily/shared';
 | 
			
		||||
import { CollectionFieldContext,TableRowContext } from './context';
 | 
			
		||||
import { Table } from '.';
 | 
			
		||||
 | 
			
		||||
export const TableCell = observer((props: any) => {
 | 
			
		||||
  const ctx = useContext(TableRowContext);
 | 
			
		||||
  const schema = props.schema;
 | 
			
		||||
  const collectionField = useContext(CollectionFieldContext);
 | 
			
		||||
  if (schema['x-component'] === 'Table.Operation') {
 | 
			
		||||
    return <Table.Operation.Cell {...props} />;
 | 
			
		||||
  }
 | 
			
		||||
  let uiSchema = collectionField?.uiSchema as Schema;
 | 
			
		||||
  if (uiSchema?.['x-component'] === 'Upload.Attachment') {
 | 
			
		||||
    uiSchema = cloneDeepWith(uiSchema);
 | 
			
		||||
    set(uiSchema, 'x-component-props.size', 'small');
 | 
			
		||||
  }
 | 
			
		||||
  const componentProps = merge(uiSchema?.['x-component-props'] || {}, schema?.['x-component-props'] || {}, {
 | 
			
		||||
    arrayMerge: (t, s) => s,
 | 
			
		||||
  });
 | 
			
		||||
  console.log('Table.Cell', collectionField?.interface, componentProps);
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={`field-interface-${collectionField?.interface}`}>
 | 
			
		||||
      <RecursionField
 | 
			
		||||
        schema={
 | 
			
		||||
          !collectionField
 | 
			
		||||
            ? schema
 | 
			
		||||
            : new Schema({
 | 
			
		||||
                type: 'void',
 | 
			
		||||
                properties: {
 | 
			
		||||
                  [collectionField.name]: {
 | 
			
		||||
                    ...uiSchema,
 | 
			
		||||
                    title: undefined,
 | 
			
		||||
                    'x-read-pretty': true,
 | 
			
		||||
                    'x-decorator-props': {
 | 
			
		||||
                      feedbackLayout: 'popover',
 | 
			
		||||
                    },
 | 
			
		||||
                    'x-decorator': 'FormilyFormItem',
 | 
			
		||||
                    'x-component-props': componentProps,
 | 
			
		||||
                    properties: {
 | 
			
		||||
                      ...schema?.properties,
 | 
			
		||||
                    },
 | 
			
		||||
                  },
 | 
			
		||||
                },
 | 
			
		||||
              })
 | 
			
		||||
        }
 | 
			
		||||
        name={ctx.index}
 | 
			
		||||
        onlyRenderProperties
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										24
									
								
								packages/client/src/schemas/table/TableColumn.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								packages/client/src/schemas/table/TableColumn.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
import React, { useContext, useEffect } from 'react';
 | 
			
		||||
import { observer } from '@formily/react';
 | 
			
		||||
import { useDesignable } from '..';
 | 
			
		||||
import { useDisplayedMapContext } from '../../constate';
 | 
			
		||||
import { useCompile } from '../../hooks/useCompile';
 | 
			
		||||
import { CollectionFieldContext } from './context';
 | 
			
		||||
 | 
			
		||||
export const TableColumn = observer((props: any) => {
 | 
			
		||||
  const collectionField = useContext(CollectionFieldContext);
 | 
			
		||||
  const { schema, DesignableBar } = useDesignable();
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
  const displayed = useDisplayedMapContext();
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (collectionField?.name) {
 | 
			
		||||
      displayed.set(collectionField.name, schema);
 | 
			
		||||
    }
 | 
			
		||||
  }, [collectionField, schema]);
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={'nb-table-column'}>
 | 
			
		||||
      {compile(schema.title || collectionField?.uiSchema?.title)}
 | 
			
		||||
      <DesignableBar />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										158
									
								
								packages/client/src/schemas/table/TableColumnDesignableBar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								packages/client/src/schemas/table/TableColumnDesignableBar.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,158 @@
 | 
			
		||||
import React, { useContext, useState } from 'react';
 | 
			
		||||
import { useField } from '@formily/react';
 | 
			
		||||
import { Select, Dropdown, Menu, Space } from 'antd';
 | 
			
		||||
import { set } from 'lodash';
 | 
			
		||||
import cls from 'classnames';
 | 
			
		||||
import { MenuOutlined } from '@ant-design/icons';
 | 
			
		||||
import { FormDialog, FormLayout, Submit } from '@formily/antd';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { SchemaField } from '../../components/schema-renderer';
 | 
			
		||||
import { useCollectionsContext, useDisplayedMapContext, useClient } from '../../constate';
 | 
			
		||||
import { DragHandle } from '../../components/Sortable';
 | 
			
		||||
import { useDesignable } from '..';
 | 
			
		||||
import { useCompile } from '../../hooks/useCompile';
 | 
			
		||||
import { useTable } from './hooks/useTable';
 | 
			
		||||
import { CollectionFieldContext } from './hooks/useTableColumns';
 | 
			
		||||
 | 
			
		||||
export const TableColumnDesignableBar = () => {
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
  const { service, refresh: refreshTable } = useTable();
 | 
			
		||||
  // const fieldSchema = useFieldSchema();
 | 
			
		||||
  const { schema, remove, refresh, insertAfter } = useDesignable();
 | 
			
		||||
  const [visible, setVisible] = useState(false);
 | 
			
		||||
  const displayed = useDisplayedMapContext();
 | 
			
		||||
  const { getFieldsByCollection } = useCollectionsContext();
 | 
			
		||||
  const collectionField = useContext(CollectionFieldContext);
 | 
			
		||||
  const { createSchema, removeSchema, updateSchema } = useClient();
 | 
			
		||||
  console.log('displayed.map', displayed.map);
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={cls('designable-bar', { active: visible })}>
 | 
			
		||||
      <span
 | 
			
		||||
        onClick={(e) => {
 | 
			
		||||
          e.stopPropagation();
 | 
			
		||||
        }}
 | 
			
		||||
        className={cls('designable-bar-actions', { active: visible })}
 | 
			
		||||
      >
 | 
			
		||||
        <Space>
 | 
			
		||||
          <DragHandle />
 | 
			
		||||
          <Dropdown
 | 
			
		||||
            trigger={['hover']}
 | 
			
		||||
            visible={visible}
 | 
			
		||||
            onVisibleChange={(visible) => {
 | 
			
		||||
              setVisible(visible);
 | 
			
		||||
            }}
 | 
			
		||||
            overlay={
 | 
			
		||||
              <Menu>
 | 
			
		||||
                <Menu.Item
 | 
			
		||||
                  onClick={async (e) => {
 | 
			
		||||
                    setVisible(false);
 | 
			
		||||
                    const values = await FormDialog(t('Custom name'), () => {
 | 
			
		||||
                      return (
 | 
			
		||||
                        <FormLayout layout={'vertical'}>
 | 
			
		||||
                          <SchemaField
 | 
			
		||||
                            schema={{
 | 
			
		||||
                              type: 'object',
 | 
			
		||||
                              properties: {
 | 
			
		||||
                                fieldName: {
 | 
			
		||||
                                  type: 'string',
 | 
			
		||||
                                  title: t('Original name'),
 | 
			
		||||
                                  'x-read-pretty': true,
 | 
			
		||||
                                  'x-decorator': 'FormItem',
 | 
			
		||||
                                  'x-component': 'Input',
 | 
			
		||||
                                },
 | 
			
		||||
                                title: {
 | 
			
		||||
                                  type: 'string',
 | 
			
		||||
                                  title: t('Custom name'),
 | 
			
		||||
                                  '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();
 | 
			
		||||
                    await updateSchema({
 | 
			
		||||
                      key: schema['key'],
 | 
			
		||||
                      title: title,
 | 
			
		||||
                    });
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  {t('Custom column name')}
 | 
			
		||||
                </Menu.Item>
 | 
			
		||||
                {collectionField?.interface === 'linkTo' && (
 | 
			
		||||
                  <Menu.Item>
 | 
			
		||||
                    <div
 | 
			
		||||
                      style={{
 | 
			
		||||
                        display: 'flex',
 | 
			
		||||
                        justifyContent: 'space-between',
 | 
			
		||||
                      }}
 | 
			
		||||
                    >
 | 
			
		||||
                      {t('Label field')}{' '}
 | 
			
		||||
                      <Select
 | 
			
		||||
                        value={schema?.['x-component-props']?.['fieldNames']?.['label']}
 | 
			
		||||
                        placeholder={t('Default is the ID field')}
 | 
			
		||||
                        onChange={async (value) => {
 | 
			
		||||
                          set(schema['x-component-props'], 'fieldNames.label', value);
 | 
			
		||||
                          await updateSchema({
 | 
			
		||||
                            key: schema['key'],
 | 
			
		||||
                            'x-component-props': {
 | 
			
		||||
                              fieldNames: {
 | 
			
		||||
                                label: value,
 | 
			
		||||
                              },
 | 
			
		||||
                            },
 | 
			
		||||
                          });
 | 
			
		||||
                          refreshTable();
 | 
			
		||||
                          // await service.refresh();
 | 
			
		||||
                        }}
 | 
			
		||||
                        bordered={false}
 | 
			
		||||
                        size={'small'}
 | 
			
		||||
                        style={{ marginLeft: 16, minWidth: 120 }}
 | 
			
		||||
                        options={getFieldsByCollection(collectionField.target)
 | 
			
		||||
                          .filter((f) => f?.uiSchema?.title)
 | 
			
		||||
                          .map((field) => ({
 | 
			
		||||
                            label: compile(field?.uiSchema?.title || field.name),
 | 
			
		||||
                            value: field.name,
 | 
			
		||||
                          }))}
 | 
			
		||||
                      />
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </Menu.Item>
 | 
			
		||||
                )}
 | 
			
		||||
                <Menu.Divider />
 | 
			
		||||
                <Menu.Item
 | 
			
		||||
                  onClick={async () => {
 | 
			
		||||
                    const fieldName = schema['x-component-props']?.['fieldName'];
 | 
			
		||||
                    displayed.remove(fieldName);
 | 
			
		||||
                    schema['x-hidden'] = true;
 | 
			
		||||
                    refresh();
 | 
			
		||||
                    await updateSchema({
 | 
			
		||||
                      key: schema['key'],
 | 
			
		||||
                      ['x-hidden']: true,
 | 
			
		||||
                    });
 | 
			
		||||
                    // const s = remove();
 | 
			
		||||
                    // await removeSchema(s);
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  {t('Hide')}
 | 
			
		||||
                </Menu.Item>
 | 
			
		||||
              </Menu>
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <MenuOutlined />
 | 
			
		||||
          </Dropdown>
 | 
			
		||||
        </Space>
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										275
									
								
								packages/client/src/schemas/table/TableDesignableBar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								packages/client/src/schemas/table/TableDesignableBar.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,275 @@
 | 
			
		||||
import React, { useContext, useState } from 'react';
 | 
			
		||||
import { Modal, Select, Dropdown, Menu, Switch, Space } from 'antd';
 | 
			
		||||
import { MenuOutlined } from '@ant-design/icons';
 | 
			
		||||
import { set } from 'lodash';
 | 
			
		||||
import { observer, useField } from '@formily/react';
 | 
			
		||||
import { FormDialog, FormLayout } from '@formily/antd';
 | 
			
		||||
import cls from 'classnames';
 | 
			
		||||
import { Trans, useTranslation } from 'react-i18next';
 | 
			
		||||
import { useCompile } from '../../hooks/useCompile';
 | 
			
		||||
import { useDesignable } from '..';
 | 
			
		||||
import { DraggableBlockContext } from '../../components/drag-and-drop';
 | 
			
		||||
import { useCollection, useClient } from '../../constate';
 | 
			
		||||
import AddNew from '../add-new';
 | 
			
		||||
import { SchemaField } from '../../components/schema-renderer';
 | 
			
		||||
import { DragHandle } from '../../components/Sortable';
 | 
			
		||||
import { fieldsToSortColumns, fieldsToFilterColumns } from './utils';
 | 
			
		||||
 | 
			
		||||
export const TableDesignableBar = observer((props) => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const { schema, refresh, deepRemove } = useDesignable();
 | 
			
		||||
  const [visible, setVisible] = useState(false);
 | 
			
		||||
  const { dragRef } = useContext(DraggableBlockContext);
 | 
			
		||||
  const defaultPageSize = field?.componentProps?.pagination?.defaultPageSize || 10;
 | 
			
		||||
  const collectionName = field?.componentProps?.collectionName;
 | 
			
		||||
  const { collection, fields } = useCollection({ collectionName });
 | 
			
		||||
  const { createSchema, removeSchema, updateSchema } = useClient();
 | 
			
		||||
  console.log({ collectionName });
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={cls('designable-bar', { active: visible })}>
 | 
			
		||||
      <div className={'designable-info'}>{compile(collection?.title || collection?.name)}</div>
 | 
			
		||||
      <span
 | 
			
		||||
        onClick={(e) => {
 | 
			
		||||
          e.stopPropagation();
 | 
			
		||||
        }}
 | 
			
		||||
        className={cls('designable-bar-actions', { active: visible })}
 | 
			
		||||
      >
 | 
			
		||||
        <Space size={2}>
 | 
			
		||||
          <AddNew.CardItem defaultAction={'insertAfter'} ghost />
 | 
			
		||||
          <DragHandle />
 | 
			
		||||
          <Dropdown
 | 
			
		||||
            trigger={['hover']}
 | 
			
		||||
            visible={visible}
 | 
			
		||||
            onVisibleChange={(visible) => {
 | 
			
		||||
              setVisible(visible);
 | 
			
		||||
            }}
 | 
			
		||||
            overlay={
 | 
			
		||||
              <Menu>
 | 
			
		||||
                <Menu.Item
 | 
			
		||||
                  key={'showIndex'}
 | 
			
		||||
                  onClick={() => {
 | 
			
		||||
                    const bool = !field.componentProps.showIndex;
 | 
			
		||||
                    schema['x-component-props']['showIndex'] = bool;
 | 
			
		||||
                    field.componentProps.showIndex = bool;
 | 
			
		||||
                    updateSchema(schema);
 | 
			
		||||
                    setVisible(false);
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  <div className={'nb-space-between'}>
 | 
			
		||||
                    {t('Display order number')} <Switch size={'small'} checked={field.componentProps.showIndex} />
 | 
			
		||||
                  </div>
 | 
			
		||||
                </Menu.Item>
 | 
			
		||||
                <Menu.Item
 | 
			
		||||
                  key={'dragSort'}
 | 
			
		||||
                  onClick={() => {
 | 
			
		||||
                    const dragSort = field.componentProps.dragSort ? false : 'sort';
 | 
			
		||||
                    schema['x-component-props']['dragSort'] = dragSort;
 | 
			
		||||
                    field.componentProps.dragSort = dragSort;
 | 
			
		||||
                    updateSchema(schema);
 | 
			
		||||
                    setVisible(false);
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  <div className={'nb-space-between'}>
 | 
			
		||||
                    {t('Enable drag and drop sorting')}
 | 
			
		||||
                      
 | 
			
		||||
                    <Switch size={'small'} checked={field.componentProps.dragSort} />
 | 
			
		||||
                  </div>
 | 
			
		||||
                </Menu.Item>
 | 
			
		||||
                {!field.componentProps.dragSort && (
 | 
			
		||||
                  <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(t('Set default sorting rules'), () => {
 | 
			
		||||
                        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': 'Radio.Group',
 | 
			
		||||
                                              'x-component-props': {
 | 
			
		||||
                                                optionType: 'button',
 | 
			
		||||
                                              },
 | 
			
		||||
                                              enum: [
 | 
			
		||||
                                                {
 | 
			
		||||
                                                  label: t('ASC'),
 | 
			
		||||
                                                  value: 'asc',
 | 
			
		||||
                                                },
 | 
			
		||||
                                                {
 | 
			
		||||
                                                  label: t('DESC'),
 | 
			
		||||
                                                  value: 'desc',
 | 
			
		||||
                                                },
 | 
			
		||||
                                              ],
 | 
			
		||||
                                            },
 | 
			
		||||
                                            remove: {
 | 
			
		||||
                                              type: 'void',
 | 
			
		||||
                                              'x-decorator': 'FormItem',
 | 
			
		||||
                                              'x-component': 'ArrayItems.Remove',
 | 
			
		||||
                                            },
 | 
			
		||||
                                          },
 | 
			
		||||
                                        },
 | 
			
		||||
                                      },
 | 
			
		||||
                                    },
 | 
			
		||||
                                    properties: {
 | 
			
		||||
                                      add: {
 | 
			
		||||
                                        type: 'void',
 | 
			
		||||
                                        title: t('Add sort field'),
 | 
			
		||||
                                        '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);
 | 
			
		||||
                      setVisible(false);
 | 
			
		||||
                      console.log('defaultSort', sort);
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                    {t('Set default sorting rules')}
 | 
			
		||||
                  </Menu.Item>
 | 
			
		||||
                )}
 | 
			
		||||
                <Menu.Item
 | 
			
		||||
                  key={'defaultFilter'}
 | 
			
		||||
                  onClick={async () => {
 | 
			
		||||
                    const { defaultFilter } = await FormDialog(t('Set the data scope'), () => {
 | 
			
		||||
                      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);
 | 
			
		||||
                    setVisible(false);
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  {t('Set the data scope')}
 | 
			
		||||
                </Menu.Item>
 | 
			
		||||
                <Menu.Item key={'defaultPageSize'}>
 | 
			
		||||
                  <Trans>
 | 
			
		||||
                    {'Display '}
 | 
			
		||||
                    <Select
 | 
			
		||||
                      bordered={false}
 | 
			
		||||
                      size={'small'}
 | 
			
		||||
                      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);
 | 
			
		||||
                        setVisible(false);
 | 
			
		||||
                      }}
 | 
			
		||||
                      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>
 | 
			
		||||
                    </Select>
 | 
			
		||||
                    {' items per page'}
 | 
			
		||||
                  </Trans>
 | 
			
		||||
                </Menu.Item>
 | 
			
		||||
                <Menu.Divider />
 | 
			
		||||
                <Menu.Item
 | 
			
		||||
                  key={'delete'}
 | 
			
		||||
                  onClick={async () => {
 | 
			
		||||
                    Modal.confirm({
 | 
			
		||||
                      title: t('Delete block'),
 | 
			
		||||
                      content: t('Are you sure you want to delete it?'),
 | 
			
		||||
                      onOk: async () => {
 | 
			
		||||
                        const removed = deepRemove();
 | 
			
		||||
                        // console.log({ removed })
 | 
			
		||||
                        const last = removed.pop();
 | 
			
		||||
                        await removeSchema(last);
 | 
			
		||||
                        setVisible(false);
 | 
			
		||||
                      },
 | 
			
		||||
                    });
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  {t('Delete')}
 | 
			
		||||
                </Menu.Item>
 | 
			
		||||
              </Menu>
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <MenuOutlined />
 | 
			
		||||
          </Dropdown>
 | 
			
		||||
        </Space>
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,134 @@
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import { Dropdown, Menu, Space } from 'antd';
 | 
			
		||||
import { useField } from '@formily/react';
 | 
			
		||||
import { FormDialog, FormLayout } from '@formily/antd';
 | 
			
		||||
import { MenuOutlined } from '@ant-design/icons';
 | 
			
		||||
import cls from 'classnames';
 | 
			
		||||
import { Trans, useTranslation } from 'react-i18next';
 | 
			
		||||
import { DragHandle } from '../../components/Sortable';
 | 
			
		||||
import { useDesignable } from '..';
 | 
			
		||||
import { useCollectionContext, useDisplayedMapContext, useClient } from '../../constate';
 | 
			
		||||
import SwitchMenuItem from '../../components/SwitchMenuItem';
 | 
			
		||||
import { SchemaField } from '../../components/schema-renderer';
 | 
			
		||||
 | 
			
		||||
export const TableExportActionDesignableBar = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const { schema, remove, refresh, insertAfter } = useDesignable();
 | 
			
		||||
  const [visible, setVisible] = useState(false);
 | 
			
		||||
  const displayed = useDisplayedMapContext();
 | 
			
		||||
  const { fields } = useCollectionContext();
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const { createSchema, removeSchema, updateSchema } = useClient();
 | 
			
		||||
  let fieldNames = field.componentProps.fieldNames || [];
 | 
			
		||||
  if (fieldNames.length === 0) {
 | 
			
		||||
    fieldNames = fields.map((field) => field.name);
 | 
			
		||||
  }
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={cls('designable-bar', { active: visible })}>
 | 
			
		||||
      <span
 | 
			
		||||
        onClick={(e) => {
 | 
			
		||||
          e.stopPropagation();
 | 
			
		||||
        }}
 | 
			
		||||
        className={cls('designable-bar-actions', { active: visible })}
 | 
			
		||||
      >
 | 
			
		||||
        <Space>
 | 
			
		||||
          <DragHandle />
 | 
			
		||||
          <Dropdown
 | 
			
		||||
            trigger={['hover']}
 | 
			
		||||
            visible={visible}
 | 
			
		||||
            onVisibleChange={(visible) => {
 | 
			
		||||
              setVisible(visible);
 | 
			
		||||
            }}
 | 
			
		||||
            overlay={
 | 
			
		||||
              <Menu>
 | 
			
		||||
                <Menu.ItemGroup title={t('Export fields')}>
 | 
			
		||||
                  {fields.map((collectionField) => (
 | 
			
		||||
                    <SwitchMenuItem
 | 
			
		||||
                      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={async (e) => {
 | 
			
		||||
                    setVisible(false);
 | 
			
		||||
                    const values = await FormDialog(t('Edit button'), () => {
 | 
			
		||||
                      return (
 | 
			
		||||
                        <FormLayout layout={'vertical'}>
 | 
			
		||||
                          <SchemaField
 | 
			
		||||
                            schema={{
 | 
			
		||||
                              type: 'object',
 | 
			
		||||
                              properties: {
 | 
			
		||||
                                title: {
 | 
			
		||||
                                  type: 'string',
 | 
			
		||||
                                  title: t('Display name'),
 | 
			
		||||
                                  required: true,
 | 
			
		||||
                                  'x-decorator': 'FormItem',
 | 
			
		||||
                                  'x-component': 'Input',
 | 
			
		||||
                                },
 | 
			
		||||
                                icon: {
 | 
			
		||||
                                  type: 'string',
 | 
			
		||||
                                  title: t('Icon'),
 | 
			
		||||
                                  '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();
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  {t('Edit button')}
 | 
			
		||||
                </Menu.Item>
 | 
			
		||||
                <Menu.Divider />
 | 
			
		||||
                <Menu.Item
 | 
			
		||||
                  onClick={async () => {
 | 
			
		||||
                    const displayName = schema?.['x-decorator-props']?.['displayName'];
 | 
			
		||||
                    const data = remove();
 | 
			
		||||
                    await removeSchema(data);
 | 
			
		||||
                    if (displayName) {
 | 
			
		||||
                      displayed.remove(displayName);
 | 
			
		||||
                    }
 | 
			
		||||
                    setVisible(false);
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  {t('Hide')}
 | 
			
		||||
                </Menu.Item>
 | 
			
		||||
              </Menu>
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <MenuOutlined />
 | 
			
		||||
          </Dropdown>
 | 
			
		||||
        </Space>
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										92
									
								
								packages/client/src/schemas/table/TableFilter.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								packages/client/src/schemas/table/TableFilter.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,92 @@
 | 
			
		||||
import React, { useMemo, useState } from 'react';
 | 
			
		||||
import {  Popover, Button,  } from 'antd';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import flatten from 'flat';
 | 
			
		||||
import { createForm } from '@formily/core';
 | 
			
		||||
import { FormProvider, observer } from '@formily/react';
 | 
			
		||||
import { FormButtonGroup,  Submit } from '@formily/antd';
 | 
			
		||||
import { interfaces } from '../database-field/interfaces';
 | 
			
		||||
import { useDesignable } from '..';
 | 
			
		||||
import { useCollectionContext,  } from '../../constate';
 | 
			
		||||
import { SchemaField } from '../../components/schema-renderer';
 | 
			
		||||
import { useTable } from './hooks/useTable';
 | 
			
		||||
import { useCompile } from '../../hooks/useCompile';
 | 
			
		||||
import IconPicker from '../../components/icon-picker';
 | 
			
		||||
 | 
			
		||||
export const TableFilter = observer((props: any) => {
 | 
			
		||||
  const { service } = useTable();
 | 
			
		||||
  const { fieldNames = [] } = props;
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const { schema, DesignableBar } = useDesignable();
 | 
			
		||||
  const form = useMemo(() => createForm(), []);
 | 
			
		||||
  const { fields = [] } = useCollectionContext();
 | 
			
		||||
  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';
 | 
			
		||||
  const properties = fieldsToFilterColumns(fields, { fieldNames });
 | 
			
		||||
  schema.mapProperties((p) => {
 | 
			
		||||
    properties[p.name] = p;
 | 
			
		||||
  });
 | 
			
		||||
  return (
 | 
			
		||||
    <Popover
 | 
			
		||||
      trigger={['click']}
 | 
			
		||||
      placement={'bottomLeft'}
 | 
			
		||||
      visible={visible}
 | 
			
		||||
      onVisibleChange={setVisible}
 | 
			
		||||
      content={
 | 
			
		||||
        <div>
 | 
			
		||||
          <FormProvider form={form}>
 | 
			
		||||
            <SchemaField
 | 
			
		||||
              schema={{
 | 
			
		||||
                type: 'object',
 | 
			
		||||
                properties: {
 | 
			
		||||
                  filter: {
 | 
			
		||||
                    type: 'object',
 | 
			
		||||
                    'x-component': 'Filter',
 | 
			
		||||
                    properties,
 | 
			
		||||
                  },
 | 
			
		||||
                },
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
            <FormButtonGroup align={'right'}>
 | 
			
		||||
              <Button
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                  setVisible(false);
 | 
			
		||||
                  form.reset();
 | 
			
		||||
                  const { filter } = form.values;
 | 
			
		||||
                  return service.run({
 | 
			
		||||
                    ...service.params[0],
 | 
			
		||||
                    filter,
 | 
			
		||||
                  });
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                {t('Reset')}
 | 
			
		||||
              </Button>
 | 
			
		||||
              <Submit
 | 
			
		||||
                onSubmit={() => {
 | 
			
		||||
                  const { filter } = form.values;
 | 
			
		||||
                  console.log('Table.Filter', form.values);
 | 
			
		||||
                  setVisible(false);
 | 
			
		||||
                  return service.run({
 | 
			
		||||
                    ...service.params[0],
 | 
			
		||||
                    filter,
 | 
			
		||||
                  });
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                {t('Submit')}
 | 
			
		||||
              </Submit>
 | 
			
		||||
            </FormButtonGroup>
 | 
			
		||||
          </FormProvider>
 | 
			
		||||
        </div>
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <Button icon={<IconPicker type={icon} />}>
 | 
			
		||||
        {count > 0 ? t('{{count}} filter items', { count }) : compile(schema.title)}
 | 
			
		||||
        <DesignableBar />
 | 
			
		||||
      </Button>
 | 
			
		||||
    </Popover>
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										140
									
								
								packages/client/src/schemas/table/TableFilterDesignableBar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								packages/client/src/schemas/table/TableFilterDesignableBar.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,140 @@
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import { Dropdown, Menu, Space } from 'antd';
 | 
			
		||||
import { useField } from '@formily/react';
 | 
			
		||||
import { FormDialog, FormLayout } from '@formily/antd';
 | 
			
		||||
import { MenuOutlined } from '@ant-design/icons';
 | 
			
		||||
import cls from 'classnames';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { DragHandle } from '../../components/Sortable';
 | 
			
		||||
import { interfaces } from '../database-field/interfaces';
 | 
			
		||||
import { useDesignable } from '..';
 | 
			
		||||
import { useCollectionContext, useDisplayedMapContext, useClient } from '../../constate';
 | 
			
		||||
import SwitchMenuItem from '../../components/SwitchMenuItem';
 | 
			
		||||
import { SchemaField } from '../../components/schema-renderer';
 | 
			
		||||
 | 
			
		||||
export const TableFilterDesignableBar = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const { schema, remove, refresh, insertAfter } = useDesignable();
 | 
			
		||||
  const [visible, setVisible] = useState(false);
 | 
			
		||||
  const displayed = useDisplayedMapContext();
 | 
			
		||||
  const { fields } = useCollectionContext();
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const { createSchema, removeSchema, updateSchema } = useClient();
 | 
			
		||||
  let fieldNames = field.componentProps.fieldNames || [];
 | 
			
		||||
  if (fieldNames.length === 0) {
 | 
			
		||||
    fieldNames = fields.map((field) => field.name);
 | 
			
		||||
  }
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={cls('designable-bar', { active: visible })}>
 | 
			
		||||
      <span
 | 
			
		||||
        onClick={(e) => {
 | 
			
		||||
          e.stopPropagation();
 | 
			
		||||
        }}
 | 
			
		||||
        className={cls('designable-bar-actions', { active: visible })}
 | 
			
		||||
      >
 | 
			
		||||
        <Space>
 | 
			
		||||
          <DragHandle />
 | 
			
		||||
          <Dropdown
 | 
			
		||||
            trigger={['hover']}
 | 
			
		||||
            visible={visible}
 | 
			
		||||
            onVisibleChange={(visible) => {
 | 
			
		||||
              setVisible(visible);
 | 
			
		||||
            }}
 | 
			
		||||
            overlay={
 | 
			
		||||
              <Menu>
 | 
			
		||||
                <Menu.ItemGroup title={t('Filterable fields')}>
 | 
			
		||||
                  {fields
 | 
			
		||||
                    .filter((collectionField) => {
 | 
			
		||||
                      const option = interfaces.get(collectionField.interface);
 | 
			
		||||
                      return option?.operations?.length;
 | 
			
		||||
                    })
 | 
			
		||||
                    .map((collectionField) => (
 | 
			
		||||
                      <SwitchMenuItem
 | 
			
		||||
                        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={async (e) => {
 | 
			
		||||
                    setVisible(false);
 | 
			
		||||
                    const values = await FormDialog(t('Edit button'), () => {
 | 
			
		||||
                      return (
 | 
			
		||||
                        <FormLayout layout={'vertical'}>
 | 
			
		||||
                          <SchemaField
 | 
			
		||||
                            schema={{
 | 
			
		||||
                              type: 'object',
 | 
			
		||||
                              properties: {
 | 
			
		||||
                                title: {
 | 
			
		||||
                                  type: 'string',
 | 
			
		||||
                                  title: t('Display name'),
 | 
			
		||||
                                  required: true,
 | 
			
		||||
                                  'x-decorator': 'FormItem',
 | 
			
		||||
                                  'x-component': 'Input',
 | 
			
		||||
                                },
 | 
			
		||||
                                icon: {
 | 
			
		||||
                                  type: 'string',
 | 
			
		||||
                                  title: t('Icon'),
 | 
			
		||||
                                  '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();
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  {t('Edit button')}
 | 
			
		||||
                </Menu.Item>
 | 
			
		||||
                <Menu.Divider />
 | 
			
		||||
                <Menu.Item
 | 
			
		||||
                  onClick={async () => {
 | 
			
		||||
                    const displayName = schema?.['x-decorator-props']?.['displayName'];
 | 
			
		||||
                    const data = remove();
 | 
			
		||||
                    await removeSchema(data);
 | 
			
		||||
                    if (displayName) {
 | 
			
		||||
                      displayed.remove(displayName);
 | 
			
		||||
                    }
 | 
			
		||||
                    setVisible(false);
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  {t('Hide')}
 | 
			
		||||
                </Menu.Item>
 | 
			
		||||
              </Menu>
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <MenuOutlined />
 | 
			
		||||
          </Dropdown>
 | 
			
		||||
        </Space>
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										8
									
								
								packages/client/src/schemas/table/TableIndex.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/client/src/schemas/table/TableIndex.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { observer } from '@formily/react';
 | 
			
		||||
import { useTableIndex } from './hooks/useTableIndex';
 | 
			
		||||
 | 
			
		||||
export const TableIndex = observer(() => {
 | 
			
		||||
  const index = useTableIndex();
 | 
			
		||||
  return <span className={'nb-table-index'}>{index + 1}</span>;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										139
									
								
								packages/client/src/schemas/table/TableMain.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								packages/client/src/schemas/table/TableMain.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,139 @@
 | 
			
		||||
import React, { DndContext } from '@dnd-kit/core';
 | 
			
		||||
import { useState } from 'react';
 | 
			
		||||
import { Table as AntdTable } from 'antd';
 | 
			
		||||
import { findIndex } from 'lodash';
 | 
			
		||||
import cls from 'classnames';
 | 
			
		||||
import { RecursionField, Schema } from '@formily/react';
 | 
			
		||||
import { isValid } from '@formily/shared';
 | 
			
		||||
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
 | 
			
		||||
import { SortableBodyRow, SortableHeaderCell, SortableHeaderRow } from './Sortable';
 | 
			
		||||
import { TableRowContext } from './context';
 | 
			
		||||
import { useDataSource } from './hooks/useDataSource';
 | 
			
		||||
import { useDefaultRowSelection } from './hooks/useDefaultRowSelection';
 | 
			
		||||
import { useTable } from './hooks/useTable';
 | 
			
		||||
import { useTableActionBars } from './hooks/useTableActionBars';
 | 
			
		||||
import { useTableColumns } from './hooks/useTableColumns';
 | 
			
		||||
import { Table } from '.';
 | 
			
		||||
 | 
			
		||||
export const TableMain = () => {
 | 
			
		||||
  const {
 | 
			
		||||
    resource,
 | 
			
		||||
    selectedRowKeys,
 | 
			
		||||
    setSelectedRowKeys,
 | 
			
		||||
    service,
 | 
			
		||||
    field,
 | 
			
		||||
    props: { rowKey, dragSort, showIndex, onSelect, useRowSelection = useDefaultRowSelection },
 | 
			
		||||
    refresh,
 | 
			
		||||
  } = useTable();
 | 
			
		||||
  const columns = useTableColumns();
 | 
			
		||||
  const dataSource = useDataSource();
 | 
			
		||||
  const actionBars = useTableActionBars();
 | 
			
		||||
  const [html, setHtml] = useState('');
 | 
			
		||||
  const { type } = useRowSelection();
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={'nb-table'}>
 | 
			
		||||
      <DndContext
 | 
			
		||||
        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) => (
 | 
			
		||||
          <RecursionField
 | 
			
		||||
            schema={
 | 
			
		||||
              new Schema({
 | 
			
		||||
                type: 'void',
 | 
			
		||||
                properties: {
 | 
			
		||||
                  [actionBar.name]: actionBar,
 | 
			
		||||
                },
 | 
			
		||||
              })
 | 
			
		||||
            }
 | 
			
		||||
          />
 | 
			
		||||
        ))}
 | 
			
		||||
        <SortableContext items={dataSource || []} strategy={verticalListSortingStrategy}>
 | 
			
		||||
          <AntdTable
 | 
			
		||||
            pagination={false}
 | 
			
		||||
            onChange={(pagination) => {}}
 | 
			
		||||
            loading={service?.loading}
 | 
			
		||||
            rowKey={rowKey}
 | 
			
		||||
            dataSource={dataSource}
 | 
			
		||||
            columns={columns}
 | 
			
		||||
            // components={{
 | 
			
		||||
            //   body: {
 | 
			
		||||
            //     row: DragableBodyRow,
 | 
			
		||||
            //   },
 | 
			
		||||
            // }}
 | 
			
		||||
            components={{
 | 
			
		||||
              header: {
 | 
			
		||||
                row: SortableHeaderRow,
 | 
			
		||||
                cell: SortableHeaderCell,
 | 
			
		||||
              },
 | 
			
		||||
              body: {
 | 
			
		||||
                // wrapper: (props) => {
 | 
			
		||||
                //   return (
 | 
			
		||||
                //     <tbody {...props}>
 | 
			
		||||
                //       <DragOverlay
 | 
			
		||||
                //         className={'ant-table-row'}
 | 
			
		||||
                //         wrapperElement={'tr'}
 | 
			
		||||
                //       >
 | 
			
		||||
                //         <div />
 | 
			
		||||
                //       </DragOverlay>
 | 
			
		||||
                //       {props.children}
 | 
			
		||||
                //     </tbody>
 | 
			
		||||
                //   );
 | 
			
		||||
                // },
 | 
			
		||||
                row: SortableBodyRow,
 | 
			
		||||
                // cell: SortableBodyCell,
 | 
			
		||||
              },
 | 
			
		||||
            }}
 | 
			
		||||
            rowSelection={{
 | 
			
		||||
              type: type || 'checkbox',
 | 
			
		||||
              selectedRowKeys,
 | 
			
		||||
              onChange: (rowKeys, rows) => {
 | 
			
		||||
                setSelectedRowKeys(rowKeys);
 | 
			
		||||
                onSelect && onSelect(rowKeys, rows);
 | 
			
		||||
              },
 | 
			
		||||
              renderCell: (checked, record, _, originNode) => {
 | 
			
		||||
                const index = findIndex(field.value, (item) => item[rowKey] === record[rowKey]);
 | 
			
		||||
                return (
 | 
			
		||||
                  <TableRowContext.Provider
 | 
			
		||||
                    value={{
 | 
			
		||||
                      index,
 | 
			
		||||
                      record,
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                    <div
 | 
			
		||||
                      className={cls('nb-table-selection', {
 | 
			
		||||
                        dragSort,
 | 
			
		||||
                        showIndex,
 | 
			
		||||
                      })}
 | 
			
		||||
                    >
 | 
			
		||||
                      {dragSort && <Table.SortHandle />}
 | 
			
		||||
                      {showIndex && <Table.Index />}
 | 
			
		||||
                      {originNode}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </TableRowContext.Provider>
 | 
			
		||||
                );
 | 
			
		||||
              },
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        </SortableContext>
 | 
			
		||||
      </DndContext>
 | 
			
		||||
      <Table.Pagination />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										16
									
								
								packages/client/src/schemas/table/TableOperation.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								packages/client/src/schemas/table/TableOperation.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { observer } from '@formily/react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useDesignable } from '..';
 | 
			
		||||
import { Table } from '.';
 | 
			
		||||
 | 
			
		||||
export const TableOperation = observer((props: any) => {
 | 
			
		||||
  const { designable, schema } = useDesignable();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={'nb-table-column'}>
 | 
			
		||||
      {t('Operations')}
 | 
			
		||||
      <Table.Operation.DesignableBar path={props.path} />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										13
									
								
								packages/client/src/schemas/table/TableOperationCell.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								packages/client/src/schemas/table/TableOperationCell.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
import React, { useContext } from 'react';
 | 
			
		||||
import { observer, RecursionField } from '@formily/react';
 | 
			
		||||
import { TableRowContext } from './context';
 | 
			
		||||
 | 
			
		||||
export const TableOperationCell = observer((props: any) => {
 | 
			
		||||
  const ctx = useContext(TableRowContext);
 | 
			
		||||
  const schema = props.schema;
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={'nb-table-column'}>
 | 
			
		||||
      <RecursionField schema={schema} name={ctx.index} onlyRenderProperties />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,194 @@
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import { Dropdown, Menu, Space } from 'antd';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { uid, merge } from '@formily/shared';
 | 
			
		||||
import cls from 'classnames';
 | 
			
		||||
import { MenuOutlined } from '@ant-design/icons';
 | 
			
		||||
import { useDesignable, ISchema } from '..';
 | 
			
		||||
import { getSchemaPath } from '../../components/schema-renderer';
 | 
			
		||||
import { useClient } from '../../constate';
 | 
			
		||||
import SwitchMenuItem from '../../components/SwitchMenuItem';
 | 
			
		||||
import { DragHandle } from '../../components/Sortable';
 | 
			
		||||
 | 
			
		||||
export const TableOperationDesignableBar = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const { schema: columnSchema } = useDesignable();
 | 
			
		||||
  const groupSchema = Object.values(columnSchema.properties || {}).shift();
 | 
			
		||||
  const groupPath = getSchemaPath(groupSchema);
 | 
			
		||||
  const { schema, remove, refresh, appendChild } = useDesignable(groupPath);
 | 
			
		||||
  const [visible, setVisible] = useState(false);
 | 
			
		||||
  const { createSchema, removeSchema, updateSchema } = useClient();
 | 
			
		||||
 | 
			
		||||
  const map = new Map();
 | 
			
		||||
  schema.mapProperties((s) => {
 | 
			
		||||
    if (!s['x-action-type']) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    map.set(s['x-action-type'], s.name);
 | 
			
		||||
  });
 | 
			
		||||
  const path = getSchemaPath(schema);
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={cls('designable-bar', { active: visible })}>
 | 
			
		||||
      <span
 | 
			
		||||
        onClick={(e) => {
 | 
			
		||||
          e.stopPropagation();
 | 
			
		||||
        }}
 | 
			
		||||
        className={cls('designable-bar-actions', { active: visible })}
 | 
			
		||||
      >
 | 
			
		||||
        <Space>
 | 
			
		||||
          <DragHandle />
 | 
			
		||||
          <Dropdown
 | 
			
		||||
            trigger={['hover']}
 | 
			
		||||
            visible={visible}
 | 
			
		||||
            onVisibleChange={(visible) => {
 | 
			
		||||
              setVisible(visible);
 | 
			
		||||
            }}
 | 
			
		||||
            overlay={
 | 
			
		||||
              <Menu>
 | 
			
		||||
                <Menu.ItemGroup title={t('Enable actions')}>
 | 
			
		||||
                  {[
 | 
			
		||||
                    { title: t('View'), name: 'view' },
 | 
			
		||||
                    { title: t('Edit'), name: 'update' },
 | 
			
		||||
                    { title: t('Delete'), name: 'destroy' },
 | 
			
		||||
                  ].map((item) => (
 | 
			
		||||
                    <SwitchMenuItem
 | 
			
		||||
                      key={item.name}
 | 
			
		||||
                      title={item.title}
 | 
			
		||||
                      checked={map.has(item.name)}
 | 
			
		||||
                      onChange={async (checked) => {
 | 
			
		||||
                        if (checked) {
 | 
			
		||||
                          const s = generateMenuActionSchema(item.name);
 | 
			
		||||
                          const data = appendChild(s);
 | 
			
		||||
                          await createSchema(data);
 | 
			
		||||
                        } else if (map.get(item.name)) {
 | 
			
		||||
                          const removed = remove([...path, map.get(item.name)]);
 | 
			
		||||
                          await removeSchema(removed);
 | 
			
		||||
                        }
 | 
			
		||||
                      }}
 | 
			
		||||
                    />
 | 
			
		||||
                  ))}
 | 
			
		||||
                </Menu.ItemGroup>
 | 
			
		||||
                <Menu.Divider />
 | 
			
		||||
                <Menu.SubMenu disabled title={t('Customize')}>
 | 
			
		||||
                  <Menu.Item style={{ minWidth: 120 }}>{t('Function')}</Menu.Item>
 | 
			
		||||
                  <Menu.Item>{t('Popup form')}</Menu.Item>
 | 
			
		||||
                  <Menu.Item>{t('Flexible popup')}</Menu.Item>
 | 
			
		||||
                </Menu.SubMenu>
 | 
			
		||||
              </Menu>
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <MenuOutlined />
 | 
			
		||||
          </Dropdown>
 | 
			
		||||
        </Space>
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
function generateMenuActionSchema(type) {
 | 
			
		||||
  const actions: { [key: string]: ISchema } = {
 | 
			
		||||
    view: {
 | 
			
		||||
      key: uid(),
 | 
			
		||||
      name: uid(),
 | 
			
		||||
      type: 'void',
 | 
			
		||||
      title: "{{ t('View') }}",
 | 
			
		||||
      'x-component': 'Action',
 | 
			
		||||
      'x-component-props': {
 | 
			
		||||
        type: 'link',
 | 
			
		||||
      },
 | 
			
		||||
      'x-designable-bar': 'Table.Action.DesignableBar',
 | 
			
		||||
      'x-action-type': 'view',
 | 
			
		||||
      properties: {
 | 
			
		||||
        [uid()]: {
 | 
			
		||||
          type: 'void',
 | 
			
		||||
          title: "{{ t('View record') }}",
 | 
			
		||||
          'x-component': 'Action.Drawer',
 | 
			
		||||
          'x-component-props': {
 | 
			
		||||
            bodyStyle: {
 | 
			
		||||
              background: '#f0f2f5',
 | 
			
		||||
              // paddingTop: 0,
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
          properties: {
 | 
			
		||||
            [uid()]: {
 | 
			
		||||
              type: 'void',
 | 
			
		||||
              'x-component': 'Tabs',
 | 
			
		||||
              'x-designable-bar': 'Tabs.DesignableBar',
 | 
			
		||||
              properties: {
 | 
			
		||||
                [uid()]: {
 | 
			
		||||
                  type: 'void',
 | 
			
		||||
                  title: "{{ t('Details') }}",
 | 
			
		||||
                  'x-designable-bar': 'Tabs.TabPane.DesignableBar',
 | 
			
		||||
                  'x-component': 'Tabs.TabPane',
 | 
			
		||||
                  'x-component-props': {},
 | 
			
		||||
                  properties: {
 | 
			
		||||
                    [uid()]: {
 | 
			
		||||
                      type: 'void',
 | 
			
		||||
                      'x-component': 'Grid',
 | 
			
		||||
                      'x-component-props': {
 | 
			
		||||
                        addNewComponent: 'AddNew.PaneItem',
 | 
			
		||||
                      },
 | 
			
		||||
                    },
 | 
			
		||||
                  },
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    update: {
 | 
			
		||||
      key: uid(),
 | 
			
		||||
      name: uid(),
 | 
			
		||||
      type: 'void',
 | 
			
		||||
      title: "{{ t('Edit') }}",
 | 
			
		||||
      'x-component': 'Action',
 | 
			
		||||
      'x-component-props': {
 | 
			
		||||
        type: 'link',
 | 
			
		||||
      },
 | 
			
		||||
      'x-designable-bar': 'Table.Action.DesignableBar',
 | 
			
		||||
      'x-action-type': 'update',
 | 
			
		||||
      properties: {
 | 
			
		||||
        [uid()]: {
 | 
			
		||||
          type: 'void',
 | 
			
		||||
          title: "{{ t('Edit record') }}",
 | 
			
		||||
          'x-decorator': 'Form',
 | 
			
		||||
          'x-decorator-props': {
 | 
			
		||||
            useResource: '{{ Table.useResource }}',
 | 
			
		||||
            useValues: '{{ Table.useTableRowRecord }}',
 | 
			
		||||
          },
 | 
			
		||||
          'x-component': 'Action.Drawer',
 | 
			
		||||
          'x-component-props': {
 | 
			
		||||
            useOkAction: '{{ Table.useTableUpdateAction }}',
 | 
			
		||||
          },
 | 
			
		||||
          properties: {
 | 
			
		||||
            [uid()]: {
 | 
			
		||||
              type: 'void',
 | 
			
		||||
              'x-component': 'Grid',
 | 
			
		||||
              'x-component-props': {
 | 
			
		||||
                addNewComponent: 'AddNew.FormItem',
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    destroy: {
 | 
			
		||||
      key: uid(),
 | 
			
		||||
      name: uid(),
 | 
			
		||||
      type: 'void',
 | 
			
		||||
      title: "{{ t('Delete') }}",
 | 
			
		||||
      'x-component': 'Action',
 | 
			
		||||
      'x-designable-bar': 'Table.Action.DesignableBar',
 | 
			
		||||
      'x-action-type': 'destroy',
 | 
			
		||||
      'x-component-props': {
 | 
			
		||||
        useAction: '{{ Table.useTableDestroyAction }}',
 | 
			
		||||
        type: 'link',
 | 
			
		||||
        confirm: {
 | 
			
		||||
          title: "{{ t('Delete record') }}",
 | 
			
		||||
          content: "{{ t('Are you sure you want to delete it?') }}",
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
  return actions[type];
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								packages/client/src/schemas/table/TablePagination.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								packages/client/src/schemas/table/TablePagination.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { Pagination } from 'antd';
 | 
			
		||||
import { observer } from '@formily/react';
 | 
			
		||||
import { useTable } from './hooks/useTable';
 | 
			
		||||
import { useTotal } from './hooks/useTotal';
 | 
			
		||||
 | 
			
		||||
export const TablePagination = observer(() => {
 | 
			
		||||
  const { service, pagination, setPagination, props } = useTable();
 | 
			
		||||
  if (!pagination || Object.keys(pagination).length === 0) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
  const { clientSidePagination } = props;
 | 
			
		||||
  const total = useTotal();
 | 
			
		||||
  const { page = 1 } = pagination;
 | 
			
		||||
  return (
 | 
			
		||||
    <div style={{ marginTop: 16 }}>
 | 
			
		||||
      <Pagination
 | 
			
		||||
        {...pagination}
 | 
			
		||||
        showSizeChanger
 | 
			
		||||
        current={page}
 | 
			
		||||
        total={total}
 | 
			
		||||
        onChange={(current, pageSize) => {
 | 
			
		||||
          const page = pagination.pageSize !== pageSize ? 1 : current;
 | 
			
		||||
          setPagination({
 | 
			
		||||
            page,
 | 
			
		||||
            pageSize,
 | 
			
		||||
          });
 | 
			
		||||
          // if (clientSidePagination) {
 | 
			
		||||
          //   return;
 | 
			
		||||
          // }
 | 
			
		||||
          // service.run({
 | 
			
		||||
          //   ...service.params[0],
 | 
			
		||||
          //   page,
 | 
			
		||||
          //   pageSize,
 | 
			
		||||
          // });
 | 
			
		||||
        }}
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										103
									
								
								packages/client/src/schemas/table/TableProvider.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								packages/client/src/schemas/table/TableProvider.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,103 @@
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import { ArrayField } from '@formily/core';
 | 
			
		||||
import { useField } from '@formily/react';
 | 
			
		||||
import { uid } from '@formily/shared';
 | 
			
		||||
import useRequest from '@ahooksjs/use-request';
 | 
			
		||||
import { useDeepCompareEffectNoCheck } from 'use-deep-compare-effect';
 | 
			
		||||
import { useResource as useGeneralResource } from '../../hooks/useResource';
 | 
			
		||||
import { useDesignable } from '..';
 | 
			
		||||
import { useCollectionContext } from '../../constate';
 | 
			
		||||
import { useDefaultSelectedRowKeys } from './hooks/useDefaultSelectedRowKeys';
 | 
			
		||||
import { usePagination } from './hooks/usePagination';
 | 
			
		||||
import { useCollectionFields } from './hooks/useCollectionFields';
 | 
			
		||||
import {TableContext} from './context';
 | 
			
		||||
import {TableMain} from './TableMain';
 | 
			
		||||
 | 
			
		||||
export const TableProvider = (props: any) => {
 | 
			
		||||
  const {
 | 
			
		||||
    rowKey = 'id',
 | 
			
		||||
    dataRequest,
 | 
			
		||||
    useResource = useGeneralResource,
 | 
			
		||||
    defaultSelectedRowKeys,
 | 
			
		||||
    useSelectedRowKeys = useDefaultSelectedRowKeys,
 | 
			
		||||
    ...others
 | 
			
		||||
  } = props;
 | 
			
		||||
  const { schema } = useDesignable();
 | 
			
		||||
  const field = useField<ArrayField>();
 | 
			
		||||
  const [pagination, setPagination] = usePagination();
 | 
			
		||||
  const { selectedRowKeys, setSelectedRowKeys } = useSelectedRowKeys();
 | 
			
		||||
  console.log('props.useSelectedRowKeys', selectedRowKeys);
 | 
			
		||||
  const [, refresh] = useState(uid());
 | 
			
		||||
  const { resource } = useResource();
 | 
			
		||||
  const { sortableField } = useCollectionContext();
 | 
			
		||||
  const dragSort = props.dragSort;
 | 
			
		||||
  const collectionFields = useCollectionFields(schema);
 | 
			
		||||
  // console.log({ collectionFields, pagination });
 | 
			
		||||
  const getDefaultParams = () => {
 | 
			
		||||
    const defaultParams = { ...pagination };
 | 
			
		||||
    if (dragSort) {
 | 
			
		||||
      defaultParams['sort'] = [sortableField || 'sort'];
 | 
			
		||||
    } else {
 | 
			
		||||
      defaultParams['sort'] = (props.defaultSort || []).join(',');
 | 
			
		||||
    }
 | 
			
		||||
    defaultParams['defaultAppends'] = [...(props.defaultAppends || []), ...collectionFields];
 | 
			
		||||
    if (props.defaultFilter) {
 | 
			
		||||
      defaultParams['defaultFilter'] = props.defaultFilter;
 | 
			
		||||
    }
 | 
			
		||||
    console.log({ defaultParams });
 | 
			
		||||
    return defaultParams;
 | 
			
		||||
  };
 | 
			
		||||
  const service = useRequest(
 | 
			
		||||
    (params?: any) => {
 | 
			
		||||
      if (!resource) {
 | 
			
		||||
        return Promise.resolve({
 | 
			
		||||
          list: field.value,
 | 
			
		||||
          total: field?.value?.length,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      return resource.list(params).then((res) => {
 | 
			
		||||
        return {
 | 
			
		||||
          list: res?.data || [],
 | 
			
		||||
          total: res?.meta?.count || res?.data?.length,
 | 
			
		||||
        };
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      onSuccess(data: any) {
 | 
			
		||||
        field.setValue(data?.list || []);
 | 
			
		||||
      },
 | 
			
		||||
      manual: true,
 | 
			
		||||
      // defaultParams: [getDefaultParams()],
 | 
			
		||||
    },
 | 
			
		||||
  );
 | 
			
		||||
  useDeepCompareEffectNoCheck(() => {
 | 
			
		||||
    service.run(getDefaultParams());
 | 
			
		||||
  }, [pagination?.pageSize, pagination?.page, props.dragSort, props.defaultSort, props.defaultFilter]);
 | 
			
		||||
  return (
 | 
			
		||||
    <TableContext.Provider
 | 
			
		||||
      value={{
 | 
			
		||||
        resource,
 | 
			
		||||
        refresh: () => {
 | 
			
		||||
          const { page = 1, pageSize } = pagination;
 | 
			
		||||
          const total = props.clientSidePagination ? field?.value?.length : service?.data?.total;
 | 
			
		||||
          const maxPage = Math.ceil(total / pageSize);
 | 
			
		||||
          if (page > maxPage) {
 | 
			
		||||
            setPagination({ page: maxPage });
 | 
			
		||||
          } else {
 | 
			
		||||
            refresh(uid());
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        selectedRowKeys,
 | 
			
		||||
        setSelectedRowKeys,
 | 
			
		||||
        pagination,
 | 
			
		||||
        setPagination,
 | 
			
		||||
        service,
 | 
			
		||||
        field,
 | 
			
		||||
        schema,
 | 
			
		||||
        props: { ...others, rowKey, dataRequest },
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <TableMain />
 | 
			
		||||
    </TableContext.Provider>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										7
									
								
								packages/client/src/schemas/table/TableSortHandle.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/client/src/schemas/table/TableSortHandle.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { observer } from '@formily/react';
 | 
			
		||||
import { SortableRowHandle } from './Sortable';
 | 
			
		||||
 | 
			
		||||
export const TableSortHandle = observer((props: any) => {
 | 
			
		||||
  return <SortableRowHandle {...props} />;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										6
									
								
								packages/client/src/schemas/table/context.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								packages/client/src/schemas/table/context.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
import {createContext} from 'react';
 | 
			
		||||
import {ITableContext,ITableRowContext} from './types';
 | 
			
		||||
 | 
			
		||||
export const TableContext = createContext<ITableContext>({} as any);
 | 
			
		||||
export const CollectionFieldContext = createContext(null);
 | 
			
		||||
export const TableRowContext = createContext<ITableRowContext>(null);
 | 
			
		||||
@ -0,0 +1,38 @@
 | 
			
		||||
import { useContext, useEffect } from 'react';
 | 
			
		||||
import { useTable } from './useTable';
 | 
			
		||||
import useRequest from '@ahooksjs/use-request';
 | 
			
		||||
import { useCollectionContext, useResourceRequest } from '../../../constate';
 | 
			
		||||
import { VisibleContext } from '../../../context';
 | 
			
		||||
import { TableRowContext } from '../context';
 | 
			
		||||
 | 
			
		||||
export const useActionLogDetailsResource = ({ onSuccess }) => {
 | 
			
		||||
  const { props } = useTable();
 | 
			
		||||
  const { collection } = useCollectionContext();
 | 
			
		||||
  const ctx = useContext(TableRowContext);
 | 
			
		||||
  const resource = useResourceRequest({
 | 
			
		||||
    resourceName: 'action_logs',
 | 
			
		||||
    resourceKey: ctx.record[props.rowKey],
 | 
			
		||||
  });
 | 
			
		||||
  const service = useRequest(
 | 
			
		||||
    (params?: any) => {
 | 
			
		||||
      return resource.get({
 | 
			
		||||
        ...params,
 | 
			
		||||
        appends: ['changes', 'user', 'collection'],
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      formatResult: (result) => result?.data,
 | 
			
		||||
      onSuccess,
 | 
			
		||||
      manual: true,
 | 
			
		||||
    },
 | 
			
		||||
  );
 | 
			
		||||
  const [visible] = useContext(VisibleContext);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (visible) {
 | 
			
		||||
      service.run({});
 | 
			
		||||
    }
 | 
			
		||||
  }, [visible]);
 | 
			
		||||
 | 
			
		||||
  return { resource, service, initialValues: service.data, ...service };
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,35 @@
 | 
			
		||||
import React, { useContext, createContext, useEffect, useState } from 'react';
 | 
			
		||||
import { useTable } from './useTable';
 | 
			
		||||
import { ListOptions, Resource } from '../../../resource';
 | 
			
		||||
import { TableRowContext } from '../context';
 | 
			
		||||
import { useResourceRequest } from '../../../constate';
 | 
			
		||||
 | 
			
		||||
export const useActionLogsResource = (options: any = {}) => {
 | 
			
		||||
  const { props } = useTable();
 | 
			
		||||
  const ctx = useContext(TableRowContext);
 | 
			
		||||
 | 
			
		||||
  class ActionLogoResource extends Resource {
 | 
			
		||||
    list(options?: ListOptions) {
 | 
			
		||||
      console.log({ options });
 | 
			
		||||
      let defaultFilter = options?.defaultFilter;
 | 
			
		||||
      if (ctx?.record) {
 | 
			
		||||
        const extra = {
 | 
			
		||||
          index: ctx?.record?.id,
 | 
			
		||||
          collection_name: props.collectionName,
 | 
			
		||||
        };
 | 
			
		||||
        if (defaultFilter) {
 | 
			
		||||
          defaultFilter = { and: [defaultFilter, extra] };
 | 
			
		||||
        } else {
 | 
			
		||||
          defaultFilter = extra;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return super.list({ ...options, defaultFilter });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const resource = useResourceRequest('action_logs', ActionLogoResource);
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    resource,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,24 @@
 | 
			
		||||
import { Schema } from '@formily/react';
 | 
			
		||||
import { isColumn } from '../utils';
 | 
			
		||||
 | 
			
		||||
export const useCollectionFields = (schema: Schema) => {
 | 
			
		||||
  const columns = schema.reduceProperties((columns, current) => {
 | 
			
		||||
    if (isColumn(current)) {
 | 
			
		||||
      if (current['x-hidden']) {
 | 
			
		||||
        return columns;
 | 
			
		||||
      }
 | 
			
		||||
      if (current['x-display'] && current['x-display'] !== 'visible') {
 | 
			
		||||
        return columns;
 | 
			
		||||
      }
 | 
			
		||||
      return [...columns, current];
 | 
			
		||||
    }
 | 
			
		||||
    return [...columns];
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return columns
 | 
			
		||||
    .map((column) => {
 | 
			
		||||
      const columnProps = column['x-component-props'] || {};
 | 
			
		||||
      return columnProps.fieldName;
 | 
			
		||||
    })
 | 
			
		||||
    .filter(Boolean);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										17
									
								
								packages/client/src/schemas/table/hooks/useDataSource.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								packages/client/src/schemas/table/hooks/useDataSource.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
import { useTable } from './useTable';
 | 
			
		||||
 | 
			
		||||
export const useDataSource = () => {
 | 
			
		||||
  const {
 | 
			
		||||
    pagination,
 | 
			
		||||
    field,
 | 
			
		||||
    props: { clientSidePagination, dataRequest },
 | 
			
		||||
  } = useTable();
 | 
			
		||||
  let dataSource = field.value;
 | 
			
		||||
  // if (pagination && (clientSidePagination || !dataRequest)) {
 | 
			
		||||
  //   const { page = 1, pageSize } = pagination;
 | 
			
		||||
  //   const startIndex = (page - 1) * pageSize;
 | 
			
		||||
  //   const endIndex = startIndex + pageSize - 1;
 | 
			
		||||
  //   dataSource = field.value?.slice(startIndex, endIndex + 1);
 | 
			
		||||
  // }
 | 
			
		||||
  return dataSource;
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,5 @@
 | 
			
		||||
export const useDefaultRowSelection = () => {
 | 
			
		||||
  return {
 | 
			
		||||
    type: 'checkbox',
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
 | 
			
		||||
export const useDefaultSelectedRowKeys = () => {
 | 
			
		||||
  const [selectedRowKeys, setSelectedRowKeys] = useState<any>([]);
 | 
			
		||||
  return { selectedRowKeys, setSelectedRowKeys };
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										33
									
								
								packages/client/src/schemas/table/hooks/usePagination.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								packages/client/src/schemas/table/hooks/usePagination.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
			
		||||
import { ArrayField } from '@formily/core';
 | 
			
		||||
import { useField } from '@formily/react';
 | 
			
		||||
 | 
			
		||||
export const usePagination = () => {
 | 
			
		||||
  const field = useField<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 };
 | 
			
		||||
    },
 | 
			
		||||
  ];
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										51
									
								
								packages/client/src/schemas/table/hooks/useResource.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								packages/client/src/schemas/table/hooks/useResource.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
			
		||||
import { useContext } from 'react';
 | 
			
		||||
import { Schema } from '@formily/react';
 | 
			
		||||
import { useTable } from './useTable';
 | 
			
		||||
import { TableRowContext } from '../context';
 | 
			
		||||
import useRequest from '@ahooksjs/use-request';
 | 
			
		||||
import { useCollectionContext, useResourceRequest } from '../../../constate';
 | 
			
		||||
import { useDesignable } from '../..';
 | 
			
		||||
 | 
			
		||||
export const useResource = ({ onSuccess, manual = true }) => {
 | 
			
		||||
  const { props } = useTable();
 | 
			
		||||
  const { collection } = useCollectionContext();
 | 
			
		||||
  const ctx = useContext(TableRowContext);
 | 
			
		||||
  const resource = useResourceRequest({
 | 
			
		||||
    resourceName: collection?.name || props.collectionName,
 | 
			
		||||
    resourceKey: ctx.record[props.rowKey],
 | 
			
		||||
  });
 | 
			
		||||
  const { schema } = useDesignable();
 | 
			
		||||
  const fieldFields = (schema: Schema) => {
 | 
			
		||||
    const names = [];
 | 
			
		||||
    schema.reduceProperties((buf, current) => {
 | 
			
		||||
      if (current['x-component'] === 'Form.Field') {
 | 
			
		||||
        const fieldName = current['x-component-props']?.['fieldName'];
 | 
			
		||||
        if (fieldName) {
 | 
			
		||||
          buf.push(fieldName);
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        const fieldNames = fieldFields(current);
 | 
			
		||||
        buf.push(...fieldNames);
 | 
			
		||||
      }
 | 
			
		||||
      return buf;
 | 
			
		||||
    }, names);
 | 
			
		||||
    return names;
 | 
			
		||||
  };
 | 
			
		||||
  console.log(
 | 
			
		||||
    'collection?.name || props.collectionName',
 | 
			
		||||
    collection?.name || props.collectionName,
 | 
			
		||||
    // fieldFields(schema),
 | 
			
		||||
  );
 | 
			
		||||
  const service = useRequest(
 | 
			
		||||
    (params?: any) => {
 | 
			
		||||
      console.log('Table.useResource', params);
 | 
			
		||||
      return resource.get({ ...params, appends: fieldFields(schema) });
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      formatResult: (result) => result?.data,
 | 
			
		||||
      onSuccess,
 | 
			
		||||
      manual,
 | 
			
		||||
    },
 | 
			
		||||
  );
 | 
			
		||||
  return { resource, service, initialValues: service.data, ...service };
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										6
									
								
								packages/client/src/schemas/table/hooks/useTable.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								packages/client/src/schemas/table/hooks/useTable.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
import React, { useContext,createContext, useEffect, useState } from 'react';
 | 
			
		||||
import {TableContext} from '../context';
 | 
			
		||||
 | 
			
		||||
export const useTable = () => {
 | 
			
		||||
  return useContext(TableContext);
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,18 @@
 | 
			
		||||
import { useTable } from './useTable';
 | 
			
		||||
 | 
			
		||||
export const useTableActionBars = () => {
 | 
			
		||||
  const {
 | 
			
		||||
    field,
 | 
			
		||||
    schema,
 | 
			
		||||
    props: { rowKey },
 | 
			
		||||
  } = useTable();
 | 
			
		||||
 | 
			
		||||
  const bars = schema.reduceProperties((bars, current) => {
 | 
			
		||||
    if (current['x-component'] === 'Table.ActionBar') {
 | 
			
		||||
      return [...bars, current];
 | 
			
		||||
    }
 | 
			
		||||
    return [...bars];
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return bars;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										69
									
								
								packages/client/src/schemas/table/hooks/useTableColumns.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								packages/client/src/schemas/table/hooks/useTableColumns.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,69 @@
 | 
			
		||||
import React,{ createContext } from 'react';
 | 
			
		||||
import { findIndex } from 'lodash';
 | 
			
		||||
import { RecursionField, Schema } from '@formily/react';
 | 
			
		||||
import { useDesignable } from '../..';
 | 
			
		||||
import { useTable } from './useTable';
 | 
			
		||||
import {TableRowContext} from '../context';
 | 
			
		||||
import { useCollectionContext } from '../../../constate';
 | 
			
		||||
import {Table,  } from '..';
 | 
			
		||||
import {isColumn} from '../utils'
 | 
			
		||||
import {AddColumn } from '../AddColumn';
 | 
			
		||||
import {CollectionFieldContext}from '../context';
 | 
			
		||||
 | 
			
		||||
export const useTableColumns = () => {
 | 
			
		||||
  const {
 | 
			
		||||
    field,
 | 
			
		||||
    schema,
 | 
			
		||||
    props: { rowKey },
 | 
			
		||||
  } = useTable();
 | 
			
		||||
  const { designable } = useDesignable();
 | 
			
		||||
 | 
			
		||||
  const { getField } = useCollectionContext();
 | 
			
		||||
 | 
			
		||||
  const columnSchemas = schema.reduceProperties((columns, current) => {
 | 
			
		||||
    if (isColumn(current)) {
 | 
			
		||||
      if (current['x-hidden']) {
 | 
			
		||||
        return columns;
 | 
			
		||||
      }
 | 
			
		||||
      if (current['x-display'] && current['x-display'] !== 'visible') {
 | 
			
		||||
        return columns;
 | 
			
		||||
      }
 | 
			
		||||
      return [...columns, current];
 | 
			
		||||
    }
 | 
			
		||||
    return [...columns];
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const columns: any[] = [].concat(
 | 
			
		||||
    columnSchemas.map((column: Schema) => {
 | 
			
		||||
      const columnProps = column['x-component-props'] || {};
 | 
			
		||||
      const collectionField = getField(columnProps.fieldName);
 | 
			
		||||
      return {
 | 
			
		||||
        title: (
 | 
			
		||||
          <CollectionFieldContext.Provider value={collectionField}>
 | 
			
		||||
            <RecursionField name={column.name} schema={column} onlyRenderSelf />
 | 
			
		||||
          </CollectionFieldContext.Provider>
 | 
			
		||||
        ),
 | 
			
		||||
        dataIndex: column.name,
 | 
			
		||||
        ...columnProps,
 | 
			
		||||
        render: (_: any, record: any) => {
 | 
			
		||||
          const index = findIndex(field.value, (item) => item[rowKey] === record[rowKey]);
 | 
			
		||||
          return (
 | 
			
		||||
            <CollectionFieldContext.Provider value={collectionField}>
 | 
			
		||||
              <TableRowContext.Provider value={{ index, record }}>
 | 
			
		||||
                <Table.Cell schema={column} />
 | 
			
		||||
              </TableRowContext.Provider>
 | 
			
		||||
            </CollectionFieldContext.Provider>
 | 
			
		||||
          );
 | 
			
		||||
        },
 | 
			
		||||
      };
 | 
			
		||||
    }),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  if (designable && schema['x-designable-bar'] && schema['x-designable-bar'] !== 'Table.SimpleDesignableBar') {
 | 
			
		||||
    columns.push({
 | 
			
		||||
      title: <AddColumn />,
 | 
			
		||||
      dataIndex: 'addnew',
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  return columns;
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,24 @@
 | 
			
		||||
import { useForm } from '@formily/react';
 | 
			
		||||
import { useTable } from './useTable';
 | 
			
		||||
 | 
			
		||||
export function useTableCreateAction() {
 | 
			
		||||
  const {
 | 
			
		||||
    field,
 | 
			
		||||
    service,
 | 
			
		||||
    resource,
 | 
			
		||||
    refresh,
 | 
			
		||||
    props: { refreshRequestOnChange },
 | 
			
		||||
  } = useTable();
 | 
			
		||||
  const form = useForm();
 | 
			
		||||
  return {
 | 
			
		||||
    async run() {
 | 
			
		||||
      console.log('refreshRequestOnChange', refreshRequestOnChange);
 | 
			
		||||
      if (refreshRequestOnChange) {
 | 
			
		||||
        await resource.create(form.values);
 | 
			
		||||
        await form.reset();
 | 
			
		||||
        return service.refresh();
 | 
			
		||||
      }
 | 
			
		||||
      field.unshift(form.values);
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,49 @@
 | 
			
		||||
import { useContext } from "react";
 | 
			
		||||
import { useTable } from "./useTable";
 | 
			
		||||
import { TableRowContext } from "../context";
 | 
			
		||||
import { VisibleContext } from '../../../context';
 | 
			
		||||
import { findIndex } from 'lodash';
 | 
			
		||||
 | 
			
		||||
export const useTableDestroyAction = () => {
 | 
			
		||||
  const {
 | 
			
		||||
    resource,
 | 
			
		||||
    field,
 | 
			
		||||
    service,
 | 
			
		||||
    selectedRowKeys,
 | 
			
		||||
    setSelectedRowKeys,
 | 
			
		||||
    refresh,
 | 
			
		||||
    props: { refreshRequestOnChange, rowKey },
 | 
			
		||||
  } = useTable();
 | 
			
		||||
  const ctx = useContext(TableRowContext);
 | 
			
		||||
  const [, setVisible] = useContext(VisibleContext);
 | 
			
		||||
  return {
 | 
			
		||||
    async run() {
 | 
			
		||||
      if (refreshRequestOnChange) {
 | 
			
		||||
        const rowKeys = selectedRowKeys || [];
 | 
			
		||||
        if (ctx) {
 | 
			
		||||
          rowKeys.push(ctx.record[rowKey]);
 | 
			
		||||
        }
 | 
			
		||||
        await resource.destroy({
 | 
			
		||||
          [`${rowKey}.in`]: rowKeys,
 | 
			
		||||
        });
 | 
			
		||||
        setSelectedRowKeys([]);
 | 
			
		||||
        setVisible && setVisible(false);
 | 
			
		||||
        return service.refresh();
 | 
			
		||||
      }
 | 
			
		||||
      if (ctx) {
 | 
			
		||||
        console.log('ctx.index', ctx.index);
 | 
			
		||||
        field.remove(ctx.index);
 | 
			
		||||
        refresh();
 | 
			
		||||
      }
 | 
			
		||||
      const rowKeys = [...selectedRowKeys];
 | 
			
		||||
      while (rowKeys.length) {
 | 
			
		||||
        const key = rowKeys.shift();
 | 
			
		||||
        const index = findIndex(field.value, (item) => item[rowKey] === key);
 | 
			
		||||
        field.remove(index);
 | 
			
		||||
      }
 | 
			
		||||
      setSelectedRowKeys([]);
 | 
			
		||||
      refresh();
 | 
			
		||||
      return;
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,51 @@
 | 
			
		||||
import { useField } from '@formily/react';
 | 
			
		||||
import { useContext } from 'react';
 | 
			
		||||
import { useTable } from './useTable';
 | 
			
		||||
import { TableRowContext } from '../context';
 | 
			
		||||
import { useCollectionContext } from '../../../constate';
 | 
			
		||||
 | 
			
		||||
export const useTableExportAction = () => {
 | 
			
		||||
  const {
 | 
			
		||||
    resource,
 | 
			
		||||
    field,
 | 
			
		||||
    service,
 | 
			
		||||
    selectedRowKeys,
 | 
			
		||||
    setSelectedRowKeys,
 | 
			
		||||
    refresh,
 | 
			
		||||
    schema,
 | 
			
		||||
    props: { refreshRequestOnChange, rowKey },
 | 
			
		||||
  } = useTable();
 | 
			
		||||
  const ctx = useContext(TableRowContext);
 | 
			
		||||
 | 
			
		||||
  const actionField = useField();
 | 
			
		||||
  const fieldNames = actionField.componentProps.fieldNames || [];
 | 
			
		||||
  const { getField } = useCollectionContext();
 | 
			
		||||
 | 
			
		||||
  const columns = fieldNames
 | 
			
		||||
    .map((name) => {
 | 
			
		||||
      const f = getField(name);
 | 
			
		||||
      return {
 | 
			
		||||
        title: f?.uiSchema.title,
 | 
			
		||||
        name,
 | 
			
		||||
        sort: f?.sort,
 | 
			
		||||
      };
 | 
			
		||||
    })
 | 
			
		||||
    .sort((a, b) => a.sort - b.sort);
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    async run() {
 | 
			
		||||
      const rowKeys = selectedRowKeys || [];
 | 
			
		||||
      const { filter = {}, ...others } = service.params[0];
 | 
			
		||||
      if (rowKeys.length) {
 | 
			
		||||
        filter[`${rowKey}.in`] = rowKeys;
 | 
			
		||||
      }
 | 
			
		||||
      await resource.export({
 | 
			
		||||
        ...others,
 | 
			
		||||
        columns,
 | 
			
		||||
        perPage: -1,
 | 
			
		||||
        page: 1,
 | 
			
		||||
        filter,
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
import { useTable } from './useTable';
 | 
			
		||||
import { useForm } from '@formily/react';
 | 
			
		||||
 | 
			
		||||
export function useTableFilterAction() {
 | 
			
		||||
  const {
 | 
			
		||||
    field,
 | 
			
		||||
    service,
 | 
			
		||||
    refresh,
 | 
			
		||||
    props: { refreshRequestOnChange },
 | 
			
		||||
  } = useTable();
 | 
			
		||||
  const form = useForm();
 | 
			
		||||
  return {
 | 
			
		||||
    async run() {
 | 
			
		||||
      console.log('useTableFilterAction', form.values);
 | 
			
		||||
      if (refreshRequestOnChange) {
 | 
			
		||||
        return service.run({
 | 
			
		||||
          ...service.params[0],
 | 
			
		||||
          // filter,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								packages/client/src/schemas/table/hooks/useTableIndex.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								packages/client/src/schemas/table/hooks/useTableIndex.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
import { useContext } from 'react';
 | 
			
		||||
import { useTable } from './useTable';
 | 
			
		||||
import { TableRowContext } from '../context';
 | 
			
		||||
 | 
			
		||||
export const useTableIndex = () => {
 | 
			
		||||
  const { pagination, props } = useTable();
 | 
			
		||||
  const ctx = useContext(TableRowContext);
 | 
			
		||||
  const { pageSize, page = 1 } = pagination;
 | 
			
		||||
  console.log({ pageSize, page }, ctx.index);
 | 
			
		||||
  return ctx.index + (page - 1) * pageSize;
 | 
			
		||||
  if (pagination && !props.clientSidePagination) {
 | 
			
		||||
    const { pageSize, page = 1 } = pagination;
 | 
			
		||||
    return ctx.index + (page - 1) * pageSize;
 | 
			
		||||
  }
 | 
			
		||||
  return ctx.index;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										7
									
								
								packages/client/src/schemas/table/hooks/useTableRow.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/client/src/schemas/table/hooks/useTableRow.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
import React, { useContext } from 'react';
 | 
			
		||||
import {TableRowContext} from '../context';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export const useTableRow = () => {
 | 
			
		||||
  return useContext(TableRowContext);
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,7 @@
 | 
			
		||||
import { useContext } from "react";
 | 
			
		||||
import { TableRowContext } from "../context";
 | 
			
		||||
 | 
			
		||||
export const useTableRowRecord = () => {
 | 
			
		||||
  const ctx = useContext(TableRowContext);
 | 
			
		||||
  return ctx.record;
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,35 @@
 | 
			
		||||
import { useForm } from "@formily/react";
 | 
			
		||||
import { useContext } from "react";
 | 
			
		||||
import { useTable } from "./useTable";
 | 
			
		||||
import { TableRowContext } from "../context";
 | 
			
		||||
import { DescriptionsContext } from '../../form';
 | 
			
		||||
 | 
			
		||||
export const useTableUpdateAction = () => {
 | 
			
		||||
  const {
 | 
			
		||||
    resource,
 | 
			
		||||
    field,
 | 
			
		||||
    service,
 | 
			
		||||
    refresh,
 | 
			
		||||
    props: { refreshRequestOnChange, rowKey },
 | 
			
		||||
  } = useTable();
 | 
			
		||||
  const ctx = useContext(TableRowContext);
 | 
			
		||||
  const form = useForm();
 | 
			
		||||
  const { service: formService } = useContext(DescriptionsContext);
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    async run() {
 | 
			
		||||
      if (refreshRequestOnChange) {
 | 
			
		||||
        await resource.save(form.values, {
 | 
			
		||||
          resourceKey: ctx.record[rowKey],
 | 
			
		||||
        });
 | 
			
		||||
        if (formService) {
 | 
			
		||||
          await formService.refresh();
 | 
			
		||||
        }
 | 
			
		||||
        await service.refresh();
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      field.value[ctx.index] = form.values;
 | 
			
		||||
      // refresh();
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										10
									
								
								packages/client/src/schemas/table/hooks/useTotal.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								packages/client/src/schemas/table/hooks/useTotal.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
import { useTable } from './useTable';
 | 
			
		||||
 | 
			
		||||
export const useTotal = () => {
 | 
			
		||||
  const {
 | 
			
		||||
    field,
 | 
			
		||||
    service,
 | 
			
		||||
    props: { clientSidePagination },
 | 
			
		||||
  } = useTable();
 | 
			
		||||
  return clientSidePagination ? field?.value?.length : service?.data?.total;
 | 
			
		||||
};
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										22
									
								
								packages/client/src/schemas/table/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								packages/client/src/schemas/table/types.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
			
		||||
import { ArrayField } from '@formily/core';
 | 
			
		||||
import { FormProvider, observer, RecursionField, Schema, useField, useForm } from '@formily/react';
 | 
			
		||||
import { BaseResult } from '@ahooksjs/use-request/lib/types';
 | 
			
		||||
import { ListOptions, Resource } from '../../resource';
 | 
			
		||||
 | 
			
		||||
export interface ITableContext {
 | 
			
		||||
  props: any;
 | 
			
		||||
  field: ArrayField;
 | 
			
		||||
  schema: Schema;
 | 
			
		||||
  service: BaseResult<any, any>;
 | 
			
		||||
  selectedRowKeys?: any;
 | 
			
		||||
  setSelectedRowKeys?: any;
 | 
			
		||||
  pagination?: any;
 | 
			
		||||
  setPagination?: any;
 | 
			
		||||
  refresh?: any;
 | 
			
		||||
  resource?: Resource;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ITableRowContext {
 | 
			
		||||
  index: number;
 | 
			
		||||
  record: any;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										164
									
								
								packages/client/src/schemas/table/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								packages/client/src/schemas/table/utils.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,164 @@
 | 
			
		||||
import { Schema } from '@formily/react';
 | 
			
		||||
import { uid } from '@formily/shared';
 | 
			
		||||
import { interfaces } from '../database-field/interfaces';
 | 
			
		||||
import { ISchema } from '..';
 | 
			
		||||
 | 
			
		||||
export 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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export 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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const generateActionSchema = (type) => {
 | 
			
		||||
  const actions: { [key: string]: ISchema } = {
 | 
			
		||||
    filter: {
 | 
			
		||||
      key: uid(),
 | 
			
		||||
      name: uid(),
 | 
			
		||||
      type: 'void',
 | 
			
		||||
      title: "{{ t('Filter') }}",
 | 
			
		||||
      'x-align': 'left',
 | 
			
		||||
      'x-decorator': 'AddNew.Displayed',
 | 
			
		||||
      'x-decorator-props': {
 | 
			
		||||
        displayName: 'filter',
 | 
			
		||||
      },
 | 
			
		||||
      'x-component': 'Table.Filter',
 | 
			
		||||
      'x-designable-bar': 'Table.Filter.DesignableBar',
 | 
			
		||||
      'x-component-props': {
 | 
			
		||||
        fieldNames: [],
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    export: {
 | 
			
		||||
      key: uid(),
 | 
			
		||||
      type: 'void',
 | 
			
		||||
      name: uid(),
 | 
			
		||||
      title: "{{ t('Export') }}",
 | 
			
		||||
      'x-align': 'right',
 | 
			
		||||
      'x-decorator': 'AddNew.Displayed',
 | 
			
		||||
      'x-decorator-props': {
 | 
			
		||||
        displayName: 'export',
 | 
			
		||||
      },
 | 
			
		||||
      'x-action-type': 'export',
 | 
			
		||||
      'x-component': 'Action',
 | 
			
		||||
      'x-designable-bar': 'Table.ExportActionDesignableBar',
 | 
			
		||||
      'x-component-props': {
 | 
			
		||||
        fieldNames: [],
 | 
			
		||||
        icon: 'ExportOutlined',
 | 
			
		||||
        useAction: '{{ Table.useTableExportAction }}',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    create: {
 | 
			
		||||
      key: uid(),
 | 
			
		||||
      type: 'void',
 | 
			
		||||
      name: uid(),
 | 
			
		||||
      title: "{{ t('Add new') }}",
 | 
			
		||||
      'x-align': 'right',
 | 
			
		||||
      'x-decorator': 'AddNew.Displayed',
 | 
			
		||||
      'x-decorator-props': {
 | 
			
		||||
        displayName: 'create',
 | 
			
		||||
      },
 | 
			
		||||
      'x-component': 'Action',
 | 
			
		||||
      'x-component-props': {
 | 
			
		||||
        type: 'primary',
 | 
			
		||||
        icon: 'PlusOutlined',
 | 
			
		||||
      },
 | 
			
		||||
      'x-designable-bar': 'Table.Action.DesignableBar',
 | 
			
		||||
      properties: {
 | 
			
		||||
        modal: {
 | 
			
		||||
          type: 'void',
 | 
			
		||||
          title: "{{ t('Add record') }}",
 | 
			
		||||
          'x-decorator': 'Form',
 | 
			
		||||
          'x-component': 'Action.Drawer',
 | 
			
		||||
          'x-component-props': {
 | 
			
		||||
            useOkAction: '{{ Table.useTableCreateAction }}',
 | 
			
		||||
          },
 | 
			
		||||
          properties: {
 | 
			
		||||
            [uid()]: {
 | 
			
		||||
              type: 'void',
 | 
			
		||||
              'x-component': 'Grid',
 | 
			
		||||
              'x-component-props': {
 | 
			
		||||
                addNewComponent: 'AddNew.FormItem',
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    destroy: {
 | 
			
		||||
      key: uid(),
 | 
			
		||||
      type: 'void',
 | 
			
		||||
      name: uid(),
 | 
			
		||||
      title: "{{ t('Delete') }}",
 | 
			
		||||
      'x-align': 'right',
 | 
			
		||||
      'x-decorator': 'AddNew.Displayed',
 | 
			
		||||
      'x-decorator-props': {
 | 
			
		||||
        displayName: 'destroy',
 | 
			
		||||
      },
 | 
			
		||||
      'x-action-type': 'destroy',
 | 
			
		||||
      'x-component': 'Action',
 | 
			
		||||
      'x-designable-bar': 'Table.Action.DesignableBar',
 | 
			
		||||
      'x-component-props': {
 | 
			
		||||
        confirm: {
 | 
			
		||||
          title: "{{ t('Delete record') }}",
 | 
			
		||||
          content: "{{ t('Are you sure you want to delete it?') }}",
 | 
			
		||||
        },
 | 
			
		||||
        useAction: '{{ Table.useTableDestroyAction }}',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    view: {},
 | 
			
		||||
    update: {},
 | 
			
		||||
  };
 | 
			
		||||
  return actions[type];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function isColumn(schema: Schema) {
 | 
			
		||||
  return ['Table.Column'].includes(schema['x-component']);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isColumnComponent(component: string) {
 | 
			
		||||
  return ['Table.Operation', 'Table.Column'].includes(component);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isOperationColumn(schema: Schema) {
 | 
			
		||||
  return ['Table.Operation'].includes(schema['x-component']);
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user