feat: collection categories (#1327)
* feat: collection categories * feat: collection category * feat: collection category * feat: collection category belongsToMany * feat: collection category * feat: collection category * feat: collection category * feat: collection category local * feat: collection category * feat: collection category collection * feat: collection category code improve * feat: collection category code improve * feat: collection category column * feat: collection category color * feat: collection category improve * feat: collection category improve * feat: collection category improve * feat: collection category improve * feat: collection category improve * feat: collection category refresh * feat: collection category improve * feat: collection category improve * feat: collection category improve * feat: collection category improve * feat: collection category improve * feat: graph collection show category color * feat: graph collection show category color * feat: graph collection category style improve * fix: change the variable names to camel case * fix: category * fix: collectionCategory * fix: field options --------- Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
2b7f138491
commit
f8a11cbbf0
@ -1,13 +1,13 @@
|
|||||||
import { Spin } from 'antd';
|
import { Spin } from 'antd';
|
||||||
import React, { useContext, useState } from 'react';
|
|
||||||
import { keyBy } from 'lodash';
|
import { keyBy } from 'lodash';
|
||||||
|
import React, { useContext, useState } from 'react';
|
||||||
import { useAPIClient, useRequest } from '../api-client';
|
import { useAPIClient, useRequest } from '../api-client';
|
||||||
import { CollectionManagerSchemaComponentProvider } from './CollectionManagerSchemaComponentProvider';
|
|
||||||
import { CollectionManagerContext } from './context';
|
|
||||||
import { CollectionManagerOptions } from './types';
|
|
||||||
import { templateOptions } from '../collection-manager/Configuration/templates';
|
import { templateOptions } from '../collection-manager/Configuration/templates';
|
||||||
import * as defaultInterfaces from './interfaces';
|
|
||||||
import { useCollectionHistory } from './CollectionHistoryProvider';
|
import { useCollectionHistory } from './CollectionHistoryProvider';
|
||||||
|
import { CollectionManagerSchemaComponentProvider } from './CollectionManagerSchemaComponentProvider';
|
||||||
|
import { CollectionCategroriesContext, CollectionManagerContext } from './context';
|
||||||
|
import * as defaultInterfaces from './interfaces';
|
||||||
|
import { CollectionManagerOptions } from './types';
|
||||||
|
|
||||||
export const CollectionManagerProvider: React.FC<CollectionManagerOptions> = (props) => {
|
export const CollectionManagerProvider: React.FC<CollectionManagerOptions> = (props) => {
|
||||||
const { service, interfaces, collections = [], refreshCM, templates } = props;
|
const { service, interfaces, collections = [], refreshCM, templates } = props;
|
||||||
@ -38,7 +38,7 @@ export const RemoteCollectionManagerProvider = (props: any) => {
|
|||||||
action: 'list',
|
action: 'list',
|
||||||
params: {
|
params: {
|
||||||
paginate: false,
|
paginate: false,
|
||||||
appends: ['fields', 'fields.uiSchema'],
|
appends: ['fields', 'fields.uiSchema','category'],
|
||||||
filter: {
|
filter: {
|
||||||
// inherit: false,
|
// inherit: false,
|
||||||
},
|
},
|
||||||
@ -72,3 +72,33 @@ export const RemoteCollectionManagerProvider = (props: any) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CollectionCategroriesProvider = (props) => {
|
||||||
|
const api = useAPIClient();
|
||||||
|
const options={
|
||||||
|
url: 'collectionCategories:list',
|
||||||
|
params: {
|
||||||
|
paginate: false,
|
||||||
|
sort:['sort']
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const result = useRequest(options);
|
||||||
|
if (result.loading) {
|
||||||
|
return <Spin />;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<CollectionCategroriesContext.Provider
|
||||||
|
value={{
|
||||||
|
...result,
|
||||||
|
data: result?.data?.data,
|
||||||
|
refresh:async ()=>{
|
||||||
|
const { data } = await api.request(options);
|
||||||
|
result.mutate(data);
|
||||||
|
return data?.data || [];
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</CollectionCategroriesContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -19,10 +19,17 @@ import {
|
|||||||
ViewFieldAction,
|
ViewFieldAction,
|
||||||
AddCollection,
|
AddCollection,
|
||||||
AddCollectionAction,
|
AddCollectionAction,
|
||||||
|
AddCategoryAction,
|
||||||
|
AddCategory,
|
||||||
EditCollection,
|
EditCollection,
|
||||||
EditCollectionAction,
|
EditCollectionAction,
|
||||||
|
ConfigurationTabs,
|
||||||
|
EditCategory,
|
||||||
|
EditCategoryAction,
|
||||||
} from './Configuration';
|
} from './Configuration';
|
||||||
|
|
||||||
|
import { CollectionCategroriesProvider } from './CollectionManagerProvider';
|
||||||
|
|
||||||
const schema: ISchema = {
|
const schema: ISchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@ -43,6 +50,7 @@ const schema2: ISchema = {
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
[uid()]: {
|
[uid()]: {
|
||||||
|
'x-decorator': 'CollectionCategroriesProvider',
|
||||||
'x-component': 'ConfigurationTable',
|
'x-component': 'ConfigurationTable',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -50,15 +58,19 @@ const schema2: ISchema = {
|
|||||||
|
|
||||||
export const CollectionManagerPane = () => {
|
export const CollectionManagerPane = () => {
|
||||||
return (
|
return (
|
||||||
<Card bordered={false}>
|
// <Card bordered={false}>
|
||||||
<SchemaComponent
|
<SchemaComponent
|
||||||
schema={schema2}
|
schema={schema2}
|
||||||
components={{
|
components={{
|
||||||
|
CollectionCategroriesProvider,
|
||||||
ConfigurationTable,
|
ConfigurationTable,
|
||||||
|
ConfigurationTabs,
|
||||||
AddFieldAction,
|
AddFieldAction,
|
||||||
AddCollectionField,
|
AddCollectionField,
|
||||||
AddCollection,
|
AddCollection,
|
||||||
AddCollectionAction,
|
AddCollectionAction,
|
||||||
|
AddCategoryAction,
|
||||||
|
AddCategory,
|
||||||
EditCollection,
|
EditCollection,
|
||||||
EditCollectionAction,
|
EditCollectionAction,
|
||||||
EditFieldAction,
|
EditFieldAction,
|
||||||
@ -67,9 +79,11 @@ export const CollectionManagerPane = () => {
|
|||||||
OverridingFieldAction,
|
OverridingFieldAction,
|
||||||
ViewCollectionField,
|
ViewCollectionField,
|
||||||
ViewFieldAction,
|
ViewFieldAction,
|
||||||
|
EditCategory,
|
||||||
|
EditCategoryAction,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Card>
|
// </Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -103,12 +117,15 @@ export const CollectionManagerShortcut2 = () => {
|
|||||||
schema={schema}
|
schema={schema}
|
||||||
components={{
|
components={{
|
||||||
ConfigurationTable,
|
ConfigurationTable,
|
||||||
|
ConfigurationTabs,
|
||||||
AddFieldAction,
|
AddFieldAction,
|
||||||
EditFieldAction,
|
EditFieldAction,
|
||||||
OverridingFieldAction,
|
OverridingFieldAction,
|
||||||
ViewFieldAction,
|
ViewFieldAction,
|
||||||
AddCollectionAction,
|
AddCollectionAction,
|
||||||
EditCollectionAction,
|
EditCollectionAction,
|
||||||
|
AddCategoryAction,
|
||||||
|
EditCategoryAction,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ActionContext.Provider>
|
</ActionContext.Provider>
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { useForm } from '@formily/react';
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useAPIClient } from '../../api-client';
|
||||||
|
import { ActionContext, SchemaComponent, useActionContext } from '../../schema-component';
|
||||||
|
import { useCancelAction } from '../action-hooks';
|
||||||
|
import { CollectionCategroriesContext } from '../context';
|
||||||
|
import * as components from './components';
|
||||||
|
import { collectionCategorySchema } from './schemas/collections';
|
||||||
|
|
||||||
|
const useCreateCategry = () => {
|
||||||
|
const form = useForm();
|
||||||
|
const ctx = useActionContext();
|
||||||
|
const { refresh } = useContext(CollectionCategroriesContext);
|
||||||
|
const api = useAPIClient();
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
await form.submit();
|
||||||
|
const values = cloneDeep(form.values);
|
||||||
|
await api.resource('collectionCategories').create({
|
||||||
|
values: {
|
||||||
|
...values,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
ctx.setVisible(false);
|
||||||
|
await form.reset();
|
||||||
|
await refresh();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AddCategory = (props) => {
|
||||||
|
return <AddCategoryAction {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AddCategoryAction = (props) => {
|
||||||
|
const { scope, getContainer, children } = props;
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<ActionContext.Provider value={{ visible, setVisible }}>
|
||||||
|
<div onClick={() => setVisible(true)} title={t('Add category')}>
|
||||||
|
{children || <PlusOutlined />}
|
||||||
|
</div>
|
||||||
|
<SchemaComponent
|
||||||
|
schema={collectionCategorySchema}
|
||||||
|
components={{ ...components }}
|
||||||
|
scope={{
|
||||||
|
getContainer,
|
||||||
|
useCancelAction,
|
||||||
|
createOnly: true,
|
||||||
|
useCreateCategry,
|
||||||
|
...scope,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ActionContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
@ -15,7 +15,7 @@ import { useResourceActionContext, useResourceContext } from '../ResourceActionP
|
|||||||
import * as components from './components';
|
import * as components from './components';
|
||||||
import { templateOptions } from './templates';
|
import { templateOptions } from './templates';
|
||||||
|
|
||||||
const getSchema = (schema, record: any, compile): ISchema => {
|
const getSchema = (schema, category, compile): ISchema => {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -30,6 +30,7 @@ const getSchema = (schema, record: any, compile): ISchema => {
|
|||||||
const initialValue: any = {
|
const initialValue: any = {
|
||||||
name: `t_${uid()}`,
|
name: `t_${uid()}`,
|
||||||
template: schema.name,
|
template: schema.name,
|
||||||
|
category,
|
||||||
...cloneDeep(schema.default),
|
...cloneDeep(schema.default),
|
||||||
};
|
};
|
||||||
if (initialValue.reverseField) {
|
if (initialValue.reverseField) {
|
||||||
@ -234,6 +235,9 @@ export const AddCollectionAction = (props) => {
|
|||||||
const items = templateOptions().map((option) => {
|
const items = templateOptions().map((option) => {
|
||||||
return { label: compile(option.title), key: option.name };
|
return { label: compile(option.title), key: option.name };
|
||||||
});
|
});
|
||||||
|
const {
|
||||||
|
state: { category },
|
||||||
|
} = useResourceActionContext();
|
||||||
return (
|
return (
|
||||||
<RecordProvider record={record}>
|
<RecordProvider record={record}>
|
||||||
<ActionContext.Provider value={{ visible, setVisible }}>
|
<ActionContext.Provider value={{ visible, setVisible }}>
|
||||||
@ -248,7 +252,7 @@ export const AddCollectionAction = (props) => {
|
|||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
}}
|
}}
|
||||||
onClick={(info) => {
|
onClick={(info) => {
|
||||||
const schema = getSchema(getTemplate(info.key), record, compile);
|
const schema = getSchema(getTemplate(info.key), category, compile);
|
||||||
setSchema(schema);
|
setSchema(schema);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
}}
|
}}
|
||||||
|
@ -7,6 +7,7 @@ import { useCurrentAppInfo } from '../../appInfo';
|
|||||||
import { useRecord } from '../../record-provider';
|
import { useRecord } from '../../record-provider';
|
||||||
import { SchemaComponent, SchemaComponentContext, useCompile } from '../../schema-component';
|
import { SchemaComponent, SchemaComponentContext, useCompile } from '../../schema-component';
|
||||||
import { useCancelAction } from '../action-hooks';
|
import { useCancelAction } from '../action-hooks';
|
||||||
|
import { CollectionCategroriesContext } from '../context';
|
||||||
import { useCollectionManager } from '../hooks/useCollectionManager';
|
import { useCollectionManager } from '../hooks/useCollectionManager';
|
||||||
import { DataSourceContext } from '../sub-table';
|
import { DataSourceContext } from '../sub-table';
|
||||||
import { AddSubFieldAction } from './AddSubFieldAction';
|
import { AddSubFieldAction } from './AddSubFieldAction';
|
||||||
@ -76,6 +77,7 @@ export const ConfigurationTable = () => {
|
|||||||
const {
|
const {
|
||||||
data: { database },
|
data: { database },
|
||||||
} = useCurrentAppInfo();
|
} = useCurrentAppInfo();
|
||||||
|
const data = useContext(CollectionCategroriesContext);
|
||||||
const collectonsRef: any = useRef();
|
const collectonsRef: any = useRef();
|
||||||
collectonsRef.current = collections;
|
collectonsRef.current = collections;
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
@ -93,9 +95,14 @@ export const ConfigurationTable = () => {
|
|||||||
value: item.name,
|
value: item.name,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
const loadCategories = async () => {
|
||||||
|
return data.data.map((item: any) => ({
|
||||||
|
label: compile(item.name),
|
||||||
|
value: item.id,
|
||||||
|
}));
|
||||||
|
};
|
||||||
const ctx = useContext(SchemaComponentContext);
|
const ctx = useContext(SchemaComponentContext);
|
||||||
return (
|
return (
|
||||||
<div>
|
|
||||||
<SchemaComponentContext.Provider value={{ ...ctx, designable: false }}>
|
<SchemaComponentContext.Provider value={{ ...ctx, designable: false }}>
|
||||||
<SchemaComponent
|
<SchemaComponent
|
||||||
schema={collectionSchema}
|
schema={collectionSchema}
|
||||||
@ -111,6 +118,7 @@ export const ConfigurationTable = () => {
|
|||||||
useSelectedRowKeys,
|
useSelectedRowKeys,
|
||||||
useAsyncDataSource,
|
useAsyncDataSource,
|
||||||
loadCollections,
|
loadCollections,
|
||||||
|
loadCategories,
|
||||||
useCurrentFields,
|
useCurrentFields,
|
||||||
useNewId,
|
useNewId,
|
||||||
useCancelAction,
|
useCancelAction,
|
||||||
@ -119,6 +127,5 @@ export const ConfigurationTable = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</SchemaComponentContext.Provider>
|
</SchemaComponentContext.Provider>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,255 @@
|
|||||||
|
import { MenuOutlined } from '@ant-design/icons';
|
||||||
|
import {
|
||||||
|
DndContext,
|
||||||
|
DragEndEvent,
|
||||||
|
DragOverlay,
|
||||||
|
MouseSensor,
|
||||||
|
useDraggable,
|
||||||
|
useDroppable,
|
||||||
|
useSensor,
|
||||||
|
useSensors
|
||||||
|
} from '@dnd-kit/core';
|
||||||
|
import { observer, RecursionField } from '@formily/react';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
|
import { Badge, Card, Dropdown, Menu, Modal, Tabs } from 'antd';
|
||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import { useAPIClient } from '../../api-client';
|
||||||
|
import { SchemaComponent, SchemaComponentOptions, useCompile } from '../../schema-component';
|
||||||
|
import { CollectionCategroriesContext } from '../context';
|
||||||
|
import { useResourceActionContext } from '../ResourceActionProvider';
|
||||||
|
import { collectionTableSchema } from './schemas/collections';
|
||||||
|
|
||||||
|
function Draggable(props) {
|
||||||
|
const { attributes, listeners, setNodeRef } = useDraggable({
|
||||||
|
id: props.id,
|
||||||
|
data: props.data,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div ref={setNodeRef} {...listeners} {...attributes}>
|
||||||
|
<div>{props.children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Droppable(props) {
|
||||||
|
const { isOver, setNodeRef } = useDroppable({
|
||||||
|
id: props.id,
|
||||||
|
data: props.data,
|
||||||
|
});
|
||||||
|
const style = isOver
|
||||||
|
? {
|
||||||
|
color: 'green',
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={setNodeRef} style={style}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TabTitle = observer(({ item }: { item: any }) => {
|
||||||
|
return (
|
||||||
|
<Droppable id={item.id.toString()} data={item}>
|
||||||
|
<div>
|
||||||
|
<Draggable id={item.id.toString()} data={item}>
|
||||||
|
<TabBar item={item} />
|
||||||
|
</Draggable>
|
||||||
|
</div>
|
||||||
|
</Droppable>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const TabBar = ({ item }) => {
|
||||||
|
const compile = useCompile();
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<Badge color={item.color} />
|
||||||
|
{compile(item.name)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const DndProvider = observer((props) => {
|
||||||
|
const [activeTab, setActiveId] = useState(null);
|
||||||
|
const { refresh } = useContext(CollectionCategroriesContext);
|
||||||
|
const { refresh: refreshCM } = useResourceActionContext();
|
||||||
|
const api = useAPIClient();
|
||||||
|
const onDragEnd = async (props: DragEndEvent) => {
|
||||||
|
const { active, over } = props;
|
||||||
|
setTimeout(() => {
|
||||||
|
setActiveId(null);
|
||||||
|
});
|
||||||
|
if (over && over.id !== active.id) {
|
||||||
|
await api.resource('collectionCategories').move({
|
||||||
|
sourceId: active.id,
|
||||||
|
targetId: over.id,
|
||||||
|
});
|
||||||
|
await refresh();
|
||||||
|
await refreshCM();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function onDragStart(event) {
|
||||||
|
setActiveId(event.active?.data.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mouseSensor = useSensor(MouseSensor, {
|
||||||
|
activationConstraint: {
|
||||||
|
distance: 10,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const sensors = useSensors(mouseSensor);
|
||||||
|
return (
|
||||||
|
<DndContext sensors={sensors} onDragEnd={onDragEnd} onDragStart={onDragStart}>
|
||||||
|
{props.children}
|
||||||
|
<DragOverlay>
|
||||||
|
{activeTab ? <span style={{ whiteSpace: 'nowrap' }}>{<TabBar item={activeTab} />}</span> : null}
|
||||||
|
</DragOverlay>
|
||||||
|
</DndContext>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
export const ConfigurationTabs = () => {
|
||||||
|
const { data, refresh } = useContext(CollectionCategroriesContext);
|
||||||
|
const { refresh: refreshCM, run, defaultRequest, setState } = useResourceActionContext();
|
||||||
|
const [key, setKey] = useState('all');
|
||||||
|
const tabsItems = data
|
||||||
|
.sort((a, b) => b.sort - a.sort)
|
||||||
|
.concat()
|
||||||
|
.map((v) => {
|
||||||
|
return {
|
||||||
|
...v,
|
||||||
|
schema: collectionTableSchema,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
!tabsItems.find((v) => v.id === 'all') &&
|
||||||
|
tabsItems.unshift({
|
||||||
|
name: '{{t("All collections")}}',
|
||||||
|
id: 'all',
|
||||||
|
sort: 0,
|
||||||
|
closable: false,
|
||||||
|
schema: collectionTableSchema,
|
||||||
|
});
|
||||||
|
const compile = useCompile();
|
||||||
|
const [activeKey, setActiveKey] = useState('all');
|
||||||
|
const api = useAPIClient();
|
||||||
|
const onChange = (key: string) => {
|
||||||
|
setActiveKey(key);
|
||||||
|
setKey(uid());
|
||||||
|
if (key !== 'all') {
|
||||||
|
const prevFilter = defaultRequest?.params?.filter;
|
||||||
|
const filter = { $and: [prevFilter, { 'category.id': key }] };
|
||||||
|
run({ filter });
|
||||||
|
setState?.({ category: [+key], params: [{ filter }] });
|
||||||
|
} else {
|
||||||
|
run();
|
||||||
|
setState?.({ category: [], params: [] });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const remove = (key: any) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: compile("{{t('Delete category')}}"),
|
||||||
|
content: compile("{{t('Are you sure you want to delete it?')}}"),
|
||||||
|
onOk: async () => {
|
||||||
|
await api.resource('collectionCategories').destroy({
|
||||||
|
filter: {
|
||||||
|
id: key,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
key === +activeKey && setActiveKey('all');
|
||||||
|
await refresh();
|
||||||
|
await refreshCM();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadCategories = async () => {
|
||||||
|
return data.map((item: any) => ({
|
||||||
|
label: compile(item.name),
|
||||||
|
value: item.id,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
const menu = (item) => (
|
||||||
|
<Menu>
|
||||||
|
<Menu.Item key={'edit'}>
|
||||||
|
<SchemaComponent
|
||||||
|
schema={{
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
'x-component': 'EditCategory',
|
||||||
|
'x-component-props': {
|
||||||
|
item: item,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="delete" onClick={() => remove(item.id)}>
|
||||||
|
{compile("{{t('Delete category')}}")}
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<DndProvider>
|
||||||
|
<Tabs
|
||||||
|
addIcon={
|
||||||
|
<SchemaComponent
|
||||||
|
schema={{
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
addCategories: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Add category") }}',
|
||||||
|
'x-component': 'AddCategory',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onChange={onChange}
|
||||||
|
defaultActiveKey="all"
|
||||||
|
type="editable-card"
|
||||||
|
destroyInactiveTabPane={true}
|
||||||
|
tabBarStyle={{ marginBottom: '0px' }}
|
||||||
|
>
|
||||||
|
{tabsItems.map((item) => {
|
||||||
|
return (
|
||||||
|
<Tabs.TabPane
|
||||||
|
tab={
|
||||||
|
item.id !== 'all' ? (
|
||||||
|
<div data-no-dnd="true">
|
||||||
|
<TabTitle item={item} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
compile(item.name)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
key={item.id}
|
||||||
|
closable={item.closable}
|
||||||
|
closeIcon={
|
||||||
|
<Dropdown overlay={menu(item)}>
|
||||||
|
<MenuOutlined />
|
||||||
|
</Dropdown>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Card bordered={false}>
|
||||||
|
<SchemaComponentOptions
|
||||||
|
inherit
|
||||||
|
scope={{ loadCategories, categoryVisible: item.id === 'all', categoryId: item.id }}
|
||||||
|
>
|
||||||
|
<RecursionField name={key} schema={item.schema} onlyRenderProperties />
|
||||||
|
</SchemaComponentOptions>
|
||||||
|
</Card>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Tabs>
|
||||||
|
</DndProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,81 @@
|
|||||||
|
import { useForm } from '@formily/react';
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import { useAPIClient, useRequest } from '../../api-client';
|
||||||
|
import { RecordProvider, useRecord } from '../../record-provider';
|
||||||
|
import { ActionContext, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
||||||
|
import { useCancelAction } from '../action-hooks';
|
||||||
|
import { CollectionCategroriesContext } from '../context';
|
||||||
|
import { useResourceActionContext } from '../ResourceActionProvider';
|
||||||
|
import * as components from './components';
|
||||||
|
import { collectionCategoryEditSchema } from './schemas/collections';
|
||||||
|
|
||||||
|
const useEditCategry = () => {
|
||||||
|
const form = useForm();
|
||||||
|
const ctx = useActionContext();
|
||||||
|
const { refresh } = useContext(CollectionCategroriesContext);
|
||||||
|
const { refresh: refreshCM } = useResourceActionContext();
|
||||||
|
|
||||||
|
const api = useAPIClient();
|
||||||
|
const { id } = useRecord();
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
await form.submit();
|
||||||
|
const values = cloneDeep(form.values);
|
||||||
|
await api.resource('collectionCategories').update({
|
||||||
|
filter: { id: id },
|
||||||
|
values: {
|
||||||
|
...values,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
ctx.setVisible(false);
|
||||||
|
await form.reset();
|
||||||
|
await refresh();
|
||||||
|
await refreshCM();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const useValuesFromRecord = (options) => {
|
||||||
|
const record = useRecord();
|
||||||
|
const result = useRequest(() => Promise.resolve({ data: { ...record } }), {
|
||||||
|
...options,
|
||||||
|
manual: true,
|
||||||
|
});
|
||||||
|
const ctx = useActionContext();
|
||||||
|
useEffect(() => {
|
||||||
|
if (ctx.visible) {
|
||||||
|
result.run();
|
||||||
|
}
|
||||||
|
}, [ctx.visible]);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditCategory = (props) => {
|
||||||
|
return <EditCategoryAction {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditCategoryAction = (props) => {
|
||||||
|
const { scope, getContainer, item, children } = props;
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const compile = useCompile();
|
||||||
|
return (
|
||||||
|
<RecordProvider record={item}>
|
||||||
|
<ActionContext.Provider value={{ visible, setVisible }}>
|
||||||
|
<>{children || <span onClick={() => setVisible(true)}>{compile('{{ t("Edit category") }}')}</span>}</>
|
||||||
|
<SchemaComponent
|
||||||
|
schema={collectionCategoryEditSchema}
|
||||||
|
components={{ ...components }}
|
||||||
|
scope={{
|
||||||
|
getContainer,
|
||||||
|
useCancelAction,
|
||||||
|
createOnly: true,
|
||||||
|
useEditCategry,
|
||||||
|
useValuesFromRecord,
|
||||||
|
...scope,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ActionContext.Provider>
|
||||||
|
</RecordProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -78,7 +78,7 @@ const getSchema = (schema: IField, record: any, compile, getContainer): ISchema
|
|||||||
|
|
||||||
export const useValuesFromRecord = (options) => {
|
export const useValuesFromRecord = (options) => {
|
||||||
const record = useRecord();
|
const record = useRecord();
|
||||||
const result = useRequest(() => Promise.resolve({ data: { autoGenId: true, ...record } }), {
|
const result = useRequest(() => Promise.resolve({ data: { autoGenId: true, ...record,category:record?.category.map((v)=>v.id) } }), {
|
||||||
...options,
|
...options,
|
||||||
manual: true,
|
manual: true,
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
import { observer } from '@formily/react';
|
||||||
|
import { Tag } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { useCompile } from '../../../schema-component';
|
||||||
|
|
||||||
|
export const CollectionCategory = observer((props: any) => {
|
||||||
|
const { value } = props;
|
||||||
|
const compile = useCompile();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{value.map((item) => {
|
||||||
|
return <Tag color={item.color}>{compile(item?.name)}</Tag>;
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
@ -11,6 +11,9 @@ export * from './OverridingCollectionField';
|
|||||||
export * from './ViewInheritedField';
|
export * from './ViewInheritedField';
|
||||||
export * from './AddCollectionAction';
|
export * from './AddCollectionAction';
|
||||||
export * from './EditCollectionAction';
|
export * from './EditCollectionAction';
|
||||||
|
export * from './ConfigurationTabs';
|
||||||
|
export * from './AddCategoryAction';
|
||||||
|
export * from './EditCategoryAction'
|
||||||
|
|
||||||
registerValidateFormats({
|
registerValidateFormats({
|
||||||
uid: /^[A-Za-z0-9][A-Za-z0-9_-]*$/,
|
uid: /^[A-Za-z0-9][A-Za-z0-9_-]*$/,
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { ISchema, Schema } from '@formily/react';
|
import { ISchema, Schema } from '@formily/react';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAPIClient } from '../../../api-client';
|
import { useAPIClient } from '../../../api-client';
|
||||||
import { i18n } from '../../../i18n';
|
import { i18n } from '../../../i18n';
|
||||||
import { CollectionOptions } from '../../types';
|
import { CollectionOptions } from '../../types';
|
||||||
import { CollectionTemplate } from '../components/CollectionTemplate';
|
import { CollectionTemplate } from '../components/CollectionTemplate';
|
||||||
|
import { CollectionCategory } from '../components/CollectionCategory';
|
||||||
import { collectionFieldSchema } from './collectionFields';
|
import { collectionFieldSchema } from './collectionFields';
|
||||||
|
|
||||||
const compile = (source) => {
|
const compile = (source) => {
|
||||||
@ -94,175 +96,197 @@ export const collectionSchema: ISchema = {
|
|||||||
filter: {
|
filter: {
|
||||||
'hidden.$isFalsy': true,
|
'hidden.$isFalsy': true,
|
||||||
},
|
},
|
||||||
appends: [],
|
appends: ['category'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
actions: {
|
tabs: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'ActionBar',
|
'x-component': 'ConfigurationTabs',
|
||||||
'x-component-props': {
|
},
|
||||||
style: {
|
},
|
||||||
marginBottom: 16,
|
},
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const collectionTableSchema: ISchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
filter: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Filter") }}',
|
||||||
|
default: {
|
||||||
|
$and: [{ title: { $includes: '' } }, { name: { $includes: '' } }],
|
||||||
},
|
},
|
||||||
properties: {
|
'x-action': 'filter',
|
||||||
filter: {
|
'x-component': 'Filter.Action',
|
||||||
type: 'void',
|
'x-component-props': {
|
||||||
title: '{{ t("Filter") }}',
|
icon: 'FilterOutlined',
|
||||||
default: {
|
useProps: '{{ cm.useFilterActionProps }}',
|
||||||
$and: [{ title: { $includes: '' } }, { name: { $includes: '' } }],
|
},
|
||||||
},
|
'x-align': 'left',
|
||||||
'x-action': 'filter',
|
},
|
||||||
'x-component': 'Filter.Action',
|
delete: {
|
||||||
'x-component-props': {
|
type: 'void',
|
||||||
icon: 'FilterOutlined',
|
title: '{{ t("Delete") }}',
|
||||||
useProps: '{{ cm.useFilterActionProps }}',
|
'x-component': 'Action',
|
||||||
},
|
'x-component-props': {
|
||||||
'x-align': 'left',
|
icon: 'DeleteOutlined',
|
||||||
},
|
useAction: '{{ cm.useBulkDestroyActionAndRefreshCM }}',
|
||||||
delete: {
|
confirm: {
|
||||||
type: 'void',
|
title: "{{t('Delete record')}}",
|
||||||
title: '{{ t("Delete") }}',
|
content: "{{t('Are you sure you want to delete it?')}}",
|
||||||
'x-component': 'Action',
|
|
||||||
'x-component-props': {
|
|
||||||
icon: 'DeleteOutlined',
|
|
||||||
useAction: '{{ cm.useBulkDestroyActionAndRefreshCM }}',
|
|
||||||
confirm: {
|
|
||||||
title: "{{t('Delete record')}}",
|
|
||||||
content: "{{t('Are you sure you want to delete it?')}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
type: 'void',
|
|
||||||
title: '{{ t("Create collection") }}',
|
|
||||||
'x-component': 'AddCollection',
|
|
||||||
'x-component-props': {
|
|
||||||
type: 'primary',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
table: {
|
create: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-uid': 'input',
|
title: '{{ t("Create collection") }}',
|
||||||
'x-component': 'Table.Void',
|
'x-component': 'AddCollection',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
rowKey: 'name',
|
type: 'primary',
|
||||||
rowSelection: {
|
},
|
||||||
type: 'checkbox',
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-uid': 'input',
|
||||||
|
'x-component': 'Table.Void',
|
||||||
|
'x-component-props': {
|
||||||
|
rowKey: 'name',
|
||||||
|
rowSelection: {
|
||||||
|
type: 'checkbox',
|
||||||
|
},
|
||||||
|
useDataSource: '{{ cm.useDataSourceFromRAC }}',
|
||||||
|
useAction() {
|
||||||
|
const api = useAPIClient();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return {
|
||||||
|
async move(from, to) {
|
||||||
|
await api.resource('collections').move({
|
||||||
|
sourceId: from.key,
|
||||||
|
targetId: to.key,
|
||||||
|
});
|
||||||
|
message.success(t('Saved successfully'), 0.2);
|
||||||
},
|
},
|
||||||
useDataSource: '{{ cm.useDataSourceFromRAC }}',
|
};
|
||||||
useAction() {
|
},
|
||||||
const api = useAPIClient();
|
},
|
||||||
const { t } = useTranslation();
|
properties: {
|
||||||
return {
|
column1: {
|
||||||
async move(from, to) {
|
type: 'void',
|
||||||
console.log(from, to);
|
'x-decorator': 'Table.Column.Decorator',
|
||||||
await api.resource('collections').move({
|
'x-component': 'Table.Column',
|
||||||
sourceId: from.key,
|
properties: {
|
||||||
targetId: to.key,
|
title: {
|
||||||
});
|
'x-component': 'CollectionField',
|
||||||
message.success(t('Saved successfully'), 0.2);
|
'x-read-pretty': true,
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
column2: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'Table.Column.Decorator',
|
||||||
|
'x-component': 'Table.Column',
|
||||||
properties: {
|
properties: {
|
||||||
column1: {
|
name: {
|
||||||
type: 'void',
|
type: 'string',
|
||||||
'x-decorator': 'Table.Column.Decorator',
|
'x-component': 'CollectionField',
|
||||||
'x-component': 'Table.Column',
|
'x-read-pretty': true,
|
||||||
properties: {
|
|
||||||
title: {
|
|
||||||
'x-component': 'CollectionField',
|
|
||||||
'x-read-pretty': true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
column2: {
|
},
|
||||||
type: 'void',
|
},
|
||||||
'x-decorator': 'Table.Column.Decorator',
|
column3: {
|
||||||
'x-component': 'Table.Column',
|
type: 'void',
|
||||||
properties: {
|
'x-decorator': 'Table.Column.Decorator',
|
||||||
name: {
|
'x-component': 'Table.Column',
|
||||||
type: 'string',
|
title: '{{t("Collection template")}}',
|
||||||
'x-component': 'CollectionField',
|
properties: {
|
||||||
'x-read-pretty': true,
|
template: {
|
||||||
},
|
'x-component': CollectionTemplate,
|
||||||
},
|
'x-read-pretty': true,
|
||||||
},
|
},
|
||||||
column3: {
|
},
|
||||||
type: 'void',
|
},
|
||||||
'x-decorator': 'Table.Column.Decorator',
|
column4: {
|
||||||
'x-component': 'Table.Column',
|
type: 'void',
|
||||||
title: '{{t("Collection template")}}',
|
'x-decorator': 'Table.Column.Decorator',
|
||||||
properties: {
|
'x-component': 'Table.Column',
|
||||||
template: {
|
'x-visible': 'categoryVisible',
|
||||||
'x-component': CollectionTemplate,
|
title: '{{t("Collection category")}}',
|
||||||
'x-read-pretty': true,
|
properties: {
|
||||||
},
|
category: {
|
||||||
},
|
'x-component': CollectionCategory,
|
||||||
|
'x-read-pretty': true,
|
||||||
},
|
},
|
||||||
column4: {
|
},
|
||||||
|
},
|
||||||
|
column5: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Actions") }}',
|
||||||
|
'x-component': 'Table.Column',
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
title: '{{ t("Actions") }}',
|
'x-component': 'Space',
|
||||||
'x-component': 'Table.Column',
|
'x-component-props': {
|
||||||
|
split: '|',
|
||||||
|
},
|
||||||
properties: {
|
properties: {
|
||||||
actions: {
|
view: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'Space',
|
title: '{{ t("Configure fields") }}',
|
||||||
'x-component-props': {
|
'x-component': 'Action.Link',
|
||||||
split: '|',
|
'x-component-props': {},
|
||||||
},
|
|
||||||
properties: {
|
properties: {
|
||||||
view: {
|
drawer: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
title: '{{ t("Configure fields") }}',
|
'x-component': 'Action.Drawer',
|
||||||
'x-component': 'Action.Link',
|
'x-component-props': {
|
||||||
'x-component-props': {},
|
destroyOnClose: true,
|
||||||
|
},
|
||||||
|
'x-reactions': (field) => {
|
||||||
|
const i = field.path.segments[1];
|
||||||
|
const table = field.form.getValuesIn(`table.${i}`);
|
||||||
|
if (table) {
|
||||||
|
field.title = `${compile(table.title)} - ${compile('{{ t("Configure fields") }}')}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
properties: {
|
properties: {
|
||||||
drawer: {
|
collectionFieldSchema,
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Action.Drawer',
|
|
||||||
'x-component-props': {
|
|
||||||
destroyOnClose: true,
|
|
||||||
},
|
|
||||||
'x-reactions': (field) => {
|
|
||||||
const i = field.path.segments[1];
|
|
||||||
const table = field.form.getValuesIn(`table.${i}`);
|
|
||||||
if (table) {
|
|
||||||
field.title = `${compile(table.title)} - ${compile('{{ t("Configure fields") }}')}`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
collectionFieldSchema,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: {
|
},
|
||||||
type: 'void',
|
},
|
||||||
title: '{{ t("Edit") }}',
|
update: {
|
||||||
'x-component': 'EditCollection',
|
type: 'void',
|
||||||
'x-component-props': {
|
title: '{{ t("Edit") }}',
|
||||||
type: 'primary',
|
'x-component': 'EditCollection',
|
||||||
},
|
'x-component-props': {
|
||||||
},
|
type: 'primary',
|
||||||
delete: {
|
},
|
||||||
type: 'void',
|
},
|
||||||
title: '{{ t("Delete") }}',
|
delete: {
|
||||||
'x-component': 'Action.Link',
|
type: 'void',
|
||||||
'x-component-props': {
|
title: '{{ t("Delete") }}',
|
||||||
confirm: {
|
'x-component': 'Action.Link',
|
||||||
title: "{{t('Delete record')}}",
|
'x-component-props': {
|
||||||
content: "{{t('Are you sure you want to delete it?')}}",
|
confirm: {
|
||||||
},
|
title: "{{t('Delete record')}}",
|
||||||
useAction: '{{ cm.useDestroyActionAndRefreshCM }}',
|
content: "{{t('Are you sure you want to delete it?')}}",
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
useAction: '{{ cm.useDestroyActionAndRefreshCM }}',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -273,3 +297,114 @@ export const collectionSchema: ISchema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const collectionCategorySchema: ISchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
form: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
'x-component': 'Action.Modal',
|
||||||
|
title: '{{ t("Add category") }}',
|
||||||
|
'x-component-props': {
|
||||||
|
width: 520,
|
||||||
|
getContainer: '{{ getContainer }}',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Category name")}}',
|
||||||
|
required: true,
|
||||||
|
'x-disabled': '{{ !createOnly }}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Color")}}',
|
||||||
|
required: false,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ColorSelect',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Modal.Footer',
|
||||||
|
properties: {
|
||||||
|
action1: {
|
||||||
|
title: '{{ t("Cancel") }}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
useAction: '{{ useCancelAction }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action2: {
|
||||||
|
title: '{{ t("Submit") }}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
useAction: '{{ useCreateCategry }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const collectionCategoryEditSchema: ISchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
form: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
'x-decorator-props': {
|
||||||
|
useValues: '{{ useValuesFromRecord }}',
|
||||||
|
},
|
||||||
|
'x-component': 'Action.Modal',
|
||||||
|
title: '{{ t("Edit category") }}',
|
||||||
|
'x-component-props': {
|
||||||
|
width: 520,
|
||||||
|
getContainer: '{{ getContainer }}',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Category name")}}',
|
||||||
|
required: true,
|
||||||
|
'x-disabled': '{{ !createOnly }}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Color")}}',
|
||||||
|
required: false,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ColorSelect',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Modal.Footer',
|
||||||
|
properties: {
|
||||||
|
action1: {
|
||||||
|
title: '{{ t("Cancel") }}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
useAction: '{{ useCancelAction }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action2: {
|
||||||
|
title: '{{ t("Submit") }}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
useAction: '{{ useEditCategry }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
@ -329,5 +329,9 @@ export const useFilterActionProps = () => {
|
|||||||
const { collection } = useResourceContext();
|
const { collection } = useResourceContext();
|
||||||
const options = useFilterFieldOptions(collection.fields);
|
const options = useFilterFieldOptions(collection.fields);
|
||||||
const service = useResourceActionContext();
|
const service = useResourceActionContext();
|
||||||
return useFilterFieldProps({ options, params: service.params, service });
|
return useFilterFieldProps({
|
||||||
|
options,
|
||||||
|
params: service.state?.params?.[0] || service.params,
|
||||||
|
service,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -9,3 +9,5 @@ export const CollectionManagerContext = createContext<CollectionManagerOptions>(
|
|||||||
export const CollectionContext = createContext<CollectionOptions>({});
|
export const CollectionContext = createContext<CollectionOptions>({});
|
||||||
|
|
||||||
export const CollectionFieldContext = createContext<CollectionFieldOptions>({});
|
export const CollectionFieldContext = createContext<CollectionFieldOptions>({});
|
||||||
|
|
||||||
|
export const CollectionCategroriesContext = createContext({ data: [], refresh: () => {} });
|
||||||
|
@ -38,5 +38,5 @@ export const calendar: ICollectionTemplate = {
|
|||||||
availableFieldInterfaces: {
|
availableFieldInterfaces: {
|
||||||
include: [],
|
include: [],
|
||||||
},
|
},
|
||||||
configurableProperties: getConfigurableProperties('title', 'name', 'inherits'),
|
configurableProperties: getConfigurableProperties('title', 'name', 'inherits','category'),
|
||||||
};
|
};
|
||||||
|
@ -9,5 +9,5 @@ export const general: ICollectionTemplate = {
|
|||||||
default: {
|
default: {
|
||||||
fields: [],
|
fields: [],
|
||||||
},
|
},
|
||||||
configurableProperties: getConfigurableProperties('title', 'name', 'inherits', 'moreOptions'),
|
configurableProperties: getConfigurableProperties('title', 'name', 'inherits', 'category', 'moreOptions'),
|
||||||
};
|
};
|
||||||
|
@ -102,6 +102,17 @@ export const defaultConfigurableProperties = {
|
|||||||
'x-visible': '{{ enableInherits}}',
|
'x-visible': '{{ enableInherits}}',
|
||||||
'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'],
|
'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'],
|
||||||
},
|
},
|
||||||
|
category: {
|
||||||
|
title: '{{t("Categories")}}',
|
||||||
|
type: 'hasMany',
|
||||||
|
name: 'category',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
'x-component-props': {
|
||||||
|
mode: 'multiple',
|
||||||
|
},
|
||||||
|
'x-reactions': ['{{useAsyncDataSource(loadCategories)}}'],
|
||||||
|
},
|
||||||
...moreOptions,
|
...moreOptions,
|
||||||
moreOptions: {
|
moreOptions: {
|
||||||
title: '{{t("More options")}}',
|
title: '{{t("More options")}}',
|
||||||
@ -124,6 +135,7 @@ export type DefaultConfigurableKeys =
|
|||||||
| 'name'
|
| 'name'
|
||||||
| 'title'
|
| 'title'
|
||||||
| 'inherits'
|
| 'inherits'
|
||||||
|
| 'category'
|
||||||
| 'autoGenId'
|
| 'autoGenId'
|
||||||
| 'createdBy'
|
| 'createdBy'
|
||||||
| 'updatedBy'
|
| 'updatedBy'
|
||||||
|
@ -34,6 +34,13 @@ export default {
|
|||||||
"UI editor": "UI editor",
|
"UI editor": "UI editor",
|
||||||
"Collection": "Collection",
|
"Collection": "Collection",
|
||||||
"Collections & Fields": "Collections & Fields",
|
"Collections & Fields": "Collections & Fields",
|
||||||
|
"All collections":"All collections",
|
||||||
|
"Add category":"Add category",
|
||||||
|
"Delete category":"Delete category",
|
||||||
|
"Edit category":"Edit category",
|
||||||
|
"Collection category":"Collection category",
|
||||||
|
"Sort":"Sort",
|
||||||
|
"Category name":"Category name",
|
||||||
"Roles & Permissions": "Roles & Permissions",
|
"Roles & Permissions": "Roles & Permissions",
|
||||||
"Edit profile": "Edit profile",
|
"Edit profile": "Edit profile",
|
||||||
"Change password": "Change password",
|
"Change password": "Change password",
|
||||||
|
@ -34,6 +34,13 @@ export default {
|
|||||||
"UI editor": "UI エディタ",
|
"UI editor": "UI エディタ",
|
||||||
"Collection": "コレクション",
|
"Collection": "コレクション",
|
||||||
"Collections & Fields": "コレクションとフィールド",
|
"Collections & Fields": "コレクションとフィールド",
|
||||||
|
"All collections":"すべてのデータテーブル",
|
||||||
|
"Add category":"分類の追加",
|
||||||
|
"Edit category":"分類の編集",
|
||||||
|
"Sort":"ソート#ソート#",
|
||||||
|
"Category name":"分類名",
|
||||||
|
"Delete category":"分類の削除",
|
||||||
|
"Collection category":"Collection category",
|
||||||
"Roles & Permissions": "役割と権限",
|
"Roles & Permissions": "役割と権限",
|
||||||
"Edit profile": "プロフィール",
|
"Edit profile": "プロフィール",
|
||||||
"Change password": "パスワード変更",
|
"Change password": "パスワード変更",
|
||||||
|
@ -34,6 +34,13 @@ export default {
|
|||||||
"UI editor": "界面配置",
|
"UI editor": "界面配置",
|
||||||
"Collection": "数据表",
|
"Collection": "数据表",
|
||||||
"Collections & Fields": "数据表配置",
|
"Collections & Fields": "数据表配置",
|
||||||
|
"All collections":"全部数据表",
|
||||||
|
"Add category":"添加分类",
|
||||||
|
"Delete category":"删除分类",
|
||||||
|
"Edit category":"编辑分类",
|
||||||
|
"Collection category":"数据表类别",
|
||||||
|
"Sort":"排序",
|
||||||
|
"Category name":"分类名称",
|
||||||
"Roles & Permissions": "角色和权限",
|
"Roles & Permissions": "角色和权限",
|
||||||
"Edit profile": "个人资料",
|
"Edit profile": "个人资料",
|
||||||
"Change password": "修改密码",
|
"Change password": "修改密码",
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
import { MenuOutlined } from '@ant-design/icons';
|
import { MenuOutlined } from '@ant-design/icons';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { ArrayField, Field } from '@formily/core';
|
import { ArrayField, Field } from '@formily/core';
|
||||||
import { observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
|
import {
|
||||||
|
observer,
|
||||||
|
RecursionField,
|
||||||
|
Schema,
|
||||||
|
useField,
|
||||||
|
useFieldSchema,
|
||||||
|
SchemaExpressionScopeContext,
|
||||||
|
} from '@formily/react';
|
||||||
import { Table, TableColumnProps } from 'antd';
|
import { Table, TableColumnProps } from 'antd';
|
||||||
import { default as classNames, default as cls } from 'classnames';
|
import { default as classNames, default as cls } from 'classnames';
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useContext } from 'react';
|
||||||
import ReactDragListView from 'react-drag-listview';
|
import ReactDragListView from 'react-drag-listview';
|
||||||
import { DndContext } from '../..';
|
import { DndContext } from '../..';
|
||||||
import { RecordIndexProvider, RecordProvider, useRequest, useSchemaInitializer } from '../../../';
|
import { RecordIndexProvider, RecordProvider, useRequest, useSchemaInitializer } from '../../../';
|
||||||
@ -13,6 +20,11 @@ const isColumnComponent = (schema: Schema) => {
|
|||||||
return schema['x-component']?.endsWith('.Column') > -1;
|
return schema['x-component']?.endsWith('.Column') > -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const useScope = (key: any) => {
|
||||||
|
const scope = useContext(SchemaExpressionScopeContext);
|
||||||
|
return scope[key] !== false;
|
||||||
|
};
|
||||||
|
|
||||||
const useTableColumns = () => {
|
const useTableColumns = () => {
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const field = useField<ArrayField>();
|
const field = useField<ArrayField>();
|
||||||
@ -20,9 +32,10 @@ const useTableColumns = () => {
|
|||||||
const { exists, render } = useSchemaInitializer(schema['x-initializer']);
|
const { exists, render } = useSchemaInitializer(schema['x-initializer']);
|
||||||
const columns = schema
|
const columns = schema
|
||||||
.reduceProperties((buf, s) => {
|
.reduceProperties((buf, s) => {
|
||||||
if (isColumnComponent(s)) {
|
if (isColumnComponent(s) && useScope(s['x-visible'])) {
|
||||||
return buf.concat([s]);
|
return buf.concat([s]);
|
||||||
}
|
}
|
||||||
|
return buf
|
||||||
}, [])
|
}, [])
|
||||||
.map((s: Schema) => {
|
.map((s: Schema) => {
|
||||||
return {
|
return {
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
import { CollectionOptions } from '@nocobase/database';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'collectionCategories',
|
||||||
|
autoGenId: true,
|
||||||
|
sortable: true,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'name',
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// type: 'integer',
|
||||||
|
// name: 'sort',
|
||||||
|
// defaultValue: 0,
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'color',
|
||||||
|
defaultValue: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'collections',
|
||||||
|
target: 'collections',
|
||||||
|
foreignKey: 'categoryId',
|
||||||
|
otherKey: 'collectionName',
|
||||||
|
targetKey: 'name',
|
||||||
|
through: 'collectionCategory',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as CollectionOptions;
|
@ -50,5 +50,15 @@ export default {
|
|||||||
foreignKey: 'collectionName',
|
foreignKey: 'collectionName',
|
||||||
sortBy: 'sort',
|
sortBy: 'sort',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'category',
|
||||||
|
target: 'collectionCategories',
|
||||||
|
sourceKey: 'name',
|
||||||
|
foreignKey: 'collectionName',
|
||||||
|
otherKey: 'categoryId',
|
||||||
|
targetKey: 'id',
|
||||||
|
through: 'collectionCategory',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
} as CollectionOptions;
|
} as CollectionOptions;
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
useCollectionManager,
|
useCollectionManager,
|
||||||
useCompile,
|
useCompile,
|
||||||
useCurrentAppInfo,
|
useCurrentAppInfo,
|
||||||
useRecord
|
useRecord,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { Badge, Dropdown, Popover, Tag } from 'antd';
|
import { Badge, Dropdown, Popover, Tag } from 'antd';
|
||||||
import { groupBy } from 'lodash';
|
import { groupBy } from 'lodash';
|
||||||
@ -33,7 +33,7 @@ import {
|
|||||||
useDestroyActionAndRefreshCM,
|
useDestroyActionAndRefreshCM,
|
||||||
useDestroyFieldActionAndRefreshCM,
|
useDestroyFieldActionAndRefreshCM,
|
||||||
useUpdateCollectionActionAndRefreshCM,
|
useUpdateCollectionActionAndRefreshCM,
|
||||||
useValuesFromRecord
|
useValuesFromRecord,
|
||||||
} from '../action-hooks';
|
} from '../action-hooks';
|
||||||
import { collectiionPopoverClass, entityContainer, headClass, tableBtnClass, tableNameClass } from '../style';
|
import { collectiionPopoverClass, entityContainer, headClass, tableBtnClass, tableNameClass } from '../style';
|
||||||
import { useGCMTranslation } from '../utils';
|
import { useGCMTranslation } from '../utils';
|
||||||
@ -60,6 +60,7 @@ const Entity: React.FC<{
|
|||||||
const database = useCurrentAppInfo();
|
const database = useCurrentAppInfo();
|
||||||
const collectionData = useRef();
|
const collectionData = useRef();
|
||||||
collectionData.current = { ...item, title, inherits: item.inherits && new Proxy(item.inherits, {}) };
|
collectionData.current = { ...item, title, inherits: item.inherits && new Proxy(item.inherits, {}) };
|
||||||
|
const { category } = item;
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const loadCollections = async (field: any) => {
|
const loadCollections = async (field: any) => {
|
||||||
return targetGraph.collections?.map((collection: any) => ({
|
return targetGraph.collections?.map((collection: any) => ({
|
||||||
@ -79,8 +80,21 @@ const Entity: React.FC<{
|
|||||||
className={cx(entityContainer)}
|
className={cx(entityContainer)}
|
||||||
style={{ boxShadow: attrs?.boxShadow, border: select ? '2px dashed #f5a20a' : 0 }}
|
style={{ boxShadow: attrs?.boxShadow, border: select ? '2px dashed #f5a20a' : 0 }}
|
||||||
>
|
>
|
||||||
<div className={headClass} style={{ background: attrs?.hightLight ? '#1890ff' : null }}>
|
{category.map((v, index) => {
|
||||||
|
return (
|
||||||
|
<Badge.Ribbon
|
||||||
|
color={v.color}
|
||||||
|
style={{ width: '103%', height: '3px', marginTop: index * 5 - 8, borderRadius: 0 }}
|
||||||
|
placement="start"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<div
|
||||||
|
className={headClass}
|
||||||
|
style={{ background: attrs?.hightLight ? '#1890ff' : null, paddingTop: category.length * 3 }}
|
||||||
|
>
|
||||||
<span className={tableNameClass}>{compile(title)}</span>
|
<span className={tableNameClass}>{compile(title)}</span>
|
||||||
|
|
||||||
<div className={tableBtnClass}>
|
<div className={tableBtnClass}>
|
||||||
<SchemaComponentProvider>
|
<SchemaComponentProvider>
|
||||||
<CollectionNodeProvder setTargetNode={setTargetNode} node={node}>
|
<CollectionNodeProvder setTargetNode={setTargetNode} node={node}>
|
||||||
@ -154,6 +168,7 @@ const Entity: React.FC<{
|
|||||||
</SchemaComponentProvider>
|
</SchemaComponentProvider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PortsCom {...portsProps} />
|
<PortsCom {...portsProps} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -128,7 +128,7 @@ export const entityContainer = css`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const headClass = css`
|
export const headClass = css`
|
||||||
height: 40px;
|
height: 50px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
Loading…
Reference in New Issue
Block a user