improve code
This commit is contained in:
parent
754be7d1d1
commit
c23d2c5c3c
@ -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}
|
||||
/>
|
||||
|
@ -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: {
|
||||
|
@ -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'}
|
||||
|
@ -25,4 +25,29 @@
|
||||
.ant-tag.disabled {
|
||||
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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user