improve code

This commit is contained in:
chenos 2021-08-03 12:12:29 +08:00
parent 754be7d1d1
commit c23d2c5c3c
7 changed files with 340 additions and 182 deletions

View File

@ -38,13 +38,16 @@ export interface SortableItemProps {}
export const SortableItemContext = createContext<any>({}); export const SortableItemContext = createContext<any>({});
export function DragHandle() { export function DragHandle(props) {
const { component, ...others } = props;
const Icon = component || DragOutlined;
return ( return (
<SortableItemContext.Consumer> <SortableItemContext.Consumer>
{({ setDraggableNodeRef, attributes, listeners }) => {({ setDraggableNodeRef, attributes, listeners }) =>
setDraggableNodeRef && ( setDraggableNodeRef && (
<DragOutlined <Icon
ref={setDraggableNodeRef} ref={setDraggableNodeRef}
{...others}
{...attributes} {...attributes}
{...listeners} {...listeners}
/> />

View File

@ -236,22 +236,27 @@ function generateCardItemSchema(component) {
'x-component': 'Action.Dropdown', 'x-component': 'Action.Dropdown',
'x-component-props': {}, 'x-component-props': {},
properties: { properties: {
[uid()]: { // [uid()]: {
type: 'void', // type: 'void',
title: '操作 1', // title: '操作 1',
'x-component': 'Menu.Action', // 'x-component': 'Menu.Action',
'x-component-props': { // 'x-component-props': {
style: { // style: {
minWidth: 150, // minWidth: 150,
}, // },
disabled: true, // disabled: true,
}, // },
}, // },
[uid()]: { [uid()]: {
type: 'void', type: 'void',
name: 'action1', name: 'action1',
title: '查看', title: '查看',
'x-component': 'Menu.Action', 'x-component': 'Menu.Action',
'x-component-props': {
style: {
minWidth: 150,
},
},
'x-designable-bar': 'Table.Action.DesignableBar', 'x-designable-bar': 'Table.Action.DesignableBar',
'x-action-type': 'view', 'x-action-type': 'view',
properties: { properties: {

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { forwardRef, useState } from 'react';
import { import {
observer, observer,
connect, connect,
@ -11,7 +11,7 @@ import {
FormConsumer, FormConsumer,
} from '@formily/react'; } from '@formily/react';
import { ArrayCollapse, FormLayout } from '@formily/antd'; import { ArrayCollapse, FormLayout } from '@formily/antd';
import { uid } from '@formily/shared'; import { uid, isValid } from '@formily/shared';
import '@formily/antd/lib/form-tab/style'; import '@formily/antd/lib/form-tab/style';
import { import {
Collapse, Collapse,
@ -32,6 +32,7 @@ import {
DatabaseOutlined, DatabaseOutlined,
PlusOutlined, PlusOutlined,
CloseOutlined, CloseOutlined,
MenuOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import cls from 'classnames'; import cls from 'classnames';
import './style.less'; import './style.less';
@ -39,8 +40,53 @@ import Modal from 'antd/lib/modal/Modal';
import { clone, cloneDeep, get } from 'lodash'; import { clone, cloneDeep, get } from 'lodash';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useRequest } from 'ahooks'; import { useRequest } from 'ahooks';
import { createOrUpdateCollection, deleteCollection } from '..'; import {
collectionMoveToAfter,
createOrUpdateCollection,
deleteCollection,
} from '..';
import { useCollectionsContext } from '../../constate/Collections'; import { useCollectionsContext } from '../../constate/Collections';
import {
DragHandle,
SortableItem,
SortableItemContext,
} from '../../components/Sortable';
import { DndContext, DragOverlay } from '@dnd-kit/core';
import { createPortal } from 'react-dom';
interface SelectOptionProps {
id: any;
title: string;
data?: any;
onRemove?: any;
showRemove?: boolean;
}
function SelectOption(props: SelectOptionProps) {
const { id, data, onRemove, showRemove } = props;
return (
<SortableItem id={id} data={data}>
<div
style={{
display: 'flex',
// justifyContent: 'space-between',
alignItems: 'center',
}}
>
<DragHandle
component={forwardRef<any>((props, ref) => {
return <MenuOutlined {...props} ref={ref} />;
})}
/>
<span style={{ width: 8 }} />
{data.title}
{showRemove && (
<DeleteOutlined style={{ marginLeft: 'auto' }} onClick={onRemove} />
)}
</div>
</SortableItem>
);
}
export const DatabaseCollection = observer((props) => { export const DatabaseCollection = observer((props) => {
const field = useField<Formily.Core.Models.ArrayField>(); const field = useField<Formily.Core.Models.ArrayField>();
@ -51,6 +97,7 @@ export const DatabaseCollection = observer((props) => {
const form = useForm(); const form = useForm();
const [newValue, setNewValue] = useState(''); const [newValue, setNewValue] = useState('');
const { loading, refresh, collections = [] } = useCollectionsContext(); const { loading, refresh, collections = [] } = useCollectionsContext();
const [dragOverlayContent, setDragOverlayContent] = useState('');
useEffect(() => { useEffect(() => {
field.setValue(collections); field.setValue(collections);
@ -87,6 +134,27 @@ export const DatabaseCollection = observer((props) => {
}} }}
title={ title={
<div style={{ textAlign: 'center' }}> <div style={{ textAlign: 'center' }}>
<DndContext
onDragStart={(event) => {
setDragOverlayContent(event.active?.data?.current?.title || '');
}}
onDragEnd={async (event) => {
const sourceName = event.active?.data?.current?.name;
const targetName = event.over?.data?.current?.name;
console.log({ sourceName, targetName });
await collectionMoveToAfter(sourceName, targetName);
await refresh();
}}
>
{createPortal(
<DragOverlay
zIndex={2000}
style={{ pointerEvents: 'none', whiteSpace: 'nowrap' }}
>
{dragOverlayContent}
</DragOverlay>,
document.body,
)}
<Select <Select
loading={loading} loading={loading}
value={activeIndex} value={activeIndex}
@ -144,18 +212,15 @@ export const DatabaseCollection = observer((props) => {
item.unsaved ? ' (未保存)' : '' item.unsaved ? ' (未保存)' : ''
}`} }`}
> >
<div <SelectOption
style={{ id={item.name}
display: 'flex', title={item.title || '未命名'}
justifyContent: 'space-between', data={{
alignItems: 'center', title: item.title,
name: item.name,
}} }}
> showRemove={item.privilege !== 'undelete'}
{item.title || '未命名'}{' '} onRemove={async (e) => {
{item.unsaved ? '(未保存)' : ''}
{item.privilege !== 'undelete' && (
<DeleteOutlined
onClick={async (e) => {
e.stopPropagation(); e.stopPropagation();
field.remove(index); field.remove(index);
if (field.value?.length === 0) { if (field.value?.length === 0) {
@ -176,12 +241,11 @@ export const DatabaseCollection = observer((props) => {
} }
}} }}
/> />
)}
</div>
</Select.Option> </Select.Option>
); );
})} })}
</Select> </Select>
</DndContext>
</div> </div>
} }
visible={visible} visible={visible}
@ -228,8 +292,30 @@ export const DatabaseField: any = observer((props) => {
} }
}, []); }, []);
const [activeKey, setActiveKey] = useState(null); const [activeKey, setActiveKey] = useState(null);
const [dragOverlayContent, setDragOverlayContent] = useState('');
return ( return (
<div> <div>
<DndContext
onDragStart={(event) => {
setDragOverlayContent(event.active?.data?.current?.title || '');
}}
onDragEnd={async (event) => {
const fromIndex = event.active?.data?.current?.index;
const toIndex = event.over?.data?.current?.index;
if (isValid(fromIndex) && isValid(toIndex)) {
field.move(fromIndex, toIndex);
}
}}
>
{createPortal(
<DragOverlay
zIndex={2000}
style={{ pointerEvents: 'none', whiteSpace: 'nowrap' }}
>
{dragOverlayContent}
</DragOverlay>,
document.body,
)}
<Collapse <Collapse
activeKey={activeKey} activeKey={activeKey}
onChange={(key) => { onChange={(key) => {
@ -255,19 +341,31 @@ export const DatabaseField: any = observer((props) => {
return ( return (
<Collapse.Panel <Collapse.Panel
header={ header={
<> <SortableItem
id={item.key}
className={'sortable-item'}
data={{
index,
title: item?.uiSchema?.title,
}}
>
<DragHandle className={'drag-handle'} />
{(item.uiSchema && item.uiSchema.title) || ( {(item.uiSchema && item.uiSchema.title) || (
<i style={{ color: 'rgba(0, 0, 0, 0.25)' }}></i> <i style={{ color: 'rgba(0, 0, 0, 0.25)' }}></i>
)}{' '} )}{' '}
<Tag <Tag
className={item.privilege ? cls(item.privilege) : undefined} className={
item.privilege ? cls(item.privilege) : undefined
}
> >
{schema.title} {schema.title}
</Tag> </Tag>
<span style={{ color: 'rgba(0, 0, 0, 0.25)', fontSize: 14 }}> <span
style={{ color: 'rgba(0, 0, 0, 0.25)', fontSize: 14 }}
>
{item.name} {item.name}
</span> </span>
</> </SortableItem>
} }
extra={ extra={
item.privilege === 'undelete' item.privilege === 'undelete'
@ -284,6 +382,7 @@ export const DatabaseField: any = observer((props) => {
] ]
} }
key={item.key} key={item.key}
forceRender
> >
<RecursionField <RecursionField
key={`${item.key}_${index}`} key={`${item.key}_${index}`}
@ -310,6 +409,7 @@ export const DatabaseField: any = observer((props) => {
); );
})} })}
</Collapse> </Collapse>
</DndContext>
<Dropdown <Dropdown
placement={'bottomCenter'} placement={'bottomCenter'}
overlayClassName={'all-fields'} overlayClassName={'all-fields'}

View File

@ -26,3 +26,28 @@
color: rgba(0, 0, 0, 0.25); color: rgba(0, 0, 0, 0.25);
cursor: not-allowed; cursor: not-allowed;
} }
.ant-collapse-header {
position: relative;
&:hover {
.drag-handle {
opacity: 1;
}
}
.drag-handle {
opacity: 0;
margin-right: 6px;
background-color: #fafafa;
position: relative;
z-index: 3;
}
.ant-collapse-arrow {
position: absolute;
top: 18px;
}
.ant-collapse-extra {
position: absolute;
right: 16px;
top: 12px;
}
}

View File

@ -60,6 +60,20 @@ export async function createSchema(schema: ISchema) {
}); });
} }
export async function collectionMoveToAfter(source, target) {
if (source && target) {
return request(`collections:sort/${source}`, {
method: 'post',
data: {
field: 'sort',
target: {
name: target,
},
},
});
}
}
export async function updateSchema(schema: ISchema) { export async function updateSchema(schema: ISchema) {
if (!schema) { if (!schema) {
return; return;

View File

@ -46,6 +46,9 @@ export const Tabs: any = observer((props: any) => {
if (!path1 || !path2) { if (!path1 || !path2) {
return; return;
} }
if (path1.join('.') === path2.join('.')) {
return;
}
const data = findPropertyByPath(root, path1); const data = findPropertyByPath(root, path1);
if (!data) { if (!data) {
return; return;

View File

@ -6,7 +6,7 @@ import { cloneDeep, omit } from 'lodash';
export const findAll = async (ctx: actions.Context, next: actions.Next) => { export const findAll = async (ctx: actions.Context, next: actions.Next) => {
const Collection = ctx.db.getModel('collections'); const Collection = ctx.db.getModel('collections');
const collections = await Collection.findAll(Collection.parseApiJson({ const collections = await Collection.findAll(Collection.parseApiJson({
sort: '-created_at', sort: 'sort',
})); }));
const data = []; const data = [];
for (const collection of collections) { for (const collection of collections) {
@ -29,6 +29,14 @@ export const createOrUpdate = async (ctx: actions.Context, next: actions.Next) =
} else { } else {
await collection.update(values); await collection.update(values);
} }
if (values.fields) {
values.fields = values.fields.map((field, index) => {
return {
...field,
sort: index + 1,
}
})
}
await collection.updateAssociations(values); await collection.updateAssociations(values);
await collection.migrate(); await collection.migrate();
} catch (error) { } catch (error) {