From 74051ff0a5809a9c10a519ac6ad2700dd281fe06 Mon Sep 17 00:00:00 2001 From: Zeke Zhang <958414905@qq.com> Date: Wed, 27 Mar 2024 18:06:28 +0800 Subject: [PATCH] refactor(DataBlock): kanban and gantt and map and calendar (#3792) * refactor: kanban * refactor: gantt * refactor: map * refactor: calendar * refactor: compat * refactor: rename to createKanbanBlockUISchema * refactor(kanban): use x-use-component-props instead of useProps * refactor(Gantt): rename to createGanttBlockUISchema * refactor: use x-use-component-props instead of useProps * refactor: rename * refactor(Map): use x-use-component-props instead of useProps * refactor(Calendar): rename * refactor(Calendar): should not get collection on getting association in UISchema * refactor(Calendar): use x-use-component-props instead of useProps * chore: add comment * chore: fix unit test * fix: add scopes to fix e2e * fix(Calendar): add association property to CalendarBlockProvider decorator * test: add e2e for Calenndar --- .../client/src/block-provider/hooks/index.ts | 1 + .../hooks/useDataBlockSourceId.tsx | 1 + .../src/schema-component/hooks/useProps.ts | 6 + .../client/__e2e__/schemaInitializer.test.ts | 35 +++ .../src/client/__e2e__/templates.ts | 238 ++++++++++++++++++ .../createCalendarBlockSchema.test.ts | 116 +++++++++ .../src/client/calendar/Calendar.tsx | 198 ++++++++------- .../hooks/useCalendarBlockDecoratorProps.ts | 18 ++ .../client/hooks/useCalendarBlockParams.ts | 22 ++ .../plugin-calendar/src/client/index.tsx | 3 +- .../CalendarBlockProvider.tsx | 44 ++-- ...tils.ts => createCalendarBlockUISchema.ts} | 30 +-- .../items/CalendarBlockInitializer.tsx | 7 +- ...ordAssociationCalendarBlockInitializer.tsx | 12 +- .../src/client/GanttBlockInitializer.tsx | 6 +- .../__tests__/createGanttBlockSchema.test.ts | 143 +++++++++++ .../src/client/components/gantt/gantt.tsx | 17 +- .../src/client/createGanttBlockUISchema.tsx | 129 ++++++++++ .../plugin-gantt/src/client/index.tsx | 4 + .../plugin-gantt/src/client/utils.tsx | 130 ---------- .../plugin-kanban/src/client/Kanban.tsx | 198 ++++++++------- .../src/client/KanbanBlockInitializer.tsx | 7 +- .../__tests__/createKanbanBlockSchema.test.ts | 126 ++++++++++ ...{utils.ts => createKanbanBlockUISchema.ts} | 30 ++- .../block/createMapBlockSchema.test.ts | 95 +++++++ .../plugin-map/src/client/block/MapBlock.tsx | 13 +- .../src/client/block/MapBlockInitializer.tsx | 8 +- .../client/block/createMapBlockUISchema.ts | 81 ++++++ .../plugin-map/src/client/block/utils.ts | 83 ------ .../src/client/components/AMap/Block.tsx | 1 + .../client/components/GoogleMaps/Block.tsx | 1 + .../@nocobase/plugin-map/src/client/index.tsx | 5 + 32 files changed, 1324 insertions(+), 484 deletions(-) create mode 100644 packages/plugins/@nocobase/plugin-calendar/src/client/__e2e__/schemaInitializer.test.ts create mode 100644 packages/plugins/@nocobase/plugin-calendar/src/client/__e2e__/templates.ts create mode 100644 packages/plugins/@nocobase/plugin-calendar/src/client/__tests__/createCalendarBlockSchema.test.ts create mode 100644 packages/plugins/@nocobase/plugin-calendar/src/client/hooks/useCalendarBlockDecoratorProps.ts create mode 100644 packages/plugins/@nocobase/plugin-calendar/src/client/hooks/useCalendarBlockParams.ts rename packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/{utils.ts => createCalendarBlockUISchema.ts} (82%) create mode 100644 packages/plugins/@nocobase/plugin-gantt/src/client/__tests__/createGanttBlockSchema.test.ts create mode 100644 packages/plugins/@nocobase/plugin-gantt/src/client/createGanttBlockUISchema.tsx create mode 100644 packages/plugins/@nocobase/plugin-kanban/src/client/__tests__/createKanbanBlockSchema.test.ts rename packages/plugins/@nocobase/plugin-kanban/src/client/{utils.ts => createKanbanBlockUISchema.ts} (83%) create mode 100644 packages/plugins/@nocobase/plugin-map/src/client/__tests__/block/createMapBlockSchema.test.ts create mode 100644 packages/plugins/@nocobase/plugin-map/src/client/block/createMapBlockUISchema.ts diff --git a/packages/core/client/src/block-provider/hooks/index.ts b/packages/core/client/src/block-provider/hooks/index.ts index 4380c4d19..8b6385c2a 100644 --- a/packages/core/client/src/block-provider/hooks/index.ts +++ b/packages/core/client/src/block-provider/hooks/index.ts @@ -36,6 +36,7 @@ import { TableFieldResource } from '../TableFieldProvider'; export * from './useFormActiveFields'; export * from './useParsedFilter'; +export * from './useDataBlockSourceId'; export const usePickActionProps = () => { const form = useForm(); diff --git a/packages/core/client/src/block-provider/hooks/useDataBlockSourceId.tsx b/packages/core/client/src/block-provider/hooks/useDataBlockSourceId.tsx index b7efefeff..d677198b7 100644 --- a/packages/core/client/src/block-provider/hooks/useDataBlockSourceId.tsx +++ b/packages/core/client/src/block-provider/hooks/useDataBlockSourceId.tsx @@ -7,6 +7,7 @@ import { } from '../..'; /** + * @internal * 注意:这里有一个需要更改 schema 才能解决的问题,就是在获取 sourceId 的时候无法确定(在关系字段和当前表同表时) * 是需要从 recordData 还是 parentRecordData 中获取;解决方法是通过更改 schema,在不同类型的关系区块中 * (`通过点击关系字段按钮打开的弹窗中创建的非关系字段区块`和`关系字段区块`)使用不同的 hook。 diff --git a/packages/core/client/src/schema-component/hooks/useProps.ts b/packages/core/client/src/schema-component/hooks/useProps.ts index f4f93fbe8..acad65d9f 100644 --- a/packages/core/client/src/schema-component/hooks/useProps.ts +++ b/packages/core/client/src/schema-component/hooks/useProps.ts @@ -8,6 +8,12 @@ interface Options { } const useDef = () => ({}); + +/** + * 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema + * @param originalProps + * @returns + */ export const useProps = (originalProps: any = {}) => { const { useProps: useDynamicHook = useDef, ...others } = originalProps; let useDynamicProps = useDynamicHook; diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/__e2e__/schemaInitializer.test.ts b/packages/plugins/@nocobase/plugin-calendar/src/client/__e2e__/schemaInitializer.test.ts new file mode 100644 index 000000000..2a2792c66 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-calendar/src/client/__e2e__/schemaInitializer.test.ts @@ -0,0 +1,35 @@ +import { test, expect } from '@nocobase/test/e2e'; +import { emptyPageWithCalendarCollection, oneTableWithCalendarCollection } from './templates'; + +test.describe('where can be added', () => { + test('page', async ({ page, mockPage }) => { + await mockPage(emptyPageWithCalendarCollection).goto(); + + await page.getByLabel('schema-initializer-Grid-page:').hover(); + await page.getByRole('menuitem', { name: 'form Calendar right' }).hover(); + await page.getByRole('menuitem', { name: 'calendar', exact: true }).click(); + + await page.getByLabel('block-item-Select-Title field').getByTestId('select-single').click(); + await page.getByRole('option', { name: 'Repeats' }).click(); + await page.getByRole('button', { name: 'OK', exact: true }).click(); + + await expect(page.getByLabel('block-item-CardItem-calendar-').getByText('Sun', { exact: true })).toBeVisible(); + }); + + test('association block in popup', async ({ page, mockPage, mockRecord }) => { + await mockPage(oneTableWithCalendarCollection).goto(); + await mockRecord('toManyCalendar'); + + // 打开弹窗 + await page.getByLabel('action-Action.Link-View-view-').first().click(); + await page.getByLabel('schema-initializer-Grid-popup').hover(); + await page.getByRole('menuitem', { name: 'form Calendar right' }).hover(); + await page.getByRole('menuitem', { name: 'manyToMany -> calendar' }).click(); + + await page.getByLabel('block-item-Select-Title field').getByTestId('select-single').click(); + await page.getByRole('option', { name: 'Repeats' }).click(); + await page.getByRole('button', { name: 'OK', exact: true }).click(); + + await expect(page.getByLabel('block-item-CardItem-calendar-').getByText('Sun', { exact: true })).toBeVisible(); + }); +}); diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/__e2e__/templates.ts b/packages/plugins/@nocobase/plugin-calendar/src/client/__e2e__/templates.ts new file mode 100644 index 000000000..9198108de --- /dev/null +++ b/packages/plugins/@nocobase/plugin-calendar/src/client/__e2e__/templates.ts @@ -0,0 +1,238 @@ +import { PageConfig } from '@nocobase/test/e2e'; + +const calendarCollection = { + name: 'calendar', + template: 'calendar', +}; + +export const emptyPageWithCalendarCollection: PageConfig = { + collections: [calendarCollection], +}; + +export const oneTableWithCalendarCollection: PageConfig = { + collections: [ + calendarCollection, + { + name: 'toManyCalendar', + fields: [ + { + name: 'manyToMany', + interface: 'm2m', + target: 'calendar', + }, + ], + }, + ], + pageSchema: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + properties: { + lqs2pzl6li1: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'page:addBlock', + properties: { + viawniezd4p: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + properties: { + s0nef2zgi5m: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + properties: { + ibwqgtls50q: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-acl-action': 'toManyCalendar:list', + 'x-use-decorator-props': 'useTableBlockDecoratorProps', + 'x-decorator-props': { + collection: 'toManyCalendar', + dataSource: 'main', + action: 'list', + params: { + pageSize: 20, + }, + rowKey: 'id', + showIndex: true, + dragSort: false, + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:table', + 'x-component': 'CardItem', + 'x-filter-targets': [], + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'table:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + 'x-uid': 'xi12fv3arso', + 'x-async': false, + 'x-index': 1, + }, + v931jk5mpmg: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'array', + 'x-initializer': 'table:configureColumns', + 'x-component': 'TableV2', + 'x-use-component-props': 'useTableBlockProps', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + }, + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Actions") }}', + 'x-action-column': 'actions', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-designer': 'TableV2.ActionColumnDesigner', + 'x-initializer': 'table:configureItemActions', + properties: { + w4rc8u7s5q0: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + properties: { + hd95fsevokf: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("View") }}', + 'x-action': 'view', + 'x-toolbar': 'ActionSchemaToolbar', + 'x-settings': 'actionSettings:view', + 'x-component': 'Action.Link', + 'x-component-props': { + openMode: 'drawer', + }, + 'x-decorator': 'ACLActionProvider', + 'x-designer-props': { + linkageAction: true, + }, + properties: { + drawer: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("View record") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'TabPaneInitializers', + properties: { + tab1: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{t("Details")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'popup:common:addBlock', + 'x-uid': 'wfpwj2q55xi', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'tvwvyrlvpv6', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'bys1tnlre1o', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'ml2scl3y6se', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'q9gwy03bevt', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'f8i1npjyidq', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'h26hp47w83k', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'bjbs9yvab1k', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': '5i6112zy12n', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '6vh9ncefvs2', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'u4b441tpuzq', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'mworqfx7jaf', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'kgor3l32s12', + 'x-async': true, + 'x-index': 1, + }, +}; diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/__tests__/createCalendarBlockSchema.test.ts b/packages/plugins/@nocobase/plugin-calendar/src/client/__tests__/createCalendarBlockSchema.test.ts new file mode 100644 index 000000000..b716f94d0 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-calendar/src/client/__tests__/createCalendarBlockSchema.test.ts @@ -0,0 +1,116 @@ +import { createCalendarBlockUISchema } from '../schema-initializer/createCalendarBlockUISchema'; + +vi.mock('@formily/shared', async () => { + const actual = await vi.importActual('@formily/shared'); + return { + ...actual, + uid: () => 'mocked-uid', + }; +}); + +describe('createCalendarBlockSchema', () => { + it('should return the correct schema', () => { + const options = { + collectionName: 'users', + dataSource: 'events', + fieldNames: { + title: 'title', + startDate: 'start_date', + endDate: 'end_date', + }, + association: 'users.roles', + }; + + const schema = createCalendarBlockUISchema(options); + + expect(schema).toMatchInlineSnapshot(` + { + "properties": { + "mocked-uid": { + "properties": { + "event": { + "properties": { + "drawer": { + "properties": { + "tabs": { + "properties": { + "tab1": { + "properties": { + "grid": { + "type": "void", + "x-component": "Grid", + "x-initializer": "popup:common:addBlock", + "x-initializer-props": { + "actionInitializers": "details:configureActions", + }, + }, + }, + "title": "{{t('Details', { ns: 'calendar' })}}", + "type": "void", + "x-component": "Tabs.TabPane", + "x-component-props": {}, + "x-designer": "Tabs.Designer", + }, + }, + "type": "void", + "x-component": "Tabs", + "x-component-props": {}, + "x-initializer": "TabPaneInitializers", + "x-initializer-props": { + "gridInitializer": "popup:common:addBlock", + }, + }, + }, + "title": "{{t('View record', { ns: 'calendar' })}}", + "type": "void", + "x-component": "Action.Drawer", + "x-component-props": { + "className": "nb-action-popup", + }, + }, + }, + "type": "void", + "x-component": "CalendarV2.Event", + }, + "toolBar": { + "type": "void", + "x-component": "CalendarV2.ActionBar", + "x-component-props": { + "style": { + "marginBottom": 24, + }, + }, + "x-initializer": "calendar:configureActions", + }, + }, + "type": "void", + "x-component": "CalendarV2", + "x-use-component-props": "useCalendarBlockProps", + }, + }, + "type": "void", + "x-acl-action": "users.roles:list", + "x-component": "CardItem", + "x-decorator": "CalendarBlockProvider", + "x-decorator-props": { + "action": "list", + "association": "users.roles", + "collection": "users", + "dataSource": "events", + "fieldNames": { + "endDate": "end_date", + "id": "id", + "startDate": "start_date", + "title": "title", + }, + "params": { + "paginate": false, + }, + }, + "x-settings": "blockSettings:calendar", + "x-toolbar": "BlockSchemaToolbar", + "x-use-decorator-props": "useCalendarBlockDecoratorProps", + } + `); + }); +}); diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Calendar.tsx b/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Calendar.tsx index 0a4c1479c..244511a68 100644 --- a/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Calendar.tsx +++ b/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Calendar.tsx @@ -1,6 +1,12 @@ import { LeftOutlined, RightOutlined } from '@ant-design/icons'; import { RecursionField, Schema, observer, useFieldSchema } from '@formily/react'; -import { ActionContextProvider, RecordProvider, useCollectionParentRecordData, useProps } from '@nocobase/client'; +import { + ActionContextProvider, + RecordProvider, + useCollectionParentRecordData, + useProps, + withDynamicSchemaProps, +} from '@nocobase/client'; import { parseExpression } from 'cron-parser'; import type { Dayjs } from 'dayjs'; import dayjs from 'dayjs'; @@ -171,100 +177,104 @@ const CalendarRecordViewer = (props) => { ); }; -export const Calendar: any = observer( - (props: any) => { - const { dataSource, fieldNames, showLunar, fixedBlock } = useProps(props); - const [date, setDate] = useState(new Date()); - const [view, setView] = useState('month'); - const events = useEvents(dataSource, fieldNames, date, view); - const [visible, setVisible] = useState(false); - const [record, setRecord] = useState({}); - const { wrapSSR, hashId, componentCls: containerClassName } = useStyle(); +export const Calendar: any = withDynamicSchemaProps( + observer( + (props: any) => { + // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema + const { dataSource, fieldNames, showLunar, fixedBlock } = useProps(props); - const components = useMemo(() => { - return { - toolbar: (props) => , - // week: { - // header: (props) =>
, - // }, - month: { - dateHeader: (props) =>
, - }, + const [date, setDate] = useState(new Date()); + const [view, setView] = useState('month'); + const events = useEvents(dataSource, fieldNames, date, view); + const [visible, setVisible] = useState(false); + const [record, setRecord] = useState({}); + const { wrapSSR, hashId, componentCls: containerClassName } = useStyle(); + + const components = useMemo(() => { + return { + toolbar: (props) => , + // week: { + // header: (props) =>
, + // }, + month: { + dateHeader: (props) =>
, + }, + }; + }, [showLunar]); + + const messages: any = { + allDay: '', + previous: ( +
+ +
+ ), + next: ( +
+ +
+ ), + today: i18nt('Today'), + month: i18nt('Month'), + week: i18nt('Week'), + work_week: i18nt('Work week'), + day: i18nt('Day'), + agenda: i18nt('Agenda'), + date: i18nt('Date'), + time: i18nt('Time'), + event: i18nt('Event'), + noEventsInRange: i18nt('None'), + showMore: (count) => i18nt('{{count}} more items', { count }), }; - }, [showLunar]); - - const messages: any = { - allDay: '', - previous: ( -
- -
- ), - next: ( -
- -
- ), - today: i18nt('Today'), - month: i18nt('Month'), - week: i18nt('Week'), - work_week: i18nt('Work week'), - day: i18nt('Day'), - agenda: i18nt('Agenda'), - date: i18nt('Date'), - time: i18nt('Time'), - event: i18nt('Event'), - noEventsInRange: i18nt('None'), - showMore: (count) => i18nt('{{count}} more items', { count }), - }; - return wrapSSR( -
- - - { - console.log('onSelectSlot', slotInfo); - }} - onDoubleClickEvent={() => { - console.log('onDoubleClickEvent'); - }} - onSelectEvent={(event) => { - const record = dataSource?.find((item) => item[fieldNames.id] === event.id); - if (!record) { - return; - } - record.__event = { ...event, start: formatDate(dayjs(event.start)), end: formatDate(dayjs(event.end)) }; - - setRecord(record); - setVisible(true); - }} - formats={{ - monthHeaderFormat: 'YYYY-M', - agendaDateFormat: 'M-DD', - dayHeaderFormat: 'YYYY-M-DD', - dayRangeHeaderFormat: ({ start, end }, culture, local) => { - if (dates.eq(start, end, 'month')) { - return local.format(start, 'YYYY-M', culture); + return wrapSSR( +
+ + + { + console.log('onSelectSlot', slotInfo); + }} + onDoubleClickEvent={() => { + console.log('onDoubleClickEvent'); + }} + onSelectEvent={(event) => { + const record = dataSource?.find((item) => item[fieldNames.id] === event.id); + if (!record) { + return; } - return `${local.format(start, 'YYYY-M', culture)} - ${local.format(end, 'YYYY-M', culture)}`; - }, - }} - components={components} - localizer={localizer} - /> -
, - ); - }, - { displayName: 'Calendar' }, + record.__event = { ...event, start: formatDate(dayjs(event.start)), end: formatDate(dayjs(event.end)) }; + + setRecord(record); + setVisible(true); + }} + formats={{ + monthHeaderFormat: 'YYYY-M', + agendaDateFormat: 'M-DD', + dayHeaderFormat: 'YYYY-M-DD', + dayRangeHeaderFormat: ({ start, end }, culture, local) => { + if (dates.eq(start, end, 'month')) { + return local.format(start, 'YYYY-M', culture); + } + return `${local.format(start, 'YYYY-M', culture)} - ${local.format(end, 'YYYY-M', culture)}`; + }, + }} + components={components} + localizer={localizer} + /> +
, + ); + }, + { displayName: 'Calendar' }, + ), ); diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/hooks/useCalendarBlockDecoratorProps.ts b/packages/plugins/@nocobase/plugin-calendar/src/client/hooks/useCalendarBlockDecoratorProps.ts new file mode 100644 index 000000000..edc67ee95 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-calendar/src/client/hooks/useCalendarBlockDecoratorProps.ts @@ -0,0 +1,18 @@ +import { useDataBlockSourceId } from '@nocobase/client'; +import { useCalendarBlockParams } from './useCalendarBlockParams'; + +export function useCalendarBlockDecoratorProps(props) { + const params = useCalendarBlockParams(props); + let sourceId: string; + + // 因为 association 是一个固定的值,所以可以在 hooks 中直接使用 + if (props.association) { + // eslint-disable-next-line react-hooks/rules-of-hooks + sourceId = useDataBlockSourceId({ association: props.association }); + } + + return { + params, + sourceId, + }; +} diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/hooks/useCalendarBlockParams.ts b/packages/plugins/@nocobase/plugin-calendar/src/client/hooks/useCalendarBlockParams.ts new file mode 100644 index 000000000..bd4f60af0 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-calendar/src/client/hooks/useCalendarBlockParams.ts @@ -0,0 +1,22 @@ +import { useMemo } from 'react'; + +export function useCalendarBlockParams(props) { + const appends = useMemo(() => { + const arr: string[] = []; + const start = props.fieldNames?.start; + const end = props.fieldNames?.end; + + if (Array.isArray(start) && start.length >= 2) { + arr.push(start[0]); + } + if (Array.isArray(end) && end.length >= 2) { + arr.push(end[0]); + } + + return arr; + }, [props.fieldNames]); + + return useMemo(() => { + return { ...props.params, appends: [...appends, ...(props.params.appends || [])], paginate: false }; + }, [appends, props.params]); +} diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/index.tsx b/packages/plugins/@nocobase/plugin-calendar/src/client/index.tsx index 49e4680f0..9d281eaa3 100644 --- a/packages/plugins/@nocobase/plugin-calendar/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-calendar/src/client/index.tsx @@ -16,6 +16,7 @@ import { useCreateAssociationCalendarBlock, } from './schema-initializer/items'; import { useMemo } from 'react'; +import { useCalendarBlockDecoratorProps } from './hooks/useCalendarBlockDecoratorProps'; export class PluginCalendarClient extends Plugin { async load() { @@ -60,7 +61,7 @@ export class PluginCalendarClient extends Plugin { RecordAssociationCalendarBlockInitializer, CalendarV2, }); - this.app.addScopes({ useCalendarBlockProps }); + this.app.addScopes({ useCalendarBlockProps, useCalendarBlockDecoratorProps }); this.schemaSettingsManager.add(calendarBlockSettings); this.app.schemaInitializerManager.add(CalendarActionInitializers_deprecated); this.app.schemaInitializerManager.add(calendarActionInitializers); diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/CalendarBlockProvider.tsx b/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/CalendarBlockProvider.tsx index 92fd7822b..04b2f18c7 100644 --- a/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/CalendarBlockProvider.tsx +++ b/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/CalendarBlockProvider.tsx @@ -1,8 +1,15 @@ import { ArrayField } from '@formily/core'; -import { useField } from '@formily/react'; -import { BlockProvider, FixedBlockWrapper, useBlockRequestContext, useParsedFilter } from '@nocobase/client'; +import { useField, useFieldSchema } from '@formily/react'; +import { + BlockProvider, + FixedBlockWrapper, + useBlockRequestContext, + useParsedFilter, + withDynamicSchemaProps, +} from '@nocobase/client'; import _ from 'lodash'; import React, { createContext, useContext, useEffect, useMemo } from 'react'; +import { useCalendarBlockParams } from '../hooks/useCalendarBlockParams'; export const CalendarBlockContext = createContext({}); CalendarBlockContext.displayName = 'CalendarBlockContext'; @@ -38,31 +45,26 @@ const InternalCalendarBlockProvider = (props) => { ); }; -export const CalendarBlockProvider = (props) => { - const appends = useMemo(() => { - const arr: string[] = []; - const start = props.fieldNames?.start; - const end = props.fieldNames?.end; +const useCompatCalendarBlockParams = (props) => { + const fieldSchema = useFieldSchema(); - if (Array.isArray(start) && start.length >= 2) { - arr.push(start[0]); - } - if (Array.isArray(end) && end.length >= 2) { - arr.push(end[0]); - } + // 因为 x-use-decorator-props 的值是固定不变的,所以可以在条件中使用 hooks + if (fieldSchema['x-use-decorator-props']) { + return props.params; + } else { + // eslint-disable-next-line react-hooks/rules-of-hooks + return useCalendarBlockParams(props); + } +}; - return arr; - }, [props.fieldNames]); +export const CalendarBlockProvider = withDynamicSchemaProps((props) => { + const params = useCompatCalendarBlockParams(props); return ( - + ); -}; +}); export const useCalendarBlockContext = () => { return useContext(CalendarBlockContext); diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/utils.ts b/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/createCalendarBlockUISchema.ts similarity index 82% rename from packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/utils.ts rename to packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/createCalendarBlockUISchema.ts index 2cfced89a..872437377 100644 --- a/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/utils.ts +++ b/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/createCalendarBlockUISchema.ts @@ -2,16 +2,23 @@ import { ISchema } from '@formily/react'; import { uid } from '@formily/shared'; import { generateNTemplate } from '../../locale'; -export const createCalendarBlockSchema = (options) => { - const { collection, dataSource, resource, fieldNames, settings, ...others } = options; - const schema: ISchema = { +export const createCalendarBlockUISchema = (options: { + dataSource: string; + fieldNames: object; + collectionName?: string; + association?: string; +}): ISchema => { + const { collectionName, dataSource, fieldNames, association } = options; + + return { type: 'void', - 'x-acl-action': `${resource || collection}:list`, + 'x-acl-action': `${association || collectionName}:list`, 'x-decorator': 'CalendarBlockProvider', + 'x-use-decorator-props': 'useCalendarBlockDecoratorProps', 'x-decorator-props': { - collection: collection, + collection: collectionName, dataSource, - resource: resource || collection, + association, action: 'list', fieldNames: { id: 'id', @@ -20,18 +27,15 @@ export const createCalendarBlockSchema = (options) => { params: { paginate: false, }, - ...others, }, 'x-toolbar': 'BlockSchemaToolbar', - 'x-settings': settings, + 'x-settings': 'blockSettings:calendar', 'x-component': 'CardItem', properties: { [uid()]: { type: 'void', 'x-component': 'CalendarV2', - 'x-component-props': { - useProps: '{{ useCalendarBlockProps }}', - }, + 'x-use-component-props': 'useCalendarBlockProps', properties: { toolBar: { type: 'void', @@ -42,7 +46,6 @@ export const createCalendarBlockSchema = (options) => { }, }, 'x-initializer': 'calendar:configureActions', - properties: {}, }, event: { type: 'void', @@ -79,7 +82,6 @@ export const createCalendarBlockSchema = (options) => { actionInitializers: 'details:configureActions', }, 'x-initializer': 'popup:common:addBlock', - properties: {}, }, }, }, @@ -93,6 +95,4 @@ export const createCalendarBlockSchema = (options) => { }, }, }; - - return schema; }; diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/items/CalendarBlockInitializer.tsx b/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/items/CalendarBlockInitializer.tsx index 8995f0e4a..b2c726e51 100644 --- a/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/items/CalendarBlockInitializer.tsx +++ b/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/items/CalendarBlockInitializer.tsx @@ -14,7 +14,7 @@ import { useSchemaInitializerItem, } from '@nocobase/client'; import React, { useContext } from 'react'; -import { createCalendarBlockSchema } from '../utils'; +import { createCalendarBlockUISchema } from '../createCalendarBlockUISchema'; import { useTranslation } from '../../../locale'; export const CalendarBlockInitializer = ({ @@ -95,13 +95,12 @@ export const CalendarBlockInitializer = ({ initialValues: {}, }); insert( - createCalendarBlockSchema({ - collection: item.name, + createCalendarBlockUISchema({ + collectionName: item.name, dataSource: item.dataSource, fieldNames: { ...values, }, - settings: 'blockSettings:calendar', }), ); }} diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/items/RecordAssociationCalendarBlockInitializer.tsx b/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/items/RecordAssociationCalendarBlockInitializer.tsx index 6a2b20ec7..97b0b327d 100644 --- a/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/items/RecordAssociationCalendarBlockInitializer.tsx +++ b/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/items/RecordAssociationCalendarBlockInitializer.tsx @@ -15,7 +15,7 @@ import { useSchemaInitializer, SchemaInitializerItem, } from '@nocobase/client'; -import { createCalendarBlockSchema } from '../utils'; +import { createCalendarBlockUISchema } from '../createCalendarBlockUISchema'; import { useTranslation } from '../../../locale'; export const RecordAssociationCalendarBlockInitializer = () => { @@ -98,10 +98,9 @@ export const RecordAssociationCalendarBlockInitializer = () => { initialValues: {}, }); insert( - createCalendarBlockSchema({ - collection: field.target, - resource, + createCalendarBlockUISchema({ association: resource, + dataSource: item.dataSource, fieldNames: { ...values, }, @@ -183,13 +182,12 @@ export function useCreateAssociationCalendarBlock() { initialValues: {}, }); insert( - createCalendarBlockSchema({ - collection: field.target, + createCalendarBlockUISchema({ association: `${field.collectionName}.${field.name}`, + dataSource: item.dataSource, fieldNames: { ...values, }, - settings: 'blockSettings:calendar', }), ); }; diff --git a/packages/plugins/@nocobase/plugin-gantt/src/client/GanttBlockInitializer.tsx b/packages/plugins/@nocobase/plugin-gantt/src/client/GanttBlockInitializer.tsx index 10835af39..041cc2a22 100644 --- a/packages/plugins/@nocobase/plugin-gantt/src/client/GanttBlockInitializer.tsx +++ b/packages/plugins/@nocobase/plugin-gantt/src/client/GanttBlockInitializer.tsx @@ -14,7 +14,7 @@ import { DataBlockInitializer, SchemaComponentOptions, } from '@nocobase/client'; -import { createGanttBlockSchema } from './utils'; +import { createGanttBlockUISchema } from './createGanttBlockUISchema'; export const GanttBlockInitializer = () => { const { insert } = useSchemaInitializer(); @@ -120,8 +120,8 @@ export const GanttBlockInitializer = () => { initialValues: {}, }); insert( - createGanttBlockSchema({ - collection: item.name, + createGanttBlockUISchema({ + collectionName: item.name, dataSource: item.dataSource, fieldNames: { ...values, diff --git a/packages/plugins/@nocobase/plugin-gantt/src/client/__tests__/createGanttBlockSchema.test.ts b/packages/plugins/@nocobase/plugin-gantt/src/client/__tests__/createGanttBlockSchema.test.ts new file mode 100644 index 000000000..9040fcb28 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-gantt/src/client/__tests__/createGanttBlockSchema.test.ts @@ -0,0 +1,143 @@ +import { createGanttBlockUISchema } from '../createGanttBlockUISchema'; + +vi.mock('@formily/shared', () => { + return { + uid: () => 'mocked-uid', + }; +}); + +describe('createGanttBlockSchema', () => { + it('should generate schema correctly', () => { + const options = { + collectionName: 'TestCollection', + fieldNames: { + label: 'field1', + value: 'field2', + }, + dataSource: 'TestDataSource', + }; + const schema = createGanttBlockUISchema(options); + + expect(schema).toMatchInlineSnapshot(` + { + "properties": { + "mocked-uid": { + "properties": { + "detail": { + "properties": { + "drawer": { + "properties": { + "tabs": { + "properties": { + "tab1": { + "properties": { + "grid": { + "type": "void", + "x-component": "Grid", + "x-initializer": "popup:common:addBlock", + }, + }, + "title": "{{t("Details")}}", + "type": "void", + "x-component": "Tabs.TabPane", + "x-component-props": {}, + "x-designer": "Tabs.Designer", + }, + }, + "type": "void", + "x-component": "Tabs", + "x-component-props": {}, + "x-initializer": "TabPaneInitializers", + }, + }, + "title": "{{ t("View record") }}", + "type": "void", + "x-component": "Action.Drawer", + "x-component-props": { + "className": "nb-action-popup", + }, + }, + }, + "type": "void", + "x-component": "Gantt.Event", + }, + "table": { + "properties": { + "actions": { + "properties": { + "actions": { + "type": "void", + "x-component": "Space", + "x-component-props": { + "split": "|", + }, + "x-decorator": "DndContext", + }, + }, + "title": "{{ t("Actions") }}", + "type": "void", + "x-action-column": "actions", + "x-component": "TableV2.Column", + "x-decorator": "TableV2.Column.ActionBar", + "x-designer": "TableV2.ActionColumnDesigner", + "x-initializer": "table:configureItemActions", + }, + }, + "type": "array", + "x-component": "TableV2", + "x-component-props": { + "pagination": false, + "rowKey": "id", + "rowSelection": { + "type": "checkbox", + }, + }, + "x-decorator": "div", + "x-decorator-props": { + "style": { + "float": "left", + "maxWidth": "35%", + }, + }, + "x-initializer": "table:configureColumns", + "x-use-component-props": "useTableBlockProps", + }, + "toolBar": { + "properties": {}, + "type": "void", + "x-component": "ActionBar", + "x-component-props": { + "style": { + "marginBottom": 24, + }, + }, + "x-initializer": "gantt:configureActions", + }, + }, + "type": "void", + "x-component": "Gantt", + "x-use-component-props": "useGanttBlockProps", + }, + }, + "type": "void", + "x-acl-action": "TestCollection:list", + "x-component": "CardItem", + "x-decorator": "GanttBlockProvider", + "x-decorator-props": { + "action": "list", + "collection": "TestCollection", + "dataSource": "TestDataSource", + "fieldNames": { + "label": "field1", + "value": "field2", + }, + "params": { + "paginate": false, + }, + }, + "x-settings": "blockSettings:gantt", + "x-toolbar": "BlockSchemaToolbar", + } + `); + }); +}); diff --git a/packages/plugins/@nocobase/plugin-gantt/src/client/components/gantt/gantt.tsx b/packages/plugins/@nocobase/plugin-gantt/src/client/components/gantt/gantt.tsx index a7f721b0c..e38f8c428 100644 --- a/packages/plugins/@nocobase/plugin-gantt/src/client/components/gantt/gantt.tsx +++ b/packages/plugins/@nocobase/plugin-gantt/src/client/components/gantt/gantt.tsx @@ -9,6 +9,8 @@ import { useCollectionParentRecordData, useTableBlockContext, useToken, + withDynamicSchemaProps, + useProps, } from '@nocobase/client'; import { message } from 'antd'; import { debounce } from 'lodash'; @@ -81,7 +83,8 @@ const debounceHandleProcessChange = debounce(async (task: Task, resource, fieldN message.success(t('Saved successfully')); await service?.refresh(); }, 300); -export const Gantt: any = (props: any) => { + +export const Gantt: any = withDynamicSchemaProps((props: any) => { const { styles } = useStyles(); const { token } = useToken(); const api = useAPIClient(); @@ -116,12 +119,13 @@ export const Gantt: any = (props: any) => { viewDate, TooltipContent = StandardTooltipContent, onDoubleClick, - onClick, onDelete, onSelect, - useProps, - } = props; - const { onExpanderClick, tasks, expandAndCollapseAll } = useProps(); + onExpanderClick, + tasks, + expandAndCollapseAll, + fieldNames, + } = useProps(props); // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema const ctx = useGanttBlockContext(); const appInfo = useCurrentAppInfo(); const { t } = useTranslation(); @@ -129,7 +133,6 @@ export const Gantt: any = (props: any) => { const tableCtx = useTableBlockContext(); const { resource, service } = useBlockRequestContext(); const fieldSchema = useFieldSchema(); - const { fieldNames } = useProps(props); const viewMode = fieldNames.range || 'day'; const wrapperRef = useRef(null); const taskListRef = useRef(null); @@ -559,4 +562,4 @@ export const Gantt: any = (props: any) => { ); -}; +}); diff --git a/packages/plugins/@nocobase/plugin-gantt/src/client/createGanttBlockUISchema.tsx b/packages/plugins/@nocobase/plugin-gantt/src/client/createGanttBlockUISchema.tsx new file mode 100644 index 000000000..8ba59d29c --- /dev/null +++ b/packages/plugins/@nocobase/plugin-gantt/src/client/createGanttBlockUISchema.tsx @@ -0,0 +1,129 @@ +import { ISchema } from '@formily/react'; +import { uid } from '@formily/shared'; + +export const createGanttBlockUISchema = (options: { + collectionName: string; + fieldNames: object; + dataSource: string; +}): ISchema => { + const { collectionName, fieldNames, dataSource } = options; + + return { + type: 'void', + 'x-acl-action': `${collectionName}:list`, + 'x-decorator': 'GanttBlockProvider', + 'x-decorator-props': { + collection: collectionName, + dataSource, + action: 'list', + fieldNames, + params: { + paginate: false, + }, + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:gantt', + // 'x-designer': 'Gantt.Designer', + 'x-component': 'CardItem', + properties: { + [uid()]: { + type: 'void', + 'x-component': 'Gantt', + 'x-use-component-props': 'useGanttBlockProps', + properties: { + toolBar: { + type: 'void', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 24, + }, + }, + 'x-initializer': 'gantt:configureActions', + properties: {}, + }, + table: { + type: 'array', + 'x-decorator': 'div', + 'x-decorator-props': { + style: { + float: 'left', + maxWidth: '35%', + }, + }, + + 'x-initializer': 'table:configureColumns', + 'x-component': 'TableV2', + 'x-use-component-props': 'useTableBlockProps', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + pagination: false, + }, + properties: { + actions: { + type: 'void', + title: '{{ t("Actions") }}', + 'x-action-column': 'actions', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-designer': 'TableV2.ActionColumnDesigner', + 'x-initializer': 'table:configureItemActions', + properties: { + actions: { + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + }, + }, + }, + }, + }, + detail: { + type: 'void', + 'x-component': 'Gantt.Event', + properties: { + drawer: { + type: 'void', + 'x-component': 'Action.Drawer', + 'x-component-props': { + className: 'nb-action-popup', + }, + title: '{{ t("View record") }}', + properties: { + tabs: { + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'TabPaneInitializers', + properties: { + tab1: { + type: 'void', + title: '{{t("Details")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'popup:common:addBlock', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }; +}; diff --git a/packages/plugins/@nocobase/plugin-gantt/src/client/index.tsx b/packages/plugins/@nocobase/plugin-gantt/src/client/index.tsx index 496f02507..c24339362 100644 --- a/packages/plugins/@nocobase/plugin-gantt/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-gantt/src/client/index.tsx @@ -41,6 +41,10 @@ export class GanttPlugin extends Plugin { title: "{{t('Gantt')}}", Component: 'GanttBlockInitializer', }); + + this.app.addScopes({ + useGanttBlockProps, + }); } } diff --git a/packages/plugins/@nocobase/plugin-gantt/src/client/utils.tsx b/packages/plugins/@nocobase/plugin-gantt/src/client/utils.tsx index 667011506..43949db23 100644 --- a/packages/plugins/@nocobase/plugin-gantt/src/client/utils.tsx +++ b/packages/plugins/@nocobase/plugin-gantt/src/client/utils.tsx @@ -1,5 +1,3 @@ -import { ISchema } from '@formily/react'; -import { uid } from '@formily/shared'; import { useCollection_deprecated, useCompile } from '@nocobase/client'; import { useTranslation } from 'react-i18next'; @@ -19,131 +17,3 @@ export const useOptions = (type = 'string') => { }); return options; }; -export const createGanttBlockSchema = (options) => { - const { collection, resource, fieldNames, ...others } = options; - const schema: ISchema = { - type: 'void', - 'x-acl-action': `${resource || collection}:list`, - 'x-decorator': 'GanttBlockProvider', - 'x-decorator-props': { - collection: collection, - resource: resource || collection, - action: 'list', - fieldNames: { - ...fieldNames, - }, - params: { - paginate: false, - }, - ...others, - }, - 'x-designer': 'Gantt.Designer', - 'x-component': 'CardItem', - properties: { - [uid()]: { - type: 'void', - 'x-component': 'Gantt', - 'x-component-props': { - useProps: '{{ useGanttBlockProps }}', - }, - properties: { - toolBar: { - type: 'void', - 'x-component': 'ActionBar', - 'x-component-props': { - style: { - marginBottom: 24, - }, - }, - 'x-initializer': 'gantt:configureActions', - properties: {}, - }, - table: { - type: 'array', - 'x-decorator': 'div', - 'x-decorator-props': { - style: { - float: 'left', - maxWidth: '35%', - }, - }, - - 'x-initializer': 'table:configureColumns', - 'x-component': 'TableV2', - 'x-component-props': { - rowKey: 'id', - rowSelection: { - type: 'checkbox', - }, - useProps: '{{ useTableBlockProps }}', - pagination: false, - }, - properties: { - actions: { - type: 'void', - title: '{{ t("Actions") }}', - 'x-action-column': 'actions', - 'x-decorator': 'TableV2.Column.ActionBar', - 'x-component': 'TableV2.Column', - 'x-designer': 'TableV2.ActionColumnDesigner', - 'x-initializer': 'table:configureItemActions', - properties: { - actions: { - type: 'void', - 'x-decorator': 'DndContext', - 'x-component': 'Space', - 'x-component-props': { - split: '|', - }, - properties: {}, - }, - }, - }, - }, - }, - detail: { - type: 'void', - 'x-component': 'Gantt.Event', - properties: { - drawer: { - type: 'void', - 'x-component': 'Action.Drawer', - 'x-component-props': { - className: 'nb-action-popup', - }, - title: '{{ t("View record") }}', - properties: { - tabs: { - type: 'void', - 'x-component': 'Tabs', - 'x-component-props': {}, - 'x-initializer': 'TabPaneInitializers', - properties: { - tab1: { - type: 'void', - title: '{{t("Details")}}', - 'x-component': 'Tabs.TabPane', - 'x-designer': 'Tabs.Designer', - 'x-component-props': {}, - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'popup:common:addBlock', - properties: {}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }; - - return schema; -}; diff --git a/packages/plugins/@nocobase/plugin-kanban/src/client/Kanban.tsx b/packages/plugins/@nocobase/plugin-kanban/src/client/Kanban.tsx index e40fd5aaf..bb4c933fe 100644 --- a/packages/plugins/@nocobase/plugin-kanban/src/client/Kanban.tsx +++ b/packages/plugins/@nocobase/plugin-kanban/src/client/Kanban.tsx @@ -6,6 +6,7 @@ import { useCreateActionProps as useCAP, useCollectionParentRecordData, useProps, + withDynamicSchemaProps, } from '@nocobase/client'; import { Spin, Tag } from 'antd'; import React, { useContext, useMemo, useState } from 'react'; @@ -56,100 +57,105 @@ export const toColumns = (groupField: any, dataSource: Array = [], primaryK return Object.values(columns); }; -export const Kanban: any = observer( - (props: any) => { - const { styles } = useStyles(); - const { groupField, onCardDragEnd, dataSource, setDataSource, ...restProps } = useProps(props); - const parentRecordData = useCollectionParentRecordData(); - const field = useField(); - const fieldSchema = useFieldSchema(); - const [disableCardDrag, setDisableCardDrag] = useState(false); - const schemas = useMemo( - () => - fieldSchema.reduceProperties( - (buf, current) => { - if (current['x-component'].endsWith('.Card')) { - buf.card = current; - } else if (current['x-component'].endsWith('.CardAdder')) { - buf.cardAdder = current; - } else if (current['x-component'].endsWith('.CardViewer')) { - buf.cardViewer = current; - } - return buf; - }, - { card: null, cardAdder: null, cardViewer: null }, - ), - [], - ); - const handleCardRemove = (card, column) => { - const updatedBoard = Board.removeCard({ columns: field.value }, column, card); - field.value = updatedBoard.columns; - setDataSource(updatedBoard.columns); - }; - const handleCardDragEnd = (card, fromColumn, toColumn) => { - onCardDragEnd?.({ columns: field.value, groupField }, fromColumn, toColumn); - const updatedBoard = Board.moveCard({ columns: field.value }, fromColumn, toColumn); - field.value = updatedBoard.columns; - setDataSource(updatedBoard.columns); - }; - return ( - - ( -
- {title} -
- )} - renderCard={(card, { column, dragging }) => { - const columnIndex = dataSource?.indexOf(column); - const cardIndex = column?.cards?.indexOf(card); - return ( - schemas.card && ( - - - - - - ) - ); - }} - renderCardAdder={({ column }) => { - if (!schemas.cardAdder) { - return null; - } - return ( - - - - - - ); - }} - > - {{ - columns: dataSource || [], - }} -
-
- ); - }, - { displayName: 'Kanban' }, +export const Kanban: any = withDynamicSchemaProps( + observer( + (props: any) => { + const { styles } = useStyles(); + + // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema + const { groupField, onCardDragEnd, dataSource, setDataSource, ...restProps } = useProps(props); + + const parentRecordData = useCollectionParentRecordData(); + const field = useField(); + const fieldSchema = useFieldSchema(); + const [disableCardDrag, setDisableCardDrag] = useState(false); + const schemas = useMemo( + () => + fieldSchema.reduceProperties( + (buf, current) => { + if (current['x-component'].endsWith('.Card')) { + buf.card = current; + } else if (current['x-component'].endsWith('.CardAdder')) { + buf.cardAdder = current; + } else if (current['x-component'].endsWith('.CardViewer')) { + buf.cardViewer = current; + } + return buf; + }, + { card: null, cardAdder: null, cardViewer: null }, + ), + [], + ); + const handleCardRemove = (card, column) => { + const updatedBoard = Board.removeCard({ columns: field.value }, column, card); + field.value = updatedBoard.columns; + setDataSource(updatedBoard.columns); + }; + const handleCardDragEnd = (card, fromColumn, toColumn) => { + onCardDragEnd?.({ columns: field.value, groupField }, fromColumn, toColumn); + const updatedBoard = Board.moveCard({ columns: field.value }, fromColumn, toColumn); + field.value = updatedBoard.columns; + setDataSource(updatedBoard.columns); + }; + return ( + + ( +
+ {title} +
+ )} + renderCard={(card, { column, dragging }) => { + const columnIndex = dataSource?.indexOf(column); + const cardIndex = column?.cards?.indexOf(card); + return ( + schemas.card && ( + + + + + + ) + ); + }} + renderCardAdder={({ column }) => { + if (!schemas.cardAdder) { + return null; + } + return ( + + + + + + ); + }} + > + {{ + columns: dataSource || [], + }} +
+
+ ); + }, + { displayName: 'Kanban' }, + ), ); diff --git a/packages/plugins/@nocobase/plugin-kanban/src/client/KanbanBlockInitializer.tsx b/packages/plugins/@nocobase/plugin-kanban/src/client/KanbanBlockInitializer.tsx index ea1b30492..409a14fe0 100644 --- a/packages/plugins/@nocobase/plugin-kanban/src/client/KanbanBlockInitializer.tsx +++ b/packages/plugins/@nocobase/plugin-kanban/src/client/KanbanBlockInitializer.tsx @@ -16,7 +16,7 @@ import { useSchemaInitializerItem, useAPIClient, } from '@nocobase/client'; -import { createKanbanBlockSchema } from './utils'; +import { createKanbanBlockUISchema } from './createKanbanBlockUISchema'; import { CreateAndSelectSort } from './CreateAndSelectSort'; import { NAMESPACE } from './locale'; @@ -157,14 +157,13 @@ export const KanbanBlockInitializer = () => { initialValues: {}, }); insert( - createKanbanBlockSchema({ + createKanbanBlockUISchema({ sortField: values.dragSortBy, groupField: values.groupField.value, - collection: item.name, + collectionName: item.name, dataSource: item.dataSource, params: { sort: [values.dragSortBy], - paginate: false, }, }), ); diff --git a/packages/plugins/@nocobase/plugin-kanban/src/client/__tests__/createKanbanBlockSchema.test.ts b/packages/plugins/@nocobase/plugin-kanban/src/client/__tests__/createKanbanBlockSchema.test.ts new file mode 100644 index 000000000..a06d344a5 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-kanban/src/client/__tests__/createKanbanBlockSchema.test.ts @@ -0,0 +1,126 @@ +import { createKanbanBlockUISchema } from '../createKanbanBlockUISchema'; + +vi.mock('@formily/shared', () => { + return { + uid: vi.fn(() => 'mocked-uid'), + }; +}); + +test('createKanbanBlockSchema should return an object with expected properties', () => { + const options = { + collectionName: 'testCollection', + groupField: 'testGroupField', + sortField: 'testSortField', + dataSource: 'testDataSource', + params: { testParam: 'testValue' }, + }; + + const result = createKanbanBlockUISchema(options); + + expect(result).toMatchInlineSnapshot(` + { + "properties": { + "actions": { + "properties": {}, + "type": "void", + "x-component": "ActionBar", + "x-component-props": { + "style": { + "marginBottom": "var(--nb-spacing)", + }, + }, + "x-initializer": "kanban:configureActions", + }, + "mocked-uid": { + "properties": { + "card": { + "properties": { + "grid": { + "type": "void", + "x-component": "Grid", + "x-component-props": { + "dndContext": false, + }, + }, + }, + "type": "void", + "x-component": "Kanban.Card", + "x-component-props": { + "openMode": "drawer", + }, + "x-decorator": "BlockItem", + "x-designer": "Kanban.Card.Designer", + "x-label-disabled": true, + "x-read-pretty": true, + }, + "cardViewer": { + "properties": { + "drawer": { + "properties": { + "tabs": { + "properties": { + "tab1": { + "properties": { + "grid": { + "properties": {}, + "type": "void", + "x-component": "Grid", + "x-initializer": "popup:common:addBlock", + }, + }, + "title": "{{t("Details")}}", + "type": "void", + "x-component": "Tabs.TabPane", + "x-component-props": {}, + "x-designer": "Tabs.Designer", + }, + }, + "type": "void", + "x-component": "Tabs", + "x-component-props": {}, + "x-initializer": "TabPaneInitializers", + }, + }, + "title": "{{ t("View record") }}", + "type": "void", + "x-component": "Action.Container", + "x-component-props": { + "className": "nb-action-popup", + }, + }, + }, + "title": "{{ t("View") }}", + "type": "void", + "x-action": "view", + "x-component": "Kanban.CardViewer", + "x-component-props": { + "openMode": "drawer", + }, + "x-designer": "Action.Designer", + }, + }, + "type": "array", + "x-component": "Kanban", + "x-use-component-props": "useKanbanBlockProps", + }, + }, + "type": "void", + "x-acl-action": "testCollection:list", + "x-component": "CardItem", + "x-decorator": "KanbanBlockProvider", + "x-decorator-props": { + "action": "list", + "collection": "testCollection", + "dataSource": "testDataSource", + "groupField": "testGroupField", + "params": { + "paginate": false, + "testParam": "testValue", + }, + "sortField": "testSortField", + }, + "x-settings": "blockSettings:kanban", + "x-toolbar": "BlockSchemaToolbar", + } + `); +}); diff --git a/packages/plugins/@nocobase/plugin-kanban/src/client/utils.ts b/packages/plugins/@nocobase/plugin-kanban/src/client/createKanbanBlockUISchema.ts similarity index 83% rename from packages/plugins/@nocobase/plugin-kanban/src/client/utils.ts rename to packages/plugins/@nocobase/plugin-kanban/src/client/createKanbanBlockUISchema.ts index 3304820da..88b5aef02 100644 --- a/packages/plugins/@nocobase/plugin-kanban/src/client/utils.ts +++ b/packages/plugins/@nocobase/plugin-kanban/src/client/createKanbanBlockUISchema.ts @@ -1,24 +1,33 @@ import { ISchema } from '@formily/react'; import { uid } from '@formily/shared'; -export const createKanbanBlockSchema = (options) => { - const { collection, resource, groupField, sortField, ...others } = options; - const schema: ISchema = { +export const createKanbanBlockUISchema = (options: { + collectionName: string; + groupField: string; + sortField: string; + dataSource: string; + params?: Record; +}): ISchema => { + const { collectionName, groupField, sortField, dataSource, params } = options; + + return { type: 'void', - 'x-acl-action': `${resource || collection}:list`, + 'x-acl-action': `${collectionName}:list`, 'x-decorator': 'KanbanBlockProvider', 'x-decorator-props': { - collection: collection, - resource: resource || collection, + collection: collectionName, action: 'list', groupField, sortField, params: { paginate: false, + ...params, }, - ...others, + dataSource, }, - 'x-designer': 'Kanban.Designer', + // 'x-designer': 'Kanban.Designer', + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:kanban', 'x-component': 'CardItem', properties: { actions: { @@ -35,9 +44,7 @@ export const createKanbanBlockSchema = (options) => { [uid()]: { type: 'array', 'x-component': 'Kanban', - 'x-component-props': { - useProps: '{{ useKanbanBlockProps }}', - }, + 'x-use-component-props': 'useKanbanBlockProps', properties: { card: { type: 'void', @@ -106,5 +113,4 @@ export const createKanbanBlockSchema = (options) => { }, }, }; - return schema; }; diff --git a/packages/plugins/@nocobase/plugin-map/src/client/__tests__/block/createMapBlockSchema.test.ts b/packages/plugins/@nocobase/plugin-map/src/client/__tests__/block/createMapBlockSchema.test.ts new file mode 100644 index 000000000..5f5726eb4 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-map/src/client/__tests__/block/createMapBlockSchema.test.ts @@ -0,0 +1,95 @@ +import { createMapBlockUISchema } from '../../block/createMapBlockUISchema'; + +vi.mock('@formily/shared', () => { + return { + uid: () => 'mocked-uid', + }; +}); + +test('createMapBlockSchema should return an object with expected properties', () => { + const options = { + collectionName: 'testCollection', + dataSource: 'testDataSource', + fieldNames: { + label: 'field1', + value: 'field2', + }, + }; + + const result = createMapBlockUISchema(options); + + expect(result).toMatchInlineSnapshot(` + { + "properties": { + "actions": { + "type": "void", + "x-component": "ActionBar", + "x-component-props": { + "style": { + "marginBottom": 16, + }, + }, + "x-initializer": "map:configureActions", + }, + "mocked-uid": { + "properties": { + "drawer": { + "properties": { + "tabs": { + "properties": { + "tab1": { + "properties": { + "grid": { + "type": "void", + "x-component": "Grid", + "x-initializer": "popup:common:addBlock", + }, + }, + "title": "{{t("Details")}}", + "type": "void", + "x-component": "Tabs.TabPane", + "x-component-props": {}, + "x-designer": "Tabs.Designer", + }, + }, + "type": "void", + "x-component": "Tabs", + "x-component-props": {}, + "x-initializer": "TabPaneInitializers", + }, + }, + "title": "{{ t("View record") }}", + "type": "void", + "x-component": "Action.Drawer", + "x-component-props": { + "className": "nb-action-popup", + }, + }, + }, + "type": "void", + "x-component": "MapBlock", + "x-use-component-props": "useMapBlockProps", + }, + }, + "type": "void", + "x-acl-action": "testCollection:list", + "x-component": "CardItem", + "x-decorator": "MapBlockProvider", + "x-decorator-props": { + "action": "list", + "collection": "testCollection", + "dataSource": "testDataSource", + "fieldNames": { + "label": "field1", + "value": "field2", + }, + "params": { + "paginate": false, + }, + }, + "x-filter-targets": [], + "x-settings": "blockSettings:map", + "x-toolbar": "BlockSchemaToolbar", + } + `); +}); diff --git a/packages/plugins/@nocobase/plugin-map/src/client/block/MapBlock.tsx b/packages/plugins/@nocobase/plugin-map/src/client/block/MapBlock.tsx index b7da6116d..8fdae6c83 100644 --- a/packages/plugins/@nocobase/plugin-map/src/client/block/MapBlock.tsx +++ b/packages/plugins/@nocobase/plugin-map/src/client/block/MapBlock.tsx @@ -1,9 +1,16 @@ -import { useCollection_deprecated, useCollectionManager_deprecated, useProps } from '@nocobase/client'; +import { + useCollection_deprecated, + useCollectionManager_deprecated, + useProps, + withDynamicSchemaProps, +} from '@nocobase/client'; import React, { useMemo } from 'react'; import { MapBlockComponent } from '../components'; -export const MapBlock = (props) => { +export const MapBlock = withDynamicSchemaProps((props) => { + // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema const { fieldNames } = useProps(props); + const { getCollectionJoinField } = useCollectionManager_deprecated(); const { name } = useCollection_deprecated(); const collectionField = useMemo(() => { @@ -12,4 +19,4 @@ export const MapBlock = (props) => { const fieldComponentProps = collectionField?.uiSchema?.['x-component-props']; return ; -}; +}); diff --git a/packages/plugins/@nocobase/plugin-map/src/client/block/MapBlockInitializer.tsx b/packages/plugins/@nocobase/plugin-map/src/client/block/MapBlockInitializer.tsx index c019c6ec2..f12504113 100644 --- a/packages/plugins/@nocobase/plugin-map/src/client/block/MapBlockInitializer.tsx +++ b/packages/plugins/@nocobase/plugin-map/src/client/block/MapBlockInitializer.tsx @@ -13,7 +13,8 @@ import { } from '@nocobase/client'; import React, { useContext } from 'react'; import { useMapTranslation } from '../locale'; -import { createMapBlockSchema, findNestedOption } from './utils'; +import { findNestedOption } from './utils'; +import { createMapBlockUISchema } from './createMapBlockUISchema'; export const MapBlockInitializer = () => { const itemConfig = useSchemaInitializerItem(); @@ -84,13 +85,12 @@ export const MapBlockInitializer = () => { initialValues: {}, }); insert( - createMapBlockSchema({ - collection: item.name, + createMapBlockUISchema({ + collectionName: item.name, dataSource: item.dataSource, fieldNames: { ...values, }, - settings: 'blockSettings:map', }), ); }} diff --git a/packages/plugins/@nocobase/plugin-map/src/client/block/createMapBlockUISchema.ts b/packages/plugins/@nocobase/plugin-map/src/client/block/createMapBlockUISchema.ts new file mode 100644 index 000000000..112a40c5d --- /dev/null +++ b/packages/plugins/@nocobase/plugin-map/src/client/block/createMapBlockUISchema.ts @@ -0,0 +1,81 @@ +import { ISchema } from '@formily/react'; +import { uid } from '@formily/shared'; + +export const createMapBlockUISchema = (options: { + collectionName: string; + dataSource: string; + fieldNames: object; +}): ISchema => { + const { collectionName, fieldNames, dataSource } = options; + + return { + type: 'void', + 'x-acl-action': `${collectionName}:list`, + 'x-decorator': 'MapBlockProvider', + 'x-decorator-props': { + collection: collectionName, + dataSource, + action: 'list', + fieldNames, + params: { + paginate: false, + }, + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:map', + 'x-component': 'CardItem', + // 保存当前筛选区块所能过滤的数据区块 + 'x-filter-targets': [], + properties: { + actions: { + type: 'void', + 'x-initializer': 'map:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 16, + }, + }, + }, + [uid()]: { + type: 'void', + 'x-component': 'MapBlock', + 'x-use-component-props': 'useMapBlockProps', + properties: { + drawer: { + type: 'void', + 'x-component': 'Action.Drawer', + 'x-component-props': { + className: 'nb-action-popup', + }, + title: '{{ t("View record") }}', + properties: { + tabs: { + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'TabPaneInitializers', + properties: { + tab1: { + type: 'void', + title: '{{t("Details")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'popup:common:addBlock', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }; +}; diff --git a/packages/plugins/@nocobase/plugin-map/src/client/block/utils.ts b/packages/plugins/@nocobase/plugin-map/src/client/block/utils.ts index e6d92cd1c..33aeeea54 100644 --- a/packages/plugins/@nocobase/plugin-map/src/client/block/utils.ts +++ b/packages/plugins/@nocobase/plugin-map/src/client/block/utils.ts @@ -1,86 +1,3 @@ -import { ISchema } from '@formily/react'; -import { uid } from '@formily/shared'; - -export const createMapBlockSchema = (options) => { - const { collection, resource, fieldNames, settings, ...others } = options; - const schema: ISchema = { - type: 'void', - 'x-acl-action': `${resource || collection}:list`, - 'x-decorator': 'MapBlockProvider', - 'x-decorator-props': { - collection: collection, - resource: resource || collection, - action: 'list', - fieldNames, - params: { - paginate: false, - }, - ...others, - }, - 'x-toolbar': 'BlockSchemaToolbar', - 'x-settings': settings, - 'x-component': 'CardItem', - // 保存当前筛选区块所能过滤的数据区块 - 'x-filter-targets': [], - properties: { - actions: { - type: 'void', - 'x-initializer': 'map:configureActions', - 'x-component': 'ActionBar', - 'x-component-props': { - style: { - marginBottom: 16, - }, - }, - properties: {}, - }, - [uid()]: { - type: 'void', - 'x-component': 'MapBlock', - 'x-component-props': { - useProps: '{{ useMapBlockProps }}', - }, - properties: { - drawer: { - type: 'void', - 'x-component': 'Action.Drawer', - 'x-component-props': { - className: 'nb-action-popup', - }, - title: '{{ t("View record") }}', - properties: { - tabs: { - type: 'void', - 'x-component': 'Tabs', - 'x-component-props': {}, - 'x-initializer': 'TabPaneInitializers', - properties: { - tab1: { - type: 'void', - title: '{{t("Details")}}', - 'x-component': 'Tabs.TabPane', - 'x-designer': 'Tabs.Designer', - 'x-component-props': {}, - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'popup:common:addBlock', - properties: {}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }; - return schema; -}; - export const findNestedOption = (value: string[] | string, options = []) => { if (typeof value === 'string') { value = [value]; diff --git a/packages/plugins/@nocobase/plugin-map/src/client/components/AMap/Block.tsx b/packages/plugins/@nocobase/plugin-map/src/client/components/AMap/Block.tsx index a4f18f56d..9ad47f5c4 100644 --- a/packages/plugins/@nocobase/plugin-map/src/client/components/AMap/Block.tsx +++ b/packages/plugins/@nocobase/plugin-map/src/client/components/AMap/Block.tsx @@ -20,6 +20,7 @@ import { getSource } from '../../utils'; import { AMapComponent, AMapForwardedRefProps } from './Map'; export const AMapBlock = (props) => { + // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema const { collectionField, fieldNames, dataSource, fixedBlock, zoom, setSelectedRecordKeys, lineSort } = useProps(props); const { name, getPrimaryKey } = useCollection_deprecated(); diff --git a/packages/plugins/@nocobase/plugin-map/src/client/components/GoogleMaps/Block.tsx b/packages/plugins/@nocobase/plugin-map/src/client/components/GoogleMaps/Block.tsx index 4e43d6cf4..976fd0b54 100644 --- a/packages/plugins/@nocobase/plugin-map/src/client/components/GoogleMaps/Block.tsx +++ b/packages/plugins/@nocobase/plugin-map/src/client/components/GoogleMaps/Block.tsx @@ -38,6 +38,7 @@ const pointClass = css` `; export const GoogleMapsBlock = (props) => { + // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema const { collectionField, fieldNames, dataSource, fixedBlock, zoom, setSelectedRecordKeys, lineSort } = useProps(props); const { getPrimaryKey } = useCollection_deprecated(); diff --git a/packages/plugins/@nocobase/plugin-map/src/client/index.tsx b/packages/plugins/@nocobase/plugin-map/src/client/index.tsx index 66c64b054..14a8e1a8e 100644 --- a/packages/plugins/@nocobase/plugin-map/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-map/src/client/index.tsx @@ -6,6 +6,7 @@ import { mapBlockSettings } from './block/MapBlock.Settings'; import { Configuration, Map } from './components'; import { fields } from './fields'; import { NAMESPACE, generateNTemplate } from './locale'; +import { useMapBlockProps } from './block/MapBlockProvider'; const MapProvider = React.memo((props) => { return ( @@ -44,6 +45,10 @@ export class MapPlugin extends Plugin { Component: Configuration, aclSnippet: 'pm.map.configuration', }); + + this.app.addScopes({ + useMapBlockProps, + }); } }