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 React, { createContext, useContext, useEffect } from 'react';
|
||||||
import { useACLRoleContext } from '../acl';
|
import { useACLRoleContext } from '../acl';
|
||||||
import { useCollection, useCollectionManager } from '../collection-manager';
|
import { useCollection, useCollectionManager } from '../collection-manager';
|
||||||
|
import { useFixedSchema } from '../schema-component';
|
||||||
import { toColumns } from '../schema-component/antd/kanban/Kanban';
|
import { toColumns } from '../schema-component/antd/kanban/Kanban';
|
||||||
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
|
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
|
||||||
|
|
||||||
@ -23,6 +24,8 @@ const useGroupField = (props) => {
|
|||||||
|
|
||||||
const InternalKanbanBlockProvider = (props) => {
|
const InternalKanbanBlockProvider = (props) => {
|
||||||
const field = useField<any>();
|
const field = useField<any>();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
useFixedSchema();
|
||||||
const { resource, service } = useBlockRequestContext();
|
const { resource, service } = useBlockRequestContext();
|
||||||
const groupField = useGroupField(props);
|
const groupField = useGroupField(props);
|
||||||
if (!groupField) {
|
if (!groupField) {
|
||||||
@ -42,6 +45,7 @@ const InternalKanbanBlockProvider = (props) => {
|
|||||||
service,
|
service,
|
||||||
resource,
|
resource,
|
||||||
groupField,
|
groupField,
|
||||||
|
fixedBlock: fieldSchema?.['x-decorator-props']?.fixedBlock,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
@ -3,6 +3,7 @@ import { FormContext, Schema, useField, useFieldSchema } from '@formily/react';
|
|||||||
import uniq from 'lodash/uniq';
|
import uniq from 'lodash/uniq';
|
||||||
import React, { createContext, useContext, useEffect, useMemo } from 'react';
|
import React, { createContext, useContext, useEffect, useMemo } from 'react';
|
||||||
import { useCollectionManager } from '../collection-manager';
|
import { useCollectionManager } from '../collection-manager';
|
||||||
|
import { useFixedSchema } from '../schema-component';
|
||||||
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
|
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
|
||||||
|
|
||||||
export const TableBlockContext = createContext<any>({});
|
export const TableBlockContext = createContext<any>({});
|
||||||
@ -14,6 +15,7 @@ const InternalTableBlockProvider = (props) => {
|
|||||||
// if (service.loading) {
|
// if (service.loading) {
|
||||||
// return <Spin />;
|
// return <Spin />;
|
||||||
// }
|
// }
|
||||||
|
useFixedSchema();
|
||||||
return (
|
return (
|
||||||
<TableBlockContext.Provider
|
<TableBlockContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { useField } from '@formily/react';
|
||||||
import React, { forwardRef, useState } from 'react';
|
import React, { forwardRef, useState } from 'react';
|
||||||
import { DragDropContext } from 'react-beautiful-dnd';
|
import { DragDropContext } from 'react-beautiful-dnd';
|
||||||
import Column from './Column';
|
import Column from './Column';
|
||||||
@ -16,7 +17,9 @@ import {
|
|||||||
import { partialRight, when } from './utils';
|
import { partialRight, when } from './utils';
|
||||||
import withDroppable from './withDroppable';
|
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);
|
const DroppableBoard = withDroppable(Columns);
|
||||||
|
|
||||||
@ -241,6 +244,7 @@ function BoardContainer(props) {
|
|||||||
onCardNew,
|
onCardNew,
|
||||||
allowAddCard,
|
allowAddCard,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
function handleOnDragEnd(event) {
|
function handleOnDragEnd(event) {
|
||||||
const coordinates = getCoordinates(event, board);
|
const coordinates = getCoordinates(event, board);
|
||||||
if (!coordinates.source) return;
|
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 { Draggable } from 'react-beautiful-dnd';
|
||||||
|
import { useKanbanBlockContext } from '../block-provider';
|
||||||
import Card from './Card';
|
import Card from './Card';
|
||||||
import CardAdder from './CardAdder';
|
import CardAdder from './CardAdder';
|
||||||
import { pickPropOut } from './utils';
|
import { pickPropOut } from './utils';
|
||||||
import withDroppable from './withDroppable';
|
import withDroppable from './withDroppable';
|
||||||
|
|
||||||
const ColumnEmptyPlaceholder = forwardRef((props, ref: any) => (
|
const ColumnEmptyPlaceholder = forwardRef(
|
||||||
<div ref={ref} style={{ minHeight: 'inherit', height: 'inherit' }} {...props} />
|
(
|
||||||
));
|
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);
|
const DroppableColumn = withDroppable(ColumnEmptyPlaceholder);
|
||||||
|
|
||||||
@ -23,6 +34,8 @@ function Column({
|
|||||||
allowAddCard,
|
allowAddCard,
|
||||||
cardAdderPosition = 'top',
|
cardAdderPosition = 'top',
|
||||||
}) {
|
}) {
|
||||||
|
const { fixedBlock } = useKanbanBlockContext();
|
||||||
|
const [headerHeight, setHeaderHeight] = useState(0);
|
||||||
return (
|
return (
|
||||||
<Draggable draggableId={`column-draggable-${children.id}`} index={columnIndex} isDragDisabled={disableColumnDrag}>
|
<Draggable draggableId={`column-draggable-${children.id}`} index={columnIndex} isDragDisabled={disableColumnDrag}>
|
||||||
{(columnProvided) => {
|
{(columnProvided) => {
|
||||||
@ -38,15 +51,26 @@ function Column({
|
|||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
verticalAlign: 'top',
|
verticalAlign: 'top',
|
||||||
...columnProvided.draggableProps.style,
|
...columnProvided.draggableProps.style,
|
||||||
|
'--column-height': fixedBlock ? `calc(100% - ${headerHeight}px)` : 'inherit',
|
||||||
}}
|
}}
|
||||||
className="react-kanban-column"
|
className="react-kanban-column"
|
||||||
data-testid={`column-${children.id}`}
|
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 })}
|
{cardAdderPosition === 'top' && allowAddCard && renderCardAdder({ column: children, onConfirm: onCardNew })}
|
||||||
<DroppableColumn droppableId={String(children.id)}>
|
<DroppableColumn droppableId={String(children.id)}>
|
||||||
{children?.cards?.length ? (
|
{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) => (
|
{children.cards.map((card, index) => (
|
||||||
<Card
|
<Card
|
||||||
key={card.id}
|
key={card.id}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
.react-kanban-board {
|
.react-kanban-board {
|
||||||
|
height: 100%;
|
||||||
// padding: 5px;
|
// padding: 5px;
|
||||||
margin-bottom: 24px;
|
// margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-kanban-card {
|
.react-kanban-card {
|
||||||
|
@ -515,4 +515,5 @@ export default {
|
|||||||
"Inner": "Inner",
|
"Inner": "Inner",
|
||||||
"Search and select collection": "Search and select collection",
|
"Search and select collection": "Search and select collection",
|
||||||
'Please fill in the iframe URL': 'Please fill in the iframe URL',
|
'Please fill in the iframe URL': 'Please fill in the iframe URL',
|
||||||
|
'Fix block': 'Fix block'
|
||||||
}
|
}
|
||||||
|
@ -611,4 +611,6 @@ 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': '固定区块'
|
||||||
}
|
}
|
||||||
|
@ -27,14 +27,24 @@ export const ActionBar = observer((props: any) => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div
|
<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}
|
{...others}
|
||||||
>
|
>
|
||||||
<div className={css`
|
<div
|
||||||
|
className={css`
|
||||||
.ant-space:last-child {
|
.ant-space:last-child {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
`} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}>
|
`}
|
||||||
|
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}
|
||||||
|
>
|
||||||
<DndContext>
|
<DndContext>
|
||||||
<Space>
|
<Space>
|
||||||
{fieldSchema.mapProperties((schema, key) => {
|
{fieldSchema.mapProperties((schema, key) => {
|
||||||
|
@ -7,6 +7,7 @@ import { useCollectionFilterOptions } from '../../../collection-manager/action-h
|
|||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
import { useDesignable } from '../../hooks';
|
import { useDesignable } from '../../hooks';
|
||||||
|
import { useFixedBlockDesignerSetting } from '../page';
|
||||||
|
|
||||||
export const KanbanDesigner = () => {
|
export const KanbanDesigner = () => {
|
||||||
const { name, title } = useCollection();
|
const { name, title } = useCollection();
|
||||||
@ -19,6 +20,8 @@ export const KanbanDesigner = () => {
|
|||||||
const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
|
const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
||||||
const template = useSchemaTemplate();
|
const template = useSchemaTemplate();
|
||||||
|
const fixedBlockDesignerSetting = useFixedBlockDesignerSetting();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||||
<SchemaSettings.BlockTitleItem />
|
<SchemaSettings.BlockTitleItem />
|
||||||
@ -52,6 +55,7 @@ export const KanbanDesigner = () => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{fixedBlockDesignerSetting}
|
||||||
<SchemaSettings.Divider />
|
<SchemaSettings.Divider />
|
||||||
<SchemaSettings.Template componentName={'Kanban'} collectionName={name} resourceName={defaultResource} />
|
<SchemaSettings.Template componentName={'Kanban'} collectionName={name} resourceName={defaultResource} />
|
||||||
<SchemaSettings.Divider />
|
<SchemaSettings.Divider />
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.react-kanban-board {
|
.react-kanban-board {
|
||||||
margin-bottom: 24px;
|
// margin-bottom: 24px;
|
||||||
.nb-block-item {
|
.nb-block-item {
|
||||||
.ant-formily-item-control .ant-space-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 { css } from '@emotion/css';
|
||||||
import { FormDialog, FormLayout } from '@formily/antd';
|
import { FormDialog, FormLayout } from '@formily/antd';
|
||||||
import { RecursionField, SchemaOptionsContext, useField, useFieldSchema } from '@formily/react';
|
import { RecursionField, SchemaOptionsContext, useField, useFieldSchema } from '@formily/react';
|
||||||
|
import { useMutationObserver } from 'ahooks';
|
||||||
import { Button, PageHeader as AntdPageHeader, Spin, Tabs } from 'antd';
|
import { Button, PageHeader as AntdPageHeader, Spin, Tabs } from 'antd';
|
||||||
import classNames from 'classnames';
|
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 { useTranslation } from 'react-i18next';
|
||||||
import { useHistory, useLocation } from 'react-router-dom';
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
import { useDocumentTitle } from '../../../document-title';
|
import { useDocumentTitle } from '../../../document-title';
|
||||||
import { Icon } from '../../../icon';
|
import { Icon } from '../../../icon';
|
||||||
import { SchemaComponent, SchemaComponentOptions } from '../../core';
|
import { SchemaComponent, SchemaComponentOptions } from '../../core';
|
||||||
import { useCompile, useDesignable } from '../../hooks';
|
import { useCompile, useDesignable } from '../../hooks';
|
||||||
|
import FixedBlock from './FixedBlock';
|
||||||
import { PageDesigner, PageTabDesigner } from './PageTabDesigner';
|
import { PageDesigner, PageTabDesigner } from './PageTabDesigner';
|
||||||
|
|
||||||
const designerCss = css`
|
const designerCss = css`
|
||||||
@ -130,10 +132,17 @@ export const Page = (props) => {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return location?.query?.tab || Object.keys(fieldSchema.properties).shift();
|
return location?.query?.tab || Object.keys(fieldSchema.properties).shift();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [height, setHeight] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<div className={pageDesignerCss}>
|
<div className={pageDesignerCss}>
|
||||||
<PageDesigner title={fieldSchema.title || title} />
|
<PageDesigner title={fieldSchema.title || title} />
|
||||||
|
<div
|
||||||
|
ref={(ref) => {
|
||||||
|
setHeight(Math.floor(ref?.getBoundingClientRect().height || 0) + 1);
|
||||||
|
}}
|
||||||
|
>
|
||||||
{!disablePageHeader && (
|
{!disablePageHeader && (
|
||||||
<AntdPageHeader
|
<AntdPageHeader
|
||||||
className={css`
|
className={css`
|
||||||
@ -234,10 +243,18 @@ export const Page = (props) => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
<div style={{ margin: 24 }}>
|
<div style={{ margin: 24 }}>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Spin />
|
<Spin />
|
||||||
) : !disablePageHeader && enablePageTabs ? (
|
) : (
|
||||||
|
<FixedBlock
|
||||||
|
height={
|
||||||
|
// header 46 margin 48
|
||||||
|
height + 46 + 48
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{!disablePageHeader && enablePageTabs ? (
|
||||||
<RecursionField
|
<RecursionField
|
||||||
schema={fieldSchema}
|
schema={fieldSchema}
|
||||||
onlyRenderProperties
|
onlyRenderProperties
|
||||||
@ -258,8 +275,9 @@ export const Page = (props) => {
|
|||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</FixedBlock>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1 +1,2 @@
|
|||||||
export * from './Page';
|
export * from './Page';
|
||||||
|
export * from './FixedBlock';
|
||||||
|
@ -68,8 +68,38 @@ export const TableColumnDesigner = (props) => {
|
|||||||
dn.refresh();
|
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
|
<SchemaSettings.SwitchItem
|
||||||
title={t('Sortable')}
|
title={t('Sortable')}
|
||||||
checked={field.componentProps.sorter}
|
checked={field.componentProps.sorter}
|
||||||
@ -79,18 +109,17 @@ export const TableColumnDesigner = (props) => {
|
|||||||
};
|
};
|
||||||
columnSchema['x-component-props'] = {
|
columnSchema['x-component-props'] = {
|
||||||
...columnSchema['x-component-props'],
|
...columnSchema['x-component-props'],
|
||||||
sorter: v
|
sorter: v,
|
||||||
}
|
};
|
||||||
schema['x-component-props'] = columnSchema['x-component-props'];
|
schema['x-component-props'] = columnSchema['x-component-props'];
|
||||||
field.componentProps.sorter = v;
|
field.componentProps.sorter = v;
|
||||||
dn.emit('patch', {
|
dn.emit('patch', {
|
||||||
schema
|
schema,
|
||||||
});
|
});
|
||||||
dn.refresh();
|
dn.refresh();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
{['linkTo', 'm2m', 'm2o', 'o2m', 'obo', 'oho'].includes(collectionField?.interface) && (
|
{['linkTo', 'm2m', 'm2o', 'o2m', 'obo', 'oho'].includes(collectionField?.interface) && (
|
||||||
<SchemaSettings.SelectItem
|
<SchemaSettings.SelectItem
|
||||||
title={t('Title field')}
|
title={t('Title field')}
|
||||||
|
@ -4,12 +4,12 @@ import { css } from '@emotion/css';
|
|||||||
import { ArrayField, Field } from '@formily/core';
|
import { ArrayField, Field } from '@formily/core';
|
||||||
import { ISchema, observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
|
import { ISchema, observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
|
||||||
import { reaction } from '@formily/reactive';
|
import { reaction } from '@formily/reactive';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useEventListener, useMemoizedFn } from 'ahooks';
|
||||||
import { Table as AntdTable, TableColumnProps } from 'antd';
|
import { Table as AntdTable, TableColumnProps } from 'antd';
|
||||||
import { default as classNames, default as cls } from 'classnames';
|
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 { useTranslation } from 'react-i18next';
|
||||||
import { DndContext } from '../..';
|
import { DndContext, useDesignable } from '../..';
|
||||||
import { RecordIndexProvider, RecordProvider, useSchemaInitializer } from '../../../';
|
import { RecordIndexProvider, RecordProvider, useSchemaInitializer } from '../../../';
|
||||||
|
|
||||||
const isColumnComponent = (schema: Schema) => {
|
const isColumnComponent = (schema: Schema) => {
|
||||||
@ -24,6 +24,7 @@ const useTableColumns = () => {
|
|||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const field = useField<ArrayField>();
|
const field = useField<ArrayField>();
|
||||||
const schema = useFieldSchema();
|
const schema = useFieldSchema();
|
||||||
|
const { designable } = useDesignable();
|
||||||
const { exists, render } = useSchemaInitializer(schema['x-initializer']);
|
const { exists, render } = useSchemaInitializer(schema['x-initializer']);
|
||||||
const columns = schema
|
const columns = schema
|
||||||
.reduceProperties((buf, s) => {
|
.reduceProperties((buf, s) => {
|
||||||
@ -38,13 +39,13 @@ const useTableColumns = () => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
const dataIndex = collectionFields?.length > 0 ? collectionFields[0].name : s.name;
|
const dataIndex = collectionFields?.length > 0 ? collectionFields[0].name : s.name;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: <RecursionField name={s.name} schema={s} onlyRenderSelf />,
|
title: <RecursionField name={s.name} schema={s} onlyRenderSelf />,
|
||||||
dataIndex,
|
dataIndex,
|
||||||
key: s.name,
|
key: s.name,
|
||||||
sorter: s['x-component-props']?.['sorter'],
|
sorter: s['x-component-props']?.['sorter'],
|
||||||
// width: 300,
|
width: 200,
|
||||||
|
...s['x-component-props'],
|
||||||
render: (v, record) => {
|
render: (v, record) => {
|
||||||
const index = field.value?.indexOf(record);
|
const index = field.value?.indexOf(record);
|
||||||
// console.log((Date.now() - start) / 1000);
|
// console.log((Date.now() - start) / 1000);
|
||||||
@ -65,6 +66,7 @@ const useTableColumns = () => {
|
|||||||
title: render(),
|
title: render(),
|
||||||
dataIndex: 'TABLE_COLUMN_INITIALIZER',
|
dataIndex: 'TABLE_COLUMN_INITIALIZER',
|
||||||
key: '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 onRowDragEnd = useMemoizedFn(others.onRowDragEnd || (() => {}));
|
||||||
const paginationProps = usePaginationProps(pagination1, pagination2);
|
const paginationProps = usePaginationProps(pagination1, pagination2);
|
||||||
const requiredValidator = field.required || required;
|
const requiredValidator = field.required || required;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
field.setValidator((value) => {
|
field.setValidator((value) => {
|
||||||
if (requiredValidator) {
|
if (requiredValidator) {
|
||||||
@ -360,10 +363,44 @@ export const Table: any = observer((props: any) => {
|
|||||||
},
|
},
|
||||||
[field, dragSort],
|
[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 (
|
return (
|
||||||
<div
|
<div
|
||||||
|
ref={mountedRef}
|
||||||
className={css`
|
className={css`
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
.ant-table-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
.ant-table {
|
.ant-table {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
@ -372,6 +409,11 @@ export const Table: any = observer((props: any) => {
|
|||||||
>
|
>
|
||||||
<SortableWrapper>
|
<SortableWrapper>
|
||||||
<AntdTable
|
<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}
|
rowKey={rowKey ?? defaultRowKey}
|
||||||
{...others}
|
{...others}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
@ -380,8 +422,8 @@ export const Table: any = observer((props: any) => {
|
|||||||
onChange={(pagination, filters, sorter, extra) => {
|
onChange={(pagination, filters, sorter, extra) => {
|
||||||
onTableChange?.(pagination, filters, sorter, extra);
|
onTableChange?.(pagination, filters, sorter, extra);
|
||||||
}}
|
}}
|
||||||
// tableLayout={'auto'}
|
tableLayout={'auto'}
|
||||||
// scroll={{ x: 12 * 300 + 80 }}
|
scroll={scroll}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={field?.value?.slice?.()}
|
dataSource={field?.value?.slice?.()}
|
||||||
/>
|
/>
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import { ArrayItems } from '@formily/antd';
|
import { ArrayItems } from '@formily/antd';
|
||||||
import { ISchema, useField, useFieldSchema } from '@formily/react';
|
import { ISchema, observer, useField, useFieldSchema } from '@formily/react';
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useTableBlockContext } from '../../../block-provider';
|
import { useTableBlockContext } from '../../../block-provider';
|
||||||
import { useCollection } from '../../../collection-manager';
|
import { useCollection } from '../../../collection-manager';
|
||||||
import { useCollectionFilterOptions, useSortFields } from '../../../collection-manager/action-hooks';
|
import { useCollectionFilterOptions, useSortFields } from '../../../collection-manager/action-hooks';
|
||||||
|
import { useRecord } from '../../../record-provider';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
import { useDesignable } from '../../hooks';
|
import { useDesignable } from '../../hooks';
|
||||||
|
import { useFixedBlockDesignerSetting } from '../page';
|
||||||
|
|
||||||
export const TableBlockDesigner = () => {
|
export const TableBlockDesigner = () => {
|
||||||
const { name, title, sortable } = useCollection();
|
const { name, title, sortable } = useCollection();
|
||||||
@ -34,10 +36,13 @@ export const TableBlockDesigner = () => {
|
|||||||
});
|
});
|
||||||
const template = useSchemaTemplate();
|
const template = useSchemaTemplate();
|
||||||
const { dragSort } = field.decoratorProps;
|
const { dragSort } = field.decoratorProps;
|
||||||
|
const fixedBlockDesignerSetting = useFixedBlockDesignerSetting();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||||
<SchemaSettings.BlockTitleItem />
|
<SchemaSettings.BlockTitleItem />
|
||||||
{sortable&& <SchemaSettings.SwitchItem
|
{sortable && (
|
||||||
|
<SchemaSettings.SwitchItem
|
||||||
title={t('Enable drag and drop sorting')}
|
title={t('Enable drag and drop sorting')}
|
||||||
checked={field.decoratorProps.dragSort}
|
checked={field.decoratorProps.dragSort}
|
||||||
onChange={(dragSort) => {
|
onChange={(dragSort) => {
|
||||||
@ -51,7 +56,9 @@ export const TableBlockDesigner = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>}
|
/>
|
||||||
|
)}
|
||||||
|
{fixedBlockDesignerSetting}
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.ModalItem
|
||||||
title={t('Set the data scope')}
|
title={t('Set the data scope')}
|
||||||
schema={
|
schema={
|
||||||
@ -176,7 +183,6 @@ export const TableBlockDesigner = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<SchemaSettings.SelectItem
|
<SchemaSettings.SelectItem
|
||||||
title={t('Records per page')}
|
title={t('Records per page')}
|
||||||
value={field.decoratorProps?.params?.pageSize || 20}
|
value={field.decoratorProps?.params?.pageSize || 20}
|
||||||
|
@ -1,11 +1,49 @@
|
|||||||
import { MenuOutlined } from '@ant-design/icons';
|
import { MenuOutlined } from '@ant-design/icons';
|
||||||
import { useFieldSchema } from '@formily/react';
|
import { ISchema, useFieldSchema } from '@formily/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SchemaInitializer } from '../..';
|
import { SchemaInitializer, SchemaSettings } from '../..';
|
||||||
import { useAPIClient } from '../../api-client';
|
import { useAPIClient } from '../../api-client';
|
||||||
import { createDesignable, useDesignable } from '../../schema-component';
|
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) => {
|
export const TableActionColumnInitializers = (props: any) => {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const api = useAPIClient();
|
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' }} />}
|
component={<MenuOutlined style={{ cursor: 'pointer' }} />}
|
||||||
/>
|
/>
|
||||||
|
Loading…
Reference in New Issue
Block a user