feat: improve kanban
This commit is contained in:
parent
dfd4b821ff
commit
5299a2ece9
@ -145,6 +145,7 @@ export const SchemaField = createSchemaField({
|
|||||||
scope: {
|
scope: {
|
||||||
Table,
|
Table,
|
||||||
Calendar,
|
Calendar,
|
||||||
|
Kanban,
|
||||||
useAsyncDataSource,
|
useAsyncDataSource,
|
||||||
ChinaRegion: {
|
ChinaRegion: {
|
||||||
loadData: loadChinaRegionData,
|
loadData: loadChinaRegionData,
|
||||||
|
@ -5,7 +5,11 @@ const [CollectionsProvider, useCollectionsContext] = constate(() => {
|
|||||||
const result = useRequest('collections:findAll', {
|
const result = useRequest('collections:findAll', {
|
||||||
formatResult: (result) => result?.data,
|
formatResult: (result) => result?.data,
|
||||||
});
|
});
|
||||||
return { ...result, collections: result.data || [] };
|
return {
|
||||||
|
...result, collections: result.data || [], findCollection(name) {
|
||||||
|
return result?.data?.find((item) => item.name === name);
|
||||||
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export { CollectionsProvider, useCollectionsContext };
|
export { CollectionsProvider, useCollectionsContext };
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
import { useCollectionContext } from '../constate';
|
import { useCollectionContext } from '../constate';
|
||||||
import { Resource } from '../resource';
|
import { Resource } from '../resource';
|
||||||
|
|
||||||
export const useResource = (options: any = {}) => {
|
export const useResource = (options: any = {}) => {
|
||||||
const { collection } = useCollectionContext();
|
const { collection } = useCollectionContext();
|
||||||
const resource = Resource.make(collection?.name);
|
const resource = Resource.make(collection?.name);
|
||||||
|
useEffect(() => {
|
||||||
|
options.onSuccess && options.onSuccess({});
|
||||||
|
}, []);
|
||||||
return {
|
return {
|
||||||
initialValues: {},
|
initialValues: {},
|
||||||
loading: false,
|
loading: false,
|
||||||
|
@ -410,40 +410,66 @@ function generateCardItemSchema(component) {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
properties: {
|
properties: {
|
||||||
|
create: {
|
||||||
|
type: 'void',
|
||||||
|
title: '添加卡片',
|
||||||
|
'x-designable-bar': 'Kanban.AddCardDesignableBar',
|
||||||
|
'x-component': 'Kanban.Card.AddNew',
|
||||||
|
// 'x-decorator': 'AddNew.Displayed',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'text',
|
||||||
|
icon: 'PlusOutlined',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
modal: {
|
||||||
|
type: 'void',
|
||||||
|
title: '新增数据',
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
'x-component-props': {
|
||||||
|
useOkAction: '{{ Kanban.useCreateAction }}',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-component-props': {
|
||||||
|
addNewComponent: 'AddNew.FormItem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
card1: {
|
card1: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
|
name: uid(),
|
||||||
|
'x-decorator': 'Form',
|
||||||
'x-component': 'Kanban.Card',
|
'x-component': 'Kanban.Card',
|
||||||
properties: {
|
'x-designable-bar': 'Kanban.Card.DesignableBar',
|
||||||
item1: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Kanban.Item',
|
|
||||||
properties: {
|
|
||||||
title: {
|
|
||||||
type: 'string',
|
|
||||||
// title: '标题',
|
|
||||||
'x-read-pretty': true,
|
'x-read-pretty': true,
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator-props': {
|
||||||
'x-component': 'Input',
|
useResource: '{{ Kanban.useResource }}',
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
properties: {},
|
||||||
},
|
},
|
||||||
view1: {
|
view1: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
|
title: '修改数据',
|
||||||
|
'x-decorator': 'Form',
|
||||||
'x-component': 'Kanban.Card.View',
|
'x-component': 'Kanban.Card.View',
|
||||||
properties: {
|
'x-component-props': {
|
||||||
item1: {
|
useOkAction: '{{ Kanban.useUpdateAction }}',
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Kanban.Item',
|
|
||||||
properties: {
|
|
||||||
title: {
|
|
||||||
type: 'string',
|
|
||||||
title: '标题',
|
|
||||||
'x-read-pretty': true,
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Input',
|
|
||||||
},
|
},
|
||||||
|
'x-decorator-props': {
|
||||||
|
useResource: '{{ Kanban.useResource }}',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-component-props': {
|
||||||
|
addNewComponent: 'AddNew.FormItem',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1225,9 +1251,15 @@ AddNew.CardItem = observer((props: any) => {
|
|||||||
title={item.title}
|
title={item.title}
|
||||||
>
|
>
|
||||||
<Menu.ItemGroup title={'选择分组字段'}>
|
<Menu.ItemGroup title={'选择分组字段'}>
|
||||||
|
{item?.generalFields
|
||||||
|
?.filter((item) => {
|
||||||
|
return item?.uiSchema?.enum;
|
||||||
|
})
|
||||||
|
?.map((field) => {
|
||||||
|
return (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
style={{ minWidth: 150 }}
|
style={{ minWidth: 150 }}
|
||||||
key={`Kanban.collection.${item.name}.${view.key}`}
|
key={`Kanban.collection.${field.name}.${view.key}`}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
let data = generateCardItemSchema('Kanban');
|
let data = generateCardItemSchema('Kanban');
|
||||||
const collectionName = item.name;
|
const collectionName = item.name;
|
||||||
@ -1239,9 +1271,11 @@ AddNew.CardItem = observer((props: any) => {
|
|||||||
data['x-component-props'] || {};
|
data['x-component-props'] || {};
|
||||||
data['x-component-props']['resource'] =
|
data['x-component-props']['resource'] =
|
||||||
collectionName;
|
collectionName;
|
||||||
data['x-component-props']['collectionName'] =
|
data['x-component-props'][
|
||||||
collectionName;
|
'collectionName'
|
||||||
// data['x-component-props']['groupField'] = '';
|
] = collectionName;
|
||||||
|
data['x-component-props']['groupName'] =
|
||||||
|
field.name;
|
||||||
}
|
}
|
||||||
if (isGridBlock(schema)) {
|
if (isGridBlock(schema)) {
|
||||||
path.pop();
|
path.pop();
|
||||||
@ -1254,7 +1288,9 @@ AddNew.CardItem = observer((props: any) => {
|
|||||||
let s;
|
let s;
|
||||||
if (isGrid(schema)) {
|
if (isGrid(schema)) {
|
||||||
s = appendChild(data, [...path]);
|
s = appendChild(data, [...path]);
|
||||||
} else if (defaultAction === 'insertAfter') {
|
} else if (
|
||||||
|
defaultAction === 'insertAfter'
|
||||||
|
) {
|
||||||
s = insertAfter(data, [...path]);
|
s = insertAfter(data, [...path]);
|
||||||
} else {
|
} else {
|
||||||
s = insertBefore(data, [...path]);
|
s = insertBefore(data, [...path]);
|
||||||
@ -1263,8 +1299,10 @@ AddNew.CardItem = observer((props: any) => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
分组字段1
|
{field?.uiSchema?.title}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</Menu.ItemGroup>
|
</Menu.ItemGroup>
|
||||||
</Menu.SubMenu>
|
</Menu.SubMenu>
|
||||||
// <Menu.Item
|
// <Menu.Item
|
||||||
@ -1553,7 +1591,7 @@ AddNew.PaneItem = observer((props: any) => {
|
|||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const blockSchema = useContext(BlockSchemaContext);
|
const blockSchema = useContext(BlockSchemaContext);
|
||||||
const useResource = `{{ ${blockSchema['x-component']}.useResource }}`;
|
const useResource = `{{ ${blockSchema['x-component']}.useResource }}`;
|
||||||
console.log('AddNew.PaneItem.useResource', useResource)
|
console.log('AddNew.PaneItem.useResource', useResource);
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
trigger={['click']}
|
trigger={['click']}
|
||||||
|
@ -12,7 +12,7 @@ import cls from 'classnames';
|
|||||||
import { Button, Dropdown, Menu, Space } from 'antd';
|
import { Button, Dropdown, Menu, Space } from 'antd';
|
||||||
import SwitchMenuItem from '../../components/SwitchMenuItem';
|
import SwitchMenuItem from '../../components/SwitchMenuItem';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { SettingOutlined } from '@ant-design/icons';
|
||||||
import { Droppable, SortableItem } from '../../components/Sortable';
|
import { Droppable, SortableItem } from '../../components/Sortable';
|
||||||
|
|
||||||
export const ActionBar = observer((props: any) => {
|
export const ActionBar = observer((props: any) => {
|
||||||
@ -257,7 +257,7 @@ function AddActionButton() {
|
|||||||
className={'designable-btn designable-btn-dash'}
|
className={'designable-btn designable-btn-dash'}
|
||||||
style={{ marginLeft: 8 }}
|
style={{ marginLeft: 8 }}
|
||||||
type={'dashed'}
|
type={'dashed'}
|
||||||
icon={<PlusOutlined />}
|
icon={<SettingOutlined />}
|
||||||
>
|
>
|
||||||
配置工具栏
|
配置工具栏
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -183,7 +183,7 @@ export const Calendar: any = observer((props: any) => {
|
|||||||
console.log('field.value', field.value);
|
console.log('field.value', field.value);
|
||||||
return (
|
return (
|
||||||
<CollectionProvider collectionName={props.collectionName}>
|
<CollectionProvider collectionName={props.collectionName}>
|
||||||
<CalendarContext.Provider value={{ resource, service, props }}>
|
<CalendarContext.Provider value={{ field, resource, service, props }}>
|
||||||
<div {...props} style={{ height: 700 }}>
|
<div {...props} style={{ height: 700 }}>
|
||||||
<Drawer
|
<Drawer
|
||||||
width={'50%'}
|
width={'50%'}
|
||||||
|
@ -28,6 +28,7 @@ import { uid } from '@formily/shared';
|
|||||||
import { getSchemaPath } from '../../components/schema-renderer';
|
import { getSchemaPath } from '../../components/schema-renderer';
|
||||||
import { useCollection, useCollectionContext } from '../../constate';
|
import { useCollection, useCollectionContext } from '../../constate';
|
||||||
import { useTable } from '../table';
|
import { useTable } from '../table';
|
||||||
|
import { BlockSchemaContext } from '../../context';
|
||||||
|
|
||||||
export const DesignableBar = observer((props) => {
|
export const DesignableBar = observer((props) => {
|
||||||
const field = useField();
|
const field = useField();
|
||||||
@ -35,12 +36,14 @@ export const DesignableBar = observer((props) => {
|
|||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const { dragRef } = useContext(DraggableBlockContext);
|
const { dragRef } = useContext(DraggableBlockContext);
|
||||||
const { props: tableProps } = useTable();
|
const { props: tableProps } = useTable();
|
||||||
const { collection } = useCollectionContext();
|
const blockSchema = useContext(BlockSchemaContext);
|
||||||
// const collectionName = field.componentProps?.collectionName || tableProps?.collectionName;
|
const collectionName = blockSchema['x-component-props']?.collectionName;
|
||||||
// const { collection } = useCollection({ collectionName });
|
const { collection } = useCollection({ collectionName });
|
||||||
return (
|
return (
|
||||||
<div className={cls('designable-bar', { active: visible })}>
|
<div className={cls('designable-bar', { active: visible })}>
|
||||||
<div className={'designable-info'}>{collection?.title || collection?.name}</div>
|
<div className={'designable-info'}>
|
||||||
|
{collection?.title || collection?.name}
|
||||||
|
</div>
|
||||||
<span
|
<span
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
115
packages/client/src/schemas/kanban/CardDesignableBar.tsx
Normal file
115
packages/client/src/schemas/kanban/CardDesignableBar.tsx
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import React, { useContext, useMemo, useRef, useState } from 'react';
|
||||||
|
import { createForm } from '@formily/core';
|
||||||
|
import {
|
||||||
|
SchemaOptionsContext,
|
||||||
|
Schema,
|
||||||
|
useFieldSchema,
|
||||||
|
observer,
|
||||||
|
SchemaExpressionScopeContext,
|
||||||
|
FormProvider,
|
||||||
|
useField,
|
||||||
|
useForm,
|
||||||
|
RecursionField,
|
||||||
|
} from '@formily/react';
|
||||||
|
import {
|
||||||
|
useSchemaPath,
|
||||||
|
SchemaField,
|
||||||
|
useDesignable,
|
||||||
|
removeSchema,
|
||||||
|
ISchema,
|
||||||
|
createSchema,
|
||||||
|
} from '../';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { Button, Dropdown, Menu, Space } from 'antd';
|
||||||
|
import { MenuOutlined, DragOutlined } from '@ant-design/icons';
|
||||||
|
import cls from 'classnames';
|
||||||
|
import { FormLayout } from '@formily/antd';
|
||||||
|
import './style.less';
|
||||||
|
import AddNew from '../add-new';
|
||||||
|
import { DraggableBlockContext } from '../../components/drag-and-drop';
|
||||||
|
import { isGridRowOrCol } from '../grid';
|
||||||
|
import constate from 'constate';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
|
import { getSchemaPath } from '../../components/schema-renderer';
|
||||||
|
import {
|
||||||
|
useCollection,
|
||||||
|
useCollectionContext,
|
||||||
|
useDisplayedMapContext,
|
||||||
|
} from '../../constate';
|
||||||
|
import { useTable } from '../table';
|
||||||
|
import SwitchMenuItem from '../../components/SwitchMenuItem';
|
||||||
|
|
||||||
|
export const CardDesignableBar = observer((props) => {
|
||||||
|
const field = useField();
|
||||||
|
const { designable, schema, appendChild, deepRemove } = useDesignable();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const { dragRef } = useContext(DraggableBlockContext);
|
||||||
|
const { collection, fields } = useCollectionContext();
|
||||||
|
const displayed = useDisplayedMapContext();
|
||||||
|
console.log('useDisplayedMapContext', schema);
|
||||||
|
return (
|
||||||
|
<div className={cls('designable-bar', { active: visible })}>
|
||||||
|
<span
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
className={cls('designable-bar-actions', { active: visible })}
|
||||||
|
>
|
||||||
|
<Space size={2}>
|
||||||
|
<Dropdown
|
||||||
|
trigger={['click']}
|
||||||
|
visible={visible}
|
||||||
|
onVisibleChange={(visible) => {
|
||||||
|
setVisible(visible);
|
||||||
|
}}
|
||||||
|
overlay={
|
||||||
|
<Menu>
|
||||||
|
<Menu.ItemGroup className={'display-fields'} title={`字段展示`}>
|
||||||
|
{fields?.map((field) => (
|
||||||
|
<SwitchMenuItem
|
||||||
|
key={field.key}
|
||||||
|
title={field?.uiSchema?.title}
|
||||||
|
checked={displayed.has(field.name)}
|
||||||
|
onChange={async (checked) => {
|
||||||
|
if (!checked) {
|
||||||
|
const s: any = displayed.get(field.name);
|
||||||
|
const p = getSchemaPath(s);
|
||||||
|
const removed = deepRemove(p);
|
||||||
|
if (!removed) {
|
||||||
|
console.log('getSchemaPath', p, removed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const last = removed.pop();
|
||||||
|
displayed.remove(field.name);
|
||||||
|
if (isGridRowOrCol(last)) {
|
||||||
|
await removeSchema(last);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let data: ISchema = {
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const s = appendChild(data);
|
||||||
|
await createSchema(s);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Menu.ItemGroup>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuOutlined />
|
||||||
|
</Dropdown>
|
||||||
|
</Space>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
201
packages/client/src/schemas/kanban/Field.DesignableBar.tsx
Normal file
201
packages/client/src/schemas/kanban/Field.DesignableBar.tsx
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
import React, { useContext, useMemo, useRef, useState } from 'react';
|
||||||
|
import { createForm } from '@formily/core';
|
||||||
|
import {
|
||||||
|
SchemaOptionsContext,
|
||||||
|
Schema,
|
||||||
|
useFieldSchema,
|
||||||
|
observer,
|
||||||
|
SchemaExpressionScopeContext,
|
||||||
|
FormProvider,
|
||||||
|
ISchema,
|
||||||
|
useField,
|
||||||
|
useForm,
|
||||||
|
RecursionField,
|
||||||
|
} from '@formily/react';
|
||||||
|
import {
|
||||||
|
useSchemaPath,
|
||||||
|
SchemaField,
|
||||||
|
useDesignable,
|
||||||
|
removeSchema,
|
||||||
|
updateSchema,
|
||||||
|
} from '../';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { Button, Dropdown, Menu, Space, Switch } from 'antd';
|
||||||
|
import { MenuOutlined, DragOutlined } from '@ant-design/icons';
|
||||||
|
import cls from 'classnames';
|
||||||
|
import { FormDialog, FormLayout } from '@formily/antd';
|
||||||
|
import './style.less';
|
||||||
|
import AddNew from '../add-new';
|
||||||
|
import { DraggableBlockContext } from '../../components/drag-and-drop';
|
||||||
|
import { isGridRowOrCol } from '../grid';
|
||||||
|
import constate from 'constate';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
|
import { getSchemaPath } from '../../components/schema-renderer';
|
||||||
|
import { RandomNameContext } from '.';
|
||||||
|
import { useCollectionContext, useDisplayedMapContext } from '../../constate';
|
||||||
|
import SwitchMenuItem from '../../components/SwitchMenuItem';
|
||||||
|
|
||||||
|
export const FieldDesignableBar = observer((props) => {
|
||||||
|
const field = useField();
|
||||||
|
const { schema, deepRemove } = useDesignable();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const { dragRef } = useContext(DraggableBlockContext);
|
||||||
|
const randomName = useContext(RandomNameContext);
|
||||||
|
const displayed = useDisplayedMapContext();
|
||||||
|
const fieldName = schema['x-component-props']?.['fieldName'];
|
||||||
|
const { getField } = useCollectionContext();
|
||||||
|
|
||||||
|
const collectionField = getField(fieldName);
|
||||||
|
|
||||||
|
console.log({ collectionField });
|
||||||
|
|
||||||
|
const realField = field
|
||||||
|
.query(field.address.concat(randomName, fieldName))
|
||||||
|
.take();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cls('designable-bar', { active: visible })}>
|
||||||
|
<span
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
className={cls('designable-bar-actions', { active: visible })}
|
||||||
|
>
|
||||||
|
<Space size={2}>
|
||||||
|
<AddNew.FormItem defaultAction={'insertAfter'} ghost />
|
||||||
|
{dragRef && <DragOutlined ref={dragRef} />}
|
||||||
|
<Dropdown
|
||||||
|
placement={'bottomRight'}
|
||||||
|
trigger={['click']}
|
||||||
|
visible={visible}
|
||||||
|
onVisibleChange={(visible) => {
|
||||||
|
setVisible(visible);
|
||||||
|
}}
|
||||||
|
overlay={
|
||||||
|
<Menu>
|
||||||
|
<Menu.Item
|
||||||
|
key={'update'}
|
||||||
|
onClick={async () => {
|
||||||
|
const values = await FormDialog('修改字段名称', () => {
|
||||||
|
return (
|
||||||
|
<FormLayout layout={'vertical'}>
|
||||||
|
<SchemaField
|
||||||
|
schema={{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
fieldName: {
|
||||||
|
type: 'string',
|
||||||
|
title: '原字段名称',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: 'string',
|
||||||
|
title: '自定义名称',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormLayout>
|
||||||
|
);
|
||||||
|
}).open({
|
||||||
|
initialValues: {
|
||||||
|
fieldName: collectionField?.uiSchema?.title,
|
||||||
|
title: schema['title'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const title = values.title || null;
|
||||||
|
field
|
||||||
|
.query(field.address.concat(randomName, fieldName))
|
||||||
|
.take((f) => {
|
||||||
|
f.title = title || collectionField?.uiSchema?.title;
|
||||||
|
});
|
||||||
|
schema['title'] = title;
|
||||||
|
await updateSchema({
|
||||||
|
key: schema['key'],
|
||||||
|
title,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
修改字段名称
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item
|
||||||
|
style={{ minWidth: 150 }}
|
||||||
|
onClick={async () => {
|
||||||
|
const values = await FormDialog('编辑描述', () => {
|
||||||
|
return (
|
||||||
|
<FormLayout layout={'vertical'}>
|
||||||
|
<SchemaField
|
||||||
|
schema={{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
description: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormLayout>
|
||||||
|
);
|
||||||
|
}).open({
|
||||||
|
initialValues: {
|
||||||
|
description: schema['description'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const description = values.description || null;
|
||||||
|
realField.description =
|
||||||
|
description || collectionField?.uiSchema?.description;
|
||||||
|
schema['description'] = description;
|
||||||
|
await updateSchema({
|
||||||
|
key: schema['key'],
|
||||||
|
description,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
编辑描述
|
||||||
|
</Menu.Item>
|
||||||
|
<SwitchMenuItem
|
||||||
|
title={'必填'}
|
||||||
|
checked={schema.required as boolean}
|
||||||
|
onChange={(checked) => {
|
||||||
|
field
|
||||||
|
.query(field.address.concat(randomName, fieldName))
|
||||||
|
.take((f: any) => {
|
||||||
|
f.required = checked;
|
||||||
|
schema.required = checked;
|
||||||
|
updateSchema(schema);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Menu.Divider />
|
||||||
|
<Menu.Item
|
||||||
|
key={'delete'}
|
||||||
|
onClick={async () => {
|
||||||
|
const removed = deepRemove();
|
||||||
|
const fieldName =
|
||||||
|
schema['x-component-props']?.['fieldName'];
|
||||||
|
console.log({ schema, removed, fieldName });
|
||||||
|
const last = removed.pop();
|
||||||
|
displayed.remove(fieldName);
|
||||||
|
if (isGridRowOrCol(last)) {
|
||||||
|
await removeSchema(last);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
移除
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuOutlined />
|
||||||
|
</Dropdown>
|
||||||
|
</Space>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@ -1,5 +1,11 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { observer, RecursionField, Schema, useField } from '@formily/react';
|
import {
|
||||||
|
observer,
|
||||||
|
RecursionField,
|
||||||
|
Schema,
|
||||||
|
useField,
|
||||||
|
useForm,
|
||||||
|
} from '@formily/react';
|
||||||
import { DndContext, DragOverlay, closestCorners } from '@dnd-kit/core';
|
import { DndContext, DragOverlay, closestCorners } from '@dnd-kit/core';
|
||||||
import {
|
import {
|
||||||
SortableContext,
|
SortableContext,
|
||||||
@ -13,7 +19,7 @@ import './style.less';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { groupBy } from 'lodash';
|
import { groupBy } from 'lodash';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { Button, Card, Drawer } from 'antd';
|
import { Button, Card, Drawer, Tag } from 'antd';
|
||||||
import {
|
import {
|
||||||
SchemaRenderer,
|
SchemaRenderer,
|
||||||
useDesignable,
|
useDesignable,
|
||||||
@ -23,6 +29,13 @@ import { useContext } from 'react';
|
|||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { DesignableBar } from './DesignableBar';
|
import { DesignableBar } from './DesignableBar';
|
||||||
|
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';
|
||||||
|
|
||||||
export function SortableItem(props) {
|
export function SortableItem(props) {
|
||||||
const {
|
const {
|
||||||
@ -59,16 +72,22 @@ export function SortableItem(props) {
|
|||||||
|
|
||||||
const useColumns = () => {
|
const useColumns = () => {
|
||||||
const field = useField<Formily.Core.Models.ArrayField>();
|
const field = useField<Formily.Core.Models.ArrayField>();
|
||||||
|
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 values = field.value;
|
||||||
const [groups, setGroups] = useState(['A', 'B', 'C']);
|
const columns = groupField?.enum?.map((group) => {
|
||||||
const columns = groups.map((name) => {
|
|
||||||
return {
|
return {
|
||||||
id: name,
|
...group,
|
||||||
title: name,
|
items: values?.filter((item) => item[groupName] === group.value) || [],
|
||||||
items: values?.filter((item) => item.type === name) || [],
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return { values, groups, columns, setGroups };
|
console.log('useColumns', { values, columns });
|
||||||
|
return { values, columns };
|
||||||
};
|
};
|
||||||
|
|
||||||
interface KanbanCardContextProps {
|
interface KanbanCardContextProps {
|
||||||
@ -79,10 +98,19 @@ interface KanbanCardContextProps {
|
|||||||
|
|
||||||
const KanbanCardContext = createContext<KanbanCardContextProps>(null);
|
const KanbanCardContext = createContext<KanbanCardContextProps>(null);
|
||||||
|
|
||||||
export const Kanban: any = observer(() => {
|
export const KanbanContext = createContext<any>(null);
|
||||||
|
export const KanbanColumnContext = createContext<any>(null);
|
||||||
|
|
||||||
|
export const useKanban = () => {
|
||||||
|
return useContext(KanbanContext);
|
||||||
|
};
|
||||||
|
|
||||||
|
const InternalKanban = observer((props: any) => {
|
||||||
const field = useField<Formily.Core.Models.ArrayField>();
|
const field = useField<Formily.Core.Models.ArrayField>();
|
||||||
|
const groupName = field.componentProps.groupName;
|
||||||
const { values, columns } = useColumns();
|
const { values, columns } = useColumns();
|
||||||
const { schema } = useDesignable();
|
const { schema } = useDesignable();
|
||||||
|
const { collectionName } = props;
|
||||||
const cardSchema = schema.reduceProperties((prev, current) => {
|
const cardSchema = schema.reduceProperties((prev, current) => {
|
||||||
if (current['x-component'] === 'Kanban.Card') {
|
if (current['x-component'] === 'Kanban.Card') {
|
||||||
return current;
|
return current;
|
||||||
@ -95,12 +123,46 @@ export const Kanban: any = observer(() => {
|
|||||||
}
|
}
|
||||||
return prev;
|
return prev;
|
||||||
}, null);
|
}, 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) => {
|
||||||
|
if (!collectionName) {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
return resource.list({
|
||||||
|
...params,
|
||||||
|
perPage: -1,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formatResult: (data) => data?.data,
|
||||||
|
onSuccess(data) {
|
||||||
|
field.setValue(data);
|
||||||
|
},
|
||||||
|
manual: true,
|
||||||
|
// refreshDeps: [props.fieldNames],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
service.run({
|
||||||
|
defaultFilter: props?.defaultFilter,
|
||||||
|
sort: 'sort',
|
||||||
|
});
|
||||||
|
}, [props.defaultFilter]);
|
||||||
return (
|
return (
|
||||||
|
<KanbanContext.Provider value={{ field, resource, service, props }}>
|
||||||
<div>
|
<div>
|
||||||
{/* <div>
|
{/* <div>
|
||||||
<Button>筛选</Button>
|
<Button>筛选</Button>
|
||||||
</div> */}
|
</div> */}
|
||||||
<div className={'kanban'}>
|
<div className={'kanban'}>
|
||||||
|
<div className={'kanban-container'}>
|
||||||
<DndContext
|
<DndContext
|
||||||
// collisionDetection={closestCorners}
|
// collisionDetection={closestCorners}
|
||||||
onDragEnd={({ active, over }) => {
|
onDragEnd={({ active, over }) => {
|
||||||
@ -110,7 +172,9 @@ export const Kanban: any = observer(() => {
|
|||||||
const fromIndex = values.findIndex(
|
const fromIndex = values.findIndex(
|
||||||
(item) => item.id === active?.id,
|
(item) => item.id === active?.id,
|
||||||
);
|
);
|
||||||
const toIndex = values.findIndex((item) => item.id === over?.id);
|
const toIndex = values.findIndex(
|
||||||
|
(item) => item.id === over?.id,
|
||||||
|
);
|
||||||
// setValues((items) => {
|
// setValues((items) => {
|
||||||
// const fromIndex = items.findIndex(
|
// const fromIndex = items.findIndex(
|
||||||
// (item) => item.id === active?.id,
|
// (item) => item.id === active?.id,
|
||||||
@ -132,14 +196,14 @@ export const Kanban: any = observer(() => {
|
|||||||
}
|
}
|
||||||
const source = values.find((item) => item.id === active?.id);
|
const source = values.find((item) => item.id === active?.id);
|
||||||
if (source && over?.data?.current?.type === 'column') {
|
if (source && over?.data?.current?.type === 'column') {
|
||||||
source.type = over.id;
|
source[groupName] = over.id;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('active.id', active.id, over?.data?.current);
|
console.log('active.id', active.id, over?.data?.current);
|
||||||
const target = values.find((item) => item.id === over?.id);
|
const target = values.find((item) => item.id === over?.id);
|
||||||
if (source && target) {
|
if (source && target) {
|
||||||
if (source.type !== target.type) {
|
if (source[groupName] !== target[groupName]) {
|
||||||
source.type = target.type;
|
source[groupName] = target[groupName];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -148,16 +212,24 @@ export const Kanban: any = observer(() => {
|
|||||||
|
|
||||||
<SortableContext
|
<SortableContext
|
||||||
strategy={horizontalListSortingStrategy}
|
strategy={horizontalListSortingStrategy}
|
||||||
items={columns}
|
items={
|
||||||
|
columns?.map((column) => ({
|
||||||
|
...column,
|
||||||
|
id: column.value,
|
||||||
|
})) || []
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{columns.map((column) => (
|
{columns?.map((column) => (
|
||||||
<SortableItem
|
<SortableItem
|
||||||
key={column.id}
|
id={column.value}
|
||||||
|
key={column.value}
|
||||||
type={'column'}
|
type={'column'}
|
||||||
className={'column'}
|
className={'column'}
|
||||||
id={column.id}
|
|
||||||
>
|
>
|
||||||
<div className={'column-title'}>{column.title}</div>
|
<KanbanColumnContext.Provider value={column}>
|
||||||
|
<div className={'column-title'}>
|
||||||
|
<Tag color={column.color}>{column.label}</Tag>
|
||||||
|
</div>
|
||||||
<SortableContext
|
<SortableContext
|
||||||
strategy={verticalListSortingStrategy}
|
strategy={verticalListSortingStrategy}
|
||||||
items={column.items}
|
items={column.items}
|
||||||
@ -180,63 +252,141 @@ export const Kanban: any = observer(() => {
|
|||||||
viewSchema: cardViewSchema,
|
viewSchema: cardViewSchema,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecursionField name={index} schema={cardSchema} />
|
<RecursionField
|
||||||
|
name={index}
|
||||||
|
schema={cardSchema}
|
||||||
|
/>
|
||||||
</KanbanCardContext.Provider>
|
</KanbanCardContext.Provider>
|
||||||
</SortableItem>
|
</SortableItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
<Button
|
<RecursionField
|
||||||
|
name={addCardSchema?.name}
|
||||||
|
schema={addCardSchema}
|
||||||
|
/>
|
||||||
|
{/* <Button
|
||||||
type={'text'}
|
type={'text'}
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
field.push({
|
field.push({
|
||||||
id: uid(),
|
id: uid(),
|
||||||
type: column.id,
|
|
||||||
title: uid(),
|
title: uid(),
|
||||||
|
[groupName]: column.value,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
添加卡片
|
添加卡片
|
||||||
</Button>
|
</Button> */}
|
||||||
|
</KanbanColumnContext.Provider>
|
||||||
</SortableItem>
|
</SortableItem>
|
||||||
))}
|
))}
|
||||||
|
<div
|
||||||
|
id={'addColumn'}
|
||||||
|
key={'addColumn'}
|
||||||
|
// type={'column'}
|
||||||
|
className={'column add-column'}
|
||||||
|
>
|
||||||
|
{/* <span className={'add-column-plus'}/> */}
|
||||||
|
<span><PlusOutlined/> 添加列表</span>
|
||||||
|
</div>
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</KanbanContext.Provider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const Kanban: any = observer((props: any) => {
|
||||||
|
return (
|
||||||
|
<CollectionProvider collectionName={props.collectionName}>
|
||||||
|
<InternalKanban {...props} />
|
||||||
|
</CollectionProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Kanban.useCreateAction = () => {
|
||||||
|
const { service, resource, props } = useKanban();
|
||||||
|
const column = useContext(KanbanColumnContext);
|
||||||
|
const groupName = props.groupName;
|
||||||
|
const form = useForm();
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
await resource.create({
|
||||||
|
...form.values,
|
||||||
|
[groupName]: column.value,
|
||||||
|
});
|
||||||
|
await form.reset();
|
||||||
|
return service.refresh();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Kanban.useUpdateAction = () => {
|
||||||
|
const { service, resource, props } = useKanban();
|
||||||
|
const ctx = useContext(KanbanCardContext);
|
||||||
|
const form = useForm();
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
await resource.save(form.values, {
|
||||||
|
resourceKey: ctx.record.id,
|
||||||
|
});
|
||||||
|
await service.refresh();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Kanban.useResource = ({ onSuccess }) => {
|
||||||
|
const { props } = useKanban();
|
||||||
|
const { collection } = useCollectionContext();
|
||||||
|
const ctx = useContext(KanbanCardContext);
|
||||||
|
const resource = Resource.make({
|
||||||
|
resourceName: collection?.name || props.collectionName,
|
||||||
|
resourceKey: ctx?.record?.id,
|
||||||
|
});
|
||||||
|
console.log(
|
||||||
|
'collection?.name || props.collectionName',
|
||||||
|
collection?.name || props.collectionName,
|
||||||
|
);
|
||||||
|
const service = useRequest(
|
||||||
|
(params?: any) => {
|
||||||
|
return Promise.resolve(ctx.record);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// formatResult: (result) => result?.data,
|
||||||
|
onSuccess,
|
||||||
|
refreshDeps: [ctx?.record],
|
||||||
|
// manual,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return { resource, service, initialValues: service.data, ...service };
|
||||||
|
};
|
||||||
|
|
||||||
Kanban.Card = observer((props) => {
|
Kanban.Card = observer((props) => {
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const { index, viewSchema } = useContext(KanbanCardContext);
|
const { index, viewSchema } = useContext(KanbanCardContext);
|
||||||
|
const { DesignableBar } = useDesignable();
|
||||||
|
const { children, ...others } = props;
|
||||||
return (
|
return (
|
||||||
<>
|
<VisibleContext.Provider value={[visible, setVisible]}>
|
||||||
<Card
|
<Card
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
}}
|
}}
|
||||||
bordered={false}
|
bordered={false}
|
||||||
|
{...others}
|
||||||
>
|
>
|
||||||
{props.children}
|
{children}
|
||||||
|
<DesignableBar />
|
||||||
</Card>
|
</Card>
|
||||||
<Drawer
|
|
||||||
width={'50%'}
|
|
||||||
title={'查看数据'}
|
|
||||||
visible={visible}
|
|
||||||
onClose={() => {
|
|
||||||
setVisible(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RecursionField name={index} schema={viewSchema} />
|
<RecursionField name={index} schema={viewSchema} />
|
||||||
</Drawer>
|
</VisibleContext.Provider>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Kanban.Card.View = observer((props) => {
|
Kanban.Card.View = Action.Drawer;
|
||||||
return <div>{props.children}</div>;
|
Kanban.Card.AddNew = Action;
|
||||||
});
|
|
||||||
|
|
||||||
Kanban.DesignableBar = DesignableBar;
|
Kanban.DesignableBar = DesignableBar;
|
||||||
|
Kanban.Card.DesignableBar = CardDesignableBar;
|
||||||
|
@ -1,9 +1,38 @@
|
|||||||
.column {
|
.column {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 25vw;
|
width: 300px;
|
||||||
background: #f9f9f9;
|
background: #f9f9f9;
|
||||||
margin: 0 12px;
|
margin-right: 24px;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
|
&.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-title {
|
.column-title {
|
||||||
@ -14,7 +43,12 @@
|
|||||||
|
|
||||||
.kanban {
|
.kanban {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0 -12px;
|
margin: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-container {
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
@ -32,3 +66,41 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.kanban .ant-card-body {
|
||||||
|
position: relative;
|
||||||
|
&:hover {
|
||||||
|
> .designable-bar {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .designable-bar {
|
||||||
|
pointer-events: none;
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
right: -1px;
|
||||||
|
left: -1px;
|
||||||
|
bottom: -1px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: none !important;
|
||||||
|
border: 2px solid rgba(241, 139, 98, 0.3) !important;
|
||||||
|
&.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.designable-bar-actions {
|
||||||
|
pointer-events: all;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
line-height: 1rem;
|
||||||
|
background-color: #1890ff;
|
||||||
|
color: #fff;
|
||||||
|
z-index: 10;
|
||||||
|
padding: 0 3px;
|
||||||
|
.anticon {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user