diff --git a/packages/core/client/src/collection-manager/CollectionManagerProvider.tsx b/packages/core/client/src/collection-manager/CollectionManagerProvider.tsx index a5c3987ca..c4f65117b 100644 --- a/packages/core/client/src/collection-manager/CollectionManagerProvider.tsx +++ b/packages/core/client/src/collection-manager/CollectionManagerProvider.tsx @@ -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 = (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 ; + } + return ( + { + const { data } = await api.request(options); + result.mutate(data); + return data?.data || []; + } + }} + > + {props.children} + + ); +}; diff --git a/packages/core/client/src/collection-manager/CollectionManagerShortcut.tsx b/packages/core/client/src/collection-manager/CollectionManagerShortcut.tsx index 9817097e3..a5e3eafc0 100644 --- a/packages/core/client/src/collection-manager/CollectionManagerShortcut.tsx +++ b/packages/core/client/src/collection-manager/CollectionManagerShortcut.tsx @@ -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 ( - + // { OverridingFieldAction, ViewCollectionField, ViewFieldAction, + EditCategory, + EditCategoryAction, }} /> - + // ); }; @@ -103,12 +117,15 @@ export const CollectionManagerShortcut2 = () => { schema={schema} components={{ ConfigurationTable, + ConfigurationTabs, AddFieldAction, EditFieldAction, OverridingFieldAction, ViewFieldAction, AddCollectionAction, EditCollectionAction, + AddCategoryAction, + EditCategoryAction, }} /> diff --git a/packages/core/client/src/collection-manager/Configuration/AddCategoryAction.tsx b/packages/core/client/src/collection-manager/Configuration/AddCategoryAction.tsx new file mode 100644 index 000000000..c5bc64195 --- /dev/null +++ b/packages/core/client/src/collection-manager/Configuration/AddCategoryAction.tsx @@ -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 ; +}; + +export const AddCategoryAction = (props) => { + const { scope, getContainer, children } = props; + const [visible, setVisible] = useState(false); + const { t } = useTranslation(); + return ( + +
setVisible(true)} title={t('Add category')}> + {children || } +
+ +
+ ); +}; diff --git a/packages/core/client/src/collection-manager/Configuration/AddCollectionAction.tsx b/packages/core/client/src/collection-manager/Configuration/AddCollectionAction.tsx index 358f3d818..9bf610f1c 100644 --- a/packages/core/client/src/collection-manager/Configuration/AddCollectionAction.tsx +++ b/packages/core/client/src/collection-manager/Configuration/AddCollectionAction.tsx @@ -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 ( @@ -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); }} diff --git a/packages/core/client/src/collection-manager/Configuration/ConfigurationTable.tsx b/packages/core/client/src/collection-manager/Configuration/ConfigurationTable.tsx index 54c94c74c..ec7981ab2 100644 --- a/packages/core/client/src/collection-manager/Configuration/ConfigurationTable.tsx +++ b/packages/core/client/src/collection-manager/Configuration/ConfigurationTable.tsx @@ -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 ( -
{ useSelectedRowKeys, useAsyncDataSource, loadCollections, + loadCategories, useCurrentFields, useNewId, useCancelAction, @@ -119,6 +127,5 @@ export const ConfigurationTable = () => { }} /> -
); }; diff --git a/packages/core/client/src/collection-manager/Configuration/ConfigurationTabs.tsx b/packages/core/client/src/collection-manager/Configuration/ConfigurationTabs.tsx new file mode 100644 index 000000000..513cfc47d --- /dev/null +++ b/packages/core/client/src/collection-manager/Configuration/ConfigurationTabs.tsx @@ -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 ( +
+
{props.children}
+
+ ); +} + +function Droppable(props) { + const { isOver, setNodeRef } = useDroppable({ + id: props.id, + data: props.data, + }); + const style = isOver + ? { + color: 'green', + } + : undefined; + + return ( +
+ {props.children} +
+ ); +} + +const TabTitle = observer(({ item }: { item: any }) => { + return ( + +
+ + + +
+
+ ); +}); + +const TabBar = ({ item }) => { + const compile = useCompile(); + return ( + + + {compile(item.name)} + + ); +}; +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 ( + + {props.children} + + {activeTab ? {} : null} + + + ); +}); +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) => ( + + + + + remove(item.id)}> + {compile("{{t('Delete category')}}")} + + + ); + return ( + + + } + onChange={onChange} + defaultActiveKey="all" + type="editable-card" + destroyInactiveTabPane={true} + tabBarStyle={{ marginBottom: '0px' }} + > + {tabsItems.map((item) => { + return ( + + + + ) : ( + compile(item.name) + ) + } + key={item.id} + closable={item.closable} + closeIcon={ + + + + } + > + + + + + + + ); + })} + + + ); +}; diff --git a/packages/core/client/src/collection-manager/Configuration/EditCategoryAction.tsx b/packages/core/client/src/collection-manager/Configuration/EditCategoryAction.tsx new file mode 100644 index 000000000..39ab96f04 --- /dev/null +++ b/packages/core/client/src/collection-manager/Configuration/EditCategoryAction.tsx @@ -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 ; +}; + +export const EditCategoryAction = (props) => { + const { scope, getContainer, item, children } = props; + const [visible, setVisible] = useState(false); + const compile = useCompile(); + return ( + + + <>{children || setVisible(true)}>{compile('{{ t("Edit category") }}')}} + + + + ); +}; diff --git a/packages/core/client/src/collection-manager/Configuration/EditCollectionAction.tsx b/packages/core/client/src/collection-manager/Configuration/EditCollectionAction.tsx index 3799e1dee..981eae8e7 100644 --- a/packages/core/client/src/collection-manager/Configuration/EditCollectionAction.tsx +++ b/packages/core/client/src/collection-manager/Configuration/EditCollectionAction.tsx @@ -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, }); diff --git a/packages/core/client/src/collection-manager/Configuration/components/CollectionCategory.tsx b/packages/core/client/src/collection-manager/Configuration/components/CollectionCategory.tsx new file mode 100644 index 000000000..e06c0c658 --- /dev/null +++ b/packages/core/client/src/collection-manager/Configuration/components/CollectionCategory.tsx @@ -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 {compile(item?.name)}; + })} + + ); +}); diff --git a/packages/core/client/src/collection-manager/Configuration/index.tsx b/packages/core/client/src/collection-manager/Configuration/index.tsx index 9eae73586..c69d4c0ea 100644 --- a/packages/core/client/src/collection-manager/Configuration/index.tsx +++ b/packages/core/client/src/collection-manager/Configuration/index.tsx @@ -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_-]*$/, diff --git a/packages/core/client/src/collection-manager/Configuration/schemas/collections.ts b/packages/core/client/src/collection-manager/Configuration/schemas/collections.ts index 34dd448a3..4cdb3e03e 100644 --- a/packages/core/client/src/collection-manager/Configuration/schemas/collections.ts +++ b/packages/core/client/src/collection-manager/Configuration/schemas/collections.ts @@ -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,175 +96,197 @@ export const collectionSchema: ISchema = { filter: { 'hidden.$isFalsy': true, }, - appends: [], + appends: ['category'], }, }, }, properties: { - actions: { + tabs: { type: 'void', - 'x-component': 'ActionBar', - 'x-component-props': { - style: { - marginBottom: 16, - }, + 'x-component': 'ConfigurationTabs', + }, + }, + }, + }, +}; + +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: { - filter: { - type: 'void', - title: '{{ t("Filter") }}', - default: { - $and: [{ title: { $includes: '' } }, { name: { $includes: '' } }], - }, - 'x-action': 'filter', - 'x-component': 'Filter.Action', - 'x-component-props': { - icon: 'FilterOutlined', - useProps: '{{ cm.useFilterActionProps }}', - }, - 'x-align': 'left', - }, - delete: { - type: 'void', - title: '{{ t("Delete") }}', - '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', - }, + 'x-action': 'filter', + 'x-component': 'Filter.Action', + 'x-component-props': { + icon: 'FilterOutlined', + useProps: '{{ cm.useFilterActionProps }}', + }, + 'x-align': 'left', + }, + delete: { + type: 'void', + title: '{{ t("Delete") }}', + '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?')}}", }, }, }, - table: { + create: { type: 'void', - 'x-uid': 'input', - 'x-component': 'Table.Void', + title: '{{ t("Create collection") }}', + 'x-component': 'AddCollection', 'x-component-props': { - rowKey: 'name', - rowSelection: { - type: 'checkbox', + type: 'primary', + }, + }, + }, + }, + [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(); - return { - async move(from, to) { - console.log(from, to); - await api.resource('collections').move({ - sourceId: from.key, - targetId: to.key, - }); - message.success(t('Saved successfully'), 0.2); - }, - }; + }; + }, + }, + properties: { + column1: { + type: 'void', + 'x-decorator': 'Table.Column.Decorator', + 'x-component': 'Table.Column', + properties: { + title: { + 'x-component': 'CollectionField', + 'x-read-pretty': true, }, }, + }, + column2: { + type: 'void', + 'x-decorator': 'Table.Column.Decorator', + 'x-component': 'Table.Column', properties: { - column1: { - type: 'void', - 'x-decorator': 'Table.Column.Decorator', - 'x-component': 'Table.Column', - properties: { - title: { - 'x-component': 'CollectionField', - 'x-read-pretty': true, - }, - }, + name: { + type: 'string', + 'x-component': 'CollectionField', + 'x-read-pretty': true, }, - column2: { - type: 'void', - 'x-decorator': 'Table.Column.Decorator', - 'x-component': 'Table.Column', - properties: { - name: { - type: 'string', - 'x-component': 'CollectionField', - 'x-read-pretty': true, - }, - }, + }, + }, + column3: { + type: 'void', + 'x-decorator': 'Table.Column.Decorator', + 'x-component': 'Table.Column', + title: '{{t("Collection template")}}', + properties: { + template: { + 'x-component': CollectionTemplate, + 'x-read-pretty': true, }, - column3: { - type: 'void', - 'x-decorator': 'Table.Column.Decorator', - 'x-component': 'Table.Column', - title: '{{t("Collection template")}}', - properties: { - template: { - 'x-component': CollectionTemplate, - 'x-read-pretty': true, - }, - }, + }, + }, + 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, }, - column4: { + }, + }, + column5: { + type: 'void', + title: '{{ t("Actions") }}', + 'x-component': 'Table.Column', + properties: { + actions: { type: 'void', - title: '{{ t("Actions") }}', - 'x-component': 'Table.Column', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, properties: { - actions: { + view: { type: 'void', - 'x-component': 'Space', - 'x-component-props': { - split: '|', - }, + title: '{{ t("Configure fields") }}', + 'x-component': 'Action.Link', + 'x-component-props': {}, properties: { - view: { + drawer: { type: 'void', - title: '{{ t("Configure fields") }}', - 'x-component': 'Action.Link', - 'x-component-props': {}, + '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: { - drawer: { - 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, - }, - }, + collectionFieldSchema, }, }, - update: { - type: 'void', - title: '{{ t("Edit") }}', - 'x-component': 'EditCollection', - 'x-component-props': { - type: 'primary', - }, - }, - delete: { - type: 'void', - title: '{{ t("Delete") }}', - 'x-component': 'Action.Link', - 'x-component-props': { - confirm: { - title: "{{t('Delete record')}}", - content: "{{t('Are you sure you want to delete it?')}}", - }, - useAction: '{{ cm.useDestroyActionAndRefreshCM }}', - }, + }, + }, + update: { + type: 'void', + title: '{{ t("Edit") }}', + 'x-component': 'EditCollection', + 'x-component-props': { + type: 'primary', + }, + }, + delete: { + type: 'void', + title: '{{ t("Delete") }}', + 'x-component': 'Action.Link', + 'x-component-props': { + confirm: { + title: "{{t('Delete record')}}", + 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 }}', + }, + }, + }, + }, + }, + }, + }, +}; diff --git a/packages/core/client/src/collection-manager/action-hooks.ts b/packages/core/client/src/collection-manager/action-hooks.ts index 1cd259b52..5698a19f6 100644 --- a/packages/core/client/src/collection-manager/action-hooks.ts +++ b/packages/core/client/src/collection-manager/action-hooks.ts @@ -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, + }); }; diff --git a/packages/core/client/src/collection-manager/context.ts b/packages/core/client/src/collection-manager/context.ts index 581428314..476ca34e5 100644 --- a/packages/core/client/src/collection-manager/context.ts +++ b/packages/core/client/src/collection-manager/context.ts @@ -9,3 +9,5 @@ export const CollectionManagerContext = createContext( export const CollectionContext = createContext({}); export const CollectionFieldContext = createContext({}); + +export const CollectionCategroriesContext = createContext({ data: [], refresh: () => {} }); diff --git a/packages/core/client/src/collection-manager/templates/calendar.tsx b/packages/core/client/src/collection-manager/templates/calendar.tsx index 92bf0977e..1ff3bfa7b 100644 --- a/packages/core/client/src/collection-manager/templates/calendar.tsx +++ b/packages/core/client/src/collection-manager/templates/calendar.tsx @@ -38,5 +38,5 @@ export const calendar: ICollectionTemplate = { availableFieldInterfaces: { include: [], }, - configurableProperties: getConfigurableProperties('title', 'name', 'inherits'), + configurableProperties: getConfigurableProperties('title', 'name', 'inherits','category'), }; diff --git a/packages/core/client/src/collection-manager/templates/general.tsx b/packages/core/client/src/collection-manager/templates/general.tsx index ed60bc7d8..347ebd5f7 100644 --- a/packages/core/client/src/collection-manager/templates/general.tsx +++ b/packages/core/client/src/collection-manager/templates/general.tsx @@ -9,5 +9,5 @@ export const general: ICollectionTemplate = { default: { fields: [], }, - configurableProperties: getConfigurableProperties('title', 'name', 'inherits', 'moreOptions'), + configurableProperties: getConfigurableProperties('title', 'name', 'inherits', 'category', 'moreOptions'), }; diff --git a/packages/core/client/src/collection-manager/templates/properties/index.ts b/packages/core/client/src/collection-manager/templates/properties/index.ts index 22af1eb5f..2b674e9cf 100644 --- a/packages/core/client/src/collection-manager/templates/properties/index.ts +++ b/packages/core/client/src/collection-manager/templates/properties/index.ts @@ -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' diff --git a/packages/core/client/src/locale/en_US.ts b/packages/core/client/src/locale/en_US.ts index 0fe92836d..2129d61e8 100644 --- a/packages/core/client/src/locale/en_US.ts +++ b/packages/core/client/src/locale/en_US.ts @@ -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", diff --git a/packages/core/client/src/locale/ja_JP.ts b/packages/core/client/src/locale/ja_JP.ts index 2d82bab56..8626d8938 100644 --- a/packages/core/client/src/locale/ja_JP.ts +++ b/packages/core/client/src/locale/ja_JP.ts @@ -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": "パスワード変更", diff --git a/packages/core/client/src/locale/zh_CN.ts b/packages/core/client/src/locale/zh_CN.ts index 134be6d7d..b7f0f5930 100644 --- a/packages/core/client/src/locale/zh_CN.ts +++ b/packages/core/client/src/locale/zh_CN.ts @@ -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": "修改密码", diff --git a/packages/core/client/src/schema-component/antd/table/Table.Array.tsx b/packages/core/client/src/schema-component/antd/table/Table.Array.tsx index 0d22ea16d..37aeb56af 100644 --- a/packages/core/client/src/schema-component/antd/table/Table.Array.tsx +++ b/packages/core/client/src/schema-component/antd/table/Table.Array.tsx @@ -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(); @@ -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 { diff --git a/packages/plugins/collection-manager/src/collections/collectionCategories.ts b/packages/plugins/collection-manager/src/collections/collectionCategories.ts new file mode 100644 index 000000000..7e6205e08 --- /dev/null +++ b/packages/plugins/collection-manager/src/collections/collectionCategories.ts @@ -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; diff --git a/packages/plugins/collection-manager/src/collections/collections.ts b/packages/plugins/collection-manager/src/collections/collections.ts index 2b08305ba..4e5485c5c 100644 --- a/packages/plugins/collection-manager/src/collections/collections.ts +++ b/packages/plugins/collection-manager/src/collections/collections.ts @@ -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; diff --git a/packages/plugins/graph-collection-manager/src/client/components/Entity.tsx b/packages/plugins/graph-collection-manager/src/client/components/Entity.tsx index 60b89b07f..000bcb415 100644 --- a/packages/plugins/graph-collection-manager/src/client/components/Entity.tsx +++ b/packages/plugins/graph-collection-manager/src/client/components/Entity.tsx @@ -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 }} > -
+ {category.map((v, index) => { + return ( + + ); + })} +
{compile(title)} +
@@ -154,6 +168,7 @@ const Entity: React.FC<{
+
); diff --git a/packages/plugins/graph-collection-manager/src/client/style.tsx b/packages/plugins/graph-collection-manager/src/client/style.tsx index b0ff32195..d95f100b0 100644 --- a/packages/plugins/graph-collection-manager/src/client/style.tsx +++ b/packages/plugins/graph-collection-manager/src/client/style.tsx @@ -128,7 +128,7 @@ export const entityContainer = css` `; export const headClass = css` - height: 40px; + height: 50px; font-size: 14px; font-weight: 500; display: flex;