cabloy 5.1.107 → 5.1.109
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/.cabloy-version +1 -1
- package/CHANGELOG.md +27 -0
- package/cabloy-docs/.vitepress/config.mjs +6 -0
- package/cabloy-docs/backend/multi-instance-and-instance-resolution.md +18 -0
- package/cabloy-docs/backend/runtime-and-flavors.md +2 -2
- package/cabloy-docs/backend/scripts.md +3 -2
- package/cabloy-docs/frontend/ssr-env.md +20 -0
- package/cabloy-docs/frontend/table-resource-crud-cookbook.md +33 -1
- package/cabloy-docs/fullstack/deploy-cloudflare-docker.md +136 -0
- package/package.json +1 -1
- package/vona/docker-compose-original/config/nginx/conf.d/nginx.conf +1 -1
- package/vona/env/.env +1 -1
- package/vona/pnpm-lock.yaml +16 -16
- package/vona/src/backend/config/config/config.ts +1 -1
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +2 -0
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +2 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +50 -1
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectResItem.tsx +21 -1
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +47 -1
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +47 -1
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +60 -2
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +27 -2
- package/zova/src/suite/cabloy-basic/modules/basic-form/src/.metadata/component/blockFormLayout.ts +31 -0
- package/zova/src/suite/cabloy-basic/modules/basic-form/src/.metadata/index.ts +13 -0
- package/zova/src/suite/cabloy-basic/modules/basic-form/src/component/blockFormLayout/controller.tsx +165 -0
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/.metadata/component/blockFilterActions.ts +31 -0
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/.metadata/index.ts +13 -0
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockFilter/controller.tsx +61 -31
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockFilterActions/controller.tsx +51 -0
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/types/page.ts +10 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockForm/controller.tsx +9 -2
- package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +5 -3
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/form/controller.tsx +44 -3
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/form/render.tsx +22 -4
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/formField/controller.tsx +3 -2
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts +203 -0
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/index.ts +1 -0
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/formField.ts +7 -4
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/formLayout.ts +59 -0
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/index.ts +1 -0
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/action.ts +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/resource/formLayout.ts +53 -0
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/resource/index.ts +1 -0
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssr.ts +1 -1
- package/zova/src/suite-vendor/a-zova/package.json +4 -4
package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockFilter/controller.tsx
CHANGED
|
@@ -4,12 +4,16 @@ import type {
|
|
|
4
4
|
IJsxRenderContextPage,
|
|
5
5
|
IResourceBlockOptionsBase,
|
|
6
6
|
IResourceFormFieldLayoutOptions,
|
|
7
|
+
IResourceRenderBlockOptionsBlock,
|
|
7
8
|
} from 'zova-module-a-openapi';
|
|
8
9
|
|
|
9
10
|
import { isNilOrEmptyString } from '@cabloy/utils';
|
|
10
|
-
import {
|
|
11
|
+
import { VNode } from 'vue';
|
|
12
|
+
import { BeanControllerBase, objectAssignReactive, Use } from 'zova';
|
|
11
13
|
import { Controller } from 'zova-module-a-bean';
|
|
12
|
-
import { TypeFormOnSubmitData, ZForm } from 'zova-module-a-form';
|
|
14
|
+
import { ControllerForm, TypeFormOnSubmitData, ZForm } from 'zova-module-a-form';
|
|
15
|
+
|
|
16
|
+
import { IPageFilterScope } from '../../types/page.js';
|
|
13
17
|
|
|
14
18
|
declare module 'zova-module-a-openapi' {
|
|
15
19
|
export interface IResourceBlockRecord {
|
|
@@ -17,22 +21,29 @@ declare module 'zova-module-a-openapi' {
|
|
|
17
21
|
}
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
export interface ControllerBlockFilterProps extends IResourceBlockOptionsBase {
|
|
24
|
+
export interface ControllerBlockFilterProps extends IResourceBlockOptionsBase {
|
|
25
|
+
blocks?: IResourceRenderBlockOptionsBlock[];
|
|
26
|
+
formFieldLayout?: IResourceFormFieldLayoutOptions;
|
|
27
|
+
}
|
|
21
28
|
|
|
22
29
|
@Controller()
|
|
23
30
|
export class ControllerBlockFilter extends BeanControllerBase {
|
|
24
|
-
static $propsDefault = {
|
|
31
|
+
static $propsDefault = {
|
|
32
|
+
formFieldLayout: { inline: true },
|
|
33
|
+
};
|
|
34
|
+
|
|
25
35
|
static $componentOptions: IComponentOptions = { inheritAttrs: false, deepExtendDefault: true };
|
|
26
36
|
|
|
27
37
|
formMeta: IFormMeta;
|
|
28
|
-
|
|
38
|
+
formRef: ControllerForm | undefined;
|
|
39
|
+
formScope: IPageFilterScope;
|
|
29
40
|
|
|
30
41
|
@Use({ injectionScope: 'host' })
|
|
31
42
|
$$renderContext: IJsxRenderContextPage;
|
|
32
43
|
|
|
33
44
|
protected async __init__() {
|
|
34
45
|
this.formMeta = { formMode: 'edit' };
|
|
35
|
-
this.
|
|
46
|
+
this.formScope = this._prepareFormScope();
|
|
36
47
|
}
|
|
37
48
|
|
|
38
49
|
get schemaFilter() {
|
|
@@ -48,6 +59,16 @@ export class ControllerBlockFilter extends BeanControllerBase {
|
|
|
48
59
|
this._onFilter(data);
|
|
49
60
|
}
|
|
50
61
|
|
|
62
|
+
public submitFilter() {
|
|
63
|
+
return this.formRef?.submit() ?? Promise.resolve(false);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public resetFilter() {
|
|
67
|
+
if (!this.formRef) return;
|
|
68
|
+
const data = this.formRef.reset();
|
|
69
|
+
this.resetData(data);
|
|
70
|
+
}
|
|
71
|
+
|
|
51
72
|
_onFilter(dataOld: any) {
|
|
52
73
|
const { $$page } = this.$$renderContext;
|
|
53
74
|
const dataNew = {};
|
|
@@ -62,40 +83,49 @@ export class ControllerBlockFilter extends BeanControllerBase {
|
|
|
62
83
|
|
|
63
84
|
protected render() {
|
|
64
85
|
const { $$page } = this.$$renderContext;
|
|
86
|
+
const blocks = this.$props.blocks;
|
|
87
|
+
const hasBlocks = !!blocks && blocks.length > 0;
|
|
88
|
+
const formFieldLayout = this.$props.formFieldLayout;
|
|
65
89
|
return (
|
|
66
90
|
<ZForm
|
|
67
91
|
class={this.$props.class}
|
|
68
|
-
|
|
92
|
+
controllerRef={ref => {
|
|
93
|
+
this.formRef = ref;
|
|
94
|
+
}}
|
|
69
95
|
data={$$page.queryFilterData}
|
|
70
96
|
schema={this.schemaFilter}
|
|
71
97
|
schemaScene="filter"
|
|
72
98
|
formMeta={this.formMeta}
|
|
73
|
-
formFieldLayout={
|
|
99
|
+
formFieldLayout={formFieldLayout}
|
|
100
|
+
blocks={blocks}
|
|
101
|
+
formScope={this.formScope}
|
|
74
102
|
onSubmitData={data => this.submitData(data as never)}
|
|
75
|
-
slotFooter={$$form =>
|
|
76
|
-
return (
|
|
77
|
-
<>
|
|
78
|
-
<button
|
|
79
|
-
class="btn btn-primary"
|
|
80
|
-
onClick={() => {
|
|
81
|
-
$$form.submit();
|
|
82
|
-
}}
|
|
83
|
-
>
|
|
84
|
-
{this.scope.locale.Search()}
|
|
85
|
-
</button>
|
|
86
|
-
<button
|
|
87
|
-
class="btn btn-warning"
|
|
88
|
-
onClick={() => {
|
|
89
|
-
const data = $$form.reset();
|
|
90
|
-
this.resetData(data);
|
|
91
|
-
}}
|
|
92
|
-
>
|
|
93
|
-
{this.scope.locale.Reset()}
|
|
94
|
-
</button>
|
|
95
|
-
</>
|
|
96
|
-
);
|
|
97
|
-
}}
|
|
103
|
+
slotFooter={hasBlocks ? undefined : $$form => this._renderActions($$form)}
|
|
98
104
|
></ZForm>
|
|
99
105
|
);
|
|
100
106
|
}
|
|
107
|
+
|
|
108
|
+
private _prepareFormScope(): IPageFilterScope {
|
|
109
|
+
// eslint-disable-next-line
|
|
110
|
+
const self = this;
|
|
111
|
+
const $$filter = this.$customRef(() => {
|
|
112
|
+
return {
|
|
113
|
+
get() {
|
|
114
|
+
return self;
|
|
115
|
+
},
|
|
116
|
+
set(_value) {},
|
|
117
|
+
};
|
|
118
|
+
}) as any;
|
|
119
|
+
return objectAssignReactive({}, this.$$renderContext.$$page.jsxCelScope, { $$filter });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private _renderActions($$form: ControllerForm): VNode {
|
|
123
|
+
const jsxRenderContext = $$form.getFormJsxRenderContext(this.formScope);
|
|
124
|
+
return $$form.zovaJsx.render(
|
|
125
|
+
'basic-page:blockFilterActions',
|
|
126
|
+
{},
|
|
127
|
+
this.formScope,
|
|
128
|
+
jsxRenderContext,
|
|
129
|
+
) as VNode;
|
|
130
|
+
}
|
|
101
131
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { IComponentOptions } from 'zova';
|
|
2
|
+
import type { IJsxRenderContextForm } from 'zova-module-a-form';
|
|
3
|
+
import type { IResourceBlockOptionsBase } from 'zova-module-a-openapi';
|
|
4
|
+
|
|
5
|
+
import { BeanControllerBase, Use } from 'zova';
|
|
6
|
+
import { Controller } from 'zova-module-a-bean';
|
|
7
|
+
|
|
8
|
+
declare module 'zova-module-a-openapi' {
|
|
9
|
+
export interface IResourceBlockRecord {
|
|
10
|
+
'basic-page:blockFilterActions'?: ControllerBlockFilterActionsProps;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ControllerBlockFilterActionsProps extends IResourceBlockOptionsBase {}
|
|
15
|
+
|
|
16
|
+
@Controller()
|
|
17
|
+
export class ControllerBlockFilterActions extends BeanControllerBase {
|
|
18
|
+
static $propsDefault = {};
|
|
19
|
+
static $componentOptions: IComponentOptions = { inheritAttrs: false, deepExtendDefault: true };
|
|
20
|
+
|
|
21
|
+
@Use({ injectionScope: 'host' })
|
|
22
|
+
$$renderContext: IJsxRenderContextForm;
|
|
23
|
+
|
|
24
|
+
protected async __init__() {}
|
|
25
|
+
|
|
26
|
+
protected render() {
|
|
27
|
+
const { $$filter } = this.$$renderContext.$celScope;
|
|
28
|
+
return (
|
|
29
|
+
<div class={this.$props.class}>
|
|
30
|
+
<button
|
|
31
|
+
class="btn btn-primary"
|
|
32
|
+
type="button"
|
|
33
|
+
onClick={() => {
|
|
34
|
+
$$filter?.submitFilter();
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
{this.scope.locale.Search()}
|
|
38
|
+
</button>
|
|
39
|
+
<button
|
|
40
|
+
class="btn btn-warning"
|
|
41
|
+
type="button"
|
|
42
|
+
onClick={() => {
|
|
43
|
+
$$filter?.resetFilter();
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
{this.scope.locale.Reset()}
|
|
47
|
+
</button>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import type { IPageScope } from 'zova-module-a-openapi';
|
|
2
2
|
|
|
3
|
+
import 'zova-module-a-form';
|
|
3
4
|
import 'zova-module-a-table';
|
|
4
5
|
import 'zova-module-a-openapi';
|
|
6
|
+
import { ControllerBlockFilter } from '../component/blockFilter/controller.jsx';
|
|
5
7
|
import { ControllerBlockPage } from '../component/blockPage/controller.jsx';
|
|
6
8
|
|
|
9
|
+
export interface IPageFilterScope extends IPageScope {
|
|
10
|
+
$$filter?: ControllerBlockFilter;
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
declare module 'zova-module-a-openapi' {
|
|
8
14
|
export interface IJsxRenderContextPage<TData extends {} = {}> {
|
|
9
15
|
$$page: ControllerBlockPage<TData>;
|
|
10
16
|
}
|
|
11
17
|
}
|
|
12
18
|
|
|
19
|
+
declare module 'zova-module-a-form' {
|
|
20
|
+
export interface IFormScope extends IPageScope, IPageFilterScope {}
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
declare module 'zova-module-a-table' {
|
|
14
24
|
export interface ITableScope extends IPageScope {}
|
|
15
25
|
}
|
package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockForm/controller.tsx
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { IComponentOptions } from 'zova';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
IJsxRenderContextPageEntry,
|
|
4
|
+
IResourceBlockOptionsBase,
|
|
5
|
+
IResourceRenderBlockOptionsBlock,
|
|
6
|
+
} from 'zova-module-a-openapi';
|
|
3
7
|
|
|
4
8
|
import { BeanControllerBase, Use } from 'zova';
|
|
5
9
|
import { Controller } from 'zova-module-a-bean';
|
|
@@ -11,7 +15,9 @@ declare module 'zova-module-a-openapi' {
|
|
|
11
15
|
}
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
export interface ControllerBlockFormProps extends IResourceBlockOptionsBase {
|
|
18
|
+
export interface ControllerBlockFormProps extends IResourceBlockOptionsBase {
|
|
19
|
+
blocks?: IResourceRenderBlockOptionsBlock[];
|
|
20
|
+
}
|
|
15
21
|
|
|
16
22
|
@Controller()
|
|
17
23
|
export class ControllerBlockForm extends BeanControllerBase {
|
|
@@ -40,6 +46,7 @@ export class ControllerBlockForm extends BeanControllerBase {
|
|
|
40
46
|
schemaScene={$$pageEntry.schemaScene}
|
|
41
47
|
formMeta={$$pageEntry.formMeta}
|
|
42
48
|
formProvider={$$pageEntry.formProvider}
|
|
49
|
+
blocks={this.$props.blocks}
|
|
43
50
|
formScope={$$pageEntry.jsxCelScope}
|
|
44
51
|
onSubmitData={data => $$pageEntry.submitData(data)}
|
|
45
52
|
onShowError={async ({ error }) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova-module-a-form",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.44",
|
|
4
4
|
"gitHead": "09d901d17140a80ee0764211b441cda72fd94663",
|
|
5
5
|
"description": "",
|
|
6
6
|
"keywords": [
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"postpack": "clean-package restore"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@tanstack/vue-form": "^1.
|
|
37
|
+
"@tanstack/vue-form": "^1.33.2",
|
|
38
|
+
"@tanstack/vue-store": "^0.11.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"clean-package": "^2.2.0",
|
|
@@ -54,7 +55,8 @@
|
|
|
54
55
|
"title": "a-form",
|
|
55
56
|
"zovaModule": {
|
|
56
57
|
"globalDependencies": {
|
|
57
|
-
"@tanstack/vue-form": true
|
|
58
|
+
"@tanstack/vue-form": true,
|
|
59
|
+
"@tanstack/vue-store": true
|
|
58
60
|
},
|
|
59
61
|
"bundle": {
|
|
60
62
|
"vendors": [
|
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
getBy,
|
|
8
8
|
isGlobalFormValidationError,
|
|
9
9
|
revalidateLogic,
|
|
10
|
-
useStore,
|
|
11
10
|
ValidationCause,
|
|
12
11
|
ValidationError,
|
|
13
12
|
} from '@tanstack/vue-form';
|
|
13
|
+
import { useSelector } from '@tanstack/vue-store';
|
|
14
14
|
import { SchemaObject } from 'openapi3-ts/oas31';
|
|
15
15
|
import { VNode } from 'vue';
|
|
16
16
|
import { z } from 'zod';
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
IFormMeta,
|
|
23
23
|
IFormProvider,
|
|
24
24
|
IResourceFormFieldLayoutOptions,
|
|
25
|
+
IResourceRenderBlockOptionsBlock,
|
|
25
26
|
ISchemaObjectExtensionField,
|
|
26
27
|
renderFormFieldTopPropsSystem,
|
|
27
28
|
ScopeModuleAOpenapi,
|
|
@@ -30,6 +31,8 @@ import {
|
|
|
30
31
|
TypeFormSchemaScene,
|
|
31
32
|
} from 'zova-module-a-openapi';
|
|
32
33
|
|
|
34
|
+
import type { IResolvedFormLayoutNode, IResolvedFormLayoutTab } from '../../types/formLayout.js';
|
|
35
|
+
|
|
33
36
|
import { BeanControllerFormBase } from '../../lib/beanControllerFormBase.js';
|
|
34
37
|
import {
|
|
35
38
|
IFormScope,
|
|
@@ -43,13 +46,13 @@ import {
|
|
|
43
46
|
IFormFieldOptions,
|
|
44
47
|
IFormFieldRenderContextPropsBucket,
|
|
45
48
|
IFormFieldScope,
|
|
49
|
+
IJsxRenderContextForm,
|
|
46
50
|
IJsxRenderContextFormField,
|
|
47
51
|
} from '../../types/formField.js';
|
|
48
52
|
import { ControllerFormField } from '../formField/controller.jsx';
|
|
49
53
|
|
|
50
54
|
export interface ControllerFormProps<TFormData extends {} = {}, TSubmitMeta = never> {
|
|
51
55
|
formTag?: string;
|
|
52
|
-
inline?: boolean;
|
|
53
56
|
data?: TFormData;
|
|
54
57
|
schema?: SchemaObject;
|
|
55
58
|
schemaScene?: TypeFormSchemaScene;
|
|
@@ -60,6 +63,7 @@ export interface ControllerFormProps<TFormData extends {} = {}, TSubmitMeta = ne
|
|
|
60
63
|
formProvider?: IFormProvider;
|
|
61
64
|
formScope?: IFormScope;
|
|
62
65
|
formFieldLayout?: IResourceFormFieldLayoutOptions;
|
|
66
|
+
blocks?: IResourceRenderBlockOptionsBlock[];
|
|
63
67
|
onFormSubmit?: (e: SubmitEvent, form: ControllerForm<TFormData, TSubmitMeta>) => any;
|
|
64
68
|
onSubmitInvalid?: TypeFormOnSubmitInvalid<TFormData, TSubmitMeta>;
|
|
65
69
|
onSubmitData?: TypeFormOnSubmit<TFormData, TSubmitMeta>;
|
|
@@ -94,7 +98,7 @@ export class ControllerForm<
|
|
|
94
98
|
protected async __init__() {
|
|
95
99
|
this.bean._setBean('$$form', this);
|
|
96
100
|
this.form = this._createForm();
|
|
97
|
-
this.formState =
|
|
101
|
+
this.formState = useSelector(this.form.store, state => state) as any;
|
|
98
102
|
this.formProvider = this.$computed(() => {
|
|
99
103
|
const formProvider = this.$$scopeOpenapi.config.formProvider;
|
|
100
104
|
return this.$props.formProvider
|
|
@@ -143,6 +147,14 @@ export class ControllerForm<
|
|
|
143
147
|
return this.$props.formMeta;
|
|
144
148
|
}
|
|
145
149
|
|
|
150
|
+
public hasErrors(node: IResolvedFormLayoutNode | IResolvedFormLayoutTab) {
|
|
151
|
+
return this.getErrorFieldCount(node) > 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public getErrorFieldCount(node: IResolvedFormLayoutNode | IResolvedFormLayoutTab) {
|
|
155
|
+
return this._getFormLayoutErrorFieldCount(node);
|
|
156
|
+
}
|
|
157
|
+
|
|
146
158
|
public getFieldValue<K extends DeepKeys<TFormData>>(name: K) {
|
|
147
159
|
return getBy(this.formState.values, name) ?? null;
|
|
148
160
|
}
|
|
@@ -195,6 +207,12 @@ export class ControllerForm<
|
|
|
195
207
|
});
|
|
196
208
|
}
|
|
197
209
|
|
|
210
|
+
public getFormScope(scopeExtra?: {}): IFormScope {
|
|
211
|
+
return objectAssignReactive({}, this.$props.formScope, {
|
|
212
|
+
...scopeExtra,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
198
216
|
public getFieldJsxRenderContext(
|
|
199
217
|
$$formField: ControllerFormField<TFormData> | undefined,
|
|
200
218
|
celScope: IFormFieldScope<TFormData>,
|
|
@@ -211,6 +229,20 @@ export class ControllerForm<
|
|
|
211
229
|
};
|
|
212
230
|
}
|
|
213
231
|
|
|
232
|
+
public getFormJsxRenderContext(
|
|
233
|
+
celScope: IFormScope,
|
|
234
|
+
): IJsxRenderContextForm<TFormData, TSubmitMeta> {
|
|
235
|
+
return {
|
|
236
|
+
app: this.app,
|
|
237
|
+
ctx: this.ctx,
|
|
238
|
+
$scene: 'form',
|
|
239
|
+
$host: this,
|
|
240
|
+
$celScope: celScope,
|
|
241
|
+
$jsx: this.zovaJsx,
|
|
242
|
+
$$form: this,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
214
246
|
public getFieldComponentPropsTop<K extends DeepKeys<TFormData>>(
|
|
215
247
|
name: K,
|
|
216
248
|
celScope: IFormFieldScope<TFormData>,
|
|
@@ -375,6 +407,15 @@ export class ControllerForm<
|
|
|
375
407
|
return typeof renderProvider === 'string' && renderProvider.includes(':formField');
|
|
376
408
|
}
|
|
377
409
|
|
|
410
|
+
private _getFormLayoutErrorFieldCount(node: IResolvedFormLayoutNode | IResolvedFormLayoutTab) {
|
|
411
|
+
if (node.type === 'field') {
|
|
412
|
+
return this.formState.fieldMeta[node.name]?.errors?.length ? 1 : 0;
|
|
413
|
+
}
|
|
414
|
+
return node.children.reduce((count, child) => {
|
|
415
|
+
return count + this._getFormLayoutErrorFieldCount(child);
|
|
416
|
+
}, 0);
|
|
417
|
+
}
|
|
418
|
+
|
|
378
419
|
private _handleError422(error: Error, cause: ValidationCause = 'submit') {
|
|
379
420
|
const formApi = this.form;
|
|
380
421
|
|
|
@@ -33,13 +33,34 @@ export class RenderForm extends BeanRenderBase {
|
|
|
33
33
|
return children;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
private _renderFromBlocks() {
|
|
37
|
+
const blocks = this.$props.blocks;
|
|
38
|
+
if (!blocks || blocks.length === 0) {
|
|
39
|
+
return this._renderSchema();
|
|
40
|
+
}
|
|
41
|
+
const celScope = this.getFormScope();
|
|
42
|
+
const jsxRenderContext = this.getFormJsxRenderContext(celScope);
|
|
43
|
+
const domBlocks: VNode[] = [];
|
|
44
|
+
blocks.forEach((block, index) => {
|
|
45
|
+
const options = Object.assign({ key: index }, block.options);
|
|
46
|
+
const domBlock = this.zovaJsx.render(block.render!, options, celScope, jsxRenderContext);
|
|
47
|
+
if (!domBlock) return;
|
|
48
|
+
if (Array.isArray(domBlock)) {
|
|
49
|
+
domBlocks.push(...domBlock);
|
|
50
|
+
} else {
|
|
51
|
+
domBlocks.push(domBlock);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
return domBlocks;
|
|
55
|
+
}
|
|
56
|
+
|
|
36
57
|
private _renderBodyInner() {
|
|
37
58
|
const FormTag = this.$props.formTag;
|
|
38
59
|
return this.$slotDefault ? (
|
|
39
60
|
this.$slotDefault(this)
|
|
40
61
|
) : (
|
|
41
62
|
<>
|
|
42
|
-
{this._renderSchema()}
|
|
63
|
+
{this.$props.blocks ? this._renderFromBlocks() : this._renderSchema()}
|
|
43
64
|
{FormTag === 'form' && <button type="submit" style={{ display: 'none' }}></button>}
|
|
44
65
|
</>
|
|
45
66
|
);
|
|
@@ -48,9 +69,6 @@ export class RenderForm extends BeanRenderBase {
|
|
|
48
69
|
private _renderProps() {
|
|
49
70
|
const FormTag = this.$props.formTag;
|
|
50
71
|
const props: any = {};
|
|
51
|
-
if (this.$props.inline) {
|
|
52
|
-
props.class = 'inline';
|
|
53
|
-
}
|
|
54
72
|
if (FormTag === 'form') {
|
|
55
73
|
props.onSubmit = (e: SubmitEvent) => {
|
|
56
74
|
if (this.$props.onFormSubmit) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isNil } from '@cabloy/utils';
|
|
2
|
-
import { useField
|
|
2
|
+
import { useField } from '@tanstack/vue-form';
|
|
3
|
+
import { useSelector } from '@tanstack/vue-store';
|
|
3
4
|
import { markRaw } from 'vue';
|
|
4
5
|
import z from 'zod';
|
|
5
6
|
import { BeanControllerBase, deepEqual, IComponentOptions, Use } from 'zova';
|
|
@@ -139,7 +140,7 @@ export class ControllerFormField<TParentData extends {} = {}> extends BeanContro
|
|
|
139
140
|
private _createField() {
|
|
140
141
|
const options = this._getFormFieldOptions();
|
|
141
142
|
const field = markRaw(useField(options as any)) as any;
|
|
142
|
-
const fieldState =
|
|
143
|
+
const fieldState = useSelector(field.api.store, state => state) as any;
|
|
143
144
|
return { api: field.api, state: fieldState };
|
|
144
145
|
}
|
|
145
146
|
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IFormLayout,
|
|
3
|
+
IFormLayoutField,
|
|
4
|
+
IFormLayoutNode,
|
|
5
|
+
IFormLayoutTab,
|
|
6
|
+
IFormLayoutTabs,
|
|
7
|
+
ISchemaObjectExtensionField,
|
|
8
|
+
} from 'zova-module-a-openapi';
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
IFormLayoutDiagnostic,
|
|
12
|
+
IResolvedFormLayout,
|
|
13
|
+
IResolvedFormLayoutField,
|
|
14
|
+
IResolvedFormLayoutGroup,
|
|
15
|
+
IResolvedFormLayoutNode,
|
|
16
|
+
IResolvedFormLayoutSection,
|
|
17
|
+
IResolvedFormLayoutTab,
|
|
18
|
+
IResolvedFormLayoutTabs,
|
|
19
|
+
} from '../types/formLayout.js';
|
|
20
|
+
|
|
21
|
+
export function resolveFormLayout(
|
|
22
|
+
layout: IFormLayout,
|
|
23
|
+
properties: ISchemaObjectExtensionField[] | undefined,
|
|
24
|
+
): IResolvedFormLayout {
|
|
25
|
+
const propertyNames = new Set(
|
|
26
|
+
properties
|
|
27
|
+
?.filter(item => item.rest?.visible !== false)
|
|
28
|
+
.map(item => item.key)
|
|
29
|
+
.filter(Boolean) as string[],
|
|
30
|
+
);
|
|
31
|
+
const fieldNames = new Set<string>();
|
|
32
|
+
const nodeIds = new Set<string>();
|
|
33
|
+
const diagnostics: IFormLayoutDiagnostic[] = [];
|
|
34
|
+
const fieldTabPaths: IResolvedFormLayout['fieldTabPaths'] = {};
|
|
35
|
+
const children = layout.children
|
|
36
|
+
.map((node, index) =>
|
|
37
|
+
resolveNode(
|
|
38
|
+
node,
|
|
39
|
+
[index],
|
|
40
|
+
[],
|
|
41
|
+
propertyNames,
|
|
42
|
+
fieldNames,
|
|
43
|
+
nodeIds,
|
|
44
|
+
diagnostics,
|
|
45
|
+
fieldTabPaths,
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
.filter(Boolean) as IResolvedFormLayoutNode[];
|
|
49
|
+
for (const property of properties ?? []) {
|
|
50
|
+
const name = property.key;
|
|
51
|
+
if (!name || !propertyNames.has(name) || fieldNames.has(name)) continue;
|
|
52
|
+
fieldNames.add(name);
|
|
53
|
+
children.push({ type: 'field', name });
|
|
54
|
+
}
|
|
55
|
+
return { children, fieldTabPaths, diagnostics };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveNode(
|
|
59
|
+
node: IFormLayoutNode,
|
|
60
|
+
indexPath: number[],
|
|
61
|
+
tabPath: IResolvedFormLayout['fieldTabPaths'][string],
|
|
62
|
+
propertyNames: Set<string>,
|
|
63
|
+
fieldNames: Set<string>,
|
|
64
|
+
nodeIds: Set<string>,
|
|
65
|
+
diagnostics: IFormLayoutDiagnostic[],
|
|
66
|
+
fieldTabPaths: IResolvedFormLayout['fieldTabPaths'],
|
|
67
|
+
): IResolvedFormLayoutNode | undefined {
|
|
68
|
+
if (node.type === 'field') {
|
|
69
|
+
return resolveField(node, tabPath, propertyNames, fieldNames, diagnostics, fieldTabPaths);
|
|
70
|
+
}
|
|
71
|
+
if (node.type === 'tabs') {
|
|
72
|
+
return resolveTabs(
|
|
73
|
+
node,
|
|
74
|
+
indexPath,
|
|
75
|
+
propertyNames,
|
|
76
|
+
fieldNames,
|
|
77
|
+
nodeIds,
|
|
78
|
+
diagnostics,
|
|
79
|
+
fieldTabPaths,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
const id = resolveId(node, indexPath);
|
|
83
|
+
if (!registerId(id, nodeIds, diagnostics)) return;
|
|
84
|
+
if (node.type === 'section') {
|
|
85
|
+
const children = node.children
|
|
86
|
+
.map(item =>
|
|
87
|
+
resolveField(item, tabPath, propertyNames, fieldNames, diagnostics, fieldTabPaths),
|
|
88
|
+
)
|
|
89
|
+
.filter(Boolean) as IResolvedFormLayoutField[];
|
|
90
|
+
return children.length ? { ...node, id, children } : undefined;
|
|
91
|
+
}
|
|
92
|
+
const children = node.children
|
|
93
|
+
.map((item, index) =>
|
|
94
|
+
resolveNode(
|
|
95
|
+
item,
|
|
96
|
+
[...indexPath, index],
|
|
97
|
+
tabPath,
|
|
98
|
+
propertyNames,
|
|
99
|
+
fieldNames,
|
|
100
|
+
nodeIds,
|
|
101
|
+
diagnostics,
|
|
102
|
+
fieldTabPaths,
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
.filter(Boolean) as Array<
|
|
106
|
+
IResolvedFormLayoutField | IResolvedFormLayoutGroup | IResolvedFormLayoutSection
|
|
107
|
+
>;
|
|
108
|
+
return children.length ? { ...node, id, children } : undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function resolveTabs(
|
|
112
|
+
node: IFormLayoutTabs,
|
|
113
|
+
indexPath: number[],
|
|
114
|
+
propertyNames: Set<string>,
|
|
115
|
+
fieldNames: Set<string>,
|
|
116
|
+
nodeIds: Set<string>,
|
|
117
|
+
diagnostics: IFormLayoutDiagnostic[],
|
|
118
|
+
fieldTabPaths: IResolvedFormLayout['fieldTabPaths'],
|
|
119
|
+
): IResolvedFormLayoutTabs | undefined {
|
|
120
|
+
const id = resolveId(node, indexPath);
|
|
121
|
+
if (!registerId(id, nodeIds, diagnostics)) return;
|
|
122
|
+
const children = node.children
|
|
123
|
+
.map((tab, index) =>
|
|
124
|
+
resolveTab(
|
|
125
|
+
tab,
|
|
126
|
+
[...indexPath, index],
|
|
127
|
+
id,
|
|
128
|
+
propertyNames,
|
|
129
|
+
fieldNames,
|
|
130
|
+
nodeIds,
|
|
131
|
+
diagnostics,
|
|
132
|
+
fieldTabPaths,
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
.filter(Boolean) as IResolvedFormLayoutTab[];
|
|
136
|
+
return children.length ? { ...node, id, children } : undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function resolveTab(
|
|
140
|
+
node: IFormLayoutTab,
|
|
141
|
+
indexPath: number[],
|
|
142
|
+
tabsId: string,
|
|
143
|
+
propertyNames: Set<string>,
|
|
144
|
+
fieldNames: Set<string>,
|
|
145
|
+
nodeIds: Set<string>,
|
|
146
|
+
diagnostics: IFormLayoutDiagnostic[],
|
|
147
|
+
fieldTabPaths: IResolvedFormLayout['fieldTabPaths'],
|
|
148
|
+
): IResolvedFormLayoutTab | undefined {
|
|
149
|
+
const id = resolveId(node, indexPath);
|
|
150
|
+
if (!registerId(id, nodeIds, diagnostics)) return;
|
|
151
|
+
const tabPath = [{ tabsId, tabId: id }];
|
|
152
|
+
const children = node.children
|
|
153
|
+
.map((item, index) =>
|
|
154
|
+
resolveNode(
|
|
155
|
+
item,
|
|
156
|
+
[...indexPath, index],
|
|
157
|
+
tabPath,
|
|
158
|
+
propertyNames,
|
|
159
|
+
fieldNames,
|
|
160
|
+
nodeIds,
|
|
161
|
+
diagnostics,
|
|
162
|
+
fieldTabPaths,
|
|
163
|
+
),
|
|
164
|
+
)
|
|
165
|
+
.filter(Boolean) as Array<
|
|
166
|
+
IResolvedFormLayoutField | IResolvedFormLayoutGroup | IResolvedFormLayoutSection
|
|
167
|
+
>;
|
|
168
|
+
return children.length ? { ...node, id, children } : undefined;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function resolveField(
|
|
172
|
+
node: IFormLayoutField,
|
|
173
|
+
tabPath: IResolvedFormLayout['fieldTabPaths'][string],
|
|
174
|
+
propertyNames: Set<string>,
|
|
175
|
+
fieldNames: Set<string>,
|
|
176
|
+
diagnostics: IFormLayoutDiagnostic[],
|
|
177
|
+
fieldTabPaths: IResolvedFormLayout['fieldTabPaths'],
|
|
178
|
+
): IResolvedFormLayoutField | undefined {
|
|
179
|
+
if (!propertyNames.has(node.name)) {
|
|
180
|
+
diagnostics.push({ type: 'unknownField', value: node.name });
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (fieldNames.has(node.name)) {
|
|
184
|
+
diagnostics.push({ type: 'duplicateField', value: node.name });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
fieldNames.add(node.name);
|
|
188
|
+
fieldTabPaths[node.name] = tabPath;
|
|
189
|
+
return node;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function resolveId(node: { type: string; id?: string }, indexPath: number[]) {
|
|
193
|
+
return node.id || `${node.type}-${indexPath.join('-')}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function registerId(id: string, ids: Set<string>, diagnostics: IFormLayoutDiagnostic[]) {
|
|
197
|
+
if (!ids.has(id)) {
|
|
198
|
+
ids.add(id);
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
diagnostics.push({ type: 'duplicateId', value: id });
|
|
202
|
+
return false;
|
|
203
|
+
}
|