feat: support fixed block (#1267)
* feat: support fixed block * feat: update locale * fix: fix block not work in non-designer * feat: improve padding * feat: update scroll * fix: the page effect is not normal when deleting fixed blocks * feat: recalculate table scroll when resize * fix: avoid scrolling effect when dragging the Kanban column * feat: improve scroll size * fix: column size * fix: unused * fix: configure action in designable * fix: has page title * fix: optimize * fix: optimize * feat: avoid fixed block * fix: action column width * fix: optimize Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
217ecb27ae
commit
c731abf82c
@ -5,6 +5,7 @@ import uniq from 'lodash/uniq';
|
||||
import React, { createContext, useContext, useEffect } from 'react';
|
||||
import { useACLRoleContext } from '../acl';
|
||||
import { useCollection, useCollectionManager } from '../collection-manager';
|
||||
import { useFixedSchema } from '../schema-component';
|
||||
import { toColumns } from '../schema-component/antd/kanban/Kanban';
|
||||
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
|
||||
|
||||
@ -23,6 +24,8 @@ const useGroupField = (props) => {
|
||||
|
||||
const InternalKanbanBlockProvider = (props) => {
|
||||
const field = useField<any>();
|
||||
const fieldSchema = useFieldSchema();
|
||||
useFixedSchema();
|
||||
const { resource, service } = useBlockRequestContext();
|
||||
const groupField = useGroupField(props);
|
||||
if (!groupField) {
|
||||
@ -42,6 +45,7 @@ const InternalKanbanBlockProvider = (props) => {
|
||||
service,
|
||||
resource,
|
||||
groupField,
|
||||
fixedBlock: fieldSchema?.['x-decorator-props']?.fixedBlock,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
|
@ -3,6 +3,7 @@ import { FormContext, Schema, useField, useFieldSchema } from '@formily/react';
|
||||
import uniq from 'lodash/uniq';
|
||||
import React, { createContext, useContext, useEffect, useMemo } from 'react';
|
||||
import { useCollectionManager } from '../collection-manager';
|
||||
import { useFixedSchema } from '../schema-component';
|
||||
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
|
||||
|
||||
export const TableBlockContext = createContext<any>({});
|
||||
@ -14,6 +15,7 @@ const InternalTableBlockProvider = (props) => {
|
||||
// if (service.loading) {
|
||||
// return <Spin />;
|
||||
// }
|
||||
useFixedSchema();
|
||||
return (
|
||||
<TableBlockContext.Provider
|
||||
value={{
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { useField } from '@formily/react';
|
||||
import React, { forwardRef, useState } from 'react';
|
||||
import { DragDropContext } from 'react-beautiful-dnd';
|
||||
import Column from './Column';
|
||||
@ -16,7 +17,9 @@ import {
|
||||
import { partialRight, when } from './utils';
|
||||
import withDroppable from './withDroppable';
|
||||
|
||||
const Columns = forwardRef((props, ref: any) => <div ref={ref} style={{ whiteSpace: 'nowrap' }} {...props} />);
|
||||
const Columns = forwardRef((props, ref: any) => (
|
||||
<div ref={ref} style={{ whiteSpace: 'nowrap', height: '100%', overflowY: 'hidden' }} {...props} />
|
||||
));
|
||||
|
||||
const DroppableBoard = withDroppable(Columns);
|
||||
|
||||
@ -241,6 +244,7 @@ function BoardContainer(props) {
|
||||
onCardNew,
|
||||
allowAddCard,
|
||||
} = props;
|
||||
|
||||
function handleOnDragEnd(event) {
|
||||
const coordinates = getCoordinates(event, board);
|
||||
if (!coordinates.source) return;
|
||||
|
@ -1,13 +1,24 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
import React, { forwardRef, useState } from 'react';
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
import { useKanbanBlockContext } from '../block-provider';
|
||||
import Card from './Card';
|
||||
import CardAdder from './CardAdder';
|
||||
import { pickPropOut } from './utils';
|
||||
import withDroppable from './withDroppable';
|
||||
|
||||
const ColumnEmptyPlaceholder = forwardRef((props, ref: any) => (
|
||||
<div ref={ref} style={{ minHeight: 'inherit', height: 'inherit' }} {...props} />
|
||||
));
|
||||
const ColumnEmptyPlaceholder = forwardRef(
|
||||
(
|
||||
props: {
|
||||
children: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
},
|
||||
ref: any,
|
||||
) => {
|
||||
return (
|
||||
<div ref={ref} {...props} style={{ minHeight: 'inherit', height: 'var(--column-height)', ...props.style }} />
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const DroppableColumn = withDroppable(ColumnEmptyPlaceholder);
|
||||
|
||||
@ -23,6 +34,8 @@ function Column({
|
||||
allowAddCard,
|
||||
cardAdderPosition = 'top',
|
||||
}) {
|
||||
const { fixedBlock } = useKanbanBlockContext();
|
||||
const [headerHeight, setHeaderHeight] = useState(0);
|
||||
return (
|
||||
<Draggable draggableId={`column-draggable-${children.id}`} index={columnIndex} isDragDisabled={disableColumnDrag}>
|
||||
{(columnProvided) => {
|
||||
@ -38,15 +51,26 @@ function Column({
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'top',
|
||||
...columnProvided.draggableProps.style,
|
||||
'--column-height': fixedBlock ? `calc(100% - ${headerHeight}px)` : 'inherit',
|
||||
}}
|
||||
className="react-kanban-column"
|
||||
data-testid={`column-${children.id}`}
|
||||
>
|
||||
<div {...columnProvided.dragHandleProps}>{renderColumnHeader(children)}</div>
|
||||
<div
|
||||
ref={fixedBlock ? (ref) => setHeaderHeight(Math.ceil(ref?.getBoundingClientRect().height || 0)) : null}
|
||||
{...columnProvided.dragHandleProps}
|
||||
>
|
||||
{renderColumnHeader(children)}
|
||||
</div>
|
||||
{cardAdderPosition === 'top' && allowAddCard && renderCardAdder({ column: children, onConfirm: onCardNew })}
|
||||
<DroppableColumn droppableId={String(children.id)}>
|
||||
{children?.cards?.length ? (
|
||||
<div className="react-kanban-card-skeleton">
|
||||
<div
|
||||
className="react-kanban-card-skeleton"
|
||||
style={{
|
||||
height: fixedBlock ? '100%' : undefined,
|
||||
}}
|
||||
>
|
||||
{children.cards.map((card, index) => (
|
||||
<Card
|
||||
key={card.id}
|
||||
|
@ -1,6 +1,7 @@
|
||||
.react-kanban-board {
|
||||
height: 100%;
|
||||
// padding: 5px;
|
||||
margin-bottom: 24px;
|
||||
// margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.react-kanban-card {
|
||||
|
@ -515,4 +515,5 @@ export default {
|
||||
"Inner": "Inner",
|
||||
"Search and select collection": "Search and select collection",
|
||||
'Please fill in the iframe URL': 'Please fill in the iframe URL',
|
||||
'Fix block': 'Fix block'
|
||||
}
|
||||
|
@ -611,4 +611,6 @@ export default {
|
||||
"Inner": "里面",
|
||||
"Search and select collection": "搜索并选择数据表",
|
||||
'Please fill in the iframe URL': '请填写嵌入的地址',
|
||||
|
||||
'Fix block': '固定区块'
|
||||
}
|
||||
|
@ -27,14 +27,24 @@ export const ActionBar = observer((props: any) => {
|
||||
}
|
||||
return (
|
||||
<div
|
||||
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', overflowX: 'auto', ...style }}
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
overflowX: 'auto',
|
||||
flexShrink: 0,
|
||||
...style,
|
||||
}}
|
||||
{...others}
|
||||
>
|
||||
<div className={css`
|
||||
<div
|
||||
className={css`
|
||||
.ant-space:last-child {
|
||||
margin-left: 8px;
|
||||
}
|
||||
`} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}>
|
||||
`}
|
||||
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}
|
||||
>
|
||||
<DndContext>
|
||||
<Space>
|
||||
{fieldSchema.mapProperties((schema, key) => {
|
||||
|
@ -7,6 +7,7 @@ import { useCollectionFilterOptions } from '../../../collection-manager/action-h
|
||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||
import { useSchemaTemplate } from '../../../schema-templates';
|
||||
import { useDesignable } from '../../hooks';
|
||||
import { useFixedBlockDesignerSetting } from '../page';
|
||||
|
||||
export const KanbanDesigner = () => {
|
||||
const { name, title } = useCollection();
|
||||
@ -19,6 +20,8 @@ export const KanbanDesigner = () => {
|
||||
const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
|
||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
||||
const template = useSchemaTemplate();
|
||||
const fixedBlockDesignerSetting = useFixedBlockDesignerSetting();
|
||||
|
||||
return (
|
||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||
<SchemaSettings.BlockTitleItem />
|
||||
@ -52,6 +55,7 @@ export const KanbanDesigner = () => {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{fixedBlockDesignerSetting}
|
||||
<SchemaSettings.Divider />
|
||||
<SchemaSettings.Template componentName={'Kanban'} collectionName={name} resourceName={defaultResource} />
|
||||
<SchemaSettings.Divider />
|
||||
|
@ -1,5 +1,5 @@
|
||||
.react-kanban-board {
|
||||
margin-bottom: 24px;
|
||||
// margin-bottom: 24px;
|
||||
.nb-block-item {
|
||||
.ant-formily-item-control .ant-space-item{
|
||||
|
||||
|
@ -0,0 +1,128 @@
|
||||
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
|
||||
import { css } from '@emotion/css';
|
||||
import { SchemaSettings } from '../../../schema-settings';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDesignable } from '../../hooks';
|
||||
import { useGridContext } from '../grid';
|
||||
import { useRecord } from '../../../record-provider';
|
||||
|
||||
const FixedBlockContext = React.createContext({
|
||||
setFixedSchema: (schema: Schema) => {},
|
||||
height: 0,
|
||||
schema: {} as unknown as Schema,
|
||||
});
|
||||
|
||||
export const useFixedSchema = () => {
|
||||
const field = useField();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { setFixedSchema } = useFixedBlock();
|
||||
const hasSet = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (fieldSchema?.['x-decorator-props']?.fixedBlock) {
|
||||
setFixedSchema(fieldSchema);
|
||||
hasSet.current = true;
|
||||
}
|
||||
}, [field?.decoratorProps?.fixedBlock, fieldSchema?.['x-decorator-props']?.fixedBlock]);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (hasSet.current) {
|
||||
setFixedSchema(null);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
export const useFixedBlock = () => {
|
||||
return useContext(FixedBlockContext);
|
||||
};
|
||||
|
||||
export const useFixedBlockDesignerSetting = () => {
|
||||
const field = useField();
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { dn } = useDesignable();
|
||||
const record = useRecord();
|
||||
|
||||
return useMemo(() => {
|
||||
if (Object.keys(record).length) {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<SchemaSettings.SwitchItem
|
||||
title={t('Fix block')}
|
||||
checked={fieldSchema['x-decorator-props']['fixedBlock']}
|
||||
onChange={(fixedBlock) => {
|
||||
field.decoratorProps.fixedBlock = fixedBlock;
|
||||
fieldSchema['x-decorator-props'].fixedBlock = fixedBlock;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
'x-decorator-props': fieldSchema['x-decorator-props'],
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}, [fieldSchema['x-decorator-props'], field.decoratorProps?.fixedBlock, dn, record]);
|
||||
};
|
||||
|
||||
interface FixedBlockProps {
|
||||
height: number;
|
||||
}
|
||||
|
||||
const FixedBlock: React.FC<FixedBlockProps> = (props) => {
|
||||
const { height } = props;
|
||||
const [fixedSchema, setFixedSchema] = useState<Schema>();
|
||||
const schema = useMemo<Schema>(() => {
|
||||
if (!fixedSchema || fixedSchema['x-decorator-props']?.fixedBlock !== true) return;
|
||||
return fixedSchema.parent;
|
||||
}, [fixedSchema, fixedSchema?.['x-decorator-props']['fixedBlock']]);
|
||||
|
||||
return (
|
||||
<FixedBlockContext.Provider value={{ schema: fixedSchema, height, setFixedSchema }}>
|
||||
{schema ? (
|
||||
<div
|
||||
className={css`
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
.noco-card-item {
|
||||
height: 100%;
|
||||
.ant-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
.ant-card-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
// padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
& .ant-spin-nested-loading {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
& .ant-spin-container {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
`}
|
||||
style={{
|
||||
height: `calc(100vh - ${height}px)`,
|
||||
}}
|
||||
>
|
||||
<RecursionField onlyRenderProperties={false} schema={schema} />
|
||||
</div>
|
||||
) : (
|
||||
props.children
|
||||
)}
|
||||
</FixedBlockContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default FixedBlock;
|
@ -1,15 +1,17 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { FormDialog, FormLayout } from '@formily/antd';
|
||||
import { RecursionField, SchemaOptionsContext, useField, useFieldSchema } from '@formily/react';
|
||||
import { useMutationObserver } from 'ahooks';
|
||||
import { Button, PageHeader as AntdPageHeader, Spin, Tabs } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { useDocumentTitle } from '../../../document-title';
|
||||
import { Icon } from '../../../icon';
|
||||
import { SchemaComponent, SchemaComponentOptions } from '../../core';
|
||||
import { useCompile, useDesignable } from '../../hooks';
|
||||
import FixedBlock from './FixedBlock';
|
||||
import { PageDesigner, PageTabDesigner } from './PageTabDesigner';
|
||||
|
||||
const designerCss = css`
|
||||
@ -130,10 +132,17 @@ export const Page = (props) => {
|
||||
// @ts-ignore
|
||||
return location?.query?.tab || Object.keys(fieldSchema.properties).shift();
|
||||
});
|
||||
|
||||
const [height, setHeight] = useState(0);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={pageDesignerCss}>
|
||||
<PageDesigner title={fieldSchema.title || title} />
|
||||
<div
|
||||
ref={(ref) => {
|
||||
setHeight(Math.floor(ref?.getBoundingClientRect().height || 0) + 1);
|
||||
}}
|
||||
>
|
||||
{!disablePageHeader && (
|
||||
<AntdPageHeader
|
||||
className={css`
|
||||
@ -234,10 +243,18 @@ export const Page = (props) => {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ margin: 24 }}>
|
||||
{loading ? (
|
||||
<Spin />
|
||||
) : !disablePageHeader && enablePageTabs ? (
|
||||
) : (
|
||||
<FixedBlock
|
||||
height={
|
||||
// header 46 margin 48
|
||||
height + 46 + 48
|
||||
}
|
||||
>
|
||||
{!disablePageHeader && enablePageTabs ? (
|
||||
<RecursionField
|
||||
schema={fieldSchema}
|
||||
onlyRenderProperties
|
||||
@ -258,8 +275,9 @@ export const Page = (props) => {
|
||||
{props.children}
|
||||
</div>
|
||||
)}
|
||||
</FixedBlock>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1 +1,2 @@
|
||||
export * from './Page';
|
||||
export * from './FixedBlock';
|
||||
|
@ -68,8 +68,38 @@ export const TableColumnDesigner = (props) => {
|
||||
dn.refresh();
|
||||
}}
|
||||
/>
|
||||
<SchemaSettings.ModalItem
|
||||
title={t('Column width')}
|
||||
schema={
|
||||
{
|
||||
intefaceCfg && intefaceCfg.sortable === true && (
|
||||
type: 'object',
|
||||
title: t('Column width'),
|
||||
properties: {
|
||||
width: {
|
||||
default: columnSchema?.['x-component-props']?.['width'] || 200,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'InputNumber',
|
||||
'x-component-props': {},
|
||||
},
|
||||
},
|
||||
} as ISchema
|
||||
}
|
||||
onSubmit={({ width }) => {
|
||||
const props = columnSchema['x-component-props'] || {};
|
||||
props['width'] = width;
|
||||
const schema: ISchema = {
|
||||
['x-uid']: columnSchema['x-uid'],
|
||||
};
|
||||
schema['x-component-props'] = props;
|
||||
columnSchema['x-component-props'] = props;
|
||||
field.componentProps.width = width;
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
dn.refresh();
|
||||
}}
|
||||
/>
|
||||
{intefaceCfg && intefaceCfg.sortable === true && (
|
||||
<SchemaSettings.SwitchItem
|
||||
title={t('Sortable')}
|
||||
checked={field.componentProps.sorter}
|
||||
@ -79,18 +109,17 @@ export const TableColumnDesigner = (props) => {
|
||||
};
|
||||
columnSchema['x-component-props'] = {
|
||||
...columnSchema['x-component-props'],
|
||||
sorter: v
|
||||
}
|
||||
sorter: v,
|
||||
};
|
||||
schema['x-component-props'] = columnSchema['x-component-props'];
|
||||
field.componentProps.sorter = v;
|
||||
dn.emit('patch', {
|
||||
schema
|
||||
schema,
|
||||
});
|
||||
dn.refresh();
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)}
|
||||
{['linkTo', 'm2m', 'm2o', 'o2m', 'obo', 'oho'].includes(collectionField?.interface) && (
|
||||
<SchemaSettings.SelectItem
|
||||
title={t('Title field')}
|
||||
|
@ -4,12 +4,12 @@ import { css } from '@emotion/css';
|
||||
import { ArrayField, Field } from '@formily/core';
|
||||
import { ISchema, observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
|
||||
import { reaction } from '@formily/reactive';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { useEventListener, useMemoizedFn } from 'ahooks';
|
||||
import { Table as AntdTable, TableColumnProps } from 'antd';
|
||||
import { default as classNames, default as cls } from 'classnames';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import React, { RefCallback, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DndContext } from '../..';
|
||||
import { DndContext, useDesignable } from '../..';
|
||||
import { RecordIndexProvider, RecordProvider, useSchemaInitializer } from '../../../';
|
||||
|
||||
const isColumnComponent = (schema: Schema) => {
|
||||
@ -24,6 +24,7 @@ const useTableColumns = () => {
|
||||
const start = Date.now();
|
||||
const field = useField<ArrayField>();
|
||||
const schema = useFieldSchema();
|
||||
const { designable } = useDesignable();
|
||||
const { exists, render } = useSchemaInitializer(schema['x-initializer']);
|
||||
const columns = schema
|
||||
.reduceProperties((buf, s) => {
|
||||
@ -38,13 +39,13 @@ const useTableColumns = () => {
|
||||
}
|
||||
}, []);
|
||||
const dataIndex = collectionFields?.length > 0 ? collectionFields[0].name : s.name;
|
||||
|
||||
return {
|
||||
title: <RecursionField name={s.name} schema={s} onlyRenderSelf />,
|
||||
dataIndex,
|
||||
key: s.name,
|
||||
sorter: s['x-component-props']?.['sorter'],
|
||||
// width: 300,
|
||||
width: 200,
|
||||
...s['x-component-props'],
|
||||
render: (v, record) => {
|
||||
const index = field.value?.indexOf(record);
|
||||
// console.log((Date.now() - start) / 1000);
|
||||
@ -65,6 +66,7 @@ const useTableColumns = () => {
|
||||
title: render(),
|
||||
dataIndex: 'TABLE_COLUMN_INITIALIZER',
|
||||
key: 'TABLE_COLUMN_INITIALIZER',
|
||||
render: designable ? () => <div style={{ minWidth: 300 }} /> : null,
|
||||
});
|
||||
};
|
||||
|
||||
@ -169,6 +171,7 @@ export const Table: any = observer((props: any) => {
|
||||
const onRowDragEnd = useMemoizedFn(others.onRowDragEnd || (() => {}));
|
||||
const paginationProps = usePaginationProps(pagination1, pagination2);
|
||||
const requiredValidator = field.required || required;
|
||||
|
||||
useEffect(() => {
|
||||
field.setValidator((value) => {
|
||||
if (requiredValidator) {
|
||||
@ -360,10 +363,44 @@ export const Table: any = observer((props: any) => {
|
||||
},
|
||||
[field, dragSort],
|
||||
);
|
||||
const fieldSchema = useFieldSchema();
|
||||
const fixedBlock = fieldSchema?.parent?.['x-decorator-props']?.fixedBlock;
|
||||
const [tableHeight, setTableHeight] = useState(0);
|
||||
|
||||
const [headerAndPaginationHeight, setHeaderAndPaginationHeight] = useState(0);
|
||||
const scroll = useMemo(() => {
|
||||
return fixedBlock
|
||||
? {
|
||||
x: 'max-content',
|
||||
y: tableHeight - headerAndPaginationHeight,
|
||||
}
|
||||
: {
|
||||
x: 'max-content',
|
||||
};
|
||||
}, [fixedBlock, tableHeight, headerAndPaginationHeight]);
|
||||
|
||||
const elementRef = useRef<HTMLDivElement>();
|
||||
const calcTableSize = () => {
|
||||
if (!elementRef.current) return;
|
||||
const clientRect = elementRef.current?.getBoundingClientRect();
|
||||
setTableHeight(Math.ceil(clientRect?.height || 0));
|
||||
};
|
||||
useEventListener('resize', calcTableSize);
|
||||
|
||||
const mountedRef: RefCallback<HTMLDivElement> = (ref) => {
|
||||
elementRef.current = ref;
|
||||
calcTableSize();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={mountedRef}
|
||||
className={css`
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
.ant-table-wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
.ant-table {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
@ -372,6 +409,11 @@ export const Table: any = observer((props: any) => {
|
||||
>
|
||||
<SortableWrapper>
|
||||
<AntdTable
|
||||
ref={(ref) => {
|
||||
const headerHeight = ref?.querySelector('.ant-table-header')?.getBoundingClientRect().height || 0;
|
||||
const paginationHeight = ref?.querySelector('.ant-table-pagination')?.getBoundingClientRect().height || 0;
|
||||
setHeaderAndPaginationHeight(Math.ceil(headerHeight + paginationHeight + 16));
|
||||
}}
|
||||
rowKey={rowKey ?? defaultRowKey}
|
||||
{...others}
|
||||
{...restProps}
|
||||
@ -380,8 +422,8 @@ export const Table: any = observer((props: any) => {
|
||||
onChange={(pagination, filters, sorter, extra) => {
|
||||
onTableChange?.(pagination, filters, sorter, extra);
|
||||
}}
|
||||
// tableLayout={'auto'}
|
||||
// scroll={{ x: 12 * 300 + 80 }}
|
||||
tableLayout={'auto'}
|
||||
scroll={scroll}
|
||||
columns={columns}
|
||||
dataSource={field?.value?.slice?.()}
|
||||
/>
|
||||
|
@ -1,13 +1,15 @@
|
||||
import { ArrayItems } from '@formily/antd';
|
||||
import { ISchema, useField, useFieldSchema } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { ISchema, observer, useField, useFieldSchema } from '@formily/react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTableBlockContext } from '../../../block-provider';
|
||||
import { useCollection } from '../../../collection-manager';
|
||||
import { useCollectionFilterOptions, useSortFields } from '../../../collection-manager/action-hooks';
|
||||
import { useRecord } from '../../../record-provider';
|
||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||
import { useSchemaTemplate } from '../../../schema-templates';
|
||||
import { useDesignable } from '../../hooks';
|
||||
import { useFixedBlockDesignerSetting } from '../page';
|
||||
|
||||
export const TableBlockDesigner = () => {
|
||||
const { name, title, sortable } = useCollection();
|
||||
@ -34,10 +36,13 @@ export const TableBlockDesigner = () => {
|
||||
});
|
||||
const template = useSchemaTemplate();
|
||||
const { dragSort } = field.decoratorProps;
|
||||
const fixedBlockDesignerSetting = useFixedBlockDesignerSetting();
|
||||
|
||||
return (
|
||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||
<SchemaSettings.BlockTitleItem />
|
||||
{sortable&& <SchemaSettings.SwitchItem
|
||||
{sortable && (
|
||||
<SchemaSettings.SwitchItem
|
||||
title={t('Enable drag and drop sorting')}
|
||||
checked={field.decoratorProps.dragSort}
|
||||
onChange={(dragSort) => {
|
||||
@ -51,7 +56,9 @@ export const TableBlockDesigner = () => {
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>}
|
||||
/>
|
||||
)}
|
||||
{fixedBlockDesignerSetting}
|
||||
<SchemaSettings.ModalItem
|
||||
title={t('Set the data scope')}
|
||||
schema={
|
||||
@ -176,7 +183,6 @@ export const TableBlockDesigner = () => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SchemaSettings.SelectItem
|
||||
title={t('Records per page')}
|
||||
value={field.decoratorProps?.params?.pageSize || 20}
|
||||
|
@ -1,11 +1,49 @@
|
||||
import { MenuOutlined } from '@ant-design/icons';
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { ISchema, useFieldSchema } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SchemaInitializer } from '../..';
|
||||
import { SchemaInitializer, SchemaSettings } from '../..';
|
||||
import { useAPIClient } from '../../api-client';
|
||||
import { createDesignable, useDesignable } from '../../schema-component';
|
||||
|
||||
const Resizable = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const { dn } = useDesignable();
|
||||
const fieldSchema = useFieldSchema();
|
||||
return (
|
||||
<SchemaSettings.ModalItem
|
||||
title={t('Column width')}
|
||||
schema={
|
||||
{
|
||||
type: 'object',
|
||||
title: t('Column width'),
|
||||
properties: {
|
||||
width: {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'InputNumber',
|
||||
'x-component-props': {},
|
||||
default: fieldSchema?.['x-component-props']?.width || 200,
|
||||
},
|
||||
},
|
||||
} as ISchema
|
||||
}
|
||||
onSubmit={({ width }) => {
|
||||
const props = fieldSchema['x-component-props'] || {};
|
||||
props['width'] = width;
|
||||
const schema: ISchema = {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
};
|
||||
schema['x-component-props'] = props;
|
||||
fieldSchema['x-component-props'] = props;
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
dn.refresh();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const TableActionColumnInitializers = (props: any) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const api = useAPIClient();
|
||||
@ -174,6 +212,14 @@ export const TableActionColumnInitializers = (props: any) => {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
title: t('Column width'),
|
||||
component: Resizable,
|
||||
},
|
||||
]}
|
||||
component={<MenuOutlined style={{ cursor: 'pointer' }} />}
|
||||
/>
|
||||
|
Loading…
Reference in New Issue
Block a user