Nocobase next kanban (#223)
* feat: add kanban component * feat: add kanban designer * feat: kanban completed * refactor: modify kanban * feat: kanban card * feat: modify kanban Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
60a915b50c
commit
36ae278302
@ -11,7 +11,7 @@ import {
|
|||||||
getCoordinates,
|
getCoordinates,
|
||||||
isAColumnMove,
|
isAColumnMove,
|
||||||
isMovingACardToAnotherPosition,
|
isMovingACardToAnotherPosition,
|
||||||
isMovingAColumnToAnotherPosition
|
isMovingAColumnToAnotherPosition,
|
||||||
} from './services';
|
} from './services';
|
||||||
import { partialRight, when } from './utils';
|
import { partialRight, when } from './utils';
|
||||||
import withDroppable from './withDroppable';
|
import withDroppable from './withDroppable';
|
||||||
@ -22,7 +22,7 @@ const DroppableBoard = withDroppable(Columns);
|
|||||||
|
|
||||||
const Board: any = (props) => {
|
const Board: any = (props) => {
|
||||||
return props.initialBoard ? <UncontrolledBoard {...props} /> : <ControlledBoard {...props} />;
|
return props.initialBoard ? <UncontrolledBoard {...props} /> : <ControlledBoard {...props} />;
|
||||||
}
|
};
|
||||||
|
|
||||||
Object.keys(helpers).forEach((key) => {
|
Object.keys(helpers).forEach((key) => {
|
||||||
Board[key] = helpers[key];
|
Board[key] = helpers[key];
|
||||||
@ -247,19 +247,18 @@ function BoardContainer(props) {
|
|||||||
|
|
||||||
isAColumnMove(event.type)
|
isAColumnMove(event.type)
|
||||||
? isMovingAColumnToAnotherPosition(coordinates) &&
|
? isMovingAColumnToAnotherPosition(coordinates) &&
|
||||||
onColumnDragEnd({ ...coordinates, subject: board.columns[coordinates.source.fromPosition] })
|
onColumnDragEnd({ ...coordinates, subject: board.columns?.[coordinates.source.fromPosition] })
|
||||||
: isMovingACardToAnotherPosition(coordinates) &&
|
: isMovingACardToAnotherPosition(coordinates) &&
|
||||||
onCardDragEnd({ ...coordinates, subject: getCard(board, coordinates.source) });
|
onCardDragEnd({ ...coordinates, subject: getCard(board, coordinates.source) });
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragDropContext onDragEnd={handleOnDragEnd}>
|
<DragDropContext onDragEnd={handleOnDragEnd}>
|
||||||
<div style={{ overflowY: 'hidden', display: 'flex', alignItems: 'flex-start' }} className="react-kanban-board">
|
<div style={{ overflowY: 'hidden', display: 'flex', alignItems: 'flex-start' }} className="react-kanban-board">
|
||||||
<DroppableBoard droppableId="board-droppable" direction="horizontal" type="BOARD">
|
<DroppableBoard droppableId="board-droppable" direction="horizontal" type="BOARD">
|
||||||
{board.columns.map((column, index) => (
|
{board.columns?.map((column, index) => (
|
||||||
<Column
|
<Column
|
||||||
key={column.id}
|
key={column.id}
|
||||||
index={index}
|
index={column?.index ?? index}
|
||||||
renderCard={renderCard}
|
renderCard={renderCard}
|
||||||
renderCardAdder={renderCardAdder}
|
renderCardAdder={renderCardAdder}
|
||||||
renderColumnHeader={(column) =>
|
renderColumnHeader={(column) =>
|
||||||
|
@ -45,7 +45,7 @@ function Column({
|
|||||||
<div {...columnProvided.dragHandleProps}>{renderColumnHeader(children)}</div>
|
<div {...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 ? (
|
||||||
children.cards.map((card, index) => (
|
children.cards.map((card, index) => (
|
||||||
<Card
|
<Card
|
||||||
key={card.id}
|
key={card.id}
|
||||||
|
@ -28,13 +28,16 @@ export const ActionDrawer: ComposedActionDrawer = observer((props) => {
|
|||||||
destroyOnClose
|
destroyOnClose
|
||||||
visible={visible}
|
visible={visible}
|
||||||
onClose={() => setVisible(false)}
|
onClose={() => setVisible(false)}
|
||||||
className={classNames(others.className, css`
|
className={classNames(
|
||||||
&.nb-action-popup {
|
others.className,
|
||||||
.ant-drawer-content {
|
css`
|
||||||
background: #f0f2f5;
|
&.nb-action-popup {
|
||||||
|
.ant-drawer-content {
|
||||||
|
background: #f0f2f5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
`,
|
||||||
`)}
|
)}
|
||||||
footer={
|
footer={
|
||||||
footerSchema && (
|
footerSchema && (
|
||||||
<div
|
<div
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
import { MenuOutlined } from '@ant-design/icons';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
|
import { Space } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useCompile, useDesignable } from '../../../schema-component';
|
||||||
|
import { SchemaInitializer } from '../../../schema-initializer';
|
||||||
|
import { useCardItemInitializerFields } from './hoooks';
|
||||||
|
|
||||||
|
const titleCss = css`
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
font-size: 12px;
|
||||||
|
background: #f18b62;
|
||||||
|
color: #fff;
|
||||||
|
padding: 0 5px;
|
||||||
|
line-height: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-bottom-right-radius: 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const KanbanCardDesigner = (props: any) => {
|
||||||
|
const { dn, designable } = useDesignable();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const field = useField();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const compile = useCompile();
|
||||||
|
const schemaSettingsProps = {
|
||||||
|
dn,
|
||||||
|
field,
|
||||||
|
fieldSchema,
|
||||||
|
};
|
||||||
|
if (!designable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={'general-schema-designer'}>
|
||||||
|
<div className={'general-schema-designer-icons'}>
|
||||||
|
<Space size={2} align={'center'}>
|
||||||
|
<SchemaInitializer.Button
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
type: 'itemGroup',
|
||||||
|
title: t('Display fields'),
|
||||||
|
children: useCardItemInitializerFields(),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
component={<MenuOutlined style={{ cursor: 'pointer', fontSize: 12 }} />}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,27 @@
|
|||||||
|
import { createForm } from '@formily/core';
|
||||||
|
import { FieldContext, FormContext, observer } from '@formily/react';
|
||||||
|
import { Card } from 'antd';
|
||||||
|
import React, { useContext, useMemo } from 'react';
|
||||||
|
import { BlockItem } from '../block-item';
|
||||||
|
import { CardContext } from './context';
|
||||||
|
|
||||||
|
export const KanbanCard: any = observer((props: any) => {
|
||||||
|
const { allowRemoveCard, onCardRemove, children } = props;
|
||||||
|
const { card, dragging } = useContext(CardContext);
|
||||||
|
const form = useMemo(
|
||||||
|
() =>
|
||||||
|
createForm({
|
||||||
|
values: { card: { ...card } },
|
||||||
|
}),
|
||||||
|
[card],
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<BlockItem className={'noco-card-item'}>
|
||||||
|
<FieldContext.Provider value={undefined}>
|
||||||
|
<FormContext.Provider value={form}>
|
||||||
|
<Card style={{ width: 220, marginBottom: 15, cursor: 'pointer' }}>{children}</Card>
|
||||||
|
</FormContext.Provider>
|
||||||
|
</FieldContext.Provider>
|
||||||
|
</BlockItem>
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1,5 @@
|
|||||||
|
import { observer } from '@formily/react';
|
||||||
|
|
||||||
|
export const KanbanCardViewer: any = observer((props: any) => {
|
||||||
|
return props.children;
|
||||||
|
});
|
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useCollection } from '../../../collection-manager';
|
||||||
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
|
|
||||||
|
export const KanbanDesigner = () => {
|
||||||
|
const { name, title } = useCollection();
|
||||||
|
return (
|
||||||
|
<GeneralSchemaDesigner title={title || name}>
|
||||||
|
<SchemaSettings.Remove
|
||||||
|
removeParentsIfNoChildren
|
||||||
|
breakRemoveOn={{
|
||||||
|
'x-component': 'Grid',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</GeneralSchemaDesigner>
|
||||||
|
);
|
||||||
|
};
|
@ -1,5 +1,131 @@
|
|||||||
import React from 'react';
|
import { ArrayField } from '@formily/core';
|
||||||
|
import { observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
|
import { Card } from 'antd';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { SchemaComponent } from '../..';
|
||||||
|
import { AsyncDataProvider, RecordProvider, useRequest } from '../../../';
|
||||||
|
import { Board } from '../../../board';
|
||||||
|
import { Action, ActionContext } from '../action';
|
||||||
|
import { CardContext, ColumnContext } from './context';
|
||||||
|
import { KanbanCardDesigner } from './Kanban.Card.Designer';
|
||||||
|
import { KanbanCardViewer } from './Kanban.CardViewer';
|
||||||
|
import { KanbanDesigner } from './Kanban.Designer';
|
||||||
|
import { toGroupDataSource } from './utils';
|
||||||
|
|
||||||
export const Kanban = () => {
|
const useRequestProps = (props) => {
|
||||||
return <div>Kanban</div>;
|
const { request, dataSource } = props;
|
||||||
|
if (request) {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
return (params: any = {}) => {
|
||||||
|
return Promise.resolve({
|
||||||
|
data: dataSource,
|
||||||
|
});
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const useDefDataSource = (options, props) => {
|
||||||
|
return useRequest(useRequestProps(props), options);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Kanban: ComposedKanban = observer((props: any) => {
|
||||||
|
const { useDataSource = useDefDataSource, groupField, useDragEndAction, ...restProps } = props;
|
||||||
|
const field = useField<ArrayField>();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const [board, setBoard] = useState<any>({ columns: [] });
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const [record, setRecord] = useState<any>({});
|
||||||
|
const { run: runDragEnd } = useDragEndAction?.() ?? {};
|
||||||
|
const result = useDataSource(
|
||||||
|
{
|
||||||
|
uid: fieldSchema['x-uid'],
|
||||||
|
// refreshDeps: [props.dataSource],
|
||||||
|
onSuccess({ data }) {
|
||||||
|
const ds = toGroupDataSource(groupField, data);
|
||||||
|
setBoard(ds);
|
||||||
|
field.value = ds.columns;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props,
|
||||||
|
);
|
||||||
|
const cardSchema: Schema = fieldSchema.reduceProperties((buf, current) => {
|
||||||
|
if (current['x-component'] === 'Kanban.Card') {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}, null);
|
||||||
|
console.log('board', board);
|
||||||
|
const cardAdderSchema: Schema = fieldSchema.reduceProperties((buf, current) => {
|
||||||
|
if (current['x-component'] === 'Kanban.CardAdder') {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}, null);
|
||||||
|
const cardViewerSchema: Schema = fieldSchema.reduceProperties((buf, current) => {
|
||||||
|
if (current['x-component'] === 'Kanban.CardViewer') {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
const cardRemoveHandler = (card, column) => {
|
||||||
|
const updatedBoard = Board.removeCard({ columns: field.value }, column, card);
|
||||||
|
field.value = updatedBoard.columns;
|
||||||
|
};
|
||||||
|
const cardDragEndHandler = (card, fromColumn, toColumn) => {
|
||||||
|
const updatedBoard = Board.moveCard({ columns: field.value }, fromColumn, toColumn);
|
||||||
|
field.value = updatedBoard.columns;
|
||||||
|
runDragEnd?.(card, fromColumn, toColumn);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<AsyncDataProvider value={result}>
|
||||||
|
{cardViewerSchema && (
|
||||||
|
<ActionContext.Provider value={{ visible, setVisible }}>
|
||||||
|
<RecordProvider record={record}>
|
||||||
|
<SchemaComponent name={record.id} schema={cardViewerSchema as any} onlyRenderProperties />
|
||||||
|
</RecordProvider>
|
||||||
|
</ActionContext.Provider>
|
||||||
|
)}
|
||||||
|
<Board
|
||||||
|
onCardRemove={cardRemoveHandler}
|
||||||
|
onCardDragEnd={cardDragEndHandler}
|
||||||
|
renderCard={(card, { column, dragging }) => {
|
||||||
|
const columnIndex = field.value?.indexOf(column);
|
||||||
|
const cardIndex = column?.cards?.indexOf(card);
|
||||||
|
return (
|
||||||
|
<RecordProvider record={card}>
|
||||||
|
<CardContext.Provider value={{ card, column, dragging }}>
|
||||||
|
<Card style={{ width: 220, marginBottom: 15, cursor: 'pointer' }}>
|
||||||
|
<RecursionField name={`${columnIndex}.cards.${cardIndex}`} schema={cardSchema} onlyRenderProperties />
|
||||||
|
</Card>
|
||||||
|
</CardContext.Provider>
|
||||||
|
</RecordProvider>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
renderCardAdder={({ column }) => {
|
||||||
|
return (
|
||||||
|
<ColumnContext.Provider value={{ column }}>
|
||||||
|
<SchemaComponent memoized name={uid()} schema={cardAdderSchema as any} />
|
||||||
|
</ColumnContext.Provider>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
columns: field.value?.slice() || [],
|
||||||
|
}}
|
||||||
|
</Board>
|
||||||
|
</AsyncDataProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Kanban.Card = () => null;
|
||||||
|
|
||||||
|
Kanban.CardAdder = Action;
|
||||||
|
|
||||||
|
Kanban.CardViewer = KanbanCardViewer;
|
||||||
|
|
||||||
|
Kanban.Card.Designer = KanbanCardDesigner;
|
||||||
|
|
||||||
|
Kanban.Designer = KanbanDesigner;
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
export const CardContext = createContext(null);
|
||||||
|
export const ColumnContext = createContext(null);
|
266
packages/client/src/schema-component/antd/kanban/demos/demo1.tsx
Normal file
266
packages/client/src/schema-component/antd/kanban/demos/demo1.tsx
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
/**
|
||||||
|
* title: Kanban
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ISchema, useForm } from '@formily/react';
|
||||||
|
import { observable } from '@formily/reactive';
|
||||||
|
import {
|
||||||
|
ActionContext,
|
||||||
|
AntdSchemaComponentProvider,
|
||||||
|
CollectionField,
|
||||||
|
CollectionManagerProvider,
|
||||||
|
CollectionProvider,
|
||||||
|
SchemaComponent,
|
||||||
|
SchemaComponentProvider,
|
||||||
|
SchemaInitializerProvider
|
||||||
|
} from '@nocobase/client';
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
|
||||||
|
const dataSource = observable([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: 'Card title 1',
|
||||||
|
description: 'Card content',
|
||||||
|
status: 'doing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: 'Card title 2',
|
||||||
|
description: 'Card content',
|
||||||
|
status: 'doing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: 'Card title 3',
|
||||||
|
description: 'Card content',
|
||||||
|
status: 'undo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
title: 'Card title 4',
|
||||||
|
description: 'Card content',
|
||||||
|
status: 'doing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
title: 'Card title 5',
|
||||||
|
description: 'Card content',
|
||||||
|
status: 'done',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const groupField = {
|
||||||
|
name: 'status',
|
||||||
|
enum: [
|
||||||
|
{ label: '未开始', value: 'undo', index: 1 },
|
||||||
|
{ label: '进行中', value: 'doing', index: 2 },
|
||||||
|
{ label: '已完成', value: 'done', index: 3 },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const schema: ISchema = {
|
||||||
|
type: 'array',
|
||||||
|
name: 'kanban',
|
||||||
|
'x-component': 'Kanban',
|
||||||
|
'x-component-props': {
|
||||||
|
dataSource,
|
||||||
|
groupField,
|
||||||
|
cardAdderPosition: 'bottom',
|
||||||
|
allowAddCard: { on: 'bottom' },
|
||||||
|
disableColumnDrag: true,
|
||||||
|
useDragEndAction: '{{ useDragEndHandler }}',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
card: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'card',
|
||||||
|
'x-component': 'Kanban.Card',
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
'x-decorator': 'div',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
'x-decorator': 'div',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 'x-designer': 'Kanban.Card.Designer',
|
||||||
|
},
|
||||||
|
cardAdder: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'cardAdder',
|
||||||
|
'x-component': 'Kanban.CardAdder',
|
||||||
|
'x-component-props': {
|
||||||
|
block: true,
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
title: '添加卡片',
|
||||||
|
properties: {
|
||||||
|
modal: {
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
type: 'void',
|
||||||
|
title: 'Drawer Title',
|
||||||
|
properties: {
|
||||||
|
grid: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': 'GridFormItemInitializers',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
'x-component': 'Action.Drawer.Footer',
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
action1: {
|
||||||
|
title: '{{ t("Cancel") }}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
useAction: '{{ useCancelAction }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action2: {
|
||||||
|
title: '{{ t("Submit") }}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
useAction: '{{ useOkAction }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cardViewer: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'cardViewer',
|
||||||
|
'x-component': 'Kanban.CardViewer',
|
||||||
|
properties: {
|
||||||
|
modal: {
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
type: 'void',
|
||||||
|
title: 'Drawer Title',
|
||||||
|
properties: {
|
||||||
|
grid: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const collection = {
|
||||||
|
name: 'KanbanCollection',
|
||||||
|
title: '看板',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'id',
|
||||||
|
interface: 'input',
|
||||||
|
title: 'ID',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'title',
|
||||||
|
interface: 'input',
|
||||||
|
title: '标题',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'description',
|
||||||
|
interface: 'input',
|
||||||
|
title: '描述',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'status',
|
||||||
|
interface: 'select',
|
||||||
|
title: '状态',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: [
|
||||||
|
{ label: '未开始', value: 'undo' },
|
||||||
|
{ label: '进行中', value: 'doing' },
|
||||||
|
{ label: '已完成', value: 'done' },
|
||||||
|
],
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const useDragEndHandler = () => {
|
||||||
|
return {
|
||||||
|
async run(card, fromColumn, toColumn) {
|
||||||
|
for (const ds of dataSource) {
|
||||||
|
if (ds.id === card.id) {
|
||||||
|
ds.status = toColumn.toColumnId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const useOkAction = () => {
|
||||||
|
const form = useForm();
|
||||||
|
const { setVisible } = useContext(ActionContext);
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
console.log(form);
|
||||||
|
dataSource.push(form.values);
|
||||||
|
setVisible(false);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const useCancelAction = () => {
|
||||||
|
const form = useForm();
|
||||||
|
const { setVisible } = useContext(ActionContext);
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
setVisible(false);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<CollectionManagerProvider>
|
||||||
|
<CollectionProvider collection={collection}>
|
||||||
|
<SchemaComponentProvider
|
||||||
|
designable={true}
|
||||||
|
components={{ CollectionField }}
|
||||||
|
scope={{ useOkAction, useCancelAction, useDragEndHandler }}
|
||||||
|
>
|
||||||
|
<SchemaInitializerProvider>
|
||||||
|
<AntdSchemaComponentProvider>
|
||||||
|
<SchemaComponent schema={schema} />
|
||||||
|
</AntdSchemaComponentProvider>
|
||||||
|
</SchemaInitializerProvider>
|
||||||
|
</SchemaComponentProvider>
|
||||||
|
</CollectionProvider>
|
||||||
|
</CollectionManagerProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export * from './useCardItemInitializerFields';
|
@ -0,0 +1,27 @@
|
|||||||
|
import { useCollection } from '../../../../collection-manager';
|
||||||
|
import type { SchemaInitializerItemOptions } from '../../../../schema-initializer';
|
||||||
|
import { removeGridFormItem } from '../../../../schema-initializer/Initializers/utils';
|
||||||
|
|
||||||
|
export const useCardItemInitializerFields = () => {
|
||||||
|
const { name, fields } = useCollection();
|
||||||
|
return fields
|
||||||
|
?.filter((field) => field?.interface)
|
||||||
|
?.map((field) => {
|
||||||
|
return {
|
||||||
|
type: 'item',
|
||||||
|
title: field?.uiSchema?.title || field.name,
|
||||||
|
component: 'CollectionFieldInitializer',
|
||||||
|
remove: removeGridFormItem,
|
||||||
|
schema: {
|
||||||
|
name: field.name,
|
||||||
|
title: field?.uiSchema?.title || field.name,
|
||||||
|
'x-designer': 'FormItem.Designer',
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-collection-field': `${name}.${field.name}`,
|
||||||
|
...field?.uiSchema,
|
||||||
|
},
|
||||||
|
} as SchemaInitializerItemOptions;
|
||||||
|
});
|
||||||
|
};
|
@ -9,15 +9,17 @@ group:
|
|||||||
|
|
||||||
## 参数说明
|
## 参数说明
|
||||||
|
|
||||||
- dataSource 数据源
|
* dataSource 数据源
|
||||||
- groupField 分组字段,dataSource 以哪个字段作为分组
|
* groupField 分组字段,dataSource 以哪个字段作为分组
|
||||||
- useDataSource,动态数据源
|
* useDataSource,动态数据源
|
||||||
- useGroupField,动态的分组字段
|
* useGroupField,动态的分组字段
|
||||||
|
|
||||||
## 示例
|
## 示例
|
||||||
|
|
||||||
### 静态数据
|
### 静态数据
|
||||||
|
|
||||||
|
<code src="./demos/demo1.tsx" />
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
{
|
{
|
||||||
'x-component': 'Kanban',
|
'x-component': 'Kanban',
|
||||||
@ -60,9 +62,9 @@ group:
|
|||||||
|
|
||||||
### 特殊节点
|
### 特殊节点
|
||||||
|
|
||||||
- Kanban.Card 卡片的 schema
|
* Kanban. Card 卡片的 schema
|
||||||
- Kanban.CardViewer 卡片点击打开的详情
|
* Kanban. CardViewer 卡片点击打开的详情
|
||||||
- Kanban.CardAdder 添加卡片的按钮
|
* Kanban. CardAdder 添加卡片的按钮
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
{
|
{
|
||||||
|
17
packages/client/src/schema-component/antd/kanban/type.d.ts
vendored
Normal file
17
packages/client/src/schema-component/antd/kanban/type.d.ts
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
interface IGroupField {
|
||||||
|
name: string;
|
||||||
|
enum: Array<{ label: string; value: string; index: number }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IBoard {
|
||||||
|
columns: Array<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComposedKanban = React.FC<any> & {
|
||||||
|
Card?: React.FC<any> & {
|
||||||
|
Designer?: React.FC<any>;
|
||||||
|
};
|
||||||
|
CardAdder?: React.FC<any>;
|
||||||
|
CardViewer?: React.FC<any>;
|
||||||
|
Designer?: React.FC<any>;
|
||||||
|
};
|
21
packages/client/src/schema-component/antd/kanban/utils.ts
Normal file
21
packages/client/src/schema-component/antd/kanban/utils.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export const toGroupDataSource = (groupField: IGroupField, dataSource: Array<any> = []) => {
|
||||||
|
if (dataSource.length === 0) {
|
||||||
|
return { columns: [] };
|
||||||
|
}
|
||||||
|
const groupDataSource = [];
|
||||||
|
groupField.enum.forEach((item, index) => {
|
||||||
|
groupDataSource.push({
|
||||||
|
id: item.value,
|
||||||
|
title: item.label,
|
||||||
|
index: item.index,
|
||||||
|
cards: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
dataSource.forEach((ds) => {
|
||||||
|
const group = groupDataSource.find((g) => g.id === ds[groupField.name]);
|
||||||
|
if (group) {
|
||||||
|
group.cards.push(ds);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { columns: groupDataSource };
|
||||||
|
};
|
@ -164,6 +164,7 @@ export const useFormItemInitializerFields = () => {
|
|||||||
remove: removeGridFormItem,
|
remove: removeGridFormItem,
|
||||||
schema: {
|
schema: {
|
||||||
name: field.name,
|
name: field.name,
|
||||||
|
title: field?.uiSchema?.title || field.name,
|
||||||
'x-designer': 'FormItem.Designer',
|
'x-designer': 'FormItem.Designer',
|
||||||
'x-component': 'CollectionField',
|
'x-component': 'CollectionField',
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
|
Loading…
Reference in New Issue
Block a user