@nocobase/client-v2 2.1.0-beta.44 → 2.1.0-beta.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/BaseApplication.d.ts +9 -0
- package/es/authRedirect.d.ts +12 -0
- package/es/collection-manager/filter-operators/index.d.ts +1 -0
- package/es/components/form/filter/CollectionFilterItem.d.ts +5 -1
- package/es/components/form/filter/FilterValueInput.d.ts +5 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +11 -0
- package/es/flow/admin-shell/admin-layout/index.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/useApplications.d.ts +3 -2
- package/es/flow/components/ConditionBuilder.d.ts +1 -0
- package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
- package/es/flow/components/FieldAssignValueInput.d.ts +1 -0
- package/es/flow/components/fieldAssignOptions.d.ts +10 -0
- package/es/flow/components/filter/LinkageFilterItem.d.ts +1 -0
- package/es/flow/components/filter/VariableFilterItem.d.ts +1 -0
- package/es/flow/components/filter/metaTreeAssociationDepth.d.ts +13 -0
- package/es/flow/internal/utils/operatorSchemaHelper.d.ts +26 -3
- package/es/flow/models/base/AssociationFieldGroupModel.d.ts +2 -33
- package/es/flow/models/blocks/filter-manager/utils.d.ts +23 -1
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +2 -0
- package/es/index.mjs +178 -100
- package/lib/index.js +187 -109
- package/package.json +7 -7
- package/src/BaseApplication.tsx +13 -0
- package/src/__tests__/browserChecker.test.ts +112 -0
- package/src/__tests__/dataSourceRuntime.test.tsx +22 -1
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +5 -0
- package/src/authRedirect.ts +43 -0
- package/src/collection-field-interface/CollectionFieldInterface.ts +14 -7
- package/src/collection-manager/filter-operators/index.ts +29 -2
- package/src/components/form/filter/CollectionFilterItem.tsx +10 -2
- package/src/components/form/filter/FilterValueInput.tsx +27 -2
- package/src/components/form/filter/__tests__/FilterValueInput.test.tsx +39 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +8 -1
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +369 -2
- package/src/flow/actions/__tests__/sortingRules.test.ts +22 -0
- package/src/flow/actions/dataScope.tsx +8 -1
- package/src/flow/actions/linkageRules.tsx +56 -1
- package/src/flow/actions/setTargetDataScope.tsx +1 -1
- package/src/flow/actions/sortingRules.tsx +3 -4
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +31 -3
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +1 -0
- package/src/flow/admin-shell/admin-layout/AppListRender.tsx +139 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +8 -1
- package/src/flow/admin-shell/admin-layout/index.ts +1 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +11 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +2 -4
- package/src/flow/admin-shell/admin-layout/useApplications.tsx +34 -1
- package/src/flow/common/Markdown/Edit.tsx +32 -4
- package/src/flow/components/ConditionBuilder.tsx +12 -2
- package/src/flow/components/DefaultValue.tsx +14 -8
- package/src/flow/components/FieldAssignRulesEditor.tsx +37 -46
- package/src/flow/components/FieldAssignValueInput.tsx +14 -14
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +112 -1
- package/src/flow/components/__tests__/fieldAssignOptions.test.ts +71 -1
- package/src/flow/components/fieldAssignOptions.ts +23 -6
- package/src/flow/components/filter/LinkageFilterItem.tsx +26 -12
- package/src/flow/components/filter/VariableFilterItem.tsx +32 -13
- package/src/flow/components/filter/__tests__/fieldsToOptions.test.ts +48 -0
- package/src/flow/components/filter/__tests__/metaTreeAssociationDepth.test.ts +60 -0
- package/src/flow/components/filter/fieldsToOptions.ts +9 -4
- package/src/flow/components/filter/metaTreeAssociationDepth.ts +69 -0
- package/src/flow/components/placeholders/BlockPlaceholder.tsx +76 -15
- package/src/flow/components/placeholders/__tests__/BlockPlaceholder.test.tsx +105 -0
- package/src/flow/internal/utils/operatorSchemaHelper.ts +112 -5
- package/src/flow/models/actions/FilterActionModel.tsx +15 -3
- package/src/flow/models/base/AssociationFieldGroupModel.tsx +20 -3
- package/src/flow/models/base/CollectionBlockModel.tsx +10 -6
- package/src/flow/models/base/__tests__/AssociationFieldGroupModel.test.ts +86 -0
- package/src/flow/models/base/__tests__/CollectionBlockModel.title.test.ts +33 -1
- package/src/flow/models/blocks/assign-form/AssignFormGridModel.tsx +11 -0
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +14 -12
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +6 -1
- package/src/flow/models/blocks/filter-manager/__tests__/getDefaultOperator.test.ts +35 -1
- package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +148 -0
- package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/defaultOperator.test.tsx +85 -0
- package/src/flow/models/blocks/filter-manager/flow-actions/connectFields.tsx +48 -6
- package/src/flow/models/blocks/filter-manager/flow-actions/customizeFilterRender.tsx +14 -44
- package/src/flow/models/blocks/filter-manager/flow-actions/defaultOperator.tsx +3 -5
- package/src/flow/models/blocks/filter-manager/flow-actions/operatorComponentProps.tsx +2 -12
- package/src/flow/models/blocks/filter-manager/utils.ts +143 -4
- package/src/flow/models/blocks/form/FormAssociationItemModel.tsx +9 -0
- package/src/flow/models/blocks/form/FormItemModel.tsx +9 -0
- package/src/flow/models/blocks/form/__tests__/FormItemModel.defineChildren.test.ts +27 -2
- package/src/flow/models/blocks/table/TableColumnModel.tsx +15 -5
- package/src/flow/models/blocks/table/TableCustomColumnModel.tsx +2 -2
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +18 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +6 -7
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { AppListProps } from '@ant-design/pro-layout/es/components/AppsLogoComponents/types';
|
|
11
|
+
import { css } from '@emotion/css';
|
|
12
|
+
import { Card, Typography, theme } from 'antd';
|
|
13
|
+
import React from 'react';
|
|
14
|
+
|
|
15
|
+
function renderIcon(icon: React.ReactNode | (() => React.ReactNode)) {
|
|
16
|
+
return typeof icon === 'function' ? icon() : icon;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useAppListRender() {
|
|
20
|
+
const { token } = theme.useToken();
|
|
21
|
+
|
|
22
|
+
return React.useCallback(
|
|
23
|
+
(appList: AppListProps) => {
|
|
24
|
+
const columnCount = Math.min(Math.max(appList.length, 1), 2);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
className={css`
|
|
29
|
+
max-width: calc(100vw - 48px);
|
|
30
|
+
max-height: calc(100vh - 48px);
|
|
31
|
+
overflow: auto;
|
|
32
|
+
padding: ${token.paddingXS}px;
|
|
33
|
+
`}
|
|
34
|
+
>
|
|
35
|
+
<ul
|
|
36
|
+
className={css`
|
|
37
|
+
display: grid;
|
|
38
|
+
grid-template-columns: repeat(${columnCount}, 260px);
|
|
39
|
+
gap: ${token.marginXXS}px;
|
|
40
|
+
margin: 0;
|
|
41
|
+
padding: 0;
|
|
42
|
+
list-style: none;
|
|
43
|
+
`}
|
|
44
|
+
>
|
|
45
|
+
{appList.map((app, index) => (
|
|
46
|
+
<li
|
|
47
|
+
key={`${app.url || ''}-${index}`}
|
|
48
|
+
className={css`
|
|
49
|
+
width: 260px;
|
|
50
|
+
height: 56px;
|
|
51
|
+
list-style: none;
|
|
52
|
+
`}
|
|
53
|
+
>
|
|
54
|
+
<a
|
|
55
|
+
href={app.url}
|
|
56
|
+
target={app.target}
|
|
57
|
+
rel="noreferrer"
|
|
58
|
+
className={css`
|
|
59
|
+
display: block;
|
|
60
|
+
height: 100%;
|
|
61
|
+
color: ${token.colorText};
|
|
62
|
+
text-decoration: none;
|
|
63
|
+
|
|
64
|
+
&:hover {
|
|
65
|
+
color: ${token.colorText};
|
|
66
|
+
}
|
|
67
|
+
`}
|
|
68
|
+
>
|
|
69
|
+
<Card
|
|
70
|
+
bordered={false}
|
|
71
|
+
style={{
|
|
72
|
+
height: '100%',
|
|
73
|
+
borderRadius: token.borderRadius,
|
|
74
|
+
boxShadow: 'none',
|
|
75
|
+
}}
|
|
76
|
+
className={css`
|
|
77
|
+
height: 100%;
|
|
78
|
+
border-radius: ${token.borderRadius}px;
|
|
79
|
+
box-shadow: none;
|
|
80
|
+
|
|
81
|
+
&:hover {
|
|
82
|
+
background: ${token.colorBgTextHover};
|
|
83
|
+
box-shadow: none;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.ant-card-body {
|
|
87
|
+
height: 100%;
|
|
88
|
+
padding: ${token.paddingXS}px;
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
gap: ${token.marginXS}px;
|
|
92
|
+
}
|
|
93
|
+
`}
|
|
94
|
+
styles={{
|
|
95
|
+
body: {
|
|
96
|
+
background: 'transparent',
|
|
97
|
+
},
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
<span
|
|
101
|
+
className={css`
|
|
102
|
+
display: inline-flex;
|
|
103
|
+
flex: 0 0 auto;
|
|
104
|
+
width: 32px;
|
|
105
|
+
height: 32px;
|
|
106
|
+
align-items: center;
|
|
107
|
+
justify-content: center;
|
|
108
|
+
|
|
109
|
+
img {
|
|
110
|
+
max-width: 32px;
|
|
111
|
+
max-height: 32px;
|
|
112
|
+
}
|
|
113
|
+
`}
|
|
114
|
+
>
|
|
115
|
+
{renderIcon(app.icon)}
|
|
116
|
+
</span>
|
|
117
|
+
<Typography.Text
|
|
118
|
+
ellipsis
|
|
119
|
+
title={typeof app.title === 'string' ? app.title : undefined}
|
|
120
|
+
className={css`
|
|
121
|
+
min-width: 0;
|
|
122
|
+
color: ${token.colorText};
|
|
123
|
+
font-size: ${token.fontSize}px;
|
|
124
|
+
line-height: ${token.lineHeight};
|
|
125
|
+
`}
|
|
126
|
+
>
|
|
127
|
+
{app.title}
|
|
128
|
+
</Typography.Text>
|
|
129
|
+
</Card>
|
|
130
|
+
</a>
|
|
131
|
+
</li>
|
|
132
|
+
))}
|
|
133
|
+
</ul>
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
},
|
|
137
|
+
[token],
|
|
138
|
+
);
|
|
139
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
12
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
13
13
|
import { FlowEngine, FlowEngineProvider } from '@nocobase/flow-engine';
|
|
14
14
|
import { observer } from '@nocobase/flow-engine';
|
|
15
15
|
import { MemoryRouter } from 'react-router-dom';
|
|
@@ -45,6 +45,9 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
45
45
|
let modalConfirmMock: ReturnType<typeof vi.fn>;
|
|
46
46
|
|
|
47
47
|
beforeEach(() => {
|
|
48
|
+
// Fixtures mount the modern client under the `v2` segment; tell the
|
|
49
|
+
// runtime-prefix helper so v2-runtime detection matches.
|
|
50
|
+
(globalThis.window as any).__nocobase_modern_client_prefix__ = 'v2';
|
|
48
51
|
engine = new FlowEngine();
|
|
49
52
|
modalConfirmMock = vi.fn().mockResolvedValue(true);
|
|
50
53
|
engine.registerModels({
|
|
@@ -104,6 +107,10 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
104
107
|
vi.spyOn(window, 'open').mockImplementation(() => null);
|
|
105
108
|
});
|
|
106
109
|
|
|
110
|
+
afterEach(() => {
|
|
111
|
+
delete (globalThis.window as any).__nocobase_modern_client_prefix__;
|
|
112
|
+
});
|
|
113
|
+
|
|
107
114
|
const createRoute = (options?: Partial<import('../../../../flow-compat').NocoBaseDesktopRoute>) => ({
|
|
108
115
|
id: 1,
|
|
109
116
|
title: 'Page 1',
|
|
@@ -18,5 +18,6 @@ export * from './resolveAdminRouteRuntimeTarget';
|
|
|
18
18
|
export * from './constants';
|
|
19
19
|
export * from './ResetThemeTokenAndKeepAlgorithm';
|
|
20
20
|
export * from './useApplications';
|
|
21
|
+
export * from './AppListRender';
|
|
21
22
|
export * from './PinnedPluginListLite';
|
|
22
23
|
export * from './mobile-layout';
|
|
@@ -23,6 +23,17 @@ const app = {
|
|
|
23
23
|
},
|
|
24
24
|
} as any;
|
|
25
25
|
|
|
26
|
+
// These fixtures mount the modern client under the `v2` segment; tell the
|
|
27
|
+
// runtime-prefix helper so `isV2AdminRuntime` detects it (the server injects
|
|
28
|
+
// this in production).
|
|
29
|
+
beforeAll(() => {
|
|
30
|
+
(window as any).__nocobase_modern_client_prefix__ = 'v2';
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterAll(() => {
|
|
34
|
+
delete (window as any).__nocobase_modern_client_prefix__;
|
|
35
|
+
});
|
|
36
|
+
|
|
26
37
|
describe('resolveAdminRouteRuntimeTarget', () => {
|
|
27
38
|
it('should resolve flowPage to v2 spa runtime target', () => {
|
|
28
39
|
expect(
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { getV2EffectiveBasePath } from '../../../authRedirect';
|
|
10
|
+
import { getModernClientPrefix, getV2EffectiveBasePath } from '../../../authRedirect';
|
|
11
11
|
import type { BaseApplication } from '../../../BaseApplication';
|
|
12
12
|
import { NocoBaseDesktopRouteType, type NocoBaseDesktopRoute } from '../../../flow-compat';
|
|
13
13
|
|
|
@@ -26,8 +26,6 @@ export type AdminRouteRuntimeTarget = {
|
|
|
26
26
|
reason: AdminRouteRuntimeTargetReason;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
const V2_PUBLIC_PATH_SUFFIX = '/v2/';
|
|
30
|
-
|
|
31
29
|
type LocationLike = {
|
|
32
30
|
pathname: string;
|
|
33
31
|
search?: string;
|
|
@@ -69,7 +67,7 @@ function normalizePublicPath(value = '/') {
|
|
|
69
67
|
}
|
|
70
68
|
|
|
71
69
|
export function isV2AdminRuntime(app?: ResolveAdminRouteRuntimeTargetOptions['app']) {
|
|
72
|
-
return !!app?.getPublicPath && normalizePublicPath(app.getPublicPath()).endsWith(
|
|
70
|
+
return !!app?.getPublicPath && normalizePublicPath(app.getPublicPath()).endsWith(`/${getModernClientPrefix()}/`);
|
|
73
71
|
}
|
|
74
72
|
|
|
75
73
|
export function toRouterNavigationPath(pathname: string, basename?: string) {
|
|
@@ -8,8 +8,41 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { useApp } from '../../../flow-compat';
|
|
11
|
+
import React from 'react';
|
|
11
12
|
|
|
12
13
|
export const useApplications = () => {
|
|
13
14
|
const app = useApp();
|
|
14
|
-
|
|
15
|
+
const loadAppList = app.apps.loadAppList;
|
|
16
|
+
const [appList, setAppList] = React.useState([]);
|
|
17
|
+
|
|
18
|
+
React.useEffect(() => {
|
|
19
|
+
let canceled = false;
|
|
20
|
+
|
|
21
|
+
if (!loadAppList) {
|
|
22
|
+
setAppList([]);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
void Promise.resolve(loadAppList(app))
|
|
27
|
+
.then((list) => {
|
|
28
|
+
if (!canceled) {
|
|
29
|
+
setAppList(Array.isArray(list) ? list : []);
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
.catch((error) => {
|
|
33
|
+
console.error('[NocoBase] Failed to load application switcher list.', error);
|
|
34
|
+
if (!canceled) {
|
|
35
|
+
setAppList([]);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return () => {
|
|
40
|
+
canceled = true;
|
|
41
|
+
};
|
|
42
|
+
}, [app, loadAppList]);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
Component: app.apps.Component,
|
|
46
|
+
appList,
|
|
47
|
+
};
|
|
15
48
|
};
|
|
@@ -34,13 +34,41 @@ const defaultToolbar = [
|
|
|
34
34
|
'code',
|
|
35
35
|
'inline-code',
|
|
36
36
|
'upload',
|
|
37
|
-
'fullscreen',
|
|
38
37
|
];
|
|
39
38
|
|
|
40
39
|
const NAMESPACE = 'block-markdown';
|
|
41
40
|
|
|
42
41
|
const locales = ['en_US', 'fr_FR', 'pt_BR', 'ja_JP', 'ko_KR', 'ru_RU', 'sv_SE', 'zh_CN', 'zh_TW'];
|
|
43
42
|
|
|
43
|
+
type MarkdownToolbarItem =
|
|
44
|
+
| string
|
|
45
|
+
| {
|
|
46
|
+
name: string;
|
|
47
|
+
tipPosition?: string;
|
|
48
|
+
toolbar?: MarkdownToolbarItem[];
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function placeToolbarTooltipsBelow(toolbar: MarkdownToolbarItem[]) {
|
|
53
|
+
return toolbar.map((item) => {
|
|
54
|
+
if (typeof item === 'string') {
|
|
55
|
+
if (item === '|' || item === 'br') {
|
|
56
|
+
return item;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
name: item,
|
|
60
|
+
tipPosition: 's',
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
...item,
|
|
66
|
+
tipPosition: 's',
|
|
67
|
+
toolbar: item.toolbar ? placeToolbarTooltipsBelow(item.toolbar) : item.toolbar,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
44
72
|
const Edit = (props) => {
|
|
45
73
|
const { disabled, onChange, value, fileCollection, toolbar, vditorRef } = props;
|
|
46
74
|
const flowCtx = useFlowContext();
|
|
@@ -73,7 +101,7 @@ const Edit = (props) => {
|
|
|
73
101
|
useEffect(() => {
|
|
74
102
|
if (!containerRef.current) return;
|
|
75
103
|
|
|
76
|
-
const toolbarConfig = toolbar ?? defaultToolbar;
|
|
104
|
+
const toolbarConfig = placeToolbarTooltipsBelow(toolbar ?? defaultToolbar);
|
|
77
105
|
const vditor = new Vditor(containerRef.current, {
|
|
78
106
|
value: value ?? '',
|
|
79
107
|
lang,
|
|
@@ -321,7 +349,7 @@ export const MarkdownWithContextSelector: React.FC<MarkdownWithContextSelectorPr
|
|
|
321
349
|
if (quoteFlag !== false) {
|
|
322
350
|
setInnerValue(value);
|
|
323
351
|
}
|
|
324
|
-
}, [value]);
|
|
352
|
+
}, [quoteFlag, value]);
|
|
325
353
|
const handleTextChange = useCallback(
|
|
326
354
|
(e) => {
|
|
327
355
|
const val = e ?? '';
|
|
@@ -375,7 +403,7 @@ export const MarkdownWithContextSelector: React.FC<MarkdownWithContextSelectorPr
|
|
|
375
403
|
editor.focus();
|
|
376
404
|
});
|
|
377
405
|
},
|
|
378
|
-
[
|
|
406
|
+
[onChange],
|
|
379
407
|
);
|
|
380
408
|
|
|
381
409
|
const handleVariableSelected = useCallback(
|
|
@@ -13,14 +13,24 @@ import { FilterGroup, LinkageFilterItem } from './filter';
|
|
|
13
13
|
import { evaluateConditions, FilterGroupType, removeInvalidFilterItems } from '@nocobase/utils/client';
|
|
14
14
|
|
|
15
15
|
export const ConditionBuilder = observer(
|
|
16
|
-
(props: {
|
|
16
|
+
(props: {
|
|
17
|
+
value: FilterGroupType;
|
|
18
|
+
onChange: (value: FilterGroupType) => void;
|
|
19
|
+
extraMetaTree?: MetaTreeNode[];
|
|
20
|
+
maxAssociationFieldDepth?: number;
|
|
21
|
+
}) => {
|
|
17
22
|
const ctx = useFlowContext();
|
|
18
23
|
|
|
19
24
|
return (
|
|
20
25
|
<FilterGroup
|
|
21
26
|
value={props.value || { logic: '$and', items: [] }}
|
|
22
27
|
FilterItem={(itemProps) => (
|
|
23
|
-
<LinkageFilterItem
|
|
28
|
+
<LinkageFilterItem
|
|
29
|
+
model={ctx.model}
|
|
30
|
+
value={itemProps.value}
|
|
31
|
+
extraMetaTree={props.extraMetaTree}
|
|
32
|
+
maxAssociationFieldDepth={props.maxAssociationFieldDepth}
|
|
33
|
+
/>
|
|
24
34
|
)}
|
|
25
35
|
onChange={props.onChange}
|
|
26
36
|
/>
|
|
@@ -31,7 +31,7 @@ import { FieldModel } from '../models';
|
|
|
31
31
|
import { RecordSelectFieldModel } from '../models/fields/AssociationFieldModel';
|
|
32
32
|
import { InputFieldModel } from '../models/fields/InputFieldModel';
|
|
33
33
|
import { ensureOptionsFromUiSchemaEnumIfAbsent } from '../internal/utils/enumOptionsUtils';
|
|
34
|
-
import { resolveOperatorComponent } from '../internal/utils/operatorSchemaHelper';
|
|
34
|
+
import { pickOperatorStyle as pickStyle, resolveOperatorComponent } from '../internal/utils/operatorSchemaHelper';
|
|
35
35
|
import { RunJSValueEditor } from './RunJSValueEditor';
|
|
36
36
|
import { buildDynamicNamePath } from '../models/blocks/form/dynamicNamePath';
|
|
37
37
|
|
|
@@ -592,7 +592,7 @@ export const DefaultValue = connect((props: Props) => {
|
|
|
592
592
|
}
|
|
593
593
|
}
|
|
594
594
|
}
|
|
595
|
-
}, [inputProps
|
|
595
|
+
}, [inputProps]);
|
|
596
596
|
return (
|
|
597
597
|
<div style={{ flexGrow: 1 }}>
|
|
598
598
|
<FlowModelRenderer model={tempRoot} showFlowSettings={false} />
|
|
@@ -600,11 +600,13 @@ export const DefaultValue = connect((props: Props) => {
|
|
|
600
600
|
);
|
|
601
601
|
};
|
|
602
602
|
return ConstantValueEditor;
|
|
603
|
-
}, [tempRoot]);
|
|
603
|
+
}, [tempRoot, value]);
|
|
604
|
+
|
|
605
|
+
const modelOperator = (model as { operator?: string })?.operator;
|
|
604
606
|
|
|
605
607
|
// 文本类多关键词:根据已注册的 operator schema 渲染(用于默认值配置)
|
|
606
608
|
React.useEffect(() => {
|
|
607
|
-
const operator =
|
|
609
|
+
const operator = modelOperator;
|
|
608
610
|
const fieldModel = tempRoot?.subModels?.fields?.[0];
|
|
609
611
|
if (!operator || !fieldModel) return;
|
|
610
612
|
const originalRender = fieldModel['__originalRender'] || fieldModel.render;
|
|
@@ -622,17 +624,21 @@ export const DefaultValue = connect((props: Props) => {
|
|
|
622
624
|
<Comp
|
|
623
625
|
{...fieldModel.props}
|
|
624
626
|
{...xProps}
|
|
625
|
-
style={{
|
|
627
|
+
style={{
|
|
628
|
+
width: '100%',
|
|
629
|
+
...pickStyle((fieldModel.props as Record<string, unknown>)?.style),
|
|
630
|
+
...pickStyle(xProps?.style),
|
|
631
|
+
}}
|
|
626
632
|
/>
|
|
627
633
|
);
|
|
628
634
|
} else if (typeof originalRender === 'function') {
|
|
629
635
|
fieldModel.render = originalRender;
|
|
630
636
|
}
|
|
631
|
-
}, [model, tempRoot,
|
|
637
|
+
}, [model, tempRoot, modelOperator]);
|
|
632
638
|
|
|
633
639
|
// 根据操作符 schema 的 x-component-props 补全临时字段的输入属性(如多选)
|
|
634
640
|
React.useEffect(() => {
|
|
635
|
-
const operator =
|
|
641
|
+
const operator = modelOperator;
|
|
636
642
|
const fieldModel = tempRoot?.subModels?.fields?.[0];
|
|
637
643
|
if (!fieldModel || !operator) return;
|
|
638
644
|
const ops = model.collectionField?.filterable?.operators || [];
|
|
@@ -641,7 +647,7 @@ export const DefaultValue = connect((props: Props) => {
|
|
|
641
647
|
if (xComponentProps) {
|
|
642
648
|
fieldModel.setProps(xComponentProps);
|
|
643
649
|
}
|
|
644
|
-
}, [model, tempRoot,
|
|
650
|
+
}, [model, tempRoot, modelOperator]);
|
|
645
651
|
|
|
646
652
|
const NullComponent = useMemo(() => {
|
|
647
653
|
function NullValuePlaceholder() {
|
|
@@ -24,7 +24,7 @@ import type { CascaderProps, CollapseProps } from 'antd';
|
|
|
24
24
|
import React from 'react';
|
|
25
25
|
import { ConditionBuilder } from './ConditionBuilder';
|
|
26
26
|
import { FieldAssignValueInput } from './FieldAssignValueInput';
|
|
27
|
-
import type
|
|
27
|
+
import { buildFieldAssignCascaderOptionsFromCollection, type FieldAssignCascaderOption } from './fieldAssignOptions';
|
|
28
28
|
import { isRunJSValue, isVariableExpression, type MetaTreeNode } from '@nocobase/flow-engine';
|
|
29
29
|
import { isToManyAssociationField } from '../internal/utils/modelUtils';
|
|
30
30
|
|
|
@@ -36,6 +36,7 @@ type CollectionFieldLike = {
|
|
|
36
36
|
type?: unknown;
|
|
37
37
|
interface?: unknown;
|
|
38
38
|
uiSchema?: unknown;
|
|
39
|
+
target?: unknown;
|
|
39
40
|
targetKey?: unknown;
|
|
40
41
|
targetCollectionTitleFieldName?: unknown;
|
|
41
42
|
targetCollection?: any;
|
|
@@ -105,6 +106,7 @@ export interface FieldAssignRulesEditorProps {
|
|
|
105
106
|
onSyncAssociationTitleField?: (params: SyncAssociationTitleFieldParams) => Promise<void> | void;
|
|
106
107
|
/** 在日期字段下启用“日期变量替换 Constant 位”。 */
|
|
107
108
|
enableDateVariableAsConstant?: boolean;
|
|
109
|
+
maxAssociationFieldDepth?: number;
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (props) => {
|
|
@@ -123,9 +125,10 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
123
125
|
isTitleFieldCandidate,
|
|
124
126
|
onSyncAssociationTitleField,
|
|
125
127
|
enableDateVariableAsConstant = false,
|
|
128
|
+
maxAssociationFieldDepth = 2,
|
|
126
129
|
} = props;
|
|
127
130
|
|
|
128
|
-
const value = Array.isArray(rawValue) ? rawValue : [];
|
|
131
|
+
const value = React.useMemo(() => (Array.isArray(rawValue) ? rawValue : []), [rawValue]);
|
|
129
132
|
const [cascaderOptions, setCascaderOptions] = React.useState<FieldAssignCascaderOption[]>(() =>
|
|
130
133
|
Array.isArray(fieldOptions) ? (fieldOptions as FieldAssignCascaderOption[]) : [],
|
|
131
134
|
);
|
|
@@ -171,6 +174,19 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
171
174
|
}
|
|
172
175
|
}, [getRuleKey, syncingRuleKey, value]);
|
|
173
176
|
|
|
177
|
+
const parseTargetPathToSegments = React.useCallback((targetPath?: string): string[] => {
|
|
178
|
+
const raw = String(targetPath || '');
|
|
179
|
+
if (!raw) return [];
|
|
180
|
+
return raw
|
|
181
|
+
.split('.')
|
|
182
|
+
.map((s) => s.trim())
|
|
183
|
+
.filter(Boolean);
|
|
184
|
+
}, []);
|
|
185
|
+
|
|
186
|
+
const isAssociationFieldLike = React.useCallback((field?: CollectionFieldLike | null) => {
|
|
187
|
+
return !!(field?.isAssociationField?.() || field?.target || field?.targetCollection);
|
|
188
|
+
}, []);
|
|
189
|
+
|
|
174
190
|
const buildCollectionMetaTreeNodes = React.useCallback(
|
|
175
191
|
(collection: any, basePaths: string[], visited: Set<any>): MetaTreeNode[] => {
|
|
176
192
|
if (!collection?.getFields) return [];
|
|
@@ -199,7 +215,7 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
199
215
|
paths: [...basePaths, name],
|
|
200
216
|
};
|
|
201
217
|
|
|
202
|
-
const isAssoc =
|
|
218
|
+
const isAssoc = isAssociationFieldLike(f);
|
|
203
219
|
const isToMany = isToManyAssociationField(f);
|
|
204
220
|
if (isAssoc && !isToMany && f.targetCollection) {
|
|
205
221
|
node.children = async () =>
|
|
@@ -211,7 +227,7 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
211
227
|
|
|
212
228
|
return nodes;
|
|
213
229
|
},
|
|
214
|
-
[t],
|
|
230
|
+
[isAssociationFieldLike, t],
|
|
215
231
|
);
|
|
216
232
|
|
|
217
233
|
const buildItemMetaTree = React.useCallback(
|
|
@@ -226,7 +242,7 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
226
242
|
for (let i = 0; i < segs.length; i++) {
|
|
227
243
|
const seg = segs[i] as string;
|
|
228
244
|
const field = current?.getField?.(seg);
|
|
229
|
-
if (!field
|
|
245
|
+
if (!isAssociationFieldLike(field)) break;
|
|
230
246
|
const toMany = isToManyAssociationField(field);
|
|
231
247
|
const nextCollection = field?.targetCollection;
|
|
232
248
|
if (!nextCollection) break;
|
|
@@ -291,7 +307,7 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
291
307
|
|
|
292
308
|
return [buildObjectNode(levels.length - 1, ['item'], 'item', 'Current item')];
|
|
293
309
|
},
|
|
294
|
-
[buildCollectionMetaTreeNodes, rootCollection, t],
|
|
310
|
+
[buildCollectionMetaTreeNodes, isAssociationFieldLike, parseTargetPathToSegments, rootCollection, t],
|
|
295
311
|
);
|
|
296
312
|
|
|
297
313
|
const patchItem = (index: number, patch: Partial<FieldAssignRuleItem>) => {
|
|
@@ -381,15 +397,6 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
381
397
|
[t],
|
|
382
398
|
);
|
|
383
399
|
|
|
384
|
-
const parseTargetPathToSegments = React.useCallback((targetPath?: string): string[] => {
|
|
385
|
-
const raw = String(targetPath || '');
|
|
386
|
-
if (!raw) return [];
|
|
387
|
-
return raw
|
|
388
|
-
.split('.')
|
|
389
|
-
.map((s) => s.trim())
|
|
390
|
-
.filter(Boolean);
|
|
391
|
-
}, []);
|
|
392
|
-
|
|
393
400
|
const getFieldLabel = (targetPath?: string) => {
|
|
394
401
|
const segs = parseTargetPathToSegments(targetPath);
|
|
395
402
|
if (!segs.length) return undefined;
|
|
@@ -413,7 +420,7 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
413
420
|
: seg,
|
|
414
421
|
),
|
|
415
422
|
);
|
|
416
|
-
if (field
|
|
423
|
+
if (isAssociationFieldLike(field as CollectionFieldLike) && field?.targetCollection) {
|
|
417
424
|
collection = field.targetCollection;
|
|
418
425
|
}
|
|
419
426
|
}
|
|
@@ -435,7 +442,7 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
435
442
|
|
|
436
443
|
const isLast = i === segs.length - 1;
|
|
437
444
|
if (isLast) {
|
|
438
|
-
if (!field
|
|
445
|
+
if (!isAssociationFieldLike(field) || !field?.targetCollection) return null;
|
|
439
446
|
|
|
440
447
|
const targetCollection = field.targetCollection;
|
|
441
448
|
const targetFields =
|
|
@@ -502,7 +509,7 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
502
509
|
};
|
|
503
510
|
}
|
|
504
511
|
|
|
505
|
-
if (!field
|
|
512
|
+
if (!isAssociationFieldLike(field) || !field?.targetCollection) {
|
|
506
513
|
return null;
|
|
507
514
|
}
|
|
508
515
|
collection = field.targetCollection;
|
|
@@ -510,7 +517,7 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
510
517
|
|
|
511
518
|
return null;
|
|
512
519
|
},
|
|
513
|
-
[isTitleFieldCandidate, parseTargetPathToSegments, rootCollection, t],
|
|
520
|
+
[isAssociationFieldLike, isTitleFieldCandidate, parseTargetPathToSegments, rootCollection, t],
|
|
514
521
|
);
|
|
515
522
|
|
|
516
523
|
const resolveTargetCollectionBySegments = React.useCallback(
|
|
@@ -519,42 +526,24 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
519
526
|
let collection = rootCollection;
|
|
520
527
|
for (const seg of segments) {
|
|
521
528
|
const field = collection?.getField?.(seg);
|
|
522
|
-
if (!field
|
|
529
|
+
if (!isAssociationFieldLike(field) || !field?.targetCollection) {
|
|
523
530
|
return null;
|
|
524
531
|
}
|
|
525
532
|
collection = field.targetCollection;
|
|
526
533
|
}
|
|
527
534
|
return collection || null;
|
|
528
535
|
},
|
|
529
|
-
[rootCollection],
|
|
536
|
+
[isAssociationFieldLike, rootCollection],
|
|
530
537
|
);
|
|
531
538
|
|
|
532
539
|
const buildChildrenFromCollection = React.useCallback(
|
|
533
|
-
(collection: any): FieldAssignCascaderOption[] => {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
const f = rawField as CollectionFieldLike;
|
|
539
|
-
const fieldInterface = typeof f.interface === 'string' ? f.interface : undefined;
|
|
540
|
-
if (!fieldInterface) continue;
|
|
541
|
-
if (fieldInterface === 'formula') continue;
|
|
542
|
-
|
|
543
|
-
const name = String(f.name || '');
|
|
544
|
-
if (!name) continue;
|
|
545
|
-
|
|
546
|
-
const title = t(typeof f.title === 'string' ? f.title : name);
|
|
547
|
-
const isAssoc = !!f.isAssociationField?.();
|
|
548
|
-
const hasTarget = !!f.targetCollection;
|
|
549
|
-
out.push({
|
|
550
|
-
label: title,
|
|
551
|
-
value: name,
|
|
552
|
-
isLeaf: !(isAssoc && hasTarget),
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
return out;
|
|
540
|
+
(collection: any, associationDepth: number): FieldAssignCascaderOption[] => {
|
|
541
|
+
return buildFieldAssignCascaderOptionsFromCollection(collection, t, {
|
|
542
|
+
associationDepth,
|
|
543
|
+
maxAssociationFieldDepth,
|
|
544
|
+
});
|
|
556
545
|
},
|
|
557
|
-
[t],
|
|
546
|
+
[maxAssociationFieldDepth, t],
|
|
558
547
|
);
|
|
559
548
|
|
|
560
549
|
const loadCascaderData = React.useCallback<NonNullable<CascaderProps<FieldAssignCascaderOption>['loadData']>>(
|
|
@@ -573,7 +562,7 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
573
562
|
return;
|
|
574
563
|
}
|
|
575
564
|
|
|
576
|
-
const children = buildChildrenFromCollection(targetCollection);
|
|
565
|
+
const children = buildChildrenFromCollection(targetCollection, segments.length);
|
|
577
566
|
if (!children.length) {
|
|
578
567
|
target.isLeaf = true;
|
|
579
568
|
} else {
|
|
@@ -889,6 +878,7 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
889
878
|
value={item.value}
|
|
890
879
|
onChange={(v) => patchItem(index, { value: v })}
|
|
891
880
|
extraMetaTree={extraMetaTree}
|
|
881
|
+
maxAssociationFieldDepth={maxAssociationFieldDepth}
|
|
892
882
|
{...(getValueInputProps?.(item, index) || {})}
|
|
893
883
|
enableDateVariableAsConstant={enableDateVariableAsConstant}
|
|
894
884
|
associationFieldNamesOverride={
|
|
@@ -959,6 +949,7 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
959
949
|
value={item.condition || { logic: '$and', items: [] }}
|
|
960
950
|
onChange={(condition) => patchItem(index, { condition })}
|
|
961
951
|
extraMetaTree={extraMetaTree}
|
|
952
|
+
maxAssociationFieldDepth={2}
|
|
962
953
|
/>
|
|
963
954
|
</div>
|
|
964
955
|
)}
|