From 71dcabec25b41a125e88563cb090481fe012b1e5 Mon Sep 17 00:00:00 2001 From: Junyi Date: Thu, 9 Feb 2023 16:24:51 +0800 Subject: [PATCH 01/21] fix(plugin-workflow): fix schedule on field null value (#1442) --- packages/plugins/workflow/src/server/triggers/schedule.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/plugins/workflow/src/server/triggers/schedule.ts b/packages/plugins/workflow/src/server/triggers/schedule.ts index 75d5c3789d..845eb46751 100644 --- a/packages/plugins/workflow/src/server/triggers/schedule.ts +++ b/packages/plugins/workflow/src/server/triggers/schedule.ts @@ -124,6 +124,9 @@ function getOnTimestampWithOffset(on, now: Date) { } function getDataOptionTime(data, on, dir = 1) { + if (!on) { + return null; + } switch (typeof on) { case 'string': const time = parseDateWithoutMs(on); From d79143e3ed0905b80dbb9e776c3c9f6ec21166a2 Mon Sep 17 00:00:00 2001 From: SemmyWong <67748948+semmywong@users.noreply.github.com> Date: Thu, 9 Feb 2023 17:04:32 +0800 Subject: [PATCH 02/21] fix: node.js 17+, add openssl-legacy-provider (#1434) * fix: node upgrade 17+, add openssl-legacy-provider * fix: node upgrade 17+, add openssl-legacy-provider * fix: param NODE_OPTIONS_OPENSSL_LEGACY_PROVIDER change to NODE_OPTIONS --- .env.example | 3 +++ packages/core/database/src/database.ts | 2 +- packages/plugins/import/src/server/actions/importXlsx.ts | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 40d6f63b47..52fdb02c5f 100644 --- a/.env.example +++ b/.env.example @@ -77,3 +77,6 @@ INIT_ALI_SMS_VERIFY_CODE_SIGN= # use any string name (no space) DEFAULT_SMS_VERIFY_CODE_PROVIDER= + +# in nodejs 17+ that SSL v3 causes some ecosystem libraries to become incompatible. Configuring this option can prevent upgrading SSL V3 +# NODE_OPTIONS=--openssl-legacy-provider diff --git a/packages/core/database/src/database.ts b/packages/core/database/src/database.ts index 5637660568..8aa902c1de 100644 --- a/packages/core/database/src/database.ts +++ b/packages/core/database/src/database.ts @@ -534,7 +534,7 @@ export class Database extends EventEmitter implements AsyncEmitter { return this.sequelize.getDialect() === 'sqlite' && lodash.get(this.options, 'storage') == ':memory:'; } - async auth(options: QueryOptions & { retry?: number } = {}) { + async auth(options: Omit & { retry?: number | Pick } = {}) { const { retry = 10, ...others } = options; const delay = (ms) => new Promise((yea) => setTimeout(yea, ms)); let count = 1; diff --git a/packages/plugins/import/src/server/actions/importXlsx.ts b/packages/plugins/import/src/server/actions/importXlsx.ts index f74fe85bd9..a1290925a0 100644 --- a/packages/plugins/import/src/server/actions/importXlsx.ts +++ b/packages/plugins/import/src/server/actions/importXlsx.ts @@ -7,7 +7,7 @@ import { transform } from '../utils'; const IMPORT_LIMIT_COUNT = 10000; export async function importXlsx(ctx: Context, next: Next) { - let { columns } = ctx.request.body; + let { columns } = ctx.request.body as any; const { ['file']: file } = ctx; const { resourceName, resourceOf } = ctx.action; if (typeof columns === 'string') { From 2b7f138491fb01579dfb36a15e21ebb48d708c34 Mon Sep 17 00:00:00 2001 From: Junyi Date: Thu, 9 Feb 2023 22:40:26 +0800 Subject: [PATCH 03/21] fix(plugin-fm): fix path config for storages (#1445) --- .../src/client/StorageOptions.tsx | 18 ------------- .../src/client/schemas/storage.ts | 26 ++++++++++++++++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/plugins/file-manager/src/client/StorageOptions.tsx b/packages/plugins/file-manager/src/client/StorageOptions.tsx index 218c7a1442..cc6fdddd6f 100644 --- a/packages/plugins/file-manager/src/client/StorageOptions.tsx +++ b/packages/plugins/file-manager/src/client/StorageOptions.tsx @@ -13,12 +13,6 @@ const schema = { 'x-component': 'Input', required: true, }, - path: { - title: '{{t("Path")}}', - type: 'string', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - }, serve: { type: 'string', 'x-decorator': 'FormItem', @@ -58,12 +52,6 @@ const schema = { 'x-component': 'Input', required: true, }, - path: { - title: '{{t("Path")}}', - type: 'string', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - }, }, }, 'tx-cos': { @@ -128,12 +116,6 @@ const schema = { 'x-component': 'Input', required: true, }, - path: { - title: '{{t("Path")}}', - type: 'string', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - }, }, }, }; diff --git a/packages/plugins/file-manager/src/client/schemas/storage.ts b/packages/plugins/file-manager/src/client/schemas/storage.ts index 87368b89de..0c7e33912e 100644 --- a/packages/plugins/file-manager/src/client/schemas/storage.ts +++ b/packages/plugins/file-manager/src/client/schemas/storage.ts @@ -54,6 +54,16 @@ const collection = { required: true, } as ISchema, }, + { + type: 'string', + name: 'path', + interface: 'input', + uiSchema: { + title: '{{t("Path")}}', + type: 'string', + 'x-component': 'Input', + } as ISchema + }, { type: 'boolean', name: 'default', @@ -162,6 +172,10 @@ export const storageSchema: ISchema = { type: 'object', 'x-component': 'StorageOptions', }, + path: { + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + }, default: { 'x-component': 'CollectionField', 'x-decorator': 'FormItem', @@ -207,7 +221,7 @@ export const storageSchema: ISchema = { useDataSource: '{{ cm.useDataSourceFromRAC }}', }, properties: { - column1: { + title: { type: 'void', 'x-decorator': 'Table.Column.Decorator', 'x-component': 'Table.Column', @@ -219,7 +233,7 @@ export const storageSchema: ISchema = { }, }, }, - column2: { + name: { type: 'void', 'x-decorator': 'Table.Column.Decorator', 'x-component': 'Table.Column', @@ -231,7 +245,7 @@ export const storageSchema: ISchema = { }, }, }, - column3: { + default: { type: 'void', 'x-decorator': 'Table.Column.Decorator', 'x-component': 'Table.Column', @@ -243,7 +257,7 @@ export const storageSchema: ISchema = { }, }, }, - column4: { + actions: { type: 'void', title: '{{t("Actions")}}', 'x-component': 'Table.Column', @@ -294,6 +308,10 @@ export const storageSchema: ISchema = { type: 'object', 'x-component': 'StorageOptions', }, + path: { + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + }, default: { title: '', 'x-component': 'CollectionField', From f8a11cbbf092244ea207c306f07fbc31499fed67 Mon Sep 17 00:00:00 2001 From: katherinehhh Date: Fri, 10 Feb 2023 09:15:37 +0800 Subject: [PATCH 04/21] 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 --- .../CollectionManagerProvider.tsx | 42 +- .../CollectionManagerShortcut.tsx | 21 +- .../Configuration/AddCategoryAction.tsx | 60 +++ .../Configuration/AddCollectionAction.tsx | 8 +- .../Configuration/ConfigurationTable.tsx | 11 +- .../Configuration/ConfigurationTabs.tsx | 255 +++++++++++ .../Configuration/EditCategoryAction.tsx | 81 ++++ .../Configuration/EditCollectionAction.tsx | 2 +- .../components/CollectionCategory.tsx | 16 + .../Configuration/index.tsx | 3 + .../Configuration/schemas/collections.ts | 419 ++++++++++++------ .../src/collection-manager/action-hooks.ts | 6 +- .../client/src/collection-manager/context.ts | 2 + .../collection-manager/templates/calendar.tsx | 2 +- .../collection-manager/templates/general.tsx | 2 +- .../templates/properties/index.ts | 12 + packages/core/client/src/locale/en_US.ts | 7 + packages/core/client/src/locale/ja_JP.ts | 7 + packages/core/client/src/locale/zh_CN.ts | 7 + .../antd/table/Table.Array.tsx | 19 +- .../src/collections/collectionCategories.ts | 32 ++ .../src/collections/collections.ts | 10 + .../src/client/components/Entity.tsx | 21 +- .../src/client/style.tsx | 2 +- 24 files changed, 882 insertions(+), 165 deletions(-) create mode 100644 packages/core/client/src/collection-manager/Configuration/AddCategoryAction.tsx create mode 100644 packages/core/client/src/collection-manager/Configuration/ConfigurationTabs.tsx create mode 100644 packages/core/client/src/collection-manager/Configuration/EditCategoryAction.tsx create mode 100644 packages/core/client/src/collection-manager/Configuration/components/CollectionCategory.tsx create mode 100644 packages/plugins/collection-manager/src/collections/collectionCategories.ts diff --git a/packages/core/client/src/collection-manager/CollectionManagerProvider.tsx b/packages/core/client/src/collection-manager/CollectionManagerProvider.tsx index a5c3987ca0..c4f65117bd 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 9817097e30..a5e3eafc01 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 0000000000..c5bc64195e --- /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 358f3d818f..9bf610f1c7 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 54c94c74c0..ec7981ab27 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 0000000000..513cfc47d9 --- /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 0000000000..39ab96f04f --- /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 3799e1deea..981eae8e78 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 0000000000..e06c0c6587 --- /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 9eae735869..c69d4c0ea7 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 34dd448a3a..4cdb3e03e2 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 1cd259b522..5698a19f64 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 5814283143..476ca34e55 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 92bf0977e3..1ff3bfa7b2 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 ed60bc7d81..347ebd5f73 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 22af1eb5f2..2b674e9cfe 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 0fe92836de..2129d61e84 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 2d82bab566..8626d8938d 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 134be6d7df..b7f0f59305 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 0d22ea16d7..37aeb56af6 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 0000000000..7e6205e08e --- /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 2b08305ba7..4e5485c5c4 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 60b89b07f5..000bcb4156 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 b0ff321956..d95f100b02 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; From 2dc9c5c444b08c1d306e570b5f0795cdd04daa1f Mon Sep 17 00:00:00 2001 From: chenos Date: Fri, 10 Feb 2023 11:16:36 +0800 Subject: [PATCH 05/21] fix: error:0308010C:digital envelope routines::unsupported (#1447) * fix: error:0308010C:digital envelope routines::unsupported * fix: process.env.UNSET_NODE_OPTIONS --- .github/workflows/nocobase-test.yml | 9 ++++++--- packages/core/cli/bin/index.js | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nocobase-test.yml b/.github/workflows/nocobase-test.yml index 91de994e4a..c1102272c1 100644 --- a/.github/workflows/nocobase-test.yml +++ b/.github/workflows/nocobase-test.yml @@ -19,7 +19,7 @@ jobs: sqlite-test: strategy: matrix: - node_version: ['16'] + node_version: ['16', '18'] runs-on: ubuntu-latest container: node:${{ matrix.node_version }} steps: @@ -30,6 +30,7 @@ jobs: node-version: ${{ matrix.node_version }} cache: 'yarn' - run: yarn install + - run: yarn build - name: Test with Sqlite run: yarn test env: @@ -39,7 +40,7 @@ jobs: postgres-test: strategy: matrix: - node_version: ['16'] + node_version: ['16', '18'] runs-on: ubuntu-latest container: node:${{ matrix.node_version }} @@ -66,6 +67,7 @@ jobs: node-version: ${{ matrix.node_version }} cache: 'yarn' - run: yarn install + - run: yarn build - name: Test with postgres run: yarn test env: @@ -79,7 +81,7 @@ jobs: mysql-test: strategy: matrix: - node_version: ['16'] + node_version: ['16', '18'] runs-on: ubuntu-latest container: node:${{ matrix.node_version }} services: @@ -97,6 +99,7 @@ jobs: node-version: ${{ matrix.node_version }} cache: 'yarn' - run: yarn install + - run: yarn build - name: Test with MySQL run: yarn test env: diff --git a/packages/core/cli/bin/index.js b/packages/core/cli/bin/index.js index 06bba306e3..3b3ac86a8e 100755 --- a/packages/core/cli/bin/index.js +++ b/packages/core/cli/bin/index.js @@ -35,6 +35,21 @@ for (const key in env) { } } +if (require('semver').satisfies(process.version, '>16') && !process.env.UNSET_NODE_OPTIONS) { + if (process.env.NODE_OPTIONS) { + let opts = process.env.NODE_OPTIONS; + if (!opts.includes('--openssl-legacy-provider')) { + opts = opts + ' --openssl-legacy-provider'; + } + if (!opts.includes('--no-experimental-fetch')) { + opts = opts + ' --no-experimental-fetch'; + } + process.env.NODE_OPTIONS = opts; + } else { + process.env.NODE_OPTIONS = '--openssl-legacy-provider --no-experimental-fetch'; + } +} + const cli = require('../src/cli'); cli.parse(process.argv); From f4958e57d1451bd31ecffef6f1e19c592d10abd9 Mon Sep 17 00:00:00 2001 From: Pearl C <63629092+pearl-cao@users.noreply.github.com> Date: Fri, 10 Feb 2023 16:34:34 +0800 Subject: [PATCH 06/21] Update action.md --- docs/en-US/api/resourcer/action.md | 108 ++++++++++++++--------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/docs/en-US/api/resourcer/action.md b/docs/en-US/api/resourcer/action.md index 9b5a0968a9..23f258bab6 100644 --- a/docs/en-US/api/resourcer/action.md +++ b/docs/en-US/api/resourcer/action.md @@ -1,67 +1,67 @@ # Action -Action 是对资源的操作过程的描述,通常包含数据库处理等,类似其他框架中的 service 层,最简化的实现可以是一个 Koa 的中间件函数。在资源管理器里,针对特定资源定义的普通操作函数会被包装成 Action 类型的实例,当请求匹配对应资源的操作时,执行对应的操作过程。 +Action is the description of the operation process on resource, including database processing and so on. It is like the service layer in other frameworks, and the most simplified implementation can be a Koa middleware function. In the resourcer, common action functions defined for particular resources are wrapped into instances of the type Action, and when the request matches the action of the corresponding resource, the corresponding action is executed. -## 构造函数 +## Constructor -通常不需要直接实例化 Action,而是由资源管理器自动调用 `Action` 的静态方法 `toInstanceMap()` 进行实例化。 +Instead of being instantiated directly, the Action is usually instantiated automatically by the resourcer by calling the static method `toInstanceMap()` of `Action`. ### `constructor(options: ActionOptions)` -**参数** +**Parameter** -| 参数名 | 类型 | 默认值 | 描述 | +| Name | Type | Default | Description | | --- | --- | --- | --- | -| `handler` | `Function` | - | 操作函数 | -| `middlewares?` | `Middleware \| Middleware[]` | - | 针对操作的中间件 | -| `values?` | `Object` | - | 默认的操作数据 | -| `fields?` | `string[]` | - | 默认针对的字段组 | -| `appends?` | `string[]` | - | 默认附加的关联字段组 | -| `except?` | `string[]` | - | 默认排除的字段组 | -| `filter` | `FilterOptions` | - | 默认的过滤参数 | -| `sort` | `string[]` | - | 默认的排序参数 | -| `page` | `number` | - | 默认的页码 | -| `pageSize` | `number` | - | 默认的每页数量 | -| `maxPageSize` | `number` | - | 默认最大每页数量 | +| `handler` | `Function` | - | Handler | +| `middlewares?` | `Middleware \| Middleware[]` | - | Middlewares for the action | +| `values?` | `Object` | - | Default action data | +| `fields?` | `string[]` | - | Default list of targeted fields | +| `appends?` | `string[]` | - | Default list of associated fields to append | +| `except?` | `string[]` | - | Default list of fields to exclude | +| `filter` | `FilterOptions` | - | Default filtering options | +| `sort` | `string[]` | - | Default sorting options | +| `page` | `number` | - | Default page number | +| `pageSize` | `number` | - | Default page size | +| `maxPageSize` | `number` | - | Default max page size | -## 实例成员 +## Instance Members ### `actionName` -被实例化后对应的操作名称。在实例化时从请求中解析获取。 +Name of the action that corresponds to when it is instantiated. It is parsed and fetched from the request at instantiation. ### `resourceName` -被实例化后对应的资源名称。在实例化时从请求中解析获取。 +Name of the resource that corresponds to when the action is instantiated. It is parsed and fetched from the request at instantiation. ### `resourceOf` -被实例化后对应的关系资源的主键值。在实例化时从请求中解析获取。 +Value of the primary key of the relational resource that corresponds to when the action is instantiated. It is parsed and fetched from the request at instantiation. ### `readonly middlewares` -针对操作的中间件列表。 +List of middlewares targeting the action. ### `params` -操作参数。包含对应操作的所有相关参数,实例化时根据定义的 action 参数初始化,之后请求中解析前端传入的参数并根据对应参数的合并策略合并。如果有其他中间件的处理,也会有类似的合并过程。直到 handler 处理时,访问 params 得到的是经过多次合并的最终参数。 +Action parameters. It contains all relevant parameters for the corresponding action, which are initialized at instantiation according to the defined action parameters. Later when parameters passed from the front-end are parsed in requests, the corresponding parameters are merged according to the merge strategy. Similar merging process is done if there is other middleware processing. When it comes to the hander, the `params` are the final parameters that have been merged for several times. -参数的合并过程提供了针对操作处理的可扩展性,可以通过自定义中间件的方式按业务需求进行参数的前置解析和处理,例如表单提交的参数验证就可以在此环节实现。 +The merging process of parameters provides scalability for action processing, and the parameters can be pre-parsed and processed according to business requirements by means of custom middleware. For example, parameter validation for form submission can be implemented in this part. -预设的参数可以参考 [/api/actions] 中不同操作的参数。 +Refer to [/api/actions] for the pre-defined parameters of different actions. -参数中还包含请求资源路由的描述部分,具体如下: +The parameters also contain a description of the request resource route: -| 参数名 | 类型 | 默认值 | 描述 | +| Name | Type | Default | Description | | --- | --- | --- | --- | -| `resourceName` | `string` | - | 资源名称 | -| `resourceIndex` | `string \| number` | - | 资源的主键值 | -| `associatedName` | `string` | - | 所属关系资源的名称 | -| `associatedIndex` | `string \| number` | - | 所属关系资源的主键值 | -| `associated` | `Object` | - | 所属关系资源的实例 | -| `actionName` | `string` | - | 操作名称 | +| `resourceName` | `string` | - | Name of the resource | +| `resourceIndex` | `string \| number` | - | Value of the primary key of the resource | +| `associatedName` | `string` | - | Name of the associated resource it belongs to | +| `associatedIndex` | `string \| number` | - | Value of the primary key of the associated resource it belongs to | +| `associated` | `Object` | - | Instance of the associated resource it belongs to | +| `actionName` | `string` | - | Name of the action | -**示例** +**Example** ```ts app.resourcer.define('books', { @@ -88,40 +88,40 @@ app.resourcer.define('books', { }); ``` -## 实例方法 +## Instance Methods ### `mergeParams()` -将额外的参数合并至当前参数集,且可以根据不同的策略进行合并。 +Merge additional parameters to the current set of parameters according to different strategies. -**签名** +**Signature** * `mergeParams(params: ActionParams, strategies: MergeStrategies = {})` -**参数** +**Parameter** -| 参数名 | 类型 | 默认值 | 描述 | +| Name | Type | Default | Description | | --- | --- | --- | --- | -| `params` | `ActionParams` | - | 额外的参数集 | -| `strategies` | `MergeStrategies` | - | 针对每个参数的合并策略 | +| `params` | `ActionParams` | - | Additional set of parameters | +| `strategies` | `MergeStrategies` | - | Merge strategies for each parameter | -内置操作的默认合并策略如下表: +The default merge strategy for built-in actions is as follows: -| 参数名 | 类型 | 默认值 | 合并策略 | 描述 | +| Name | Type | Default | Merge Strategy |Description | | --- | --- | --- | --- | --- | -| `filterByTk` | `number \| string` | - | SQL `and` | 查询主键值 | -| `filter` | `FilterOptions` | - | SQL `and` | 查询过滤参数 | -| `fields` | `string[]` | - | 取并集 | 字段组 | -| `appends` | `string[]` | `[]` | 取并集 | 附加的关联字段组 | -| `except` | `string[]` | `[]` | 取并集 | 排除的字段组 | -| `whitelist` | `string[]` | `[]` | 取交集 | 可处理字段的白名单 | -| `blacklist` | `string[]` | `[]` | 取并集 | 可处理字段的黑名单 | -| `sort` | `string[]` | - | SQL `order by` | 查询排序参数 | -| `page` | `number` | - | 覆盖 | 页码 | -| `pageSize` | `number` | - | 覆盖 | 每页数量 | -| `values` | `Object` | - | 深度合并 | 操作提交的数据 | +| `filterByTk` | `number \| string` | - | SQL `and` | Get value of the primary key | +| `filter` | `FilterOptions` | - | SQL `and` | Get filtering options | +| `fields` | `string[]` | - | Take the union | List of fields | +| `appends` | `string[]` | `[]` | Take the union | List of associated fields to append | +| `except` | `string[]` | `[]` | Take the union | List of associated fields to exclude | +| `whitelist` | `string[]` | `[]` | Take the intersection | Whitelist of fields that can be handled | +| `blacklist` | `string[]` | `[]` | Take the union | Blacklist of fields that can be handled | +| `sort` | `string[]` | - | SQL `order by` | Get the sorting options | +| `page` | `number` | - | Override | Page number | +| `pageSize` | `number` | - | Override | Page size | +| `values` | `Object` | - | Deep merge | Operation of the submitted data | -**示例** +**Example** ```ts ctx.action.mergeParams({ From 032408f725707d56f07cca5c166657e8d50b95a4 Mon Sep 17 00:00:00 2001 From: Pearl C <63629092+pearl-cao@users.noreply.github.com> Date: Fri, 10 Feb 2023 16:35:05 +0800 Subject: [PATCH 07/21] Update action.md --- docs/en-US/api/resourcer/action.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en-US/api/resourcer/action.md b/docs/en-US/api/resourcer/action.md index 23f258bab6..9cd1813011 100644 --- a/docs/en-US/api/resourcer/action.md +++ b/docs/en-US/api/resourcer/action.md @@ -22,7 +22,7 @@ Instead of being instantiated directly, the Action is usually instantiated autom | `sort` | `string[]` | - | Default sorting options | | `page` | `number` | - | Default page number | | `pageSize` | `number` | - | Default page size | -| `maxPageSize` | `number` | - | Default max page size | +| `maxPageSize` | `number` | - | Default maximum page size | ## Instance Members From 26f0de0488ee32b07d2b31f7d77b6add874b0ee9 Mon Sep 17 00:00:00 2001 From: Pearl C <63629092+pearl-cao@users.noreply.github.com> Date: Fri, 10 Feb 2023 16:56:40 +0800 Subject: [PATCH 08/21] Update middleware.md --- docs/zh-CN/api/resourcer/middleware.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh-CN/api/resourcer/middleware.md b/docs/zh-CN/api/resourcer/middleware.md index 1f0a4d3a8b..1285acf81b 100644 --- a/docs/zh-CN/api/resourcer/middleware.md +++ b/docs/zh-CN/api/resourcer/middleware.md @@ -96,7 +96,7 @@ app.resourcer.use(middleware.getHandler()); **示例** -以下示例在请求处理是只输出 `1`,不执行 fn1 中的 `2` 输出。 +以下示例在请求处理时只输出 `1`,不执行 fn1 中的 `2` 输出。 ```ts const middleware = new Middleware((ctx, next) => { From 882090fda5cd1ac9a135e778806a01fb8b500b51 Mon Sep 17 00:00:00 2001 From: Pearl C <63629092+pearl-cao@users.noreply.github.com> Date: Fri, 10 Feb 2023 16:57:56 +0800 Subject: [PATCH 09/21] Update middleware.md --- docs/en-US/api/resourcer/middleware.md | 94 +++++++++++++------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/docs/en-US/api/resourcer/middleware.md b/docs/en-US/api/resourcer/middleware.md index 1f0a4d3a8b..6401cfcf89 100644 --- a/docs/en-US/api/resourcer/middleware.md +++ b/docs/en-US/api/resourcer/middleware.md @@ -1,29 +1,29 @@ # Middleware -与 Koa 的中间件类似,但提供了更多增强的功能,可以方便的进行更多的扩展。 +It is similar to the middleware of Koa, but with more enhanced features for easy extensions. -中间件定义后可以在资源管理器等多处进行插入使用,由开发者自行控制调用的时机。 +The defined middleware can be inserted for use in multiple places, such as the resourcer, and it is up to the developer for when to invoke it. -## 构造函数 +## Constructor -**签名** +**Signature** * `constructor(options: Function)` * `constructor(options: MiddlewareOptions)` -**参数** +**Parameter** -| 参数名 | 类型 | 默认值 | 描述 | +| Name | Type | Default | Description | | --- | --- | --- | --- | -| `options` | `Function` | - | 中间件处理函数 | -| `options` | `MiddlewareOptions ` | - | 中间件配置项 | -| `options.only` | `string[]` | - | 仅允许指定的操作 | -| `options.except` | `string[]` | - | 排除指定的操作 | -| `options.handler` | `Function` | - | 处理函数 | +| `options` | `Function` | - | Handler function of middlware | +| `options` | `MiddlewareOptions ` | - | Configuration items of middlware | +| `options.only` | `string[]` | - | Only the specified actions are allowed | +| `options.except` | `string[]` | - | The specified actions are excluded | +| `options.handler` | `Function` | - | Handler function | -**示例** +**Example** -简单定义: +Simple definition: ```ts const middleware = new Middleware((ctx, next) => { @@ -31,7 +31,7 @@ const middleware = new Middleware((ctx, next) => { }); ``` -使用相关参数: +Definition with relevant parameters: ```ts const middleware = new Middleware({ @@ -42,15 +42,15 @@ const middleware = new Middleware({ }); ``` -## 实例方法 +## Instance Methods ### `getHandler()` -返回已经过编排的处理函数。 +Get the orchestrated handler functions. -**示例** +**Example** -以下中间件在请求时会先输出 `1`,再输出 `2`。 +The following middleware will output `1` and then `2` when requested. ```ts const middleware = new Middleware((ctx, next) => { @@ -68,35 +68,35 @@ app.resourcer.use(middleware.getHandler()); ### `use()` -对当前中间件添加中间件函数。用于提供中间件的扩展点。示例见 `getHandler()`。 +Add a middleware function to the current middleware. Used to provide extension points for the middleware. See `getHandler()` for the examples. -**签名** +**Signature** * `use(middleware: Function)` -**参数** +**Parameter** -| 参数名 | 类型 | 默认值 | 描述 | +| Name | Type | Default | Description | | --- | --- | --- | --- | -| `middleware` | `Function` | - | 中间件处理函数 | +| `middleware` | `Function` | - | Handler function of the middleware | ### `disuse()` -移除当前中间件已添加的中间件函数。 +Remove the middleware functions that have been added to the current middleware. -**签名** +**Signature** * `disuse(middleware: Function)` -**参数** +**Parameter** -| 参数名 | 类型 | 默认值 | 描述 | +| Name | Type | Default | Description | | --- | --- | --- | --- | -| `middleware` | `Function` | - | 中间件处理函数 | +| `middleware` | `Function` | - | Handler function of the middleware | -**示例** +**Example** -以下示例在请求处理是只输出 `1`,不执行 fn1 中的 `2` 输出。 +The following example will only output `1` when requested, the output of `2` in fn1 will not be executed. ```ts const middleware = new Middleware((ctx, next) => { @@ -118,41 +118,41 @@ middleware.disuse(fn1); ### `canAccess()` -判断当前中间件针对特定操作是否要被调用,通常由资源管理器内部处理。 +Check whether the current middleware is to be invoked for a specific action, it is usually handled by the resourcer internally. -**签名** +**Signature** * `canAccess(name: string): boolean` -**参数** +**Parameter** -| 参数名 | 类型 | 默认值 | 描述 | +| Name | Type | Default | Description | | --- | --- | --- | --- | -| `name` | `string` | - | 操作名称 | +| `name` | `string` | - | Name of the action | -## 其他导出 +## Other Exports ### `branch()` -创建一个分支中间件,用于在中间件中进行分支处理。 +Create a branch middleware for branching in the middleware. -**签名** +**Signature** * `branch(map: { [key: string]: Function }, reducer: Function, options): Function` -**参数** +**Parameter** -| 参数名 | 类型 | 默认值 | 描述 | +| Name | Type | Default | Description | | --- | --- | --- | --- | -| `map` | `{ [key: string]: Function }` | - | 分支处理函数映射表,键名由后续计算函数在调用时给出 | -| `reducer` | `(ctx) => string` | - | 计算函数,用于基于上下文计算出分支的键名 | -| `options?` | `Object` | - | 分支配置项 | -| `options.keyNotFound?` | `Function` | `ctx.throw(404)` | 未找到键名时的处理函数 | -| `options.handlerNotSet?` | `Function` | `ctx.throw(404)` | 未定义处理函数时的处理 | +| `map` | `{ [key: string]: Function }` | - | Mapping table of the branch handler function, key names are given by subsequent calculation functions when called | +| `reducer` | `(ctx) => string` | - | Calculation function, it is used to calculate the key name of the branch based on the context | +| `options?` | `Object` | - | Configuration items of the branch | +| `options.keyNotFound?` | `Function` | `ctx.throw(404)` | Handler function when key name is not found | +| `options.handlerNotSet?` | `Function` | `ctx.throw(404)` | The function when no handler function is defined | -**示例** +**Example** -用户验证时,根据请求 URL 中 query 部分的 `authenticator` 参数的值决定后续需要如何处理: +When authenticating user, determine what to do next according to the value of the `authenticator` parameter in the query section of the request URL. ```ts app.resourcer.use(branch({ From 0cc007250bcf6ec5e0319614f31a737b01445ce3 Mon Sep 17 00:00:00 2001 From: Pearl C <63629092+pearl-cao@users.noreply.github.com> Date: Fri, 10 Feb 2023 16:58:42 +0800 Subject: [PATCH 10/21] Update action.md --- docs/en-US/api/resourcer/action.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en-US/api/resourcer/action.md b/docs/en-US/api/resourcer/action.md index 9cd1813011..9afed49b82 100644 --- a/docs/en-US/api/resourcer/action.md +++ b/docs/en-US/api/resourcer/action.md @@ -12,7 +12,7 @@ Instead of being instantiated directly, the Action is usually instantiated autom | Name | Type | Default | Description | | --- | --- | --- | --- | -| `handler` | `Function` | - | Handler | +| `handler` | `Function` | - | Handler function | | `middlewares?` | `Middleware \| Middleware[]` | - | Middlewares for the action | | `values?` | `Object` | - | Default action data | | `fields?` | `string[]` | - | Default list of targeted fields | From e2e85808e4aa2f070e38831924dd31500bb27f6f Mon Sep 17 00:00:00 2001 From: Junyi Date: Fri, 10 Feb 2023 23:41:30 +0800 Subject: [PATCH 11/21] fix(plugin-sequence): fix missed createdAt field in bulk hook (#1448) --- .../src/server/__tests__/sequence-field.test.ts | 10 +++++++--- .../src/server/fields/sequence-field.ts | 12 ++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/plugins/sequence-field/src/server/__tests__/sequence-field.test.ts b/packages/plugins/sequence-field/src/server/__tests__/sequence-field.test.ts index a271b1fd9e..0c7466f84d 100644 --- a/packages/plugins/sequence-field/src/server/__tests__/sequence-field.test.ts +++ b/packages/plugins/sequence-field/src/server/__tests__/sequence-field.test.ts @@ -764,6 +764,7 @@ describe('sequence field', () => { type: 'sequence', name: 'seq', patterns: [ + { type: 'date' }, { type: 'integer', options: { key: 1 } } ] } @@ -800,6 +801,9 @@ describe('sequence field', () => { await db.sync(); + const now = new Date(); + const dateStr = moment(now).format('YYYYMMDD'); + const tagsRepo = db.getRepository('tags'); const tags = await tagsRepo.create({ values: [ @@ -828,9 +832,9 @@ describe('sequence field', () => { order: [['seq', 'ASC']] }); - expect(postsTags[0].seq).toBe('0'); - expect(postsTags[1].seq).toBe('1'); - expect(postsTags[2].seq).toBe('2'); + expect(postsTags[0].seq).toBe(`${dateStr}0`); + expect(postsTags[1].seq).toBe(`${dateStr}1`); + expect(postsTags[2].seq).toBe(`${dateStr}2`); }); }); }); diff --git a/packages/plugins/sequence-field/src/server/fields/sequence-field.ts b/packages/plugins/sequence-field/src/server/fields/sequence-field.ts index e30c8d85c1..f4a792ab6d 100644 --- a/packages/plugins/sequence-field/src/server/fields/sequence-field.ts +++ b/packages/plugins/sequence-field/src/server/fields/sequence-field.ts @@ -65,7 +65,7 @@ sequencePatterns.register('integer', { // return null; // }, async generate(this: SequenceField, instance: Model, options, { transaction }) { - const recordTime = instance.get('createdAt'); + const recordTime = instance.get('createdAt') ?? new Date(); const { digits = 1, start = 0, base = 10, cycle, key } = options; const { repository: SeqRepo, model: SeqModel } = this.database.getCollection('sequences'); const lastSeq = (await SeqRepo.findOne({ @@ -137,7 +137,7 @@ sequencePatterns.register('integer', { }); instances.forEach((instance, i) => { - const recordTime = instance.get('createdAt'); + const recordTime = instance.get('createdAt') ?? new Date(); const value = instance.get(name); if (value != null && this.options.inputable) { const matcher = this.match(value); @@ -192,7 +192,7 @@ sequencePatterns.register('integer', { }, async update(instance, value, options, { transaction }) { - const recordTime = instance.get('createdAt'); + const recordTime = instance.get('createdAt') ?? new Date(); const { digits = 1, start = 0, base = 10, cycle, key } = options; const SeqRepo = this.database.getRepository('sequences'); const lastSeq = await SeqRepo.findOne({ @@ -260,9 +260,9 @@ sequencePatterns.register('date', { return moment(instance.get(options?.field ?? 'createdAt')).format(options?.format ?? 'YYYYMMDD'); }, batchGenerate(instances, values, options) { - const { name, inputable } = options; + const { field, inputable } = options; instances.forEach((instance, i) => { - if (!inputable || instance.get(name) == null) { + if (!inputable || instance.get(field ?? 'createdAt') == null) { values[i] = sequencePatterns.get('date').generate.call(this, instance, options); } }); @@ -367,7 +367,7 @@ export class SequenceField extends Field { const array = Array(patterns.length).fill(null).map(() => Array(instances.length)); await patterns.reduce((promise, p, i) => promise.then(() => - sequencePatterns.get(p.type).batchGenerate.call(this, instances, array[i], p.options, options)), + sequencePatterns.get(p.type).batchGenerate.call(this, instances, array[i], p.options ?? {}, options)), Promise.resolve()); instances.forEach((instance, i) => { From 68511f05bc7dbca49e0ab95eb868a193a3502d71 Mon Sep 17 00:00:00 2001 From: chenos Date: Sat, 11 Feb 2023 00:09:39 +0800 Subject: [PATCH 12/21] docs: add plug-in documentation --- packages/plugins/acl/README.md | 11 ++ packages/plugins/acl/README.zh-CN.md | 11 ++ packages/plugins/audit-logs/README.md | 11 ++ packages/plugins/audit-logs/README.zh-CN.md | 11 ++ packages/plugins/china-region/README.md | 11 ++ packages/plugins/china-region/README.zh-CN.md | 11 ++ packages/plugins/client/README.md | 9 ++ packages/plugins/client/README.zh-CN.md | 9 ++ packages/plugins/collection-manager/README.md | 9 ++ .../collection-manager/README.zh-CN.md | 9 ++ packages/plugins/duplicator/README.md | 118 ++++++++++++++++++ packages/plugins/duplicator/README.zh-CN.md | 118 ++++++++++++++++++ .../duplicator/src/server/app-migrator.ts | 20 +-- packages/plugins/error-handler/README.md | 9 ++ .../plugins/error-handler/README.zh-CN.md | 9 ++ .../plugins/excel-formula-field/README.md | 9 ++ .../excel-formula-field/README.zh-CN.md | 9 ++ packages/plugins/export/README.md | 9 ++ packages/plugins/export/README.zh-CN.md | 9 ++ packages/plugins/file-manager/README.md | 9 ++ packages/plugins/file-manager/README.zh-CN.md | 9 ++ .../graph-collection-manager/README.md | 11 ++ .../graph-collection-manager/README.zh-CN.md | 11 ++ packages/plugins/iframe-block/README.md | 11 ++ packages/plugins/iframe-block/README.zh-CN.md | 11 ++ packages/plugins/import/README.md | 11 ++ packages/plugins/import/README.zh-CN.md | 11 ++ packages/plugins/map/README.md | 13 ++ packages/plugins/map/README.zh-CN.md | 13 ++ packages/plugins/math-formula-field/README.md | 9 ++ .../math-formula-field/README.zh-CN.md | 9 ++ packages/plugins/multi-app-manager/README.md | 13 ++ .../plugins/multi-app-manager/README.zh-CN.md | 13 ++ packages/plugins/notifications/README.md | 9 ++ .../plugins/notifications/README.zh-CN.md | 9 ++ packages/plugins/oidc/README.md | 11 ++ packages/plugins/oidc/README.zh-CN.md | 11 ++ packages/plugins/saml/README.md | 11 ++ packages/plugins/saml/README.zh-CN.md | 11 ++ packages/plugins/sequence-field/README.md | 9 ++ .../plugins/sequence-field/README.zh-CN.md | 9 ++ packages/plugins/snapshot-field/README.md | 13 ++ .../plugins/snapshot-field/README.zh-CN.md | 13 ++ packages/plugins/system-settings/README.md | 9 ++ .../plugins/system-settings/README.zh-CN.md | 9 ++ packages/plugins/ui-routes-storage/README.md | 9 ++ .../plugins/ui-routes-storage/README.zh-CN.md | 9 ++ packages/plugins/ui-schema-storage/README.md | 9 ++ .../plugins/ui-schema-storage/README.zh-CN.md | 9 ++ packages/plugins/users/README.md | 9 ++ packages/plugins/users/README.zh-CN.md | 9 ++ packages/plugins/verification/README.md | 9 ++ packages/plugins/verification/README.zh-CN.md | 9 ++ packages/plugins/workflow/README.md | 9 ++ packages/plugins/workflow/README.zh-CN.md | 9 ++ 55 files changed, 770 insertions(+), 10 deletions(-) create mode 100644 packages/plugins/acl/README.md create mode 100644 packages/plugins/acl/README.zh-CN.md create mode 100644 packages/plugins/audit-logs/README.md create mode 100644 packages/plugins/audit-logs/README.zh-CN.md create mode 100644 packages/plugins/china-region/README.md create mode 100644 packages/plugins/china-region/README.zh-CN.md create mode 100644 packages/plugins/client/README.md create mode 100644 packages/plugins/client/README.zh-CN.md create mode 100644 packages/plugins/collection-manager/README.md create mode 100644 packages/plugins/collection-manager/README.zh-CN.md create mode 100644 packages/plugins/duplicator/README.md create mode 100644 packages/plugins/duplicator/README.zh-CN.md create mode 100644 packages/plugins/error-handler/README.md create mode 100644 packages/plugins/error-handler/README.zh-CN.md create mode 100644 packages/plugins/excel-formula-field/README.md create mode 100644 packages/plugins/excel-formula-field/README.zh-CN.md create mode 100644 packages/plugins/export/README.md create mode 100644 packages/plugins/export/README.zh-CN.md create mode 100644 packages/plugins/file-manager/README.md create mode 100644 packages/plugins/file-manager/README.zh-CN.md create mode 100644 packages/plugins/graph-collection-manager/README.md create mode 100644 packages/plugins/graph-collection-manager/README.zh-CN.md create mode 100644 packages/plugins/iframe-block/README.md create mode 100644 packages/plugins/iframe-block/README.zh-CN.md create mode 100644 packages/plugins/import/README.md create mode 100644 packages/plugins/import/README.zh-CN.md create mode 100644 packages/plugins/map/README.md create mode 100644 packages/plugins/map/README.zh-CN.md create mode 100644 packages/plugins/math-formula-field/README.md create mode 100644 packages/plugins/math-formula-field/README.zh-CN.md create mode 100644 packages/plugins/multi-app-manager/README.md create mode 100644 packages/plugins/multi-app-manager/README.zh-CN.md create mode 100644 packages/plugins/notifications/README.md create mode 100644 packages/plugins/notifications/README.zh-CN.md create mode 100644 packages/plugins/oidc/README.md create mode 100644 packages/plugins/oidc/README.zh-CN.md create mode 100644 packages/plugins/saml/README.md create mode 100644 packages/plugins/saml/README.zh-CN.md create mode 100644 packages/plugins/sequence-field/README.md create mode 100644 packages/plugins/sequence-field/README.zh-CN.md create mode 100644 packages/plugins/snapshot-field/README.md create mode 100644 packages/plugins/snapshot-field/README.zh-CN.md create mode 100644 packages/plugins/system-settings/README.md create mode 100644 packages/plugins/system-settings/README.zh-CN.md create mode 100644 packages/plugins/ui-routes-storage/README.md create mode 100644 packages/plugins/ui-routes-storage/README.zh-CN.md create mode 100644 packages/plugins/ui-schema-storage/README.md create mode 100644 packages/plugins/ui-schema-storage/README.zh-CN.md create mode 100644 packages/plugins/users/README.md create mode 100644 packages/plugins/users/README.zh-CN.md create mode 100644 packages/plugins/verification/README.md create mode 100644 packages/plugins/verification/README.zh-CN.md create mode 100644 packages/plugins/workflow/README.md create mode 100644 packages/plugins/workflow/README.zh-CN.md diff --git a/packages/plugins/acl/README.md b/packages/plugins/acl/README.md new file mode 100644 index 0000000000..af0dd46bba --- /dev/null +++ b/packages/plugins/acl/README.md @@ -0,0 +1,11 @@ +# acl + +English | [中文](./README.zh-CN.md) + +基于角色的权限控制插件。 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/acl/README.zh-CN.md b/packages/plugins/acl/README.zh-CN.md new file mode 100644 index 0000000000..8260a114b8 --- /dev/null +++ b/packages/plugins/acl/README.zh-CN.md @@ -0,0 +1,11 @@ +# acl + +[English](./README.md) | 中文 + +基于角色的权限控制插件。 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 \ No newline at end of file diff --git a/packages/plugins/audit-logs/README.md b/packages/plugins/audit-logs/README.md new file mode 100644 index 0000000000..e617cc5b1a --- /dev/null +++ b/packages/plugins/audit-logs/README.md @@ -0,0 +1,11 @@ +# audit-logs + +English | [中文](./README.zh-CN.md) + +审计日志插件。 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/audit-logs/README.zh-CN.md b/packages/plugins/audit-logs/README.zh-CN.md new file mode 100644 index 0000000000..8dc8105860 --- /dev/null +++ b/packages/plugins/audit-logs/README.zh-CN.md @@ -0,0 +1,11 @@ +# audit-logs + +[English](./README.md) | 中文 + +审计日志插件。 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/china-region/README.md b/packages/plugins/china-region/README.md new file mode 100644 index 0000000000..e762c6d8d6 --- /dev/null +++ b/packages/plugins/china-region/README.md @@ -0,0 +1,11 @@ +# china-region + +English | [中文](./README.zh-CN.md) + +中国行政区划插件。 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/china-region/README.zh-CN.md b/packages/plugins/china-region/README.zh-CN.md new file mode 100644 index 0000000000..9180fab21f --- /dev/null +++ b/packages/plugins/china-region/README.zh-CN.md @@ -0,0 +1,11 @@ +# china-region + +[English](./README.md) | 中文 + +中国行政区划插件。 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/client/README.md b/packages/plugins/client/README.md new file mode 100644 index 0000000000..cc7dc122ce --- /dev/null +++ b/packages/plugins/client/README.md @@ -0,0 +1,9 @@ +# client + +English | [中文](./README.zh-CN.md) + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/client/README.zh-CN.md b/packages/plugins/client/README.zh-CN.md new file mode 100644 index 0000000000..7eb06a44ea --- /dev/null +++ b/packages/plugins/client/README.zh-CN.md @@ -0,0 +1,9 @@ +# client + +[English](./README.md) | 中文 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/collection-manager/README.md b/packages/plugins/collection-manager/README.md new file mode 100644 index 0000000000..43b192faee --- /dev/null +++ b/packages/plugins/collection-manager/README.md @@ -0,0 +1,9 @@ +# collection-manager + +English | [中文](./README.zh-CN.md) + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/collection-manager/README.zh-CN.md b/packages/plugins/collection-manager/README.zh-CN.md new file mode 100644 index 0000000000..3140973a84 --- /dev/null +++ b/packages/plugins/collection-manager/README.zh-CN.md @@ -0,0 +1,9 @@ +# collection-manager + +[English](./README.md) | 中文 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/duplicator/README.md b/packages/plugins/duplicator/README.md new file mode 100644 index 0000000000..3ebd07f182 --- /dev/null +++ b/packages/plugins/duplicator/README.md @@ -0,0 +1,118 @@ +# Duplicator + +English | [中文](./README.zh-CN.md) + +NocoBase 应用的备份与还原插件,可用于应用的复制、迁移、升级等场景。 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 + +Duplicator 插件提供了 `dump` 和 `restore` 命令,分别用于备份和还原应用数据,可用于单应用的备份和还原,也可以跨应用。如果跨应用还原数据,请保证目标应用 NocoBase 版本与源应用一致,相对应插件也已下载本地。 + +**⚠️ 如果使用了继承(PostgreSQL)、视图、触发器等不兼容的特性,跨数据库还原备份数据可能失败。** + +### 备份数据 + +```bash +yarn nocobase dump +``` + +选择需要备份的插件表结构及其数据 + +```bash +? Select the plugin collections to be dumped (Press to select, to toggle all, to invert selection, and to proceed) + == Required == + - migration (core) (Disabled) + - collections (collection-manager) (Disabled) + - uiSchemas (ui-schema-storage) (Disabled) + - uiRoutes (ui-routes-storage) (Disabled) + - acl (acl) (Disabled) + - workflowConfig (workflow) (Disabled) + - snapshot-field (snapshot-field) (Disabled) + - sequences (sequence-field) (Disabled) + == Optional == +❯◉ executionLogs (workflow) + ◉ users (users) + ◉ storageSetting (file-manager) + ◉ attachmentRecords (file-manager) + ◉ systemSettings (system-settings) + ◉ verificationProviders (verification) + ◉ verificationData (verification) + ◉ oidcProviders (oidc) + ◉ samlProviders (saml) + ◉ mapConfiguration (map) +(Move up and down to reveal more choices) +``` + +选择需要备份的其他数据表的记录 + +```bash +? Select the collection records to be dumped (Press to select, to toggle all, to invert selection, and to proceed) +❯◉ Test1 +❯◉ Test2 +❯◉ Test3 +``` + +数据备份成功之后,备份文件位于 `storage/duplicator` 目录下: + +```bash +dumped to /your/apps/a/storage/duplicator/dump-20230210T223910.nbdump +dumped file size: 20.8 kB +``` + +### 还原数据 + +```bash +yarn nocobase restore /your/apps/a/storage/duplicator/dump-20230210T223910.nbdump +``` + +导入前请先备份数据 + +```bash +? Danger !!! This action will overwrite your current data, please make sure you have a backup❗️❗️ (y/N) +``` + +选择需要还原的插件表结构及其数据 + +```bash +? Select the plugin collections to be restored (Press to select, to toggle all, to invert selection, and to proceed) + == Required == + - migration (core) (Disabled) + - collections (collection-manager) (Disabled) + - uiSchemas (ui-schema-storage) (Disabled) + - uiRoutes (ui-routes-storage) (Disabled) + - acl (acl) (Disabled) + - workflowConfig (workflow) (Disabled) + - sequences (sequence-field) (Disabled) + == Optional == +❯◯ executionLogs (workflow) + ◯ users (users) + ◯ storageSetting (file-manager) + ◯ attachmentRecords (file-manager) + ◯ systemSettings (system-settings) + ◯ verificationProviders (verification) + ◯ verificationData (verification) + ◯ auditLogs (audit-logs) + ◯ iframe html storage (iframe-block) +``` + +选择需要还原的其他数据表的记录 + +```bash +? Select the collection records to be restored (Press to select, to toggle all, to invert selection, and to proceed) +❯◉ Test1 +❯◉ Test2 +❯◉ Test3 +``` + +成功之后,重启应用 + +```bash +# for development +yarn dev +# for production +yarn start +``` diff --git a/packages/plugins/duplicator/README.zh-CN.md b/packages/plugins/duplicator/README.zh-CN.md new file mode 100644 index 0000000000..2deed9c0ff --- /dev/null +++ b/packages/plugins/duplicator/README.zh-CN.md @@ -0,0 +1,118 @@ +# Duplicator + +[English](./README.md) | 中文 + +NocoBase 应用的备份与还原插件,可用于应用的复制、迁移、升级等场景。 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 + +Duplicator 插件提供了 `dump` 和 `restore` 命令,分别用于备份和还原应用数据,可用于单应用的备份和还原,也可以跨应用。如果跨应用还原数据,请保证目标应用 NocoBase 版本与源应用一致,相对应插件也已下载本地。 + +**⚠️ 如果使用了继承(PostgreSQL)、视图、触发器等不兼容的特性,跨数据库还原备份数据可能失败。** + +### 备份数据 + +```bash +yarn nocobase dump +``` + +选择需要备份的插件表结构及其数据 + +```bash +? Select the plugin collections to be dumped (Press to select, to toggle all, to invert selection, and to proceed) + == Required == + - migration (core) (Disabled) + - collections (collection-manager) (Disabled) + - uiSchemas (ui-schema-storage) (Disabled) + - uiRoutes (ui-routes-storage) (Disabled) + - acl (acl) (Disabled) + - workflowConfig (workflow) (Disabled) + - snapshot-field (snapshot-field) (Disabled) + - sequences (sequence-field) (Disabled) + == Optional == +❯◉ executionLogs (workflow) + ◉ users (users) + ◉ storageSetting (file-manager) + ◉ attachmentRecords (file-manager) + ◉ systemSettings (system-settings) + ◉ verificationProviders (verification) + ◉ verificationData (verification) + ◉ oidcProviders (oidc) + ◉ samlProviders (saml) + ◉ mapConfiguration (map) +(Move up and down to reveal more choices) +``` + +选择需要备份的其他数据表的记录 + +```bash +? Select the collection records to be dumped (Press to select, to toggle all, to invert selection, and to proceed) +❯◉ Test1 +❯◉ Test2 +❯◉ Test3 +``` + +数据备份成功之后,备份文件位于 `storage/duplicator` 目录下: + +```bash +dumped to /your/apps/a/storage/duplicator/dump-20230210T223910.nbdump +dumped file size: 20.8 kB +``` + +### 还原数据 + +```bash +yarn nocobase restore /your/apps/a/storage/duplicator/dump-20230210T223910.nbdump +``` + +导入前请先备份数据 + +```bash +? Danger !!! This action will overwrite your current data, please make sure you have a backup❗️❗️ (y/N) +``` + +选择需要还原的插件表结构及其数据 + +```bash +? Select the plugin collections to be restored (Press to select, to toggle all, to invert selection, and to proceed) + == Required == + - migration (core) (Disabled) + - collections (collection-manager) (Disabled) + - uiSchemas (ui-schema-storage) (Disabled) + - uiRoutes (ui-routes-storage) (Disabled) + - acl (acl) (Disabled) + - workflowConfig (workflow) (Disabled) + - sequences (sequence-field) (Disabled) + == Optional == +❯◯ executionLogs (workflow) + ◯ users (users) + ◯ storageSetting (file-manager) + ◯ attachmentRecords (file-manager) + ◯ systemSettings (system-settings) + ◯ verificationProviders (verification) + ◯ verificationData (verification) + ◯ auditLogs (audit-logs) + ◯ iframe html storage (iframe-block) +``` + +选择需要还原的其他数据表的记录 + +```bash +? Select the collection records to be restored (Press to select, to toggle all, to invert selection, and to proceed) +❯◉ Test1 +❯◉ Test2 +❯◉ Test3 +``` + +成功之后,重启应用 + +```bash +# for development +yarn dev +# for production +yarn start +``` diff --git a/packages/plugins/duplicator/src/server/app-migrator.ts b/packages/plugins/duplicator/src/server/app-migrator.ts index c0cd8428cf..9554e4ef12 100644 --- a/packages/plugins/duplicator/src/server/app-migrator.ts +++ b/packages/plugins/duplicator/src/server/app-migrator.ts @@ -1,12 +1,12 @@ import { Application } from '@nocobase/server'; +import { applyMixins, AsyncEmitter } from '@nocobase/utils'; +import crypto from 'crypto'; +import EventEmitter from 'events'; +import fsPromises from 'fs/promises'; +import inquirer from 'inquirer'; +import lodash from 'lodash'; import * as os from 'os'; import path from 'path'; -import lodash from 'lodash'; -import fsPromises from 'fs/promises'; -import crypto from 'crypto'; -import inquirer from 'inquirer'; -import EventEmitter from 'events'; -import { applyMixins, AsyncEmitter, requireModule } from '@nocobase/utils'; abstract class AppMigrator extends EventEmitter { protected workDir: string; @@ -64,11 +64,11 @@ abstract class AppMigrator extends EventEmitter { return { type: 'checkbox', name: 'collectionGroups', - message: `选择需要${this.direction}的插件数据`, + message: `Select the plugin collections to be ${this.direction === 'dump' ? 'dumped' : 'restored'}`, loop: false, pageSize: 20, choices: [ - new inquirer.Separator('== 必选数据 =='), + new inquirer.Separator('== Required =='), ...requiredGroups.map((collectionGroup) => ({ name: `${collectionGroup.function} (${collectionGroup.pluginName})`, value: `${collectionGroup.pluginName}.${collectionGroup.function}`, @@ -76,7 +76,7 @@ abstract class AppMigrator extends EventEmitter { disabled: true, })), - new inquirer.Separator('== 可选数据 =='), + new inquirer.Separator('== Optional =='), ...optionalGroups.map((collectionGroup) => ({ name: `${collectionGroup.function} (${collectionGroup.pluginName})`, value: `${collectionGroup.pluginName}.${collectionGroup.function}`, @@ -95,7 +95,7 @@ abstract class AppMigrator extends EventEmitter { return { type: 'checkbox', name: 'userCollections', - message: `选择需要${this.direction}的Collection数据`, + message: `Select the collection records to be ${this.direction === 'dump' ? 'dumped' : 'restored'}`, loop: false, pageSize: 30, choices: collections.map((collection) => { diff --git a/packages/plugins/error-handler/README.md b/packages/plugins/error-handler/README.md new file mode 100644 index 0000000000..5a9532ed61 --- /dev/null +++ b/packages/plugins/error-handler/README.md @@ -0,0 +1,9 @@ +# error-handler + +English | [中文](./README.zh-CN.md) + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/error-handler/README.zh-CN.md b/packages/plugins/error-handler/README.zh-CN.md new file mode 100644 index 0000000000..1537b48996 --- /dev/null +++ b/packages/plugins/error-handler/README.zh-CN.md @@ -0,0 +1,9 @@ +# error-handler + +[English](./README.md) | 中文 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/excel-formula-field/README.md b/packages/plugins/excel-formula-field/README.md new file mode 100644 index 0000000000..8b0979dd3c --- /dev/null +++ b/packages/plugins/excel-formula-field/README.md @@ -0,0 +1,9 @@ +# excel-formula-field + +English | [中文](./README.zh-CN.md) + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/excel-formula-field/README.zh-CN.md b/packages/plugins/excel-formula-field/README.zh-CN.md new file mode 100644 index 0000000000..4171af5ead --- /dev/null +++ b/packages/plugins/excel-formula-field/README.zh-CN.md @@ -0,0 +1,9 @@ +# excel-formula-field + +[English](./README.md) | 中文 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/export/README.md b/packages/plugins/export/README.md new file mode 100644 index 0000000000..4540842319 --- /dev/null +++ b/packages/plugins/export/README.md @@ -0,0 +1,9 @@ +# export + +English | [中文](./README.zh-CN.md) + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/export/README.zh-CN.md b/packages/plugins/export/README.zh-CN.md new file mode 100644 index 0000000000..3e5e641b2a --- /dev/null +++ b/packages/plugins/export/README.zh-CN.md @@ -0,0 +1,9 @@ +# Export + +[English](./README.md) | 中文 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/file-manager/README.md b/packages/plugins/file-manager/README.md new file mode 100644 index 0000000000..36ca419eb7 --- /dev/null +++ b/packages/plugins/file-manager/README.md @@ -0,0 +1,9 @@ +# file-manager + +English | [中文](./README.zh-CN.md) + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/file-manager/README.zh-CN.md b/packages/plugins/file-manager/README.zh-CN.md new file mode 100644 index 0000000000..4fe1a5a487 --- /dev/null +++ b/packages/plugins/file-manager/README.zh-CN.md @@ -0,0 +1,9 @@ +# file-manager + +[English](./README.md) | 中文 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/graph-collection-manager/README.md b/packages/plugins/graph-collection-manager/README.md new file mode 100644 index 0000000000..9dc8a57f15 --- /dev/null +++ b/packages/plugins/graph-collection-manager/README.md @@ -0,0 +1,11 @@ +# graph-collection-manager + +English | [中文](./README.zh-CN.md) + +## 安装激活 + +```bash +yarn pm enable graph-collection-manager +``` + +## 使用方法 diff --git a/packages/plugins/graph-collection-manager/README.zh-CN.md b/packages/plugins/graph-collection-manager/README.zh-CN.md new file mode 100644 index 0000000000..e36cca20a3 --- /dev/null +++ b/packages/plugins/graph-collection-manager/README.zh-CN.md @@ -0,0 +1,11 @@ +# graph-collection-manager + +[English](./README.md) | 中文 + +## 安装激活 + +```bash +yarn pm enable graph-collection-manager +``` + +## 使用方法 diff --git a/packages/plugins/iframe-block/README.md b/packages/plugins/iframe-block/README.md new file mode 100644 index 0000000000..490d73457c --- /dev/null +++ b/packages/plugins/iframe-block/README.md @@ -0,0 +1,11 @@ +# iframe-block + +English | [中文](./README.zh-CN.md) + +Iframe 区块插件。 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/iframe-block/README.zh-CN.md b/packages/plugins/iframe-block/README.zh-CN.md new file mode 100644 index 0000000000..2b000415fc --- /dev/null +++ b/packages/plugins/iframe-block/README.zh-CN.md @@ -0,0 +1,11 @@ +# iframe-block + +[English](./README.md) | 中文 + +Iframe 区块插件。 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/import/README.md b/packages/plugins/import/README.md new file mode 100644 index 0000000000..938722f9e1 --- /dev/null +++ b/packages/plugins/import/README.md @@ -0,0 +1,11 @@ +# import + +English | [中文](./README.zh-CN.md) + +Excel 数据导入插件。 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/import/README.zh-CN.md b/packages/plugins/import/README.zh-CN.md new file mode 100644 index 0000000000..ef02d799c8 --- /dev/null +++ b/packages/plugins/import/README.zh-CN.md @@ -0,0 +1,11 @@ +# import + +[English](./README.md) | 中文 + +Excel 数据导入插件。 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/map/README.md b/packages/plugins/map/README.md new file mode 100644 index 0000000000..d5fe6974a5 --- /dev/null +++ b/packages/plugins/map/README.md @@ -0,0 +1,13 @@ +# map + +English | [中文](./README.zh-CN.md) + +地图插件。 + +## 安装激活 + +```bash +yarn pm enable map +``` + +## 使用方法 diff --git a/packages/plugins/map/README.zh-CN.md b/packages/plugins/map/README.zh-CN.md new file mode 100644 index 0000000000..a8b6938a54 --- /dev/null +++ b/packages/plugins/map/README.zh-CN.md @@ -0,0 +1,13 @@ +# map + +[English](./README.md) | 中文 + +地图插件。 + +## 安装激活 + +```bash +yarn pm enable map +``` + +## 使用方法 diff --git a/packages/plugins/math-formula-field/README.md b/packages/plugins/math-formula-field/README.md new file mode 100644 index 0000000000..09eeec1137 --- /dev/null +++ b/packages/plugins/math-formula-field/README.md @@ -0,0 +1,9 @@ +# math-formula-field + +English | [中文](./README.zh-CN.md) + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/math-formula-field/README.zh-CN.md b/packages/plugins/math-formula-field/README.zh-CN.md new file mode 100644 index 0000000000..d408567ed9 --- /dev/null +++ b/packages/plugins/math-formula-field/README.zh-CN.md @@ -0,0 +1,9 @@ +# math-formula-field + +[English](./README.md) | 中文 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/multi-app-manager/README.md b/packages/plugins/multi-app-manager/README.md new file mode 100644 index 0000000000..910ff80817 --- /dev/null +++ b/packages/plugins/multi-app-manager/README.md @@ -0,0 +1,13 @@ +# multi-app-manager + +English | [中文](./README.zh-CN.md) + +多应用管理插件。 + +## 安装激活 + +```bash +yarn pm enable multi-app-manager +``` + +## 使用方法 diff --git a/packages/plugins/multi-app-manager/README.zh-CN.md b/packages/plugins/multi-app-manager/README.zh-CN.md new file mode 100644 index 0000000000..c220fc2001 --- /dev/null +++ b/packages/plugins/multi-app-manager/README.zh-CN.md @@ -0,0 +1,13 @@ +# multi-app-manager + +[English](./README.md) | 中文 + +多应用管理插件。 + +## 安装激活 + +```bash +yarn pm enable multi-app-manager +``` + +## 使用方法 diff --git a/packages/plugins/notifications/README.md b/packages/plugins/notifications/README.md new file mode 100644 index 0000000000..a91904239f --- /dev/null +++ b/packages/plugins/notifications/README.md @@ -0,0 +1,9 @@ +# notifications + +English | [中文](./README.zh-CN.md) + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/notifications/README.zh-CN.md b/packages/plugins/notifications/README.zh-CN.md new file mode 100644 index 0000000000..981a48b5d9 --- /dev/null +++ b/packages/plugins/notifications/README.zh-CN.md @@ -0,0 +1,9 @@ +# notifications + +[English](./README.md) | 中文 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/oidc/README.md b/packages/plugins/oidc/README.md new file mode 100644 index 0000000000..10ecf52247 --- /dev/null +++ b/packages/plugins/oidc/README.md @@ -0,0 +1,11 @@ +# oidc + +English | [中文](./README.zh-CN.md) + +## 安装激活 + +```bash +yarn pm enable oidc +``` + +## 使用方法 diff --git a/packages/plugins/oidc/README.zh-CN.md b/packages/plugins/oidc/README.zh-CN.md new file mode 100644 index 0000000000..c7ef8987bf --- /dev/null +++ b/packages/plugins/oidc/README.zh-CN.md @@ -0,0 +1,11 @@ +# OIDC + +[English](./README.md) | 中文 + +## 安装激活 + +```bash +yarn pm enable oidc +``` + +## 使用方法 diff --git a/packages/plugins/saml/README.md b/packages/plugins/saml/README.md new file mode 100644 index 0000000000..3ec0e4bb71 --- /dev/null +++ b/packages/plugins/saml/README.md @@ -0,0 +1,11 @@ +# SAML + +English | [中文](./README.zh-CN.md) + +## 安装激活 + +```bash +yarn pm enable saml +``` + +## 使用方法 diff --git a/packages/plugins/saml/README.zh-CN.md b/packages/plugins/saml/README.zh-CN.md new file mode 100644 index 0000000000..8121817e3b --- /dev/null +++ b/packages/plugins/saml/README.zh-CN.md @@ -0,0 +1,11 @@ +# SAML + +[English](./README.md) | 中文 + +## 安装激活 + +```bash +yarn pm enable saml +``` + +## 使用方法 diff --git a/packages/plugins/sequence-field/README.md b/packages/plugins/sequence-field/README.md new file mode 100644 index 0000000000..5631580273 --- /dev/null +++ b/packages/plugins/sequence-field/README.md @@ -0,0 +1,9 @@ +# sequence-field + +English | [中文](./README.zh-CN.md) + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/sequence-field/README.zh-CN.md b/packages/plugins/sequence-field/README.zh-CN.md new file mode 100644 index 0000000000..97ab1000d5 --- /dev/null +++ b/packages/plugins/sequence-field/README.zh-CN.md @@ -0,0 +1,9 @@ +# sequence-field + +[English](./README.md) | 中文 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/snapshot-field/README.md b/packages/plugins/snapshot-field/README.md new file mode 100644 index 0000000000..5a52e16db4 --- /dev/null +++ b/packages/plugins/snapshot-field/README.md @@ -0,0 +1,13 @@ +# snapshot-field + +English | [中文](./README.zh-CN.md) + +关系数据快照插件。 + +## 安装激活 + +```bash +yarn pm enable snapshot-field +``` + +## 使用方法 diff --git a/packages/plugins/snapshot-field/README.zh-CN.md b/packages/plugins/snapshot-field/README.zh-CN.md new file mode 100644 index 0000000000..70c20cac35 --- /dev/null +++ b/packages/plugins/snapshot-field/README.zh-CN.md @@ -0,0 +1,13 @@ +# snapshot-field + +[English](./README.md) | 中文 + +关系数据快照插件。 + +## 安装激活 + +```bash +yarn pm enable snapshot-field +``` + +## 使用方法 diff --git a/packages/plugins/system-settings/README.md b/packages/plugins/system-settings/README.md new file mode 100644 index 0000000000..f5f5951609 --- /dev/null +++ b/packages/plugins/system-settings/README.md @@ -0,0 +1,9 @@ +# system-settings + +English | [中文](./README.zh-CN.md) + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/system-settings/README.zh-CN.md b/packages/plugins/system-settings/README.zh-CN.md new file mode 100644 index 0000000000..225ecd1eff --- /dev/null +++ b/packages/plugins/system-settings/README.zh-CN.md @@ -0,0 +1,9 @@ +# system-settings + +[English](./README.md) | 中文 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/ui-routes-storage/README.md b/packages/plugins/ui-routes-storage/README.md new file mode 100644 index 0000000000..bbe62202e0 --- /dev/null +++ b/packages/plugins/ui-routes-storage/README.md @@ -0,0 +1,9 @@ +# ui-routes-storage + +English | [中文](./README.zh-CN.md) + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/ui-routes-storage/README.zh-CN.md b/packages/plugins/ui-routes-storage/README.zh-CN.md new file mode 100644 index 0000000000..aee1e64f00 --- /dev/null +++ b/packages/plugins/ui-routes-storage/README.zh-CN.md @@ -0,0 +1,9 @@ +# ui-routes-storage + +[English](./README.md) | 中文 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/ui-schema-storage/README.md b/packages/plugins/ui-schema-storage/README.md new file mode 100644 index 0000000000..0f55f13eaa --- /dev/null +++ b/packages/plugins/ui-schema-storage/README.md @@ -0,0 +1,9 @@ +# ui-schema-storage + +English | [中文](./README.zh-CN.md) + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/ui-schema-storage/README.zh-CN.md b/packages/plugins/ui-schema-storage/README.zh-CN.md new file mode 100644 index 0000000000..aae9dff326 --- /dev/null +++ b/packages/plugins/ui-schema-storage/README.zh-CN.md @@ -0,0 +1,9 @@ +# ui-schema-storage + +[English](./README.md) | 中文 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/users/README.md b/packages/plugins/users/README.md new file mode 100644 index 0000000000..a09ae43731 --- /dev/null +++ b/packages/plugins/users/README.md @@ -0,0 +1,9 @@ +# users + +English | [中文](./README.zh-CN.md) + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/users/README.zh-CN.md b/packages/plugins/users/README.zh-CN.md new file mode 100644 index 0000000000..a8c284262e --- /dev/null +++ b/packages/plugins/users/README.zh-CN.md @@ -0,0 +1,9 @@ +# users + +[English](./README.md) | 中文 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/verification/README.md b/packages/plugins/verification/README.md new file mode 100644 index 0000000000..83a586f6f6 --- /dev/null +++ b/packages/plugins/verification/README.md @@ -0,0 +1,9 @@ +# verification + +English | [中文](./README.zh-CN.md) + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/verification/README.zh-CN.md b/packages/plugins/verification/README.zh-CN.md new file mode 100644 index 0000000000..96fc3c5564 --- /dev/null +++ b/packages/plugins/verification/README.zh-CN.md @@ -0,0 +1,9 @@ +# verification + +[English](./README.md) | 中文 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/workflow/README.md b/packages/plugins/workflow/README.md new file mode 100644 index 0000000000..e83d0373a4 --- /dev/null +++ b/packages/plugins/workflow/README.md @@ -0,0 +1,9 @@ +# workflow + +English | [中文](./README.zh-CN.md) + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 diff --git a/packages/plugins/workflow/README.zh-CN.md b/packages/plugins/workflow/README.zh-CN.md new file mode 100644 index 0000000000..e690848dd1 --- /dev/null +++ b/packages/plugins/workflow/README.zh-CN.md @@ -0,0 +1,9 @@ +# workflow + +[English](./README.md) | 中文 + +## 安装激活 + +内置插件无需手动安装激活。 + +## 使用方法 From d7dad1855f06a5845e370b7c16eb6038a58c1ef6 Mon Sep 17 00:00:00 2001 From: chenos Date: Sat, 11 Feb 2023 11:09:14 +0800 Subject: [PATCH 13/21] fix(duplicator): automatic file path recognition --- packages/plugins/duplicator/src/server/restorer.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/plugins/duplicator/src/server/restorer.ts b/packages/plugins/duplicator/src/server/restorer.ts index eed4daa368..39a75ae8bd 100644 --- a/packages/plugins/duplicator/src/server/restorer.ts +++ b/packages/plugins/duplicator/src/server/restorer.ts @@ -1,4 +1,5 @@ import decompress from 'decompress'; +import fs from 'fs'; import fsPromises from 'fs/promises'; import inquirer from 'inquirer'; import path from 'path'; @@ -6,15 +7,22 @@ import { AppMigrator } from './app-migrator'; import { CollectionGroupManager } from './collection-group-manager'; import { FieldValueWriter } from './field-value-writer'; import { readLines, sqlAdapter } from './utils'; -import fs from 'fs'; export class Restorer extends AppMigrator { direction = 'restore' as const; importedCollections: string[] = []; async restore(backupFilePath: string) { - const dirname = path.resolve(process.cwd(), 'storage', 'duplicator'); - const filePath = path.isAbsolute(backupFilePath) ? backupFilePath : path.resolve(dirname, backupFilePath); + let filePath: string; + + if (path.isAbsolute(backupFilePath)) { + filePath = backupFilePath; + } else if (path.basename(backupFilePath) === backupFilePath) { + const dirname = path.resolve(process.cwd(), 'storage', 'duplicator'); + filePath = path.resolve(dirname, backupFilePath); + } else { + filePath = path.resolve(process.cwd(), backupFilePath); + } const results = await inquirer.prompt([ { From 7fda978fe875f4532f13e43fa19359585695aef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=AB=E9=9B=A8=E6=B0=B4=E8=BF=87=E6=BB=A4=E7=9A=84?= =?UTF-8?q?=E7=A9=BA=E6=B0=94-Rairn?= <958414905@qq.com> Date: Sat, 11 Feb 2023 23:34:38 +0800 Subject: [PATCH 14/21] fix(table): make filed overflow behavior right (#1392) * fix(Table): fix overflow issue on td element (#1392) * chore(table): update style --- .../core/client/src/schema-component/antd/table-v2/Table.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/client/src/schema-component/antd/table-v2/Table.tsx b/packages/core/client/src/schema-component/antd/table-v2/Table.tsx index c2cbc9675c..ca388d6b36 100644 --- a/packages/core/client/src/schema-component/antd/table-v2/Table.tsx +++ b/packages/core/client/src/schema-component/antd/table-v2/Table.tsx @@ -235,6 +235,7 @@ export const Table: any = observer((props: any) => { css` max-width: 300px; white-space: nowrap; + overflow-x: scroll; .nb-read-pretty-input-number { text-align: right; } From 6c837ee08cfc9eb083d6995ecc83cfa42702cc2c Mon Sep 17 00:00:00 2001 From: anuoua Date: Sun, 12 Feb 2023 14:43:48 +0800 Subject: [PATCH 15/21] feat: association snapshot (#1438) * feat: snapshota * feat: snapshota i18n & bugs * feat: snapshota association value fix * feat: snapshota remove require true * feat: snapshota params.values null fix * feat: snapshota i18n * feat: snapshota CR fix * feat: snapshota field change fix * feat: snapshota magicstring fix * feat: snapshota field del fix * feat: snapshota CR fix * feat: snapshota tag fix * feat: snapshota depth < 3 * fix: improve code * feat: snapshota test * feat: snapshota remove disabled * fix: disabled isOverride --------- Co-authored-by: chenos --- .../src/block-provider/BlockProvider.tsx | 2 +- .../block-provider/KanbanBlockProvider.tsx | 2 +- .../src/block-provider/TableBlockProvider.tsx | 3 +- .../block-provider/TableSelectorProvider.tsx | 4 +- .../collection-manager/CollectionProvider.tsx | 4 +- .../antd/form-item/FormItem.tsx | 6 +- .../record-picker/ReadPrettyRecordPicker.tsx | 5 +- .../antd/table-v2/Table.Column.Designer.tsx | 2 +- .../src/__tests__/data/field_linkto.ts | 12 ++ .../src/__tests__/data/field_m2m.ts | 15 ++ .../src/__tests__/data/field_o2m.ts | 14 ++ .../src/__tests__/data/field_o2o.ts | 14 ++ .../src/__tests__/data/snap_linkto.ts | 12 ++ .../src/__tests__/data/snap_m2m.ts | 12 ++ .../src/__tests__/data/snap_o2m.ts | 12 ++ .../src/__tests__/data/snap_o2o.ts | 13 ++ .../src/__tests__/data/table_a.ts | 37 +++++ .../src/__tests__/data/table_b.ts | 37 +++++ .../src/__tests__/data/table_m2m.ts | 37 +++++ .../src/__tests__/fieldsHistory.test.ts | 3 +- .../src/__tests__/snapshots.test.ts | 155 ++++++++++++++++++ .../SnapshotBlockInitializers.tsx | 34 ---- .../src/client/SnapshotRecordPicker.tsx | 29 +--- .../client/components/AppendsTreeSelect.tsx | 122 ++++++++++++++ .../SnapshotOwnerCollectionFieldsSelect.tsx | 22 +++ .../snapshot-field/src/client/index.tsx | 6 +- .../snapshot-field/src/client/interface.ts | 81 ++++++--- .../snapshot-field/src/client/locale/en-US.ts | 6 +- .../snapshot-field/src/client/locale/zh-CN.ts | 5 +- .../src/server/fields/snapshot-field.ts | 54 +++++- .../snapshot-field/src/server/plugin.ts | 13 ++ 31 files changed, 675 insertions(+), 98 deletions(-) create mode 100644 packages/plugins/snapshot-field/src/__tests__/data/field_linkto.ts create mode 100644 packages/plugins/snapshot-field/src/__tests__/data/field_m2m.ts create mode 100644 packages/plugins/snapshot-field/src/__tests__/data/field_o2m.ts create mode 100644 packages/plugins/snapshot-field/src/__tests__/data/field_o2o.ts create mode 100644 packages/plugins/snapshot-field/src/__tests__/data/snap_linkto.ts create mode 100644 packages/plugins/snapshot-field/src/__tests__/data/snap_m2m.ts create mode 100644 packages/plugins/snapshot-field/src/__tests__/data/snap_o2m.ts create mode 100644 packages/plugins/snapshot-field/src/__tests__/data/snap_o2o.ts create mode 100644 packages/plugins/snapshot-field/src/__tests__/data/table_a.ts create mode 100644 packages/plugins/snapshot-field/src/__tests__/data/table_b.ts create mode 100644 packages/plugins/snapshot-field/src/__tests__/data/table_m2m.ts create mode 100644 packages/plugins/snapshot-field/src/__tests__/snapshots.test.ts create mode 100644 packages/plugins/snapshot-field/src/client/components/AppendsTreeSelect.tsx create mode 100644 packages/plugins/snapshot-field/src/client/components/SnapshotOwnerCollectionFieldsSelect.tsx diff --git a/packages/core/client/src/block-provider/BlockProvider.tsx b/packages/core/client/src/block-provider/BlockProvider.tsx index badbfc81cf..d64407f17a 100644 --- a/packages/core/client/src/block-provider/BlockProvider.tsx +++ b/packages/core/client/src/block-provider/BlockProvider.tsx @@ -92,7 +92,7 @@ export const useResourceAction = (props, opts = {}) => { */ const { resource, action, fieldName: tableFieldName } = props; const { fields } = useCollection(); - const appends = fields?.filter((field) => field.target && field.interface !== 'snapshot').map((field) => field.name); + const appends = fields?.filter((field) => field.target).map((field) => field.name); const params = useActionParams(props); const api = useAPIClient(); const fieldSchema = useFieldSchema(); diff --git a/packages/core/client/src/block-provider/KanbanBlockProvider.tsx b/packages/core/client/src/block-provider/KanbanBlockProvider.tsx index 74d738f362..3c274aa3c0 100644 --- a/packages/core/client/src/block-provider/KanbanBlockProvider.tsx +++ b/packages/core/client/src/block-provider/KanbanBlockProvider.tsx @@ -75,7 +75,7 @@ const useAssociationNames = (collection) => { const collectionFields = getCollectionFields(collection); const associationFields = new Set(); for (const collectionField of collectionFields) { - if (collectionField.target && collectionField.interface !== 'snapshot') { + if (collectionField.target) { associationFields.add(collectionField.name); const fields = getCollectionFields(collectionField.target); for (const field of fields) { diff --git a/packages/core/client/src/block-provider/TableBlockProvider.tsx b/packages/core/client/src/block-provider/TableBlockProvider.tsx index 0028968348..c7cfb335c9 100644 --- a/packages/core/client/src/block-provider/TableBlockProvider.tsx +++ b/packages/core/client/src/block-provider/TableBlockProvider.tsx @@ -6,7 +6,6 @@ import { useCollectionManager } from '../collection-manager'; import { BlockProvider, RenderChildrenWithAssociationFilter, useBlockRequestContext } from './BlockProvider'; import { useFixedSchema } from '../schema-component'; - export const TableBlockContext = createContext({}); const InternalTableBlockProvider = (props) => { @@ -39,7 +38,7 @@ export const useAssociationNames = (collection) => { const collectionFields = getCollectionFields(collection); const associationFields = new Set(); for (const collectionField of collectionFields) { - if (collectionField.target && collectionField.interface !== 'snapshot') { + if (collectionField.target) { associationFields.add(collectionField.name); const fields = getCollectionFields(collectionField.target); for (const field of fields) { diff --git a/packages/core/client/src/block-provider/TableSelectorProvider.tsx b/packages/core/client/src/block-provider/TableSelectorProvider.tsx index 159b91bbeb..1dd28e5f74 100644 --- a/packages/core/client/src/block-provider/TableSelectorProvider.tsx +++ b/packages/core/client/src/block-provider/TableSelectorProvider.tsx @@ -37,7 +37,7 @@ const InternalTableSelectorProvider = (props) => { const useAssociationNames2 = (collection) => { const { getCollectionFields } = useCollectionManager(); const names = getCollectionFields(collection) - ?.filter((field) => field.target && field.interface !== 'snapshot') + ?.filter((field) => field.target) .map((field) => field.name); return names; }; @@ -55,7 +55,7 @@ const useAssociationNames = (collection) => { const collectionFields = getCollectionFields(collection); const associationFields = new Set(); for (const collectionField of collectionFields) { - if (collectionField.target && collectionField.interface !== 'snapshot') { + if (collectionField.target) { associationFields.add(collectionField.name); const fields = getCollectionFields(collectionField.target); for (const field of fields) { diff --git a/packages/core/client/src/collection-manager/CollectionProvider.tsx b/packages/core/client/src/collection-manager/CollectionProvider.tsx index 2150ce225c..63aa61ea20 100644 --- a/packages/core/client/src/collection-manager/CollectionProvider.tsx +++ b/packages/core/client/src/collection-manager/CollectionProvider.tsx @@ -3,7 +3,9 @@ import { CollectionContext } from './context'; import { useCollectionManager } from './hooks'; import { CollectionOptions } from './types'; -export const CollectionProvider: React.FC<{ allowNull?: boolean; name?: string; collection?: CollectionOptions }> = (props) => { +export const CollectionProvider: React.FC<{ allowNull?: boolean; name?: string; collection?: CollectionOptions }> = ( + props, +) => { const { allowNull, name, collection, children } = props; const { getCollection } = useCollectionManager(); const value = getCollection(collection || name); diff --git a/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx b/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx index 8c412c06a9..81d7a9385a 100644 --- a/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx +++ b/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx @@ -67,7 +67,9 @@ FormItem.Designer = (props) => { const interfaceConfig = getInterface(collectionField?.interface); const validateSchema = interfaceConfig?.['validateSchema']?.(fieldSchema); const originalTitle = collectionField?.uiSchema?.title; - const targetFields = collectionField?.target ? getCollectionFields(collectionField.target) : []; + const targetFields = collectionField?.target + ? getCollectionFields(collectionField.target) + : getCollectionFields(collectionField?.targetCollection) ?? []; const fieldComponentOptions = useFieldComponentOptions(); const isSubFormAssocitionField = field.address.segments.includes('__form_grid'); const initialValue = { @@ -466,7 +468,7 @@ FormItem.Designer = (props) => { }} /> )} - {collectionField?.target && fieldSchema['x-component'] === 'CollectionField' && ( + {options.length > 0 && fieldSchema['x-component'] === 'CollectionField' && ( { return collectionField ? (
- + {renderRecords()} diff --git a/packages/core/client/src/schema-component/antd/table-v2/Table.Column.Designer.tsx b/packages/core/client/src/schema-component/antd/table-v2/Table.Column.Designer.tsx index b69c36f181..eead4655fd 100644 --- a/packages/core/client/src/schema-component/antd/table-v2/Table.Column.Designer.tsx +++ b/packages/core/client/src/schema-component/antd/table-v2/Table.Column.Designer.tsx @@ -31,7 +31,7 @@ export const TableColumnDesigner = (props) => { const { dn } = useDesignable(); const fieldNames = fieldSchema?.['x-component-props']?.['fieldNames'] || uiSchema?.['x-component-props']?.['fieldNames']; - const options = useLabelFields(collectionField?.target); + const options = useLabelFields(collectionField?.target ?? collectionField?.targetCollection); const intefaceCfg = getInterface(collectionField?.interface); return ( diff --git a/packages/plugins/snapshot-field/src/__tests__/data/field_linkto.ts b/packages/plugins/snapshot-field/src/__tests__/data/field_linkto.ts new file mode 100644 index 0000000000..61b3bc3a4b --- /dev/null +++ b/packages/plugins/snapshot-field/src/__tests__/data/field_linkto.ts @@ -0,0 +1,12 @@ +export const field_linkto = { + name: 'field_linkto', + type: 'belongsToMany', + uiSchema: { + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: 'field_linkto', + }, + interface: 'linkTo', + target: 'table_a', + collectionName: 'table_b', +}; diff --git a/packages/plugins/snapshot-field/src/__tests__/data/field_m2m.ts b/packages/plugins/snapshot-field/src/__tests__/data/field_m2m.ts new file mode 100644 index 0000000000..cbbeb085a1 --- /dev/null +++ b/packages/plugins/snapshot-field/src/__tests__/data/field_m2m.ts @@ -0,0 +1,15 @@ +export const field_m2m = { + foreignKey: 'fk_table_b', + otherKey: 'fk_table_a', + name: 'field_m2m', + type: 'belongsToMany', + uiSchema: { + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: 'field_m2m', + }, + interface: 'm2m', + through: 'table_m2m', + target: 'table_a', + collectionName: 'table_b', +}; diff --git a/packages/plugins/snapshot-field/src/__tests__/data/field_o2m.ts b/packages/plugins/snapshot-field/src/__tests__/data/field_o2m.ts new file mode 100644 index 0000000000..b53c950767 --- /dev/null +++ b/packages/plugins/snapshot-field/src/__tests__/data/field_o2m.ts @@ -0,0 +1,14 @@ +export const field_o2m = { + foreignKey: 'fk_table_b', + onDelete: 'SET NULL', + name: 'field_o2m', + type: 'hasMany', + uiSchema: { + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: 'field_o2m', + }, + interface: 'o2m', + target: 'table_a', + collectionName: 'table_b', +}; diff --git a/packages/plugins/snapshot-field/src/__tests__/data/field_o2o.ts b/packages/plugins/snapshot-field/src/__tests__/data/field_o2o.ts new file mode 100644 index 0000000000..c2bb3785ff --- /dev/null +++ b/packages/plugins/snapshot-field/src/__tests__/data/field_o2o.ts @@ -0,0 +1,14 @@ +export const field_o2o = { + foreignKey: 'fk_table_b', + onDelete: 'SET NULL', + name: 'field_o2o', + type: 'hasOne', + uiSchema: { + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: false, fieldNames: { label: 'id', value: 'id' } }, + title: 'field_o2o', + }, + interface: 'oho', + target: 'table_a', + collectionName: 'table_b', +}; diff --git a/packages/plugins/snapshot-field/src/__tests__/data/snap_linkto.ts b/packages/plugins/snapshot-field/src/__tests__/data/snap_linkto.ts new file mode 100644 index 0000000000..e45a90c516 --- /dev/null +++ b/packages/plugins/snapshot-field/src/__tests__/data/snap_linkto.ts @@ -0,0 +1,12 @@ +export const snap_linkto = { + name: 'snap_linkto', + type: 'snapshot', + uiSchema: { + 'x-component': 'SnapshotRecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: 'snap_linkto', + }, + interface: 'snapshot', + targetField: 'field_linkto', + collectionName: 'table_b', +}; diff --git a/packages/plugins/snapshot-field/src/__tests__/data/snap_m2m.ts b/packages/plugins/snapshot-field/src/__tests__/data/snap_m2m.ts new file mode 100644 index 0000000000..f9e60cb938 --- /dev/null +++ b/packages/plugins/snapshot-field/src/__tests__/data/snap_m2m.ts @@ -0,0 +1,12 @@ +export const snap_m2m = { + name: 'snap_m2m', + type: 'snapshot', + uiSchema: { + 'x-component': 'SnapshotRecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: 'snap_m2m', + }, + interface: 'snapshot', + targetField: 'field_m2m', + collectionName: 'table_b', +}; diff --git a/packages/plugins/snapshot-field/src/__tests__/data/snap_o2m.ts b/packages/plugins/snapshot-field/src/__tests__/data/snap_o2m.ts new file mode 100644 index 0000000000..7d1a81167b --- /dev/null +++ b/packages/plugins/snapshot-field/src/__tests__/data/snap_o2m.ts @@ -0,0 +1,12 @@ +export const snap_o2m = { + name: 'snap_o2m', + type: 'snapshot', + uiSchema: { + 'x-component': 'SnapshotRecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: 'snap_o2m', + }, + interface: 'snapshot', + targetField: 'field_o2m', + collectionName: 'table_b', +}; diff --git a/packages/plugins/snapshot-field/src/__tests__/data/snap_o2o.ts b/packages/plugins/snapshot-field/src/__tests__/data/snap_o2o.ts new file mode 100644 index 0000000000..f85c14eea5 --- /dev/null +++ b/packages/plugins/snapshot-field/src/__tests__/data/snap_o2o.ts @@ -0,0 +1,13 @@ +export const snap_o2o = { + name: 'snap_o2o', + type: 'snapshot', + uiSchema: { + 'x-component': 'SnapshotRecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: 'snap_o2o', + }, + interface: 'snapshot', + targetField: 'field_o2o', + appends: ['createdBy'], + collectionName: 'table_b', +}; diff --git a/packages/plugins/snapshot-field/src/__tests__/data/table_a.ts b/packages/plugins/snapshot-field/src/__tests__/data/table_a.ts new file mode 100644 index 0000000000..2baedb247e --- /dev/null +++ b/packages/plugins/snapshot-field/src/__tests__/data/table_a.ts @@ -0,0 +1,37 @@ +export const table_a = { + logging: true, + autoGenId: true, + createdBy: true, + updatedBy: false, + createdAt: false, + updatedAt: false, + sortable: true, + name: 'table_a', + template: 'general', + fields: [ + { + name: 'id', + type: 'bigInt', + autoIncrement: true, + primaryKey: true, + allowNull: false, + uiSchema: { type: 'number', title: '{{t("ID")}}', 'x-component': 'InputNumber', 'x-read-pretty': true }, + interface: 'id', + }, + { + name: 'createdBy', + interface: 'createdBy', + type: 'belongsTo', + target: 'users', + foreignKey: 'createdById', + uiSchema: { + type: 'object', + title: '{{t("Created by")}}', + 'x-component': 'RecordPicker', + 'x-component-props': { fieldNames: { value: 'id', label: 'nickname' } }, + 'x-read-pretty': true, + }, + }, + ], + title: 'table_a', +}; diff --git a/packages/plugins/snapshot-field/src/__tests__/data/table_b.ts b/packages/plugins/snapshot-field/src/__tests__/data/table_b.ts new file mode 100644 index 0000000000..ac505779fa --- /dev/null +++ b/packages/plugins/snapshot-field/src/__tests__/data/table_b.ts @@ -0,0 +1,37 @@ +export const table_b = { + logging: true, + autoGenId: true, + createdBy: true, + updatedBy: false, + createdAt: false, + updatedAt: false, + sortable: true, + name: 'table_b', + template: 'general', + fields: [ + { + name: 'id', + type: 'bigInt', + autoIncrement: true, + primaryKey: true, + allowNull: false, + uiSchema: { type: 'number', title: '{{t("ID")}}', 'x-component': 'InputNumber', 'x-read-pretty': true }, + interface: 'id', + }, + { + name: 'createdBy', + interface: 'createdBy', + type: 'belongsTo', + target: 'users', + foreignKey: 'createdById', + uiSchema: { + type: 'object', + title: '{{t("Created by")}}', + 'x-component': 'RecordPicker', + 'x-component-props': { fieldNames: { value: 'id', label: 'nickname' } }, + 'x-read-pretty': true, + }, + }, + ], + title: 'table_b', +}; diff --git a/packages/plugins/snapshot-field/src/__tests__/data/table_m2m.ts b/packages/plugins/snapshot-field/src/__tests__/data/table_m2m.ts new file mode 100644 index 0000000000..d1efa13c19 --- /dev/null +++ b/packages/plugins/snapshot-field/src/__tests__/data/table_m2m.ts @@ -0,0 +1,37 @@ +export const table_m2m = { + logging: true, + autoGenId: true, + createdBy: true, + updatedBy: false, + createdAt: false, + updatedAt: false, + sortable: true, + name: 'table_m2m', + template: 'general', + fields: [ + { + name: 'id', + type: 'bigInt', + autoIncrement: true, + primaryKey: true, + allowNull: false, + uiSchema: { type: 'number', title: '{{t("ID")}}', 'x-component': 'InputNumber', 'x-read-pretty': true }, + interface: 'id', + }, + { + name: 'createdBy', + interface: 'createdBy', + type: 'belongsTo', + target: 'users', + foreignKey: 'createdById', + uiSchema: { + type: 'object', + title: '{{t("Created by")}}', + 'x-component': 'RecordPicker', + 'x-component-props': { fieldNames: { value: 'id', label: 'nickname' } }, + 'x-read-pretty': true, + }, + }, + ], + title: 'table_m2m', +}; diff --git a/packages/plugins/snapshot-field/src/__tests__/fieldsHistory.test.ts b/packages/plugins/snapshot-field/src/__tests__/fieldsHistory.test.ts index 0d7311d231..0317b57581 100644 --- a/packages/plugins/snapshot-field/src/__tests__/fieldsHistory.test.ts +++ b/packages/plugins/snapshot-field/src/__tests__/fieldsHistory.test.ts @@ -1,6 +1,5 @@ import { mockServer, MockServer } from '@nocobase/test'; import SnapshotFieldPlugin from '../server'; -import path from 'path'; describe('actions', () => { let app: MockServer; @@ -22,7 +21,7 @@ describe('actions', () => { await app.destroy(); }); - it.only('fieldsHistory collectionName and name conflict between tables', async () => { + it('fieldsHistory collectionName and name conflict between tables', async () => { const agent = app.agent(); const field = { diff --git a/packages/plugins/snapshot-field/src/__tests__/snapshots.test.ts b/packages/plugins/snapshot-field/src/__tests__/snapshots.test.ts new file mode 100644 index 0000000000..3d04a0daad --- /dev/null +++ b/packages/plugins/snapshot-field/src/__tests__/snapshots.test.ts @@ -0,0 +1,155 @@ +import { mockServer, MockServer } from '@nocobase/test'; +import SnapshotFieldPlugin from '../server'; +import { field_linkto } from './data/field_linkto'; +import { field_m2m } from './data/field_m2m'; +import { field_o2m } from './data/field_o2m'; +import { field_o2o } from './data/field_o2o'; +import { snap_linkto } from './data/snap_linkto'; +import { snap_m2m } from './data/snap_m2m'; +import { snap_o2m } from './data/snap_o2m'; +import { snap_o2o } from './data/snap_o2o'; +import { table_a } from './data/table_a'; +import { table_b } from './data/table_b'; +import { table_m2m } from './data/table_m2m'; + +describe('actions', () => { + let app: MockServer; + + beforeEach(async () => { + app = mockServer({ + registerActions: true, + acl: false, + plugins: ['error-handler', 'users', 'ui-schema-storage', 'collection-manager'], + }); + + app.plugin(SnapshotFieldPlugin, { name: 'snapshot-field' }); + + await app.loadAndInstall({ clean: true }); + }); + + afterEach(async () => { + await app.cleanDb(); + await app.destroy(); + }); + + it.only('associations save', async () => { + const agent = app.agent(); + + await agent.resource('collections').create({ + values: table_a, + }); + + await agent.resource('collections').create({ + values: table_b, + }); + + await agent.resource('collections').create({ + values: table_m2m, + }); + + await agent.resource('fields').create({ + values: field_o2m, + }); + + await agent.resource('fields').create({ + values: field_m2m, + }); + + await agent.resource('fields').create({ + values: field_o2o, + }); + + await agent.resource('fields').create({ + values: field_linkto, + }); + + await agent.resource('fields').create({ + values: snap_o2m, + }); + + await agent.resource('fields').create({ + values: snap_m2m, + }); + + await agent.resource('fields').create({ + values: snap_o2o, + }); + + await agent.resource('fields').create({ + values: snap_linkto, + }); + + await agent.resource('table_a').create({ + values: {}, + }); + + await agent.resource('table_b').create({ + values: { + field_o2m: [{ createdById: 1, id: 1, fk_table_b: null }], + field_m2m: [{ createdById: 1, id: 1, fk_table_b: null }], + field_o2o: { createdById: 1, id: 1, fk_table_b: null }, + field_linkto: [{ createdById: 1, id: 1, fk_table_b: null }], + }, + }); + + const res = await agent.resource('table_b').list(); + const { snap_o2o: o2o, snap_m2m: m2m, snap_o2m: o2m, snap_linkto: linkto } = res.body.data[0]; + + const snapshotItem = { createdById: null, id: 1, fk_table_b: 1 }; + + expect(o2o).toMatchObject({ + collectionName: 'table_b', + data: snapshotItem, + }); + expect(m2m).toMatchObject({ + collectionName: 'table_b', + data: [snapshotItem], + }); + expect(o2m).toMatchObject({ + collectionName: 'table_b', + data: [snapshotItem], + }); + expect(linkto).toMatchObject({ + collectionName: 'table_b', + data: [snapshotItem], + }); + + await agent.resource('table_a').create({ + values: {}, + }); + + await agent.resource('collections.fields', 'table_b').destroy({ filter: { name: 'field_o2m' } }); + + const { statusCode: code2 } = await agent.resource('table_b').create({ + values: { + field_m2m: [{ createdById: 1, id: 2, fk_table_b: null }], + field_o2o: { createdById: 1, id: 2, fk_table_b: null }, + field_linkto: [{ createdById: 1, id: 2, fk_table_b: null }], + }, + }); + + expect(code2).toBe(200); + + const { statusCode: code3 } = await agent + .resource('collections.fields', 'table_a') + .destroy({ filter: { name: 'createdBy' } }); + + expect(code3).toBe(200); + + const { statusCode: code4 } = await agent.resource('table_a').create({ + values: {}, + }); + + expect(code4).toBe(200); + + const { statusCode: code5 } = await agent.resource('table_b').create({ + values: { + field_m2m: [{ createdById: 1, id: 3, fk_table_b: null }], + field_o2o: { createdById: 1, id: 3, fk_table_b: null }, + field_linkto: [{ createdById: 1, id: 3, fk_table_b: null }], + }, + }); + + expect(code5).toBe(200); + }); +}); diff --git a/packages/plugins/snapshot-field/src/client/SnapshotBlock/SnapshotBlockInitializers/SnapshotBlockInitializers.tsx b/packages/plugins/snapshot-field/src/client/SnapshotBlock/SnapshotBlockInitializers/SnapshotBlockInitializers.tsx index ea9cbbfc95..ca69b3576f 100644 --- a/packages/plugins/snapshot-field/src/client/SnapshotBlock/SnapshotBlockInitializers/SnapshotBlockInitializers.tsx +++ b/packages/plugins/snapshot-field/src/client/SnapshotBlock/SnapshotBlockInitializers/SnapshotBlockInitializers.tsx @@ -41,38 +41,4 @@ export const SnapshotBlockInitializers = (props: any) => { ]} /> ); - - // const { t } = useSnapshotTranslation(); - // return ( - // - // ); }; diff --git a/packages/plugins/snapshot-field/src/client/SnapshotRecordPicker.tsx b/packages/plugins/snapshot-field/src/client/SnapshotRecordPicker.tsx index f9055cf274..b4627d030c 100644 --- a/packages/plugins/snapshot-field/src/client/SnapshotRecordPicker.tsx +++ b/packages/plugins/snapshot-field/src/client/SnapshotRecordPicker.tsx @@ -1,41 +1,22 @@ -import React from 'react'; import { connect, mapReadPretty, useFieldSchema } from '@formily/react'; -import { - InputRecordPicker, - ReadPrettyRecordPicker, - useActionContext, - useCollection, - useCollectionHistory, -} from '@nocobase/client'; +import { ReadPrettyRecordPicker, useCollection } from '@nocobase/client'; +import React from 'react'; import { SnapshotHistoryCollectionProvider } from './SnapshotHistoryCollectionProvider'; -const useSnapshotFieldTargetCollectionName = () => { +const ReadPrettyRecordPickerWrapper = (props) => { const fieldSchema = useFieldSchema(); const { getField } = useCollection(); const collectionField = getField(fieldSchema.name); - const { historyCollections } = useCollectionHistory(); - return historyCollections.find((i) => i.name === collectionField.target)?.name; -}; - -const ReadPrettyRecordPickerWrapper = (props) => { - const collectionName = useSnapshotFieldTargetCollectionName(); return ( - + ); }; const SnapshotRecordPickerInner: any = connect( - (props) => { - const actionCtx = useActionContext(); - - const isUpdateAction = actionCtx.fieldSchema['x-action'] === 'update'; - - return isUpdateAction ? : ; - }, - // mapProps(mapSuffixProps), + ReadPrettyRecordPickerWrapper, mapReadPretty(ReadPrettyRecordPickerWrapper), ); diff --git a/packages/plugins/snapshot-field/src/client/components/AppendsTreeSelect.tsx b/packages/plugins/snapshot-field/src/client/components/AppendsTreeSelect.tsx new file mode 100644 index 0000000000..ef81dbc24f --- /dev/null +++ b/packages/plugins/snapshot-field/src/client/components/AppendsTreeSelect.tsx @@ -0,0 +1,122 @@ +import React from 'react'; +import { TreeSelect, Tag } from 'antd'; +import type { DefaultOptionType } from 'rc-tree-select/lib/TreeSelect'; +import { useForm } from '@formily/react'; +import { CollectionFieldOptions, useCollectionManager, useCompile, useRecord } from '@nocobase/client'; +import { useSnapshotTranslation } from '../locale'; +import { useTopRecord } from '../interface'; + +export type TreeCacheMapNode = { + parent?: TreeCacheMapNode; + title: string; + path: string; + children?: TreeCacheMapNode[]; +}; + +export type AppendsTreeSelectProps = { + value: string[]; + onChange: (value: string[]) => void; +}; + +type TreeOptionType = Omit & { value: string }; + +export const AppendsTreeSelect: React.FC = (props) => { + const { value = [], onChange, ...restProps } = props; + const record = useTopRecord(); + const { getCollectionFields, getCollectionField } = useCollectionManager(); + const compile = useCompile(); + const formValues = useForm().values; + const { t } = useSnapshotTranslation(); + + const fieldsToOptions = ( + fields: CollectionFieldOptions[] = [], + fieldPath: CollectionFieldOptions[] = [], + ): TreeOptionType[] => { + const filter = (i: CollectionFieldOptions) => + !!i.target && !!i.interface && fieldPath.length < 3 && !fieldPath.find((p) => p.target === i.target); + return fields.filter(filter).map((i) => ({ + title: compile(i.uiSchema?.title) ?? i.name, + value: fieldPath + .map((p) => p.name) + .concat(i.name) + .join('.'), + children: fieldsToOptions(getCollectionFields(i.target), [...fieldPath, i]), + })); + }; + + const treeData = fieldsToOptions( + getCollectionFields(getCollectionField(`${record.name}.${formValues.targetField}`)?.target), + ); + + const valueMap: Record = {}; + + function loops(list: TreeOptionType[], parent?: TreeCacheMapNode) { + return (list || []).map(({ children, value, title }) => { + const node: TreeCacheMapNode = (valueMap[value] = { + parent, + path: value, + title, + }); + node.children = loops(children, node); + return node; + }); + } + + loops(treeData); + + const handleChange = (newNodes: DefaultOptionType[]) => { + const newValue = newNodes.map((i) => i.value) as string[]; + const valueSet = new Set(newValue); + const delValue = value.find((i) => !newValue.includes(i)); + + if (delValue) { + const delNode = valueMap[delValue]; + const delNodeValue = (node: TreeCacheMapNode) => { + valueSet.delete(node.path); + node.children?.forEach((child) => delNodeValue(child)); + }; + delNodeValue(delNode); + } else { + newValue.forEach((v) => { + let current = valueMap[v]; + while ((current = current.parent)) { + valueSet.add(current.path); + } + }); + } + onChange(Array.from(valueSet)); + }; + + const TreeTag = (props) => { + const { value, onClose, disabled, closable } = props; + let node = valueMap[value]; + let text = node?.title; + while ((node = node?.parent)) { + text = `${node.title} / ${text}`; + } + return ( + + {text} + + ); + }; + + const filterdValue = Array.isArray(value) ? value.filter((i) => i in valueMap) : value; + + return ( + void} + treeData={treeData} + {...restProps} + /> + ); +}; diff --git a/packages/plugins/snapshot-field/src/client/components/SnapshotOwnerCollectionFieldsSelect.tsx b/packages/plugins/snapshot-field/src/client/components/SnapshotOwnerCollectionFieldsSelect.tsx new file mode 100644 index 0000000000..f77cbe0a71 --- /dev/null +++ b/packages/plugins/snapshot-field/src/client/components/SnapshotOwnerCollectionFieldsSelect.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Select, SelectProps } from 'antd'; +import { useTopRecord } from '../interface'; +import { useCollectionManager, useCompile } from '@nocobase/client'; + +export type SnapshotOwnerCollectionFieldsSelectProps = Omit; + +export const useSnapshotOwnerCollectionFields = () => { + const record = useTopRecord(); + const { getCollection } = useCollectionManager(); + const collection = getCollection(record.name); + const compile = useCompile(); + + return collection.fields + .filter((i) => !!i.target && !!i.interface) + .map((i) => ({ ...i, label: compile(i.uiSchema?.title), value: i.name })); +}; + +export const SnapshotOwnerCollectionFieldsSelect: React.FC = (props) => { + const options = useSnapshotOwnerCollectionFields(); + return