mirror of
https://gitee.com/nocobase/nocobase.git
synced 2024-12-02 12:18:15 +08:00
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
This commit is contained in:
parent
71005ff9bf
commit
74051ff0a5
@ -36,6 +36,7 @@ import { TableFieldResource } from '../TableFieldProvider';
|
||||
|
||||
export * from './useFormActiveFields';
|
||||
export * from './useParsedFilter';
|
||||
export * from './useDataBlockSourceId';
|
||||
|
||||
export const usePickActionProps = () => {
|
||||
const form = useForm();
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
} from '../..';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* 注意:这里有一个需要更改 schema 才能解决的问题,就是在获取 sourceId 的时候无法确定(在关系字段和当前表同表时)
|
||||
* 是需要从 recordData 还是 parentRecordData 中获取;解决方法是通过更改 schema,在不同类型的关系区块中
|
||||
* (`通过点击关系字段按钮打开的弹窗中创建的非关系字段区块`和`关系字段区块`)使用不同的 hook。
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
@ -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,
|
||||
},
|
||||
};
|
@ -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",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
@ -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<Date>(new Date());
|
||||
const [view, setView] = useState<View>('month');
|
||||
const events = useEvents(dataSource, fieldNames, date, view);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [record, setRecord] = useState<any>({});
|
||||
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) => <Toolbar {...props} showLunar={showLunar}></Toolbar>,
|
||||
// week: {
|
||||
// header: (props) => <Header {...props} type="week" showLunar={showLunar}></Header>,
|
||||
// },
|
||||
month: {
|
||||
dateHeader: (props) => <Header {...props} showLunar={showLunar}></Header>,
|
||||
},
|
||||
const [date, setDate] = useState<Date>(new Date());
|
||||
const [view, setView] = useState<View>('month');
|
||||
const events = useEvents(dataSource, fieldNames, date, view);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [record, setRecord] = useState<any>({});
|
||||
const { wrapSSR, hashId, componentCls: containerClassName } = useStyle();
|
||||
|
||||
const components = useMemo(() => {
|
||||
return {
|
||||
toolbar: (props) => <Toolbar {...props} showLunar={showLunar}></Toolbar>,
|
||||
// week: {
|
||||
// header: (props) => <Header {...props} type="week" showLunar={showLunar}></Header>,
|
||||
// },
|
||||
month: {
|
||||
dateHeader: (props) => <Header {...props} showLunar={showLunar}></Header>,
|
||||
},
|
||||
};
|
||||
}, [showLunar]);
|
||||
|
||||
const messages: any = {
|
||||
allDay: '',
|
||||
previous: (
|
||||
<div>
|
||||
<LeftOutlined />
|
||||
</div>
|
||||
),
|
||||
next: (
|
||||
<div>
|
||||
<RightOutlined />
|
||||
</div>
|
||||
),
|
||||
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: (
|
||||
<div>
|
||||
<LeftOutlined />
|
||||
</div>
|
||||
),
|
||||
next: (
|
||||
<div>
|
||||
<RightOutlined />
|
||||
</div>
|
||||
),
|
||||
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(
|
||||
<div className={`${hashId} ${containerClassName}`} style={{ height: fixedBlock ? '100%' : 700 }}>
|
||||
<GlobalStyle />
|
||||
<CalendarRecordViewer visible={visible} setVisible={setVisible} record={record} />
|
||||
<BigCalendar
|
||||
popup
|
||||
selectable
|
||||
events={events}
|
||||
view={view}
|
||||
views={Weeks}
|
||||
date={date}
|
||||
step={60}
|
||||
showMultiDayTimes
|
||||
messages={messages}
|
||||
onNavigate={setDate}
|
||||
onView={setView}
|
||||
onSelectSlot={(slotInfo) => {
|
||||
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(
|
||||
<div className={`${hashId} ${containerClassName}`} style={{ height: fixedBlock ? '100%' : 700 }}>
|
||||
<GlobalStyle />
|
||||
<CalendarRecordViewer visible={visible} setVisible={setVisible} record={record} />
|
||||
<BigCalendar
|
||||
popup
|
||||
selectable
|
||||
events={events}
|
||||
view={view}
|
||||
views={Weeks}
|
||||
date={date}
|
||||
step={60}
|
||||
showMultiDayTimes
|
||||
messages={messages}
|
||||
onNavigate={setDate}
|
||||
onView={setView}
|
||||
onSelectSlot={(slotInfo) => {
|
||||
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}
|
||||
/>
|
||||
</div>,
|
||||
);
|
||||
},
|
||||
{ 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}
|
||||
/>
|
||||
</div>,
|
||||
);
|
||||
},
|
||||
{ displayName: 'Calendar' },
|
||||
),
|
||||
);
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
@ -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]);
|
||||
}
|
@ -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);
|
||||
|
@ -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<any>({});
|
||||
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 (
|
||||
<BlockProvider
|
||||
name="calendar"
|
||||
{...props}
|
||||
params={{ ...props.params, appends: [...appends, ...(props.params.appends || [])], paginate: false }}
|
||||
>
|
||||
<BlockProvider name="calendar" {...props} params={params}>
|
||||
<InternalCalendarBlockProvider {...props} />
|
||||
</BlockProvider>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export const useCalendarBlockContext = () => {
|
||||
return useContext(CalendarBlockContext);
|
||||
|
@ -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;
|
||||
};
|
@ -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',
|
||||
}),
|
||||
);
|
||||
}}
|
||||
|
@ -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',
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
@ -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<HTMLDivElement>(null);
|
||||
const taskListRef = useRef<HTMLDivElement>(null);
|
||||
@ -559,4 +562,4 @@ export const Gantt: any = (props: any) => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
@ -41,6 +41,10 @@ export class GanttPlugin extends Plugin {
|
||||
title: "{{t('Gantt')}}",
|
||||
Component: 'GanttBlockInitializer',
|
||||
});
|
||||
|
||||
this.app.addScopes({
|
||||
useGanttBlockProps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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<any> = [], 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<ArrayField>();
|
||||
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 (
|
||||
<Spin wrapperClassName={styles.nbKanban} spinning={field.loading || false}>
|
||||
<Board
|
||||
{...restProps}
|
||||
allowAddCard={!!schemas.cardAdder}
|
||||
disableColumnDrag
|
||||
cardAdderPosition={'bottom'}
|
||||
disableCardDrag={restProps.disableCardDrag || disableCardDrag}
|
||||
onCardRemove={handleCardRemove}
|
||||
onCardDragEnd={handleCardDragEnd}
|
||||
renderColumnHeader={({ title, color }) => (
|
||||
<div className={'react-kanban-column-header'}>
|
||||
<Tag color={color}>{title}</Tag>
|
||||
</div>
|
||||
)}
|
||||
renderCard={(card, { column, dragging }) => {
|
||||
const columnIndex = dataSource?.indexOf(column);
|
||||
const cardIndex = column?.cards?.indexOf(card);
|
||||
return (
|
||||
schemas.card && (
|
||||
<RecordProvider record={card} parent={parentRecordData}>
|
||||
<KanbanCardContext.Provider
|
||||
value={{
|
||||
setDisableCardDrag,
|
||||
cardViewerSchema: schemas.cardViewer,
|
||||
cardField: field,
|
||||
card,
|
||||
column,
|
||||
dragging,
|
||||
columnIndex,
|
||||
cardIndex,
|
||||
}}
|
||||
>
|
||||
<RecursionField name={schemas.card.name} schema={schemas.card} />
|
||||
</KanbanCardContext.Provider>
|
||||
</RecordProvider>
|
||||
)
|
||||
);
|
||||
}}
|
||||
renderCardAdder={({ column }) => {
|
||||
if (!schemas.cardAdder) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<KanbanColumnContext.Provider value={{ column, groupField }}>
|
||||
<SchemaComponentOptions scope={{ useCreateActionProps }}>
|
||||
<RecursionField name={schemas.cardAdder.name} schema={schemas.cardAdder} />
|
||||
</SchemaComponentOptions>
|
||||
</KanbanColumnContext.Provider>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{{
|
||||
columns: dataSource || [],
|
||||
}}
|
||||
</Board>
|
||||
</Spin>
|
||||
);
|
||||
},
|
||||
{ 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<ArrayField>();
|
||||
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 (
|
||||
<Spin wrapperClassName={styles.nbKanban} spinning={field.loading || false}>
|
||||
<Board
|
||||
{...restProps}
|
||||
allowAddCard={!!schemas.cardAdder}
|
||||
disableColumnDrag
|
||||
cardAdderPosition={'bottom'}
|
||||
disableCardDrag={restProps.disableCardDrag || disableCardDrag}
|
||||
onCardRemove={handleCardRemove}
|
||||
onCardDragEnd={handleCardDragEnd}
|
||||
renderColumnHeader={({ title, color }) => (
|
||||
<div className={'react-kanban-column-header'}>
|
||||
<Tag color={color}>{title}</Tag>
|
||||
</div>
|
||||
)}
|
||||
renderCard={(card, { column, dragging }) => {
|
||||
const columnIndex = dataSource?.indexOf(column);
|
||||
const cardIndex = column?.cards?.indexOf(card);
|
||||
return (
|
||||
schemas.card && (
|
||||
<RecordProvider record={card} parent={parentRecordData}>
|
||||
<KanbanCardContext.Provider
|
||||
value={{
|
||||
setDisableCardDrag,
|
||||
cardViewerSchema: schemas.cardViewer,
|
||||
cardField: field,
|
||||
card,
|
||||
column,
|
||||
dragging,
|
||||
columnIndex,
|
||||
cardIndex,
|
||||
}}
|
||||
>
|
||||
<RecursionField name={schemas.card.name} schema={schemas.card} />
|
||||
</KanbanCardContext.Provider>
|
||||
</RecordProvider>
|
||||
)
|
||||
);
|
||||
}}
|
||||
renderCardAdder={({ column }) => {
|
||||
if (!schemas.cardAdder) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<KanbanColumnContext.Provider value={{ column, groupField }}>
|
||||
<SchemaComponentOptions scope={{ useCreateActionProps }}>
|
||||
<RecursionField name={schemas.cardAdder.name} schema={schemas.cardAdder} />
|
||||
</SchemaComponentOptions>
|
||||
</KanbanColumnContext.Provider>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{{
|
||||
columns: dataSource || [],
|
||||
}}
|
||||
</Board>
|
||||
</Spin>
|
||||
);
|
||||
},
|
||||
{ displayName: 'Kanban' },
|
||||
),
|
||||
);
|
||||
|
@ -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,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
@ -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",
|
||||
}
|
||||
`);
|
||||
});
|
@ -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<string, any>;
|
||||
}): 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;
|
||||
};
|
@ -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",
|
||||
}
|
||||
`);
|
||||
});
|
@ -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 <MapBlockComponent {...fieldComponentProps} {...props} collectionField={collectionField} />;
|
||||
};
|
||||
});
|
||||
|
@ -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',
|
||||
}),
|
||||
);
|
||||
}}
|
||||
|
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
@ -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];
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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 (
|
||||
<CurrentAppInfoProvider>
|
||||
@ -44,6 +45,10 @@ export class MapPlugin extends Plugin {
|
||||
Component: Configuration,
|
||||
aclSnippet: 'pm.map.configuration',
|
||||
});
|
||||
|
||||
this.app.addScopes({
|
||||
useMapBlockProps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user