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 function DragHandle() {
export function DragHandle(props) {
const { component, ...others } = props;
const Icon = component || DragOutlined;
return (
<SortableItemContext.Consumer>
{({ setDraggableNodeRef, attributes, listeners }) =>
setDraggableNodeRef && (
<DragOutlined
<Icon
ref={setDraggableNodeRef}
{...others}
{...attributes}
{...listeners}
/>

View File

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

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { forwardRef, useState } from 'react';
import {
observer,
connect,
@ -11,7 +11,7 @@ import {
FormConsumer,
} from '@formily/react';
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 {
Collapse,
@ -32,6 +32,7 @@ import {
DatabaseOutlined,
PlusOutlined,
CloseOutlined,
MenuOutlined,
} from '@ant-design/icons';
import cls from 'classnames';
import './style.less';
@ -39,8 +40,53 @@ import Modal from 'antd/lib/modal/Modal';
import { clone, cloneDeep, get } from 'lodash';
import { useEffect } from 'react';
import { useRequest } from 'ahooks';
import { createOrUpdateCollection, deleteCollection } from '..';
import {
collectionMoveToAfter,
createOrUpdateCollection,
deleteCollection,
} from '..';
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) => {
const field = useField<Formily.Core.Models.ArrayField>();
@ -51,6 +97,7 @@ export const DatabaseCollection = observer((props) => {
const form = useForm();
const [newValue, setNewValue] = useState('');
const { loading, refresh, collections = [] } = useCollectionsContext();
const [dragOverlayContent, setDragOverlayContent] = useState('');
useEffect(() => {
field.setValue(collections);
@ -87,101 +134,118 @@ export const DatabaseCollection = observer((props) => {
}}
title={
<div style={{ textAlign: 'center' }}>
<Select
loading={loading}
value={activeIndex}
style={{ minWidth: 300, textAlign: 'center' }}
onChange={(value) => {
setActiveIndex(value as any);
<DndContext
onDragStart={(event) => {
setDragOverlayContent(event.active?.data?.current?.title || '');
}}
open={open}
onDropdownVisibleChange={setOpen}
optionLabelProp={'label'}
dropdownRender={(menu) => {
return (
<div>
{menu}
<Divider style={{ margin: '5px 0 4px' }} />
<div
style={{
cursor: 'pointer',
padding: '5px 12px',
}}
>
<Input.Search
size={'middle'}
placeholder={'新增数据表'}
enterButton={<PlusOutlined />}
value={newValue}
onChange={(e) => {
setNewValue(e.target.value);
}}
onSearch={async (value) => {
const data = {
name: `t_${uid()}`,
title: value,
fields: getDefaultFields(),
};
field.push(data);
setActiveIndex(field.value.length - 1);
setOpen(false);
setNewValue('');
await createOrUpdateCollection(data);
await refresh();
}}
/>
</div>
</div>
);
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();
}}
>
{field.value?.map((item, index) => {
return (
<Select.Option
key={index}
value={index}
label={`${item.title || '未命名'}${
item.unsaved ? ' (未保存)' : ''
}`}
>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
{item.title || '未命名'}{' '}
{item.unsaved ? '(未保存)' : ''}
{item.privilege !== 'undelete' && (
<DeleteOutlined
onClick={async (e) => {
e.stopPropagation();
field.remove(index);
if (field.value?.length === 0) {
field.push({
name: `t_${uid()}`,
unsaved: true,
fields: getDefaultFields(),
});
}
if (activeIndex === index) {
setActiveIndex(0);
} else if (activeIndex > index) {
setActiveIndex(activeIndex - 1);
}
if (item.name) {
await deleteCollection(item.name);
await refresh();
}
{createPortal(
<DragOverlay
zIndex={2000}
style={{ pointerEvents: 'none', whiteSpace: 'nowrap' }}
>
{dragOverlayContent}
</DragOverlay>,
document.body,
)}
<Select
loading={loading}
value={activeIndex}
style={{ minWidth: 300, textAlign: 'center' }}
onChange={(value) => {
setActiveIndex(value as any);
}}
open={open}
onDropdownVisibleChange={setOpen}
optionLabelProp={'label'}
dropdownRender={(menu) => {
return (
<div>
{menu}
<Divider style={{ margin: '5px 0 4px' }} />
<div
style={{
cursor: 'pointer',
padding: '5px 12px',
}}
>
<Input.Search
size={'middle'}
placeholder={'新增数据表'}
enterButton={<PlusOutlined />}
value={newValue}
onChange={(e) => {
setNewValue(e.target.value);
}}
onSearch={async (value) => {
const data = {
name: `t_${uid()}`,
title: value,
fields: getDefaultFields(),
};
field.push(data);
setActiveIndex(field.value.length - 1);
setOpen(false);
setNewValue('');
await createOrUpdateCollection(data);
await refresh();
}}
/>
)}
</div>
</div>
</Select.Option>
);
})}
</Select>
);
}}
>
{field.value?.map((item, index) => {
return (
<Select.Option
key={index}
value={index}
label={`${item.title || '未命名'}${
item.unsaved ? ' (未保存)' : ''
}`}
>
<SelectOption
id={item.name}
title={item.title || '未命名'}
data={{
title: item.title,
name: item.name,
}}
showRemove={item.privilege !== 'undelete'}
onRemove={async (e) => {
e.stopPropagation();
field.remove(index);
if (field.value?.length === 0) {
field.push({
name: `t_${uid()}`,
unsaved: true,
fields: getDefaultFields(),
});
}
if (activeIndex === index) {
setActiveIndex(0);
} else if (activeIndex > index) {
setActiveIndex(activeIndex - 1);
}
if (item.name) {
await deleteCollection(item.name);
await refresh();
}
}}
/>
</Select.Option>
);
})}
</Select>
</DndContext>
</div>
}
visible={visible}
@ -228,88 +292,124 @@ export const DatabaseField: any = observer((props) => {
}
}, []);
const [activeKey, setActiveKey] = useState(null);
const [dragOverlayContent, setDragOverlayContent] = useState('');
return (
<div>
<Collapse
activeKey={activeKey}
onChange={(key) => {
setActiveKey(key);
<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);
}
}}
className={cls({ empty: !field.value?.length })}
accordion
>
{field.value?.map((item, index) => {
if (!item.interface) {
return;
}
const schema = cloneDeep(interfaces.get(item.interface));
if (!schema) {
console.error('schema invalid');
return;
}
const path = field.address.concat(index);
const errors = field.form.queryFeedbacks({
type: 'error',
address: `*(${path},${path}.*)`,
});
return (
<Collapse.Panel
header={
<>
{(item.uiSchema && item.uiSchema.title) || (
<i style={{ color: 'rgba(0, 0, 0, 0.25)' }}></i>
)}{' '}
<Tag
className={item.privilege ? cls(item.privilege) : undefined}
{createPortal(
<DragOverlay
zIndex={2000}
style={{ pointerEvents: 'none', whiteSpace: 'nowrap' }}
>
{dragOverlayContent}
</DragOverlay>,
document.body,
)}
<Collapse
activeKey={activeKey}
onChange={(key) => {
setActiveKey(key);
}}
className={cls({ empty: !field.value?.length })}
accordion
>
{field.value?.map((item, index) => {
if (!item.interface) {
return;
}
const schema = cloneDeep(interfaces.get(item.interface));
if (!schema) {
console.error('schema invalid');
return;
}
const path = field.address.concat(index);
const errors = field.form.queryFeedbacks({
type: 'error',
address: `*(${path},${path}.*)`,
});
return (
<Collapse.Panel
header={
<SortableItem
id={item.key}
className={'sortable-item'}
data={{
index,
title: item?.uiSchema?.title,
}}
>
{schema.title}
</Tag>
<span style={{ color: 'rgba(0, 0, 0, 0.25)', fontSize: 14 }}>
{item.name}
</span>
</>
}
extra={
item.privilege === 'undelete'
? []
: [
<Badge key={'1'} count={errors.length} />,
<DeleteOutlined
key={'2'}
onClick={(e) => {
e.stopPropagation();
field.remove(index);
}}
/>,
]
}
key={item.key}
>
<RecursionField
key={`${item.key}_${index}`}
name={index}
schema={
new Schema({
type: 'object',
properties: {
layout: {
type: 'void',
'x-component': 'FormLayout',
'x-component-props': {
layout: 'vertical',
// labelCol: 4,
// wrapperCol: 20,
},
properties: schema.properties,
},
},
})
<DragHandle className={'drag-handle'} />
{(item.uiSchema && item.uiSchema.title) || (
<i style={{ color: 'rgba(0, 0, 0, 0.25)' }}></i>
)}{' '}
<Tag
className={
item.privilege ? cls(item.privilege) : undefined
}
>
{schema.title}
</Tag>
<span
style={{ color: 'rgba(0, 0, 0, 0.25)', fontSize: 14 }}
>
{item.name}
</span>
</SortableItem>
}
/>
</Collapse.Panel>
);
})}
</Collapse>
extra={
item.privilege === 'undelete'
? []
: [
<Badge key={'1'} count={errors.length} />,
<DeleteOutlined
key={'2'}
onClick={(e) => {
e.stopPropagation();
field.remove(index);
}}
/>,
]
}
key={item.key}
forceRender
>
<RecursionField
key={`${item.key}_${index}`}
name={index}
schema={
new Schema({
type: 'object',
properties: {
layout: {
type: 'void',
'x-component': 'FormLayout',
'x-component-props': {
layout: 'vertical',
// labelCol: 4,
// wrapperCol: 20,
},
properties: schema.properties,
},
},
})
}
/>
</Collapse.Panel>
);
})}
</Collapse>
</DndContext>
<Dropdown
placement={'bottomCenter'}
overlayClassName={'all-fields'}

View File

@ -26,3 +26,28 @@
color: rgba(0, 0, 0, 0.25);
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) {
if (!schema) {
return;

View File

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

View File

@ -6,7 +6,7 @@ import { cloneDeep, omit } from 'lodash';
export const findAll = async (ctx: actions.Context, next: actions.Next) => {
const Collection = ctx.db.getModel('collections');
const collections = await Collection.findAll(Collection.parseApiJson({
sort: '-created_at',
sort: 'sort',
}));
const data = [];
for (const collection of collections) {
@ -29,6 +29,14 @@ export const createOrUpdate = async (ctx: actions.Context, next: actions.Next) =
} else {
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.migrate();
} catch (error) {