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 React, { useContext, useState } from 'react';
|
||||
import { keyBy } from 'lodash';
|
||||
import React, { useContext, useState } from 'react';
|
||||
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 * as defaultInterfaces from './interfaces';
|
||||
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) => {
|
||||
const { service, interfaces, collections = [], refreshCM, templates } = props;
|
||||
@ -38,7 +38,7 @@ export const RemoteCollectionManagerProvider = (props: any) => {
|
||||
action: 'list',
|
||||
params: {
|
||||
paginate: false,
|
||||
appends: ['fields', 'fields.uiSchema'],
|
||||
appends: ['fields', 'fields.uiSchema','category'],
|
||||
filter: {
|
||||
// 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,
|
||||
AddCollection,
|
||||
AddCollectionAction,
|
||||
AddCategoryAction,
|
||||
AddCategory,
|
||||
EditCollection,
|
||||
EditCollectionAction,
|
||||
ConfigurationTabs,
|
||||
EditCategory,
|
||||
EditCategoryAction,
|
||||
} from './Configuration';
|
||||
|
||||
import { CollectionCategroriesProvider } from './CollectionManagerProvider';
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@ -43,6 +50,7 @@ const schema2: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
'x-decorator': 'CollectionCategroriesProvider',
|
||||
'x-component': 'ConfigurationTable',
|
||||
},
|
||||
},
|
||||
@ -50,15 +58,19 @@ const schema2: ISchema = {
|
||||
|
||||
export const CollectionManagerPane = () => {
|
||||
return (
|
||||
<Card bordered={false}>
|
||||
// <Card bordered={false}>
|
||||
<SchemaComponent
|
||||
schema={schema2}
|
||||
components={{
|
||||
CollectionCategroriesProvider,
|
||||
ConfigurationTable,
|
||||
ConfigurationTabs,
|
||||
AddFieldAction,
|
||||
AddCollectionField,
|
||||
AddCollection,
|
||||
AddCollectionAction,
|
||||
AddCategoryAction,
|
||||
AddCategory,
|
||||
EditCollection,
|
||||
EditCollectionAction,
|
||||
EditFieldAction,
|
||||
@ -67,9 +79,11 @@ export const CollectionManagerPane = () => {
|
||||
OverridingFieldAction,
|
||||
ViewCollectionField,
|
||||
ViewFieldAction,
|
||||
EditCategory,
|
||||
EditCategoryAction,
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
// </Card>
|
||||
);
|
||||
};
|
||||
|
||||
@ -103,12 +117,15 @@ export const CollectionManagerShortcut2 = () => {
|
||||
schema={schema}
|
||||
components={{
|
||||
ConfigurationTable,
|
||||
ConfigurationTabs,
|
||||
AddFieldAction,
|
||||
EditFieldAction,
|
||||
OverridingFieldAction,
|
||||
ViewFieldAction,
|
||||
AddCollectionAction,
|
||||
EditCollectionAction,
|
||||
AddCategoryAction,
|
||||
EditCategoryAction,
|
||||
}}
|
||||
/>
|
||||
</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 { templateOptions } from './templates';
|
||||
|
||||
const getSchema = (schema, record: any, compile): ISchema => {
|
||||
const getSchema = (schema, category, compile): ISchema => {
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
@ -30,6 +30,7 @@ const getSchema = (schema, record: any, compile): ISchema => {
|
||||
const initialValue: any = {
|
||||
name: `t_${uid()}`,
|
||||
template: schema.name,
|
||||
category,
|
||||
...cloneDeep(schema.default),
|
||||
};
|
||||
if (initialValue.reverseField) {
|
||||
@ -234,6 +235,9 @@ export const AddCollectionAction = (props) => {
|
||||
const items = templateOptions().map((option) => {
|
||||
return { label: compile(option.title), key: option.name };
|
||||
});
|
||||
const {
|
||||
state: { category },
|
||||
} = useResourceActionContext();
|
||||
return (
|
||||
<RecordProvider record={record}>
|
||||
<ActionContext.Provider value={{ visible, setVisible }}>
|
||||
@ -248,7 +252,7 @@ export const AddCollectionAction = (props) => {
|
||||
overflow: 'auto',
|
||||
}}
|
||||
onClick={(info) => {
|
||||
const schema = getSchema(getTemplate(info.key), record, compile);
|
||||
const schema = getSchema(getTemplate(info.key), category, compile);
|
||||
setSchema(schema);
|
||||
setVisible(true);
|
||||
}}
|
||||
|
@ -7,6 +7,7 @@ import { useCurrentAppInfo } from '../../appInfo';
|
||||
import { useRecord } from '../../record-provider';
|
||||
import { SchemaComponent, SchemaComponentContext, useCompile } from '../../schema-component';
|
||||
import { useCancelAction } from '../action-hooks';
|
||||
import { CollectionCategroriesContext } from '../context';
|
||||
import { useCollectionManager } from '../hooks/useCollectionManager';
|
||||
import { DataSourceContext } from '../sub-table';
|
||||
import { AddSubFieldAction } from './AddSubFieldAction';
|
||||
@ -76,6 +77,7 @@ export const ConfigurationTable = () => {
|
||||
const {
|
||||
data: { database },
|
||||
} = useCurrentAppInfo();
|
||||
const data = useContext(CollectionCategroriesContext);
|
||||
const collectonsRef: any = useRef();
|
||||
collectonsRef.current = collections;
|
||||
const compile = useCompile();
|
||||
@ -93,9 +95,14 @@ export const ConfigurationTable = () => {
|
||||
value: item.name,
|
||||
}));
|
||||
};
|
||||
const loadCategories = async () => {
|
||||
return data.data.map((item: any) => ({
|
||||
label: compile(item.name),
|
||||
value: item.id,
|
||||
}));
|
||||
};
|
||||
const ctx = useContext(SchemaComponentContext);
|
||||
return (
|
||||
<div>
|
||||
<SchemaComponentContext.Provider value={{ ...ctx, designable: false }}>
|
||||
<SchemaComponent
|
||||
schema={collectionSchema}
|
||||
@ -111,6 +118,7 @@ export const ConfigurationTable = () => {
|
||||
useSelectedRowKeys,
|
||||
useAsyncDataSource,
|
||||
loadCollections,
|
||||
loadCategories,
|
||||
useCurrentFields,
|
||||
useNewId,
|
||||
useCancelAction,
|
||||
@ -119,6 +127,5 @@ export const ConfigurationTable = () => {
|
||||
}}
|
||||
/>
|
||||
</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) => {
|
||||
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,
|
||||
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 './AddCollectionAction';
|
||||
export * from './EditCollectionAction';
|
||||
export * from './ConfigurationTabs';
|
||||
export * from './AddCategoryAction';
|
||||
export * from './EditCategoryAction'
|
||||
|
||||
registerValidateFormats({
|
||||
uid: /^[A-Za-z0-9][A-Za-z0-9_-]*$/,
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { ISchema, Schema } from '@formily/react';
|
||||
import { message } from 'antd';
|
||||
import { uid } from '@formily/shared';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAPIClient } from '../../../api-client';
|
||||
import { i18n } from '../../../i18n';
|
||||
import { CollectionOptions } from '../../types';
|
||||
import { CollectionTemplate } from '../components/CollectionTemplate';
|
||||
import { CollectionCategory } from '../components/CollectionCategory';
|
||||
import { collectionFieldSchema } from './collectionFields';
|
||||
|
||||
const compile = (source) => {
|
||||
@ -94,12 +96,24 @@ export const collectionSchema: ISchema = {
|
||||
filter: {
|
||||
'hidden.$isFalsy': true,
|
||||
},
|
||||
appends: [],
|
||||
appends: ['category'],
|
||||
},
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
actions: {
|
||||
tabs: {
|
||||
type: 'void',
|
||||
'x-component': 'ConfigurationTabs',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const collectionTableSchema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
@ -145,7 +159,7 @@ export const collectionSchema: ISchema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
table: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-uid': 'input',
|
||||
'x-component': 'Table.Void',
|
||||
@ -160,7 +174,6 @@ export const collectionSchema: ISchema = {
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
async move(from, to) {
|
||||
console.log(from, to);
|
||||
await api.resource('collections').move({
|
||||
sourceId: from.key,
|
||||
targetId: to.key,
|
||||
@ -207,6 +220,19 @@ export const collectionSchema: ISchema = {
|
||||
},
|
||||
},
|
||||
column4: {
|
||||
type: 'void',
|
||||
'x-decorator': 'Table.Column.Decorator',
|
||||
'x-component': 'Table.Column',
|
||||
'x-visible': 'categoryVisible',
|
||||
title: '{{t("Collection category")}}',
|
||||
properties: {
|
||||
category: {
|
||||
'x-component': CollectionCategory,
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
column5: {
|
||||
type: 'void',
|
||||
title: '{{ t("Actions") }}',
|
||||
'x-component': 'Table.Column',
|
||||
@ -270,6 +296,115 @@ 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 options = useFilterFieldOptions(collection.fields);
|
||||
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 CollectionFieldContext = createContext<CollectionFieldOptions>({});
|
||||
|
||||
export const CollectionCategroriesContext = createContext({ data: [], refresh: () => {} });
|
||||
|
@ -38,5 +38,5 @@ export const calendar: ICollectionTemplate = {
|
||||
availableFieldInterfaces: {
|
||||
include: [],
|
||||
},
|
||||
configurableProperties: getConfigurableProperties('title', 'name', 'inherits'),
|
||||
configurableProperties: getConfigurableProperties('title', 'name', 'inherits','category'),
|
||||
};
|
||||
|
@ -9,5 +9,5 @@ export const general: ICollectionTemplate = {
|
||||
default: {
|
||||
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-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: {
|
||||
title: '{{t("More options")}}',
|
||||
@ -124,6 +135,7 @@ export type DefaultConfigurableKeys =
|
||||
| 'name'
|
||||
| 'title'
|
||||
| 'inherits'
|
||||
| 'category'
|
||||
| 'autoGenId'
|
||||
| 'createdBy'
|
||||
| 'updatedBy'
|
||||
|
@ -34,6 +34,13 @@ export default {
|
||||
"UI editor": "UI editor",
|
||||
"Collection": "Collection",
|
||||
"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",
|
||||
"Edit profile": "Edit profile",
|
||||
"Change password": "Change password",
|
||||
|
@ -34,6 +34,13 @@ export default {
|
||||
"UI editor": "UI エディタ",
|
||||
"Collection": "コレクション",
|
||||
"Collections & Fields": "コレクションとフィールド",
|
||||
"All collections":"すべてのデータテーブル",
|
||||
"Add category":"分類の追加",
|
||||
"Edit category":"分類の編集",
|
||||
"Sort":"ソート#ソート#",
|
||||
"Category name":"分類名",
|
||||
"Delete category":"分類の削除",
|
||||
"Collection category":"Collection category",
|
||||
"Roles & Permissions": "役割と権限",
|
||||
"Edit profile": "プロフィール",
|
||||
"Change password": "パスワード変更",
|
||||
|
@ -34,6 +34,13 @@ export default {
|
||||
"UI editor": "界面配置",
|
||||
"Collection": "数据表",
|
||||
"Collections & Fields": "数据表配置",
|
||||
"All collections":"全部数据表",
|
||||
"Add category":"添加分类",
|
||||
"Delete category":"删除分类",
|
||||
"Edit category":"编辑分类",
|
||||
"Collection category":"数据表类别",
|
||||
"Sort":"排序",
|
||||
"Category name":"分类名称",
|
||||
"Roles & Permissions": "角色和权限",
|
||||
"Edit profile": "个人资料",
|
||||
"Change password": "修改密码",
|
||||
|
@ -1,10 +1,17 @@
|
||||
import { MenuOutlined } from '@ant-design/icons';
|
||||
import { css } from '@emotion/css';
|
||||
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 { 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 { DndContext } from '../..';
|
||||
import { RecordIndexProvider, RecordProvider, useRequest, useSchemaInitializer } from '../../../';
|
||||
@ -13,6 +20,11 @@ const isColumnComponent = (schema: Schema) => {
|
||||
return schema['x-component']?.endsWith('.Column') > -1;
|
||||
};
|
||||
|
||||
const useScope = (key: any) => {
|
||||
const scope = useContext(SchemaExpressionScopeContext);
|
||||
return scope[key] !== false;
|
||||
};
|
||||
|
||||
const useTableColumns = () => {
|
||||
const start = Date.now();
|
||||
const field = useField<ArrayField>();
|
||||
@ -20,9 +32,10 @@ const useTableColumns = () => {
|
||||
const { exists, render } = useSchemaInitializer(schema['x-initializer']);
|
||||
const columns = schema
|
||||
.reduceProperties((buf, s) => {
|
||||
if (isColumnComponent(s)) {
|
||||
if (isColumnComponent(s) && useScope(s['x-visible'])) {
|
||||
return buf.concat([s]);
|
||||
}
|
||||
return buf
|
||||
}, [])
|
||||
.map((s: Schema) => {
|
||||
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',
|
||||
sortBy: 'sort',
|
||||
},
|
||||
{
|
||||
type: 'belongsToMany',
|
||||
name: 'category',
|
||||
target: 'collectionCategories',
|
||||
sourceKey: 'name',
|
||||
foreignKey: 'collectionName',
|
||||
otherKey: 'categoryId',
|
||||
targetKey: 'id',
|
||||
through: 'collectionCategory',
|
||||
},
|
||||
],
|
||||
} as CollectionOptions;
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
useCollectionManager,
|
||||
useCompile,
|
||||
useCurrentAppInfo,
|
||||
useRecord
|
||||
useRecord,
|
||||
} from '@nocobase/client';
|
||||
import { Badge, Dropdown, Popover, Tag } from 'antd';
|
||||
import { groupBy } from 'lodash';
|
||||
@ -33,7 +33,7 @@ import {
|
||||
useDestroyActionAndRefreshCM,
|
||||
useDestroyFieldActionAndRefreshCM,
|
||||
useUpdateCollectionActionAndRefreshCM,
|
||||
useValuesFromRecord
|
||||
useValuesFromRecord,
|
||||
} from '../action-hooks';
|
||||
import { collectiionPopoverClass, entityContainer, headClass, tableBtnClass, tableNameClass } from '../style';
|
||||
import { useGCMTranslation } from '../utils';
|
||||
@ -60,6 +60,7 @@ const Entity: React.FC<{
|
||||
const database = useCurrentAppInfo();
|
||||
const collectionData = useRef();
|
||||
collectionData.current = { ...item, title, inherits: item.inherits && new Proxy(item.inherits, {}) };
|
||||
const { category } = item;
|
||||
const compile = useCompile();
|
||||
const loadCollections = async (field: any) => {
|
||||
return targetGraph.collections?.map((collection: any) => ({
|
||||
@ -79,8 +80,21 @@ const Entity: React.FC<{
|
||||
className={cx(entityContainer)}
|
||||
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>
|
||||
|
||||
<div className={tableBtnClass}>
|
||||
<SchemaComponentProvider>
|
||||
<CollectionNodeProvder setTargetNode={setTargetNode} node={node}>
|
||||
@ -154,6 +168,7 @@ const Entity: React.FC<{
|
||||
</SchemaComponentProvider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PortsCom {...portsProps} />
|
||||
</div>
|
||||
);
|
||||
|
@ -128,7 +128,7 @@ export const entityContainer = css`
|
||||
`;
|
||||
|
||||
export const headClass = css`
|
||||
height: 40px;
|
||||
height: 50px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
|
Loading…
Reference in New Issue
Block a user