feat: add kanban block
This commit is contained in:
parent
23db248743
commit
b6f359bbc5
101
packages/client/src/schemas/kanban/demos/demo1.tsx
Normal file
101
packages/client/src/schemas/kanban/demos/demo1.tsx
Normal 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} />;
|
||||
};
|
32
packages/client/src/schemas/kanban/index.md
Normal file
32
packages/client/src/schemas/kanban/index.md
Normal 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" />
|
234
packages/client/src/schemas/kanban/index.tsx
Normal file
234
packages/client/src/schemas/kanban/index.tsx
Normal 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>;
|
||||
});
|
33
packages/client/src/schemas/kanban/style.less
Normal file
33
packages/client/src/schemas/kanban/style.less
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user