feat: add kanban block

This commit is contained in:
chenos 2021-08-12 11:34:46 +08:00
parent 23db248743
commit b6f359bbc5
4 changed files with 400 additions and 0 deletions

View File

@ -0,0 +1,101 @@
import { ISchema } from '@formily/react';
import React from 'react';
import { SchemaRenderer } from '../../';
import { Kanban } from '..';
const schema: ISchema = {
type: 'array',
name: 'kanban1',
'x-component': 'Kanban',
default: [
{
id: '1',
type: 'A',
title: 'A1',
},
{
id: '2',
type: 'A',
title: 'A2',
},
{
id: '3',
type: 'A',
title: 'A3',
},
{
id: '4',
type: 'B',
title: 'B4',
},
{
id: '5',
type: 'B',
title: 'B5',
},
{
id: '6',
type: 'B',
title: 'B6',
},
{
id: '7',
type: 'C',
title: 'C7',
},
{
id: '8',
type: 'C',
title: 'C8',
},
{
id: '9',
type: 'C',
title: 'C9',
},
],
properties: {
card1: {
type: 'void',
'x-component': 'Kanban.Card',
properties: {
item1: {
type: 'void',
'x-component': 'Kanban.Item',
properties: {
title: {
type: 'string',
// title: '标题',
'x-read-pretty': true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
},
view1: {
type: 'void',
'x-component': 'Kanban.Card.View',
properties: {
item1: {
type: 'void',
'x-component': 'Kanban.Item',
properties: {
title: {
type: 'string',
title: '标题',
'x-read-pretty': true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
},
},
};
export default () => {
return <SchemaRenderer components={{ Kanban }} schema={schema} />;
};

View File

@ -0,0 +1,32 @@
---
title: Kanban - 看板
nav:
title: 组件
path: /client
group:
order: 1
title: Schemas
path: /client/schemas
---
# Kanban - 看板
## Node Tree
<pre lang="tsx">
<Kanban>
<Kanban.Card>
// 看板卡片
<Kanban.Item>
<Input/>
</Kanban.Item>
</Kanban.Card>
<Kanban.Card.View>
// 查看卡片详情
</Kanban.Card.View>
</Kanban>
</pre>
## Examples
<code src="./demos/demo1.tsx" />

View File

@ -0,0 +1,234 @@
import React from 'react';
import { observer, RecursionField, Schema, useField } from '@formily/react';
import { DndContext, DragOverlay, closestCorners } from '@dnd-kit/core';
import {
SortableContext,
rectSortingStrategy,
verticalListSortingStrategy,
horizontalListSortingStrategy,
} from '@dnd-kit/sortable';
import { useSortable, arrayMove } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import './style.less';
import { useState } from 'react';
import { groupBy } from 'lodash';
import cls from 'classnames';
import { Button, Card, Drawer } from 'antd';
import {
SchemaRenderer,
useDesignable,
} from '../../components/schema-renderer';
import { createContext } from 'react';
import { useContext } from 'react';
import { PlusOutlined } from '@ant-design/icons';
import { uid } from '@formily/shared';
export function SortableItem(props) {
const {
attributes,
listeners,
setNodeRef,
isDragging,
transform,
transition,
} = useSortable({
id: props.id,
data: {
type: props.type,
},
});
const style = {
transform: CSS.Transform.toString(transform),
transition,
};
return (
<div
className={cls({ isDragging }, props.className)}
ref={setNodeRef}
style={style}
{...attributes}
{...listeners}
>
{props.children}
</div>
);
}
const useColumns = () => {
const field = useField<Formily.Core.Models.ArrayField>();
const values = field.value;
const [groups, setGroups] = useState(['A', 'B', 'C']);
const columns = groups.map((name) => {
return {
id: name,
title: name,
items: values?.filter((item) => item.type === name) || [],
};
});
return { values, groups, columns, setGroups };
};
interface KanbanCardContextProps {
index: number;
record?: any;
viewSchema: Schema;
}
const KanbanCardContext = createContext<KanbanCardContextProps>(null);
export const Kanban: any = observer(() => {
const field = useField<Formily.Core.Models.ArrayField>();
const { values, columns } = useColumns();
const { schema } = useDesignable();
const cardSchema = schema.reduceProperties((prev, current) => {
if (current['x-component'] === 'Kanban.Card') {
return current;
}
return prev;
}, null);
const cardViewSchema = schema.reduceProperties((prev, current) => {
if (current['x-component'] === 'Kanban.Card.View') {
return current;
}
return prev;
}, null);
return (
<div className={'kanban'}>
<DndContext
// collisionDetection={closestCorners}
onDragEnd={({ active, over }) => {
const source = values.find((item) => item.id === active?.id);
const target = values.find((item) => item.id === over?.id);
if (source && target) {
const fromIndex = values.findIndex(
(item) => item.id === active?.id,
);
const toIndex = values.findIndex((item) => item.id === over?.id);
// setValues((items) => {
// const fromIndex = items.findIndex(
// (item) => item.id === active?.id,
// );
// const toIndex = items.findIndex(
// (item) => item.id === over?.id,
// );
// return arrayMove(items, fromIndex, toIndex);
// });
field.move(fromIndex, toIndex);
}
}}
onDragOver={({ active, over }) => {
if (!over) {
return;
}
if (active?.id === over?.id) {
return;
}
const source = values.find((item) => item.id === active?.id);
if (source && over?.data?.current?.type === 'column') {
source.type = over.id;
return;
}
console.log('active.id', active.id, over?.data?.current);
const target = values.find((item) => item.id === over?.id);
if (source && target) {
if (source.type !== target.type) {
source.type = target.type;
}
}
}}
>
<DragOverlay style={{ pointerEvents: 'none' }}>aaa</DragOverlay>
<SortableContext
strategy={horizontalListSortingStrategy}
items={columns}
>
{columns.map((column) => (
<SortableItem
key={column.id}
type={'column'}
className={'column'}
id={column.id}
>
<div className={'column-title'}>{column.title}</div>
<SortableContext
strategy={verticalListSortingStrategy}
items={column.items}
>
{column.items.map((item) => {
const index = values.findIndex(
(value) => value.id === item.id,
);
return (
<SortableItem
key={item.id}
className={'item'}
id={item.id}
type={'item'}
>
<KanbanCardContext.Provider
value={{
index: index,
record: item,
viewSchema: cardViewSchema,
}}
>
<RecursionField name={index} schema={cardSchema} />
</KanbanCardContext.Provider>
</SortableItem>
);
})}
</SortableContext>
<Button
type={'text'}
icon={<PlusOutlined />}
onClick={() => {
field.push({
id: uid(),
type: column.id,
title: uid(),
});
}}
>
</Button>
</SortableItem>
))}
</SortableContext>
</DndContext>
</div>
);
});
Kanban.Card = observer((props) => {
const [visible, setVisible] = useState(false);
const { index, viewSchema } = useContext(KanbanCardContext);
return (
<>
<Card
onClick={(e) => {
setVisible(true);
}}
bordered={false}
>
{props.children}
</Card>
<Drawer
width={'50%'}
title={'查看数据'}
visible={visible}
onClose={() => {
setVisible(false);
}}
>
<RecursionField name={index} schema={viewSchema} />
</Drawer>
</>
);
});
Kanban.Card.View = observer((props) => {
return <div>{props.children}</div>;
});

View File

@ -0,0 +1,33 @@
.column {
position: relative;
width: 25vw;
background: #f9f9f9;
margin: 0 1%;
padding: 24px;
}
.column-title {
line-height: 24px;
font-weight: 500;
margin-bottom: 12px;
}
.kanban {
display: flex;
}
.item {
position: relative;
min-height: 50px;
background: #f1f1f1;
margin-bottom: 24px;
&.isDragging {
opacity: 0.5;
}
}
.drag-handle {
position: absolute;
top: 0;
right: 0;
}