cabloy 5.1.148 → 5.1.149
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 +11 -0
- package/package.json +1 -1
- package/repo-docs/.vitepress/config.mjs +4 -0
- package/repo-docs/backend/markdown-guide.md +1 -0
- package/repo-docs/backend/relations-guide.md +1 -0
- package/repo-docs/frontend/form-layout-guide.md +15 -1
- package/repo-docs/frontend/markdown-guide.md +1 -0
- package/repo-docs/fullstack/edition-collaboration-differences.md +1 -0
- package/repo-docs/fullstack/one-to-one-companion-resource-guide.md +447 -0
- package/repo-e2e/specs/cabloy-basic.spec.ts +176 -1
- package/vona/src/suite/a-training/modules/training-student/package.json +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/.metadata/index.ts +177 -103
- package/vona/src/suite/a-training/modules/training-student/src/bean/meta.index.ts +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/bean/meta.version.ts +19 -1
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +3 -0
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +3 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +21 -2
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentSummary.tsx +7 -1
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +21 -2
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +21 -2
- package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +0 -3
- package/vona/src/suite/a-training/modules/training-student/src/entity/studentContent.tsx +36 -0
- package/vona/src/suite/a-training/modules/training-student/src/model/student.ts +6 -0
- package/vona/src/suite/a-training/modules/training-student/src/model/studentContent.ts +22 -0
- package/vona/src/suite/a-training/modules/training-student/src/service/student.ts +63 -6
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +117 -7
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +4 -0
- package/zova/src/suite/a-training/modules/training-student/package.json +3 -1
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +40 -40
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +358 -337
- package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.actionSummary.tsx +18 -7
- package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts +13 -5
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
|
@@ -10,7 +10,12 @@ import { ModelStudent } from '../model/student.ts';
|
|
|
10
10
|
import { DtoDetailRecordMutate } from './detailRecordMutate.tsx';
|
|
11
11
|
import { DtoDetailRecordResItem } from './detailRecordResItem.tsx';
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
const studentContentFormField = $makeMetadata(
|
|
14
|
+
ZovaRender.fieldSource('studentContentForm.descriptionMarkdown'),
|
|
15
|
+
ZovaRender.field('basic-markdown:formFieldMarkdown'),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export interface IDtoOptionsStudentCreate extends IDecoratorDtoOptions<'studentContentForm'> {}
|
|
14
19
|
|
|
15
20
|
@Dto<IDtoOptionsStudentCreate>({
|
|
16
21
|
blocks: [
|
|
@@ -43,6 +48,16 @@ export interface IDtoOptionsStudentCreate extends IDecoratorDtoOptions {}
|
|
|
43
48
|
},
|
|
44
49
|
],
|
|
45
50
|
},
|
|
51
|
+
{
|
|
52
|
+
type: 'group',
|
|
53
|
+
title: $locale('StudentContent'),
|
|
54
|
+
children: [
|
|
55
|
+
{
|
|
56
|
+
type: 'section',
|
|
57
|
+
children: [{ type: 'field', name: 'studentContentForm' }],
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
46
61
|
],
|
|
47
62
|
},
|
|
48
63
|
{
|
|
@@ -78,6 +93,7 @@ export interface IDtoOptionsStudentCreate extends IDecoratorDtoOptions {}
|
|
|
78
93
|
}),
|
|
79
94
|
],
|
|
80
95
|
fields: {
|
|
96
|
+
studentContentForm: studentContentFormField,
|
|
81
97
|
trainingRecords: $makeMetadata(
|
|
82
98
|
v.title($locale('TrainingRecords')),
|
|
83
99
|
ZovaRender.order(6),
|
|
@@ -87,7 +103,10 @@ export interface IDtoOptionsStudentCreate extends IDecoratorDtoOptions {}
|
|
|
87
103
|
},
|
|
88
104
|
})
|
|
89
105
|
export class DtoStudentCreate extends $Dto.create(() => ModelStudent, {
|
|
90
|
-
include: {
|
|
106
|
+
include: {
|
|
107
|
+
studentContentForm: true,
|
|
108
|
+
trainingRecords: { dtoClass: DtoDetailRecordMutate },
|
|
109
|
+
},
|
|
91
110
|
}) {
|
|
92
111
|
@Api.field(ZovaRender.visible(false), v.optional(), v.array(DtoDetailRecordResItem))
|
|
93
112
|
_trainingRecords?: DtoDetailRecordResItem[];
|
|
@@ -11,8 +11,14 @@ export interface IDtoOptionsStudentSummary extends IDecoratorDtoOptions {}
|
|
|
11
11
|
|
|
12
12
|
@Dto<IDtoOptionsStudentSummary>()
|
|
13
13
|
export class DtoStudentSummary extends $Dto.get(() => ModelStudent, {
|
|
14
|
-
columns: ['id', 'name', 'mobile', 'level'
|
|
14
|
+
columns: ['id', 'name', 'mobile', 'level'],
|
|
15
15
|
}) {
|
|
16
|
+
@Api.field(v.title($locale('Description')), v.optional())
|
|
17
|
+
description?: string;
|
|
18
|
+
|
|
19
|
+
@Api.field(v.title($locale('DescriptionHtml')), v.optional())
|
|
20
|
+
descriptionHtml?: string;
|
|
21
|
+
|
|
16
22
|
@Api.field(v.title($locale('LevelTitle')))
|
|
17
23
|
levelTitle: string;
|
|
18
24
|
|
|
@@ -10,7 +10,12 @@ import { ModelStudent } from '../model/student.ts';
|
|
|
10
10
|
import { DtoDetailRecordMutate } from './detailRecordMutate.tsx';
|
|
11
11
|
import { DtoDetailRecordResItem } from './detailRecordResItem.tsx';
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
const studentContentFormField = $makeMetadata(
|
|
14
|
+
ZovaRender.fieldSource('studentContentForm.descriptionMarkdown'),
|
|
15
|
+
ZovaRender.field('basic-markdown:formFieldMarkdown'),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export interface IDtoOptionsStudentUpdate extends IDecoratorDtoOptions<'studentContentForm'> {}
|
|
14
19
|
|
|
15
20
|
@Dto<IDtoOptionsStudentUpdate>({
|
|
16
21
|
blocks: [
|
|
@@ -43,6 +48,16 @@ export interface IDtoOptionsStudentUpdate extends IDecoratorDtoOptions {}
|
|
|
43
48
|
},
|
|
44
49
|
],
|
|
45
50
|
},
|
|
51
|
+
{
|
|
52
|
+
type: 'group',
|
|
53
|
+
title: $locale('StudentContent'),
|
|
54
|
+
children: [
|
|
55
|
+
{
|
|
56
|
+
type: 'section',
|
|
57
|
+
children: [{ type: 'field', name: 'studentContentForm' }],
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
46
61
|
],
|
|
47
62
|
},
|
|
48
63
|
{
|
|
@@ -75,6 +90,7 @@ export interface IDtoOptionsStudentUpdate extends IDecoratorDtoOptions {}
|
|
|
75
90
|
}),
|
|
76
91
|
],
|
|
77
92
|
fields: {
|
|
93
|
+
studentContentForm: studentContentFormField,
|
|
78
94
|
trainingRecords: $makeMetadata(
|
|
79
95
|
v.title($locale('TrainingRecords')),
|
|
80
96
|
ZovaRender.order(6),
|
|
@@ -83,7 +99,10 @@ export interface IDtoOptionsStudentUpdate extends IDecoratorDtoOptions {}
|
|
|
83
99
|
},
|
|
84
100
|
})
|
|
85
101
|
export class DtoStudentUpdate extends $Dto.update(() => ModelStudent, {
|
|
86
|
-
include: {
|
|
102
|
+
include: {
|
|
103
|
+
studentContentForm: true,
|
|
104
|
+
trainingRecords: { dtoClass: DtoDetailRecordMutate },
|
|
105
|
+
},
|
|
87
106
|
}) {
|
|
88
107
|
@Api.field(ZovaRender.visible(false), v.optional(), v.array(DtoDetailRecordResItem))
|
|
89
108
|
_trainingRecords?: DtoDetailRecordResItem[];
|
|
@@ -11,7 +11,12 @@ import { ModelStudent } from '../model/student.ts';
|
|
|
11
11
|
import { DtoDetailRecordResItem } from './detailRecordResItem.tsx';
|
|
12
12
|
import { DtoDetailRecordView } from './detailRecordView.tsx';
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
const studentContentFormField = $makeMetadata(
|
|
15
|
+
ZovaRender.fieldSource('studentContentForm.descriptionMarkdown'),
|
|
16
|
+
ZovaRender.field('basic-markdown:formFieldMarkdown'),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export interface IDtoOptionsStudentView extends IDecoratorDtoOptions<'studentContentForm'> {}
|
|
15
20
|
|
|
16
21
|
@Dto<IDtoOptionsStudentView>({
|
|
17
22
|
blocks: [
|
|
@@ -44,6 +49,16 @@ export interface IDtoOptionsStudentView extends IDecoratorDtoOptions {}
|
|
|
44
49
|
},
|
|
45
50
|
],
|
|
46
51
|
},
|
|
52
|
+
{
|
|
53
|
+
type: 'group',
|
|
54
|
+
title: $locale('StudentContent'),
|
|
55
|
+
children: [
|
|
56
|
+
{
|
|
57
|
+
type: 'section',
|
|
58
|
+
children: [{ type: 'field', name: 'studentContentForm' }],
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
47
62
|
],
|
|
48
63
|
},
|
|
49
64
|
{
|
|
@@ -73,6 +88,7 @@ export interface IDtoOptionsStudentView extends IDecoratorDtoOptions {}
|
|
|
73
88
|
}),
|
|
74
89
|
],
|
|
75
90
|
fields: {
|
|
91
|
+
studentContentForm: studentContentFormField,
|
|
76
92
|
trainingRecords: $makeMetadata(
|
|
77
93
|
v.title($locale('TrainingRecords')),
|
|
78
94
|
ZovaRender.order(6),
|
|
@@ -81,7 +97,10 @@ export interface IDtoOptionsStudentView extends IDecoratorDtoOptions {}
|
|
|
81
97
|
},
|
|
82
98
|
})
|
|
83
99
|
export class DtoStudentView extends $Dto.get(() => ModelStudent, {
|
|
84
|
-
include: {
|
|
100
|
+
include: {
|
|
101
|
+
studentContentForm: true,
|
|
102
|
+
trainingRecords: { dtoClass: DtoDetailRecordView },
|
|
103
|
+
},
|
|
85
104
|
}) {
|
|
86
105
|
@Api.field(
|
|
87
106
|
ZovaRender.visible(false),
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { TableIdentity } from 'table-identity';
|
|
2
|
+
import type { IDecoratorEntityOptions } from 'vona-module-a-orm';
|
|
3
|
+
|
|
4
|
+
import { $makeMetadata, Api, v } from 'vona-module-a-openapiutils';
|
|
5
|
+
import { Entity, EntityBase } from 'vona-module-a-orm';
|
|
6
|
+
import { ZovaRender } from 'zova-rest-cabloy-basic-admin';
|
|
7
|
+
|
|
8
|
+
import { $locale } from '../.metadata/locales.ts';
|
|
9
|
+
|
|
10
|
+
export interface IEntityOptionsStudentContent extends IDecoratorEntityOptions {}
|
|
11
|
+
|
|
12
|
+
@Entity<IEntityOptionsStudentContent>('trainingStudentContent', {
|
|
13
|
+
openapi: { title: $locale('StudentContent') },
|
|
14
|
+
fields: {
|
|
15
|
+
id: $makeMetadata(ZovaRender.visible(false)),
|
|
16
|
+
iid: $makeMetadata(ZovaRender.visible(false)),
|
|
17
|
+
deleted: $makeMetadata(ZovaRender.visible(false)),
|
|
18
|
+
createdAt: $makeMetadata(ZovaRender.visible(false)),
|
|
19
|
+
updatedAt: $makeMetadata(ZovaRender.visible(false)),
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
export class EntityStudentContent extends EntityBase {
|
|
23
|
+
@Api.field(v.tableIdentity(), ZovaRender.visible(false))
|
|
24
|
+
studentId: TableIdentity;
|
|
25
|
+
|
|
26
|
+
@Api.field(
|
|
27
|
+
v.title($locale('DescriptionMarkdown')),
|
|
28
|
+
v.optional(),
|
|
29
|
+
ZovaRender.order(1),
|
|
30
|
+
ZovaRender.field('basic-markdown:formFieldMarkdown'),
|
|
31
|
+
)
|
|
32
|
+
descriptionMarkdown?: string;
|
|
33
|
+
|
|
34
|
+
@Api.field(v.title($locale('DescriptionHtml')), v.optional(), ZovaRender.visible(false))
|
|
35
|
+
descriptionHtml?: string;
|
|
36
|
+
}
|
|
@@ -22,6 +22,12 @@ export interface IModelOptionsStudent extends IDecoratorModelOptions<EntityStude
|
|
|
22
22
|
'description',
|
|
23
23
|
],
|
|
24
24
|
}),
|
|
25
|
+
studentContent: $relation.hasOne('training-student:studentContent', 'studentId', {
|
|
26
|
+
columns: ['id', 'studentId', 'descriptionMarkdown', 'descriptionHtml'],
|
|
27
|
+
}),
|
|
28
|
+
studentContentForm: $relation.hasOne('training-student:studentContent', 'studentId', {
|
|
29
|
+
columns: ['id', 'studentId', 'descriptionMarkdown'],
|
|
30
|
+
}),
|
|
25
31
|
},
|
|
26
32
|
})
|
|
27
33
|
export class ModelStudent extends BeanModelBase<EntityStudent> {}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { IDecoratorModelOptions } from 'vona-module-a-orm';
|
|
2
|
+
|
|
3
|
+
import { $relation, BeanModelBase, Model } from 'vona-module-a-orm';
|
|
4
|
+
|
|
5
|
+
import { EntityStudentContent } from '../entity/studentContent.tsx';
|
|
6
|
+
|
|
7
|
+
export interface IModelOptionsStudentContent extends IDecoratorModelOptions<EntityStudentContent> {}
|
|
8
|
+
|
|
9
|
+
@Model<IModelOptionsStudentContent>({
|
|
10
|
+
entity: EntityStudentContent,
|
|
11
|
+
relations: {
|
|
12
|
+
student: $relation.belongsTo(
|
|
13
|
+
'training-student:studentContent',
|
|
14
|
+
'training-student:student',
|
|
15
|
+
'studentId',
|
|
16
|
+
),
|
|
17
|
+
},
|
|
18
|
+
cache: {
|
|
19
|
+
modelsClear: 'training-student:student',
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
export class ModelStudentContent extends BeanModelBase<EntityStudentContent> {}
|
|
@@ -18,12 +18,27 @@ function getStudentRecordSubjectsInclude(): {
|
|
|
18
18
|
return { trainingRecords: { include: { trainingRecordSubjects: true } } };
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
function getStudentContentFormInclude() {
|
|
22
|
+
return { studentContentForm: true };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getStudentContentInclude() {
|
|
26
|
+
return { studentContent: true };
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
@Service()
|
|
22
30
|
export class ServiceStudent extends BeanBase {
|
|
23
31
|
async create(student: DtoStudentCreate): Promise<EntityStudent> {
|
|
24
|
-
|
|
32
|
+
const { studentContentForm, ...studentFields } = student as DtoStudentCreate & {
|
|
33
|
+
studentContentForm?: { descriptionMarkdown?: string };
|
|
34
|
+
};
|
|
35
|
+
const created = await this.scope.model.student.insert(studentFields, {
|
|
25
36
|
include: getStudentRecordSubjectsInclude(),
|
|
26
37
|
});
|
|
38
|
+
if (studentContentForm !== undefined) {
|
|
39
|
+
await this._updateStudentContent(created.id, studentContentForm.descriptionMarkdown);
|
|
40
|
+
}
|
|
41
|
+
return created;
|
|
27
42
|
}
|
|
28
43
|
|
|
29
44
|
async select(params?: IQueryParams<ModelStudent>): Promise<DtoStudentSelectRes> {
|
|
@@ -32,20 +47,34 @@ export class ServiceStudent extends BeanBase {
|
|
|
32
47
|
|
|
33
48
|
async view(id: TableIdentity): Promise<DtoStudentView | undefined> {
|
|
34
49
|
return await this.scope.model.student.getById(id, {
|
|
35
|
-
include:
|
|
50
|
+
include: {
|
|
51
|
+
...getStudentRecordSubjectsInclude(),
|
|
52
|
+
...getStudentContentFormInclude(),
|
|
53
|
+
},
|
|
36
54
|
});
|
|
37
55
|
}
|
|
38
56
|
|
|
39
57
|
async update(id: TableIdentity, student: DtoStudentUpdate) {
|
|
40
|
-
|
|
58
|
+
const { studentContentForm, ...studentFields } = student as DtoStudentUpdate & {
|
|
59
|
+
studentContentForm?: { descriptionMarkdown?: string };
|
|
60
|
+
};
|
|
61
|
+
const updateResult = await this.scope.model.student.updateById(id, studentFields, {
|
|
41
62
|
include: getStudentRecordSubjectsInclude(),
|
|
42
63
|
});
|
|
64
|
+
if (studentContentForm !== undefined) {
|
|
65
|
+
await this._updateStudentContent(id, studentContentForm.descriptionMarkdown);
|
|
66
|
+
}
|
|
67
|
+
return updateResult;
|
|
43
68
|
}
|
|
44
69
|
|
|
45
70
|
async summary(id: TableIdentity): Promise<DtoStudentSummary | undefined> {
|
|
46
|
-
const student = await this.scope.model.student.getById(id
|
|
71
|
+
const student = await this.scope.model.student.getById(id, {
|
|
72
|
+
include: getStudentContentInclude(),
|
|
73
|
+
});
|
|
47
74
|
if (!student) return undefined;
|
|
48
|
-
const
|
|
75
|
+
const description = student.studentContent?.descriptionMarkdown;
|
|
76
|
+
const descriptionHtml = student.studentContent?.descriptionHtml;
|
|
77
|
+
const descriptionLength = description?.length ?? 0;
|
|
49
78
|
const levelTitle = String(student.level);
|
|
50
79
|
return {
|
|
51
80
|
id: student.id,
|
|
@@ -53,22 +82,50 @@ export class ServiceStudent extends BeanBase {
|
|
|
53
82
|
mobile: student.mobile,
|
|
54
83
|
level: student.level,
|
|
55
84
|
levelTitle,
|
|
56
|
-
description
|
|
85
|
+
description,
|
|
86
|
+
descriptionHtml,
|
|
57
87
|
descriptionLength,
|
|
58
88
|
summaryText: `${student.name} is in level ${student.level}. Description length: ${descriptionLength}.`,
|
|
59
89
|
};
|
|
60
90
|
}
|
|
61
91
|
|
|
62
92
|
async delete(id: TableIdentity) {
|
|
93
|
+
await this.scope.model.studentContent.delete({ studentId: id });
|
|
63
94
|
return await this.scope.model.student.deleteById(id, {
|
|
64
95
|
include: getStudentRecordSubjectsInclude(),
|
|
65
96
|
});
|
|
66
97
|
}
|
|
67
98
|
|
|
68
99
|
async deleteForce(id: TableIdentity) {
|
|
100
|
+
await this.scope.model.studentContent.delete({ studentId: id }, { disableDeleted: true });
|
|
69
101
|
return await this.scope.model.student.deleteById(id, {
|
|
70
102
|
disableDeleted: true,
|
|
71
103
|
include: getStudentRecordSubjectsInclude(),
|
|
72
104
|
});
|
|
73
105
|
}
|
|
106
|
+
|
|
107
|
+
private async _updateStudentContent(
|
|
108
|
+
studentId: TableIdentity,
|
|
109
|
+
descriptionMarkdown?: string,
|
|
110
|
+
): Promise<void> {
|
|
111
|
+
const markdown = descriptionMarkdown?.trim();
|
|
112
|
+
const studentContent = await this.scope.model.studentContent.get({ studentId });
|
|
113
|
+
if (!markdown) {
|
|
114
|
+
if (studentContent) await this.scope.model.studentContent.deleteById(studentContent.id);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const descriptionHtml = this.bean.markdown.renderHtml(markdown);
|
|
118
|
+
if (studentContent) {
|
|
119
|
+
await this.scope.model.studentContent.updateById(studentContent.id, {
|
|
120
|
+
descriptionMarkdown: markdown,
|
|
121
|
+
descriptionHtml,
|
|
122
|
+
});
|
|
123
|
+
} else {
|
|
124
|
+
await this.scope.model.studentContent.insert({
|
|
125
|
+
studentId,
|
|
126
|
+
descriptionMarkdown: markdown,
|
|
127
|
+
descriptionHtml,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
74
131
|
}
|
|
@@ -25,17 +25,36 @@ describe('student.test.ts', () => {
|
|
|
25
25
|
const formLayout =
|
|
26
26
|
blocks?.[0]?.options?.blocks?.[0]?.options?.blocks?.[0]?.options?.formLayout;
|
|
27
27
|
const tabs = formLayout?.children[0];
|
|
28
|
-
const
|
|
28
|
+
const groups = tabs?.children[0]?.children;
|
|
29
|
+
const profileSection = groups?.[0]?.children[0];
|
|
30
|
+
const studentContentSection = groups?.[1]?.children[0];
|
|
29
31
|
const trainingRecordsSection = tabs?.children[1]?.children[1];
|
|
30
32
|
assert.equal(tabs?.type, 'tabs');
|
|
31
33
|
assert.equal(tabs?.id, undefined);
|
|
32
34
|
assert.equal(tabs?.children[1]?.type, 'tab');
|
|
33
35
|
assert.equal(tabs?.children[1]?.id, undefined);
|
|
36
|
+
assert.equal(groups?.length, 2);
|
|
37
|
+
assert.deepEqual(
|
|
38
|
+
groups?.map(item => item.type),
|
|
39
|
+
['group', 'group'],
|
|
40
|
+
);
|
|
34
41
|
assert.deepEqual(profileSection?.columns, { default: 1, md: 2 });
|
|
35
42
|
assert.deepEqual(
|
|
36
43
|
profileSection?.children.map(item => item.name),
|
|
37
44
|
['name', 'mobile', 'imageId'],
|
|
38
45
|
);
|
|
46
|
+
assert.deepEqual(
|
|
47
|
+
studentContentSection?.children.map(item => item.name),
|
|
48
|
+
['studentContentForm'],
|
|
49
|
+
);
|
|
50
|
+
assert.equal(
|
|
51
|
+
component.properties.studentContentForm.rest.fieldSource,
|
|
52
|
+
'studentContentForm.descriptionMarkdown',
|
|
53
|
+
);
|
|
54
|
+
assert.equal(
|
|
55
|
+
component.properties.studentContentForm.rest.form.render,
|
|
56
|
+
'basic-markdown:formFieldMarkdown',
|
|
57
|
+
);
|
|
39
58
|
assert.equal(tabs?.children[1]?.children[0]?.name, 'level');
|
|
40
59
|
assert.deepEqual(
|
|
41
60
|
trainingRecordsSection?.children.map(item => item.name),
|
|
@@ -93,6 +112,7 @@ describe('student.test.ts', () => {
|
|
|
93
112
|
'mobile',
|
|
94
113
|
'level',
|
|
95
114
|
'description',
|
|
115
|
+
'descriptionHtml',
|
|
96
116
|
'levelTitle',
|
|
97
117
|
'descriptionLength',
|
|
98
118
|
'summaryText',
|
|
@@ -118,9 +138,13 @@ describe('student.test.ts', () => {
|
|
|
118
138
|
const maskedMobileUpdate = '139****4321';
|
|
119
139
|
const trainingTime = new Date('2026-03-10T08:00:00.000Z');
|
|
120
140
|
const trainingTimeUpdate = new Date('2026-04-18T13:20:00.000Z');
|
|
141
|
+
const description =
|
|
142
|
+
`# Student profile\n\n${'This is a long Markdown description. '.repeat(10)}`.trim();
|
|
143
|
+
const descriptionUpdate =
|
|
144
|
+
`## Updated profile\n\n${'This is updated Markdown content. '.repeat(10)}`.trim();
|
|
121
145
|
const data = {
|
|
122
146
|
name: '__Tom__',
|
|
123
|
-
|
|
147
|
+
studentContentForm: { descriptionMarkdown: description },
|
|
124
148
|
mobile,
|
|
125
149
|
level: 1,
|
|
126
150
|
trainingRecords: [
|
|
@@ -157,6 +181,7 @@ describe('student.test.ts', () => {
|
|
|
157
181
|
assert.equal(!!studentItem, true);
|
|
158
182
|
assert.equal(studentItem!.level, data.level);
|
|
159
183
|
assert.equal(studentItem!.mobile, maskedMobile);
|
|
184
|
+
assert.equal((studentItem as any).description, undefined);
|
|
160
185
|
// findMany: level filter
|
|
161
186
|
const selectResByLevel: DtoStudentSelectRes = await app.bean.executor.performAction(
|
|
162
187
|
'get',
|
|
@@ -182,6 +207,7 @@ describe('student.test.ts', () => {
|
|
|
182
207
|
const record = student.trainingRecords?.[0];
|
|
183
208
|
const recordSubject = record?.trainingRecordSubjects?.[0];
|
|
184
209
|
assert.equal(student.trainingRecords?.length, 1);
|
|
210
|
+
assert.equal(student.studentContentForm?.descriptionMarkdown, description);
|
|
185
211
|
assert.equal(record?.name, '__Record__');
|
|
186
212
|
assert.equal(record?.subjectCount, 1);
|
|
187
213
|
assert.equal(record?.totalScore, 88);
|
|
@@ -193,7 +219,7 @@ describe('student.test.ts', () => {
|
|
|
193
219
|
// update
|
|
194
220
|
const dataUpdate = {
|
|
195
221
|
name: '__TomNew__',
|
|
196
|
-
|
|
222
|
+
studentContentForm: { descriptionMarkdown: descriptionUpdate },
|
|
197
223
|
mobile: mobileUpdate,
|
|
198
224
|
level: 2,
|
|
199
225
|
trainingRecords: [
|
|
@@ -236,6 +262,7 @@ describe('student.test.ts', () => {
|
|
|
236
262
|
assert.equal(student.name, dataUpdate.name);
|
|
237
263
|
assert.equal(student.level, dataUpdate.level);
|
|
238
264
|
assert.equal(student.mobile, maskedMobileUpdate);
|
|
265
|
+
assert.equal(student.studentContentForm?.descriptionMarkdown, descriptionUpdate);
|
|
239
266
|
assert.equal(student.trainingRecords?.length, 1);
|
|
240
267
|
assert.equal(updatedRecord?.name, '__RecordNew__');
|
|
241
268
|
assert.equal(updatedRecord?.subjectCount, 2);
|
|
@@ -254,6 +281,15 @@ describe('student.test.ts', () => {
|
|
|
254
281
|
disableDeleted: true,
|
|
255
282
|
});
|
|
256
283
|
assert.equal(studentRaw!.mobile, mobileUpdate);
|
|
284
|
+
assert.equal((studentRaw as any).description, undefined);
|
|
285
|
+
const studentContent = await app
|
|
286
|
+
.scope('training-student')
|
|
287
|
+
.model.studentContent.get({ studentId });
|
|
288
|
+
assert.equal(studentContent?.descriptionMarkdown, descriptionUpdate);
|
|
289
|
+
assert.equal(
|
|
290
|
+
studentContent?.descriptionHtml,
|
|
291
|
+
app.bean.markdown.renderHtml(descriptionUpdate),
|
|
292
|
+
);
|
|
257
293
|
// summary
|
|
258
294
|
const summary: DtoStudentSummary = await app.bean.executor.performAction(
|
|
259
295
|
'get',
|
|
@@ -263,7 +299,9 @@ describe('student.test.ts', () => {
|
|
|
263
299
|
assert.equal(summary.name, dataUpdate.name);
|
|
264
300
|
assert.equal(summary.mobile, maskedMobileUpdate);
|
|
265
301
|
assert.equal(summary.level, dataUpdate.level);
|
|
266
|
-
assert.equal(summary.
|
|
302
|
+
assert.equal(summary.description, descriptionUpdate);
|
|
303
|
+
assert.equal(summary.descriptionHtml, app.bean.markdown.renderHtml(descriptionUpdate));
|
|
304
|
+
assert.equal(summary.descriptionLength, descriptionUpdate.length);
|
|
267
305
|
assert.equal(typeof summary.levelTitle, 'string');
|
|
268
306
|
assert.equal(typeof summary.summaryText, 'string');
|
|
269
307
|
// delete
|
|
@@ -294,11 +332,83 @@ describe('student.test.ts', () => {
|
|
|
294
332
|
disableDeleted: true,
|
|
295
333
|
});
|
|
296
334
|
assert.equal(studentForce, undefined);
|
|
335
|
+
assert.equal(
|
|
336
|
+
await app
|
|
337
|
+
.scope('training-student')
|
|
338
|
+
.model.studentContent.get({ studentId: studentIdForce }, { disableDeleted: true }),
|
|
339
|
+
undefined,
|
|
340
|
+
);
|
|
297
341
|
// logout
|
|
298
342
|
await app.bean.passport.signout();
|
|
299
343
|
});
|
|
300
344
|
});
|
|
301
345
|
|
|
346
|
+
it('action:student:contentPersistence', async () => {
|
|
347
|
+
await app.bean.executor.mockCtx(async () => {
|
|
348
|
+
await app.bean.passport.signinMock();
|
|
349
|
+
let studentId: string | undefined;
|
|
350
|
+
try {
|
|
351
|
+
studentId = await app.bean.executor.performAction('post', '/training/student', {
|
|
352
|
+
body: {
|
|
353
|
+
name: '__ContentStudent__',
|
|
354
|
+
mobile: '13812345678',
|
|
355
|
+
level: 1,
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
assert.equal(
|
|
359
|
+
await app.scope('training-student').model.studentContent.get({ studentId }),
|
|
360
|
+
undefined,
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
await app.bean.executor.performAction('patch', '/training/student/:id', {
|
|
364
|
+
params: { id: studentId },
|
|
365
|
+
body: {
|
|
366
|
+
name: '__ContentStudent__',
|
|
367
|
+
mobile: '13812345678',
|
|
368
|
+
level: 1,
|
|
369
|
+
studentContentForm: {
|
|
370
|
+
descriptionMarkdown: ' ## Created later ',
|
|
371
|
+
descriptionHtml: '<p>Forged HTML</p>',
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
const studentContent = await app
|
|
376
|
+
.scope('training-student')
|
|
377
|
+
.model.studentContent.get({ studentId });
|
|
378
|
+
assert.equal(studentContent?.descriptionMarkdown, '## Created later');
|
|
379
|
+
assert.equal(
|
|
380
|
+
studentContent?.descriptionHtml,
|
|
381
|
+
app.bean.markdown.renderHtml('## Created later'),
|
|
382
|
+
);
|
|
383
|
+
assert.notEqual(studentContent?.descriptionHtml, '<p>Forged HTML</p>');
|
|
384
|
+
|
|
385
|
+
await app.bean.executor.performAction('patch', '/training/student/:id', {
|
|
386
|
+
params: { id: studentId },
|
|
387
|
+
body: {
|
|
388
|
+
name: '__ContentStudent__',
|
|
389
|
+
mobile: '13812345678',
|
|
390
|
+
level: 1,
|
|
391
|
+
studentContentForm: { descriptionMarkdown: ' \n\t ' },
|
|
392
|
+
},
|
|
393
|
+
});
|
|
394
|
+
assert.equal(
|
|
395
|
+
await app.scope('training-student').model.studentContent.get({ studentId }),
|
|
396
|
+
undefined,
|
|
397
|
+
);
|
|
398
|
+
} finally {
|
|
399
|
+
if (studentId) {
|
|
400
|
+
await app
|
|
401
|
+
.scope('training-student')
|
|
402
|
+
.model.studentContent.delete({ studentId }, { disableDeleted: true });
|
|
403
|
+
await app
|
|
404
|
+
.scope('training-student')
|
|
405
|
+
.model.student.deleteById(studentId, { disableDeleted: true });
|
|
406
|
+
}
|
|
407
|
+
await app.bean.passport.signout();
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
302
412
|
it('action:student:systemAdmin', async () => {
|
|
303
413
|
await app.bean.executor.mockCtx(async () => {
|
|
304
414
|
await app.bean.passport.signinMock();
|
|
@@ -327,7 +437,7 @@ describe('student.test.ts', () => {
|
|
|
327
437
|
await app.bean.executor.performAction('post', '/training/student', {
|
|
328
438
|
body: {
|
|
329
439
|
name: '__Tom__',
|
|
330
|
-
|
|
440
|
+
studentContentForm: { descriptionMarkdown: 'This is a test' },
|
|
331
441
|
mobile: '13812345678',
|
|
332
442
|
level: 4,
|
|
333
443
|
},
|
|
@@ -344,7 +454,7 @@ describe('student.test.ts', () => {
|
|
|
344
454
|
await app.bean.executor.performAction('post', '/training/student', {
|
|
345
455
|
body: {
|
|
346
456
|
name: '__Tom__',
|
|
347
|
-
|
|
457
|
+
studentContentForm: { descriptionMarkdown: 'This is a test' },
|
|
348
458
|
level: 1,
|
|
349
459
|
},
|
|
350
460
|
});
|
|
@@ -360,7 +470,7 @@ describe('student.test.ts', () => {
|
|
|
360
470
|
await app.bean.executor.performAction('post', '/training/student', {
|
|
361
471
|
body: {
|
|
362
472
|
name: '__Tom__',
|
|
363
|
-
|
|
473
|
+
studentContentForm: { descriptionMarkdown: 'This is a test' },
|
|
364
474
|
mobile: '1381234567',
|
|
365
475
|
level: 1,
|
|
366
476
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.154",
|
|
4
4
|
"gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
|
|
5
5
|
"description": "A vue3 framework with ioc",
|
|
6
6
|
"keywords": [
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"zova-core": "^5.1.88",
|
|
49
|
-
"zova-suite-a-zova": "^5.1.
|
|
49
|
+
"zova-suite-a-zova": "^5.1.153"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"clean-package": "^2.2.0",
|
package/zova/pnpm-lock.yaml
CHANGED
|
@@ -1804,6 +1804,10 @@ importers:
|
|
|
1804
1804
|
version: link:modules/training-student
|
|
1805
1805
|
|
|
1806
1806
|
src/suite/a-training/modules/training-student:
|
|
1807
|
+
dependencies:
|
|
1808
|
+
zova-module-basic-markdown:
|
|
1809
|
+
specifier: ^5.0.0
|
|
1810
|
+
version: link:../../../cabloy-basic/modules/basic-markdown
|
|
1807
1811
|
devDependencies:
|
|
1808
1812
|
clean-package:
|
|
1809
1813
|
specifier: ^2.2.0
|