diff --git a/packages/client/src/schemas/add-new/index.tsx b/packages/client/src/schemas/add-new/index.tsx index daf4a86dd..bbcad2cf7 100644 --- a/packages/client/src/schemas/add-new/index.tsx +++ b/packages/client/src/schemas/add-new/index.tsx @@ -350,7 +350,9 @@ function generateCardItemSchema(component) { }, Kanban: { type: 'array', - name: 'kanban1', + 'x-component': 'Kanban', + 'x-designable-bar': 'Kanban.DesignableBar', + 'x-component-props': {}, 'x-decorator': 'CardItem', 'x-decorator-props': { style: { @@ -360,60 +362,11 @@ function generateCardItemSchema(component) { padding: 0, }, }, - 'x-designable-bar': 'Kanban.DesignableBar', - '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: { create: { type: 'void', title: '添加卡片', - 'x-designable-bar': 'Kanban.AddCardDesignableBar', + // 'x-designable-bar': 'Kanban.AddCardDesignableBar', 'x-component': 'Kanban.Card.AddNew', // 'x-decorator': 'AddNew.Displayed', 'x-component-props': { @@ -446,24 +399,12 @@ function generateCardItemSchema(component) { name: uid(), 'x-decorator': 'Form', 'x-component': 'Kanban.Card', + 'x-designable-bar': 'Kanban.Card.DesignableBar', 'x-read-pretty': true, 'x-decorator-props': { - useResource: '{{ Kanban.useResource }}', - }, - properties: { - [uid()]: { - type: 'void', - 'x-decorator': 'BlockItem', - 'x-decorator-props': { - draggable: false, - }, - 'x-component': 'Grid', - 'x-designable-bar': 'Kanban.Card.DesignableBar', - // 'x-component-props': { - // addNewComponent: 'AddNew.FormItem', - // }, - }, + useResource: '{{ Kanban.useSingleResource }}', }, + properties: {}, }, view1: { type: 'void', @@ -474,7 +415,7 @@ function generateCardItemSchema(component) { useOkAction: '{{ Kanban.useUpdateAction }}', }, 'x-decorator-props': { - useResource: '{{ Kanban.useResource }}', + useResource: '{{ Kanban.useSingleResource }}', }, properties: { [uid()]: { @@ -1286,8 +1227,9 @@ AddNew.CardItem = observer((props: any) => { data['x-component-props'][ 'collectionName' ] = collectionName; - data['x-component-props']['groupName'] = - field.name; + data['x-component-props']['groupField'] = { + name: field.name, + }; } if (isGridBlock(schema)) { path.pop(); diff --git a/packages/client/src/schemas/kanban/CardDesignableBar.tsx b/packages/client/src/schemas/kanban/CardDesignableBar.tsx index 821e063b8..97197813c 100644 --- a/packages/client/src/schemas/kanban/CardDesignableBar.tsx +++ b/packages/client/src/schemas/kanban/CardDesignableBar.tsx @@ -87,26 +87,14 @@ export const CardDesignableBar = observer((props) => { return; } let data: ISchema = { + key: uid(), + name: uid(), type: 'void', - 'x-component': 'Grid.Row', - properties: { - [uid()]: { - type: 'void', - 'x-component': 'Grid.Col', - properties: { - [uid()]: { - key: uid(), - type: 'void', - 'x-decorator': 'Form.Field.Item', - 'x-designable-bar': - 'Form.Field.DesignableBar', - 'x-component': 'Form.Field', - 'x-component-props': { - fieldName: field.name, - }, - }, - }, - }, + 'x-decorator': 'Form.Field.Item', + // 'x-designable-bar': 'Form.Field.DesignableBar', + 'x-component': 'Form.Field', + 'x-component-props': { + fieldName: field.name, }, }; const s = appendChild(data); diff --git a/packages/client/src/schemas/kanban/demos/demo1.tsx b/packages/client/src/schemas/kanban/demos/demo1.tsx index 17b0afd90..7d266b184 100644 --- a/packages/client/src/schemas/kanban/demos/demo1.tsx +++ b/packages/client/src/schemas/kanban/demos/demo1.tsx @@ -1,22 +1,61 @@ -import { ISchema } from '@formily/react'; import React from 'react'; -import { SchemaRenderer } from '../../'; +import { ISchema, SchemaRenderer } from '../../'; import { Kanban } from '..'; +import { uid } from '@formily/shared'; const schema: ISchema = { type: 'array', name: 'kanban1', 'x-component': 'Kanban', + 'x-component-props': { + groupField: { + name: 'type', + enum: [ + { + label: 'A', + value: 'A', + color: 'magenta', + }, + { + label: 'B', + value: 'B', + color: 'green', + }, + { + label: 'C', + value: 'C', + color: 'blue', + }, + { + label: 'D', + value: 'D', + color: 'purple', + }, + ], + }, + }, default: [ { id: '1', type: 'A', title: 'A1', + content: ( +

+ Lorem ipsum dolor sit amet, no dolor graeco pro, te sea bonorum + dolorum theophrastus.{' '} +

+ ), }, { id: '2', type: 'A', title: 'A2', + content: ( +

+ Lorem ipsum dolor sit amet, no dolor graeco pro, te sea bonorum + dolorum theophrastus.{' '} +

+ ), }, { id: '3', @@ -27,11 +66,35 @@ const schema: ISchema = { id: '4', type: 'B', title: 'B4', + content: ( +

+ Lorem ipsum dolor sit amet, no dolor graeco pro, te sea bonorum + dolorum theophrastus. Vim ea utamur appetere molestiae, ad harum + alienum indoctum ius. No quo laoreet laboramus comprehensam, eos paulo + integre vivendo eu, an nam alia facilisi consetetur. Pro exerci iisque + et, no amet magna iracundia vim. Vis erant consectetuer te, mei + menandri liberavisse at, no latine consulatu deseruisse eos. Mel an + novum nostrud scripserit, velit virtute delicata eam ad, eum ne etiam + omnesque. +

+ ), }, { id: '5', type: 'B', title: 'B5', + content: ( +

+ Lorem ipsum dolor sit amet, no dolor graeco pro, te sea bonorum + dolorum theophrastus. Vim ea utamur appetere molestiae, ad harum + alienum indoctum ius. No quo laoreet laboramus comprehensam, eos paulo + integre vivendo eu, an nam alia facilisi consetetur. Pro exerci iisque + et, no amet magna iracundia vim. Vis erant consectetuer te, mei + menandri liberavisse at, no latine consulatu deseruisse eos. Mel an + novum nostrud scripserit, velit virtute delicata eam ad, eum ne etiam + omnesque. +

+ ), }, { id: '6', @@ -42,6 +105,12 @@ const schema: ISchema = { id: '7', type: 'C', title: 'C7', + content: ( +

+ Lorem ipsum dolor sit amet, no dolor graeco pro, te sea bonorum + dolorum theophrastus.{' '} +

+ ), }, { id: '8', @@ -55,40 +124,79 @@ const schema: ISchema = { }, ], properties: { - card1: { + create: { type: 'void', - 'x-component': 'Kanban.Card', + title: '添加卡片', + // 'x-designable-bar': 'Kanban.AddCardDesignableBar', + 'x-component': 'Kanban.Card.AddNew', + // 'x-decorator': 'AddNew.Displayed', + 'x-component-props': { + type: 'text', + icon: 'PlusOutlined', + }, properties: { - item1: { + modal: { type: 'void', - 'x-component': 'Kanban.Item', + title: '新增数据', + 'x-decorator': 'Form', + 'x-component': 'Action.Drawer', + 'x-component-props': { + useOkAction: '{{ Kanban.useCreateAction }}', + }, properties: { - title: { - type: 'string', - // title: '标题', - 'x-read-pretty': true, - 'x-decorator': 'FormItem', - 'x-component': 'Input', + [uid()]: { + type: 'void', + 'x-component': 'Grid', + 'x-component-props': { + addNewComponent: 'AddNew.FormItem', + }, }, }, }, }, }, + card1: { + type: 'void', + name: uid(), + 'x-decorator': 'Form', + 'x-component': 'Kanban.Card', + 'x-designable-bar': 'Kanban.Card.DesignableBar', + 'x-read-pretty': true, + 'x-decorator-props': { + useResource: '{{ Kanban.useSingleResource }}', + }, + properties: { + // [uid()]: { + // type: 'void', + // 'x-decorator': 'BlockItem', + // 'x-decorator-props': { + // draggable: false, + // }, + // 'x-component': 'Grid', + // 'x-designable-bar': 'Kanban.Card.DesignableBar', + // // 'x-component-props': { + // // addNewComponent: 'AddNew.FormItem', + // // }, + // }, + }, + }, view1: { type: 'void', + title: '修改数据', + 'x-decorator': 'Form', 'x-component': 'Kanban.Card.View', + 'x-component-props': { + useOkAction: '{{ Kanban.useUpdateAction }}', + }, + 'x-decorator-props': { + useResource: '{{ Kanban.useSingleResource }}', + }, properties: { - item1: { + [uid()]: { type: 'void', - 'x-component': 'Kanban.Item', - properties: { - title: { - type: 'string', - title: '标题', - 'x-read-pretty': true, - 'x-decorator': 'FormItem', - 'x-component': 'Input', - }, + 'x-component': 'Grid', + 'x-component-props': { + addNewComponent: 'AddNew.FormItem', }, }, }, @@ -97,5 +205,5 @@ const schema: ISchema = { }; export default () => { - return ; + return ; }; diff --git a/packages/client/src/schemas/kanban/index.md b/packages/client/src/schemas/kanban/index.md index 2c322aa19..f18960536 100644 --- a/packages/client/src/schemas/kanban/index.md +++ b/packages/client/src/schemas/kanban/index.md @@ -1,32 +1 @@ ---- -title: Kanban - 看板 -nav: - title: 组件 - path: /client -group: - order: 1 - title: Schemas - path: /client/schemas ---- - -# Kanban - 看板 - -## Node Tree - -
-
-  
-    // 看板卡片
-    
-      
-    
-  
-  
-    // 查看卡片详情
-  
-
-
- -## Examples - - + \ No newline at end of file diff --git a/packages/client/src/schemas/kanban/index.tsx b/packages/client/src/schemas/kanban/index.tsx index f5e5f12fa..3d634fea2 100644 --- a/packages/client/src/schemas/kanban/index.tsx +++ b/packages/client/src/schemas/kanban/index.tsx @@ -1,4 +1,17 @@ -import React, { useEffect, useRef } from 'react'; +import { + DndContext, + DragOverlay, + PointerSensor, + useDroppable, + useSensor, + useSensors, +} from '@dnd-kit/core'; +import { + SortableContext, + useSortable, + verticalListSortingStrategy, +} from '@dnd-kit/sortable'; +import { ArrayField } from '@formily/core'; import { observer, RecursionField, @@ -6,164 +19,147 @@ import { useField, useForm, } from '@formily/react'; -import { - DndContext, - DragOverlay, - closestCorners, - rectIntersection, - closestCenter, - useSensors, - useSensor, - MouseSensor, -} 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 { Card, Spin, Tag } from 'antd'; import { groupBy } from 'lodash'; +import React, { createContext, useContext, useRef } from 'react'; +import { useState } from 'react'; +import { CSS } from '@dnd-kit/utilities'; import cls from 'classnames'; -import { Button, Card, Drawer, Tag } 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'; -import { DesignableBar } from './DesignableBar'; +import './style.less'; import { CollectionProvider, useCollectionContext } from '../../constate'; import { Resource } from '../../resource'; import { useRequest } from 'ahooks'; import { Action } from '../action'; -import { Form } from '../form'; import { CardDesignableBar } from './CardDesignableBar'; import { VisibleContext } from '../../context'; +import { DesignableBar } from './DesignableBar'; +import { useDesignable } from '../../components/schema-renderer'; -export function SortableItem(props) { +function Droppable(props) { + const { id, data, ...others } = props; + const { setNodeRef } = useDroppable({ + id, + data, + }); + + return
; +} + +function SortableItem(props) { + const { id, data, className, ...others } = props; const nodeRef = useRef(); const { + isDragging, attributes, listeners, setNodeRef, - isDragging, transform, transition, } = useSortable({ - id: props.id, - disabled: props.disabled, - data: { - type: props.type, - nodeRef, - }, + id, + data: { ...data, nodeRef }, }); const style = { + ...props.style, transform: CSS.Transform.toString(transform), transition, }; return (
{ setNodeRef(el); nodeRef.current = el; }} style={style} + className={cls(className, { isDragging })} {...attributes} {...listeners} > - {/*
drag
*/} {props.children}
); } -const useColumns = () => { - const field = useField(); - const { getField } = useCollectionContext(); - let groupField = field.componentProps.groupField; - const groupName = field.componentProps.groupName; - const collectionField = getField(groupName); - if (collectionField) { - groupField = collectionField?.uiSchema; - } - const values = field.value; - const columns = groupField?.enum?.map((group) => { - return { - ...group, - items: values?.filter((item) => item[groupName] === group.value) || [], - }; - }); - console.log('useColumns', { values, columns }); - return { values, columns }; -}; - -interface KanbanCardContextProps { - index: number; - record?: any; - viewSchema: Schema; +interface KanbanContextProps { + props?: any; + service?: any; + resource?: Resource; + [key: string]: any; } -const KanbanCardContext = createContext(null); +interface KanbanCardContextProps { + record?: any; + [key: string]: any; +} -export const KanbanContext = createContext(null); -export const KanbanColumnContext = createContext(null); +interface KanbanColumnContextProps { + [key: string]: any; +} + +export const KanbanContext = createContext(null); +export const KanbanColumnContext = + createContext(null); +export const KanbanCardContext = createContext(null); export const useKanban = () => { return useContext(KanbanContext); }; -class MyMouseSensor extends MouseSensor { - static activators = [ - { - eventName: 'onMouseDown' as const, - handler: (event) => { - if (event.target.tagName === 'DIV') { - return true; - } - console.log('event.target.tagName', event.target.tagName); - event.target; - return false; - }, - }, - ]; - constructor(props) { - super(props); - } -} +const KanbanColumn = (props) => { + const option = useContext(KanbanColumnContext); + const { field, schemas } = useKanban(); + const { items } = props; + return ( + + {items?.map((item) => { + const index = field.value?.findIndex((val) => val.id === item.id); + return ( + + + {/* {item.id} */} + + + + ); + })} + + ); +}; const InternalKanban = observer((props: any) => { - const field = useField(); - const groupName = field.componentProps.groupName; - const { values, columns } = useColumns(); - const { schema } = useDesignable(); + const { getField } = useCollectionContext(); + const collectionField = getField(props.groupField?.name); + const groupField = { + ...collectionField?.uiSchema, + ...props.groupField, + }; + const field = useField(); + const sensors = useSensors( + useSensor(PointerSensor, { + activationConstraint: { + distance: 10, + }, + }), + ); + const groups = groupBy(field.value, groupField.name); const { collectionName } = props; - 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); - const addCardSchema = schema.reduceProperties((prev, current) => { - if (current['x-component'] === 'Kanban.Card.AddNew') { - return current; - } - return prev; - }, null); const resource = Resource.make(collectionName); const service = useRequest( (params) => { @@ -180,163 +176,153 @@ const InternalKanban = observer((props: any) => { onSuccess(data) { field.setValue(data); }, - manual: true, + // manual: true, // refreshDeps: [props.fieldNames], }, ); - useEffect(() => { - service.run({ - defaultFilter: props?.defaultFilter, - sort: 'sort', - }); - }, [props.defaultFilter]); - const [dragOverlayContent, setDragOverlayContent] = useState(''); - const [style, setStyle] = useState({}); - const containerRef = useRef(); - console.log({ style }); - const sensors = useSensors(useSensor(MyMouseSensor)); + const { schema } = useDesignable(); + const [schemas, setSchemas] = useState(() => { + const schemas = new Map(); + schema.reduceProperties((map, current) => { + if (current['x-component'] === 'Kanban.Card') { + map.set('Kanban.Card', current); + } + if (current['x-component'] === 'Kanban.Card.AddNew') { + map.set('Kanban.Card.AddNew', current); + } + if (current['x-component'] === 'Kanban.Card.View') { + map.set('Kanban.Card.View', current); + } + return map; + }, schemas); + return schemas; + }); + const addNewCardSchema = schemas.get('Kanban.Card.AddNew'); + console.log('field.value', schemas); return ( - -
- {/*
- -
*/} -
-
- { - const el = event?.active?.data?.current?.nodeRef - ?.current as HTMLElement; - console.log(event, el); - setDragOverlayContent(el?.outerHTML); - setStyle({ - width: el.clientWidth, - height: containerRef.current?.clientHeight, - }); - }} - collisionDetection={closestCenter} - 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[groupName] = 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[groupName] !== target[groupName]) { - source[groupName] = target[groupName]; - } - } - }} - > - -
- - - ({ - ...column, - id: column.value, - })) || [] - } - > - {columns?.map((column) => ( - + { + const overId = over?.id; + const activeId = active?.id; + if (!overId || !activeId) { + return; + } + if (overId === activeId) { + return; + } + const overType = over?.data?.current?.type; + const activeItem = field.value.find((item) => item.id === activeId); + if (overType === 'column') { + if (overId === activeItem?.[groupField.name]) { + return; + } + const len = groups?.[overId]?.length; + if (len > 0) { + const last = groups?.[overId]?.[len - 1]; + const activeIndex = field.value.findIndex( + (item) => item.id === activeId, + ); + const overIndex = field.value.findIndex( + (item) => item.id === last.id, + ); + console.log({ overId, last, overIndex, activeIndex }); + field.move(activeIndex, overIndex); + } + activeItem[groupField.name] = overId; + } else { + const overColumnId = over?.data?.current?.columnId; + const activeColumnId = active?.data?.current?.columnId; + if (!overColumnId || !activeColumnId) { + return; + } + if (overColumnId !== activeColumnId) { + activeItem[groupField.name] = overColumnId; + const activeIndex = field.value.findIndex( + (item) => item.id === activeId, + ); + const overIndex = field.value.findIndex( + (item) => item.id === overId, + ); + console.log({ overId, overIndex, activeIndex }); + field.move(activeIndex, overIndex); + } + } + }} + onDragEnd={({ active, over }) => { + const overId = over?.id; + const activeId = active?.id; + if (!overId || !activeId) { + return; + } + if (overId === activeId) { + return; + } + const overType = over?.data?.current?.type; + if (overType !== 'column') { + const overColumnId = over?.data?.current?.columnId; + const activeColumnId = active?.data?.current?.columnId; + if (!overColumnId || !activeColumnId) { + return; + } + if (overColumnId !== activeColumnId) { + return; + } + const activeIndex = field.value.findIndex( + (item) => item.id === activeId, + ); + const overIndex = field.value.findIndex( + (item) => item.id === overId, + ); + field.move(activeIndex, overIndex); + } + }} + > + +
aaa
+
+ +
+
+ {groupField?.enum?.map((option) => { + const items = field.value?.filter( + (item) => item?.[groupField.name] === option.value, + ); + return ( + - -
- {column.label} + +
+ {option.label}
- - {column.items.map((item) => { - const index = values.findIndex( - (value) => value.id === item.id, - ); - return ( - - - - - - ); - })} - +
- - ))} - - + + ); + })} +
-
-
+ +
); }); @@ -352,13 +338,13 @@ export const Kanban: any = observer((props: any) => { Kanban.useCreateAction = () => { const { service, resource, props } = useKanban(); const column = useContext(KanbanColumnContext); - const groupName = props.groupName; + const groupField = props.groupField; const form = useForm(); return { async run() { await resource.create({ ...form.values, - [groupName]: column.value, + [groupField.name]: column.value, }); await form.reset(); return service.refresh(); @@ -380,7 +366,7 @@ Kanban.useUpdateAction = () => { }; }; -Kanban.useResource = ({ onSuccess }) => { +Kanban.useSingleResource = ({ onSuccess }) => { const { props } = useKanban(); const { collection } = useCollectionContext(); const ctx = useContext(KanbanCardContext); @@ -408,7 +394,7 @@ Kanban.useResource = ({ onSuccess }) => { Kanban.Card = observer((props) => { const [visible, setVisible] = useState(false); - const { index, viewSchema } = useContext(KanbanCardContext); + const { index, schemas } = useContext(KanbanCardContext); const { DesignableBar } = useDesignable(); const { children, ...others } = props; return ( @@ -421,9 +407,9 @@ Kanban.Card = observer((props) => { {...others} > {children} - - + + ); }); diff --git a/packages/client/src/schemas/kanban/style.less b/packages/client/src/schemas/kanban/style.less index 51377c1e5..96a985af8 100644 --- a/packages/client/src/schemas/kanban/style.less +++ b/packages/client/src/schemas/kanban/style.less @@ -1,80 +1,31 @@ -.column { +.nb-kanban-board { + display: flex; + margin: 0; + overflow: auto; +} +.nb-kanban-container { + display: flex; + min-height: 60vh; +} + +.nb-kanban-column { position: relative; width: 300px; background: #f9f9f9; margin-right: 12px; padding: 12px; - &.isDragging { - opacity: .5; - } - &.add-column { - padding: 0; - margin-right: 0; - background: rgba(241, 139, 98, 0.05); - cursor: pointer; - color: #f18b62; - text-align: center; - justify-content: center; - align-items: center; - display: flex; - .add-column-plus { - position: relative; - &::before { - position: absolute; - width: 1px; - height: 50px; - background: #f18b62; - } - &::after { - position: absolute; - width: 50px; - height: 1px; - background: #f18b62; - } - } - &:hover { - background: rgba(241, 139, 98, 0.1); - } - } } -.nb-kanban-drag-overlay { - opacity: .8; -} - -.column-title { - line-height: 24px; - font-weight: 500; +.nb-kanban-item { margin-bottom: 12px; -} - -.kanban { - display: flex; - margin: 0; - overflow: auto; - .item { - position: relative; - // background: #f1f1f1; - margin-bottom: 12px; - &.isDragging { - opacity: 0.5; - } + &.isDragging { + opacity: .2; } } -.kanban-container { - display: flex; -} - -.drag-handle { - position: absolute; - top: 0; - right: 0; -} - -.kanban .ant-card-body { +.nb-kanban-item { position: relative; - padding: 0; + // padding: 0; > .nb-block-item { padding: 12px; @@ -117,3 +68,8 @@ } } } + +.nb-kanban-column-header { + margin-bottom: 12px; + line-height: 32px; +}