@pylonts/dsl 1.1.3 → 1.1.5
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/dist/bases.d.ts +1 -1
- package/dist/bases.js +2 -2
- package/dist/convert.d.ts +4 -1
- package/dist/convert.js +2 -2
- package/dist/dao.d.ts +9 -0
- package/dist/dao.js +3 -0
- package/dist/db.d.ts +2 -2
- package/dist/db.js +3 -0
- package/dist/dsl.d.ts +9 -1
- package/dist/dsl.js +10 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mysql-driver.js +4 -1
- package/dist/service.d.ts +16 -0
- package/dist/service.js +3 -0
- package/dist/typebox-driver.js +3 -2
- package/dist/utils.d.ts +9 -0
- package/dist/utils.js +3 -0
- package/docs/curd.md +110 -110
- package/docs/dto.md +66 -66
- package/docs/table.md +135 -135
- package/package.json +2 -2
- package/src/action.ts +10 -10
- package/src/asset.ts +62 -62
- package/src/bases.ts +2 -2
- package/src/component.ts +21 -21
- package/src/convert.ts +16 -13
- package/src/curd.ts +93 -93
- package/src/dao.ts +13 -0
- package/src/db.ts +181 -178
- package/src/dsl.ts +23 -0
- package/src/dto.ts +247 -247
- package/src/event.ts +12 -12
- package/src/index.ts +32 -30
- package/src/mermaid-driver.ts +84 -84
- package/src/mock.ts +12 -12
- package/src/mysql-driver.ts +4 -2
- package/src/navigation.ts +28 -28
- package/src/page-def.ts +79 -79
- package/src/page-flow.ts +153 -153
- package/src/page.ts +76 -76
- package/src/popup.ts +25 -25
- package/src/project.ts +97 -97
- package/src/provider.ts +72 -72
- package/src/ref.ts +18 -18
- package/src/route.ts +11 -11
- package/src/service.ts +21 -0
- package/src/typebox-driver.ts +193 -192
- package/src/utils.ts +16 -0
package/dist/bases.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { DtoMessage, ImportBase, ImportRef } from './dto.js';
|
|
|
3
3
|
export declare const PageRequest: ImportBase;
|
|
4
4
|
/** Paginated list response base — renders `import { PageResult } from '@pylonts/core'` + `PageResult(<row>)` */
|
|
5
5
|
export declare const PageResult: (row: DtoMessage) => ImportRef;
|
|
6
|
-
/** Paged rows type base without generic args — renders `import { PagedRows } from '@pylonts/core'` */
|
|
6
|
+
/** Paged rows type base without generic args — renders `import type { PagedRows } from '@pylonts/core'` */
|
|
7
7
|
export declare const PagedRows: ImportBase;
|
|
8
8
|
/** Paged rows type base — renders `import { PagedRows } from '@pylonts/core'` + `PagedRows(<row>)` */
|
|
9
9
|
export declare const PageRows: (row: DtoMessage) => ImportRef;
|
package/dist/bases.js
CHANGED
|
@@ -14,8 +14,8 @@ export const PageResult = (row) => ({
|
|
|
14
14
|
name: 'PageResult',
|
|
15
15
|
args: [row],
|
|
16
16
|
});
|
|
17
|
-
/** Paged rows type base without generic args — renders `import { PagedRows } from '@pylonts/core'` */
|
|
18
|
-
export const PagedRows = { from: '@pylonts/core', name: 'PagedRows' };
|
|
17
|
+
/** Paged rows type base without generic args — renders `import type { PagedRows } from '@pylonts/core'` */
|
|
18
|
+
export const PagedRows = { from: '@pylonts/core', name: 'PagedRows', type: true };
|
|
19
19
|
/** Paged rows type base — renders `import { PagedRows } from '@pylonts/core'` + `PagedRows(<row>)` */
|
|
20
20
|
export const PageRows = (row) => ({
|
|
21
21
|
from: '@pylonts/core',
|
package/dist/convert.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { SchemaBase } from './dsl.js';
|
|
2
|
+
import type { FrontAppSchema } from './project.js';
|
|
2
3
|
/** Declares post-call result → page data field mapping.
|
|
3
4
|
* Driver generates per-item transform (e.g. .map()) before setData. */
|
|
4
5
|
export interface ConvertSchema extends SchemaBase {
|
|
5
6
|
type: 'convert';
|
|
7
|
+
/** The frontend app this convert belongs to (shared instance from project.config). */
|
|
8
|
+
app: FrontAppSchema;
|
|
6
9
|
/** { targetField: sourceField } — renames or copies fields from call result. */
|
|
7
10
|
fields: Record<string, string>;
|
|
8
11
|
}
|
|
9
|
-
export declare function defineConvert(name: string, fields: Record<string, string>): ConvertSchema;
|
|
12
|
+
export declare function defineConvert(name: string, app: FrontAppSchema, fields: Record<string, string>): ConvertSchema;
|
package/dist/convert.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export function defineConvert(name, fields) {
|
|
2
|
-
return { name, type: 'convert', fields };
|
|
1
|
+
export function defineConvert(name, app, fields) {
|
|
2
|
+
return { name, type: 'convert', app, fields };
|
|
3
3
|
}
|
package/dist/dao.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SchemaBase } from './dsl.js';
|
|
2
|
+
import { FrontAppSchema } from './project.js';
|
|
3
|
+
/** A data-access layer bound to exactly one frontend app. */
|
|
4
|
+
export interface DaoSchema extends SchemaBase {
|
|
5
|
+
type: 'dao';
|
|
6
|
+
/** The frontend app this DAO belongs to (shared instance from project.config). */
|
|
7
|
+
app: FrontAppSchema;
|
|
8
|
+
}
|
|
9
|
+
export declare function defineDao(name: string, app: FrontAppSchema, description?: string): DaoSchema;
|
package/dist/dao.js
ADDED
package/dist/db.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export type ForeignKey = {
|
|
|
11
11
|
};
|
|
12
12
|
export interface TableSchemaOptions<N extends string = string, C extends Record<string, Field> = Record<string, Field>, E extends Record<string, EnumDef> = Record<string, EnumDef>> {
|
|
13
13
|
description?: string;
|
|
14
|
-
paginated
|
|
14
|
+
paginated: boolean;
|
|
15
15
|
actor?: boolean;
|
|
16
16
|
generator?: string;
|
|
17
17
|
autoIncrement?: Field;
|
|
@@ -31,7 +31,7 @@ export declare class TableSchema<N extends string = string, C extends Record<str
|
|
|
31
31
|
name: N;
|
|
32
32
|
description?: string;
|
|
33
33
|
/** 分页 */
|
|
34
|
-
paginated
|
|
34
|
+
paginated: boolean;
|
|
35
35
|
/** 系统操作者(如小程序为 C 端用户,管理端为运营) */
|
|
36
36
|
actor?: boolean;
|
|
37
37
|
/** id 生成器 */
|
package/dist/db.js
CHANGED
|
@@ -27,6 +27,9 @@ export class TableSchema {
|
|
|
27
27
|
if (!options.enums) {
|
|
28
28
|
throw new Error(`table '${name}': enums is required — declare enums: {} when the table has no enums`);
|
|
29
29
|
}
|
|
30
|
+
if (options.paginated === undefined || options.paginated === null) {
|
|
31
|
+
throw new Error(`table '${name}': paginated is required — discuss with user whether this table needs pagination, then set paginated: true or paginated: false`);
|
|
32
|
+
}
|
|
30
33
|
this.name = name;
|
|
31
34
|
this.description = options.description;
|
|
32
35
|
this.paginated = options.paginated;
|
package/dist/dsl.d.ts
CHANGED
|
@@ -54,6 +54,13 @@ interface DecimalField extends BaseField {
|
|
|
54
54
|
precision: number;
|
|
55
55
|
scale: number;
|
|
56
56
|
}
|
|
57
|
+
type RateUnit = 'pct' | 'pm' | 'bp';
|
|
58
|
+
export declare function rateScale(unit: RateUnit): number;
|
|
59
|
+
interface RateField extends BaseField {
|
|
60
|
+
type: 'rate';
|
|
61
|
+
jsType: 'string';
|
|
62
|
+
unit: RateUnit;
|
|
63
|
+
}
|
|
57
64
|
interface BooleanField extends BaseField {
|
|
58
65
|
type: 'boolean';
|
|
59
66
|
jsType: 'boolean';
|
|
@@ -93,13 +100,14 @@ interface JsonField extends BaseField {
|
|
|
93
100
|
type: 'json';
|
|
94
101
|
jsType: 'object';
|
|
95
102
|
}
|
|
96
|
-
export type Field = StringField | TextField | IntField | BigintField | DecimalField | BooleanField | DateField | TimeField | DateTimeField | EnumField | JsonField;
|
|
103
|
+
export type Field = StringField | TextField | IntField | BigintField | DecimalField | RateField | BooleanField | DateField | TimeField | DateTimeField | EnumField | JsonField;
|
|
97
104
|
type FieldExtras<T extends Field> = Omit<T, 'name' | 'type' | 'jsType'>;
|
|
98
105
|
export declare function stringField(extra?: FieldExtras<StringField>): StringField;
|
|
99
106
|
export declare function textField(extra?: FieldExtras<TextField>): TextField;
|
|
100
107
|
export declare function intField(extra?: FieldExtras<IntField>): IntField;
|
|
101
108
|
export declare function bigintField(extra?: FieldExtras<BigintField>): BigintField;
|
|
102
109
|
export declare function decimalField(extra: FieldExtras<DecimalField>): DecimalField;
|
|
110
|
+
export declare function rateField(unit: RateUnit, extra?: Omit<FieldExtras<RateField>, 'unit'>): RateField;
|
|
103
111
|
export declare function booleanField(extra?: FieldExtras<BooleanField>): BooleanField;
|
|
104
112
|
export declare function dateField(extra?: FieldExtras<DateField>): DateField;
|
|
105
113
|
export declare function timeField(extra?: FieldExtras<TimeField>): TimeField;
|
package/dist/dsl.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
// DSL field type definitions.
|
|
2
2
|
// Shape: { type: <type name>, <extension fields> }
|
|
3
|
+
export function rateScale(unit) {
|
|
4
|
+
switch (unit) {
|
|
5
|
+
case 'pct': return 2;
|
|
6
|
+
case 'pm': return 3;
|
|
7
|
+
case 'bp': return 4;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
3
10
|
export function defineEnum(jsName, valueType, values) {
|
|
4
11
|
return { jsName, valueType, values };
|
|
5
12
|
}
|
|
@@ -18,6 +25,9 @@ export function bigintField(extra = {}) {
|
|
|
18
25
|
export function decimalField(extra) {
|
|
19
26
|
return { name: '', type: 'decimal', jsType: 'string', ...extra };
|
|
20
27
|
}
|
|
28
|
+
export function rateField(unit, extra = {}) {
|
|
29
|
+
return { name: '', type: 'rate', jsType: 'string', unit, ...extra };
|
|
30
|
+
}
|
|
21
31
|
export function booleanField(extra = {}) {
|
|
22
32
|
return { name: '', type: 'boolean', jsType: 'boolean', ...extra };
|
|
23
33
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export * from './component.js';
|
|
|
21
21
|
export * from './convert.js';
|
|
22
22
|
export * from './ref.js';
|
|
23
23
|
export * from './route.js';
|
|
24
|
+
export * from './service.js';
|
|
25
|
+
export * from './dao.js';
|
|
24
26
|
export * from './page.js';
|
|
25
27
|
export * from './curd.js';
|
|
26
28
|
export * from './page-flow.js';
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,8 @@ export * from './component.js';
|
|
|
21
21
|
export * from './convert.js';
|
|
22
22
|
export * from './ref.js';
|
|
23
23
|
export * from './route.js';
|
|
24
|
+
export * from './service.js';
|
|
25
|
+
export * from './dao.js';
|
|
24
26
|
export * from './page.js';
|
|
25
27
|
export * from './curd.js';
|
|
26
28
|
export * from './page-flow.js';
|
package/dist/mysql-driver.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { rateScale } from './dsl.js';
|
|
1
2
|
// MySQL driver: converts a TableSchema into a CREATE TABLE statement.
|
|
2
3
|
/** Default value constant for created_at columns. */
|
|
3
4
|
export const CURRENT_TIMESTAMP = 'CURRENT_TIMESTAMP';
|
|
@@ -17,6 +18,8 @@ function columnType(field) {
|
|
|
17
18
|
return 'BIGINT';
|
|
18
19
|
case 'decimal':
|
|
19
20
|
return `DECIMAL(${field.precision}, ${field.scale})`;
|
|
21
|
+
case 'rate':
|
|
22
|
+
return `DECIMAL(5, ${rateScale(field.unit)})`;
|
|
20
23
|
case 'boolean':
|
|
21
24
|
return 'TINYINT(1)';
|
|
22
25
|
case 'date':
|
|
@@ -40,7 +43,7 @@ function renderDefault(field) {
|
|
|
40
43
|
if (/^[A-Z]/.test(field.default))
|
|
41
44
|
return ` DEFAULT ${field.default}`;
|
|
42
45
|
// Numeric columns take a bare literal, not a quoted one.
|
|
43
|
-
if (field.type === 'integer' || field.type === 'bigint' || field.type === 'decimal' || field.type === 'boolean') {
|
|
46
|
+
if (field.type === 'integer' || field.type === 'bigint' || field.type === 'decimal' || field.type === 'rate' || field.type === 'boolean') {
|
|
44
47
|
return ` DEFAULT ${field.default}`;
|
|
45
48
|
}
|
|
46
49
|
return ` DEFAULT '${field.default}'`;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SchemaBase } from './dsl.js';
|
|
2
|
+
import { FrontAppSchema } from './project.js';
|
|
3
|
+
import type { DtoArrayField, DtoField, DtoMessage, DtoObjectField } from './dto.js';
|
|
4
|
+
/** A frontend service bound to exactly one frontend app. */
|
|
5
|
+
export interface ServiceSchema extends SchemaBase {
|
|
6
|
+
type: 'service';
|
|
7
|
+
/** The frontend app this service belongs to (shared instance from project.config). */
|
|
8
|
+
app: FrontAppSchema;
|
|
9
|
+
}
|
|
10
|
+
export declare function defineService(name: string, app: FrontAppSchema, description?: string): ServiceSchema;
|
|
11
|
+
/** A method exposed by a service. */
|
|
12
|
+
export interface ServiceMethodSchema extends SchemaBase {
|
|
13
|
+
type: 'method';
|
|
14
|
+
name: string;
|
|
15
|
+
fields: Record<string, DtoField | DtoMessage | DtoArrayField | DtoObjectField>;
|
|
16
|
+
}
|
package/dist/service.js
ADDED
package/dist/typebox-driver.js
CHANGED
|
@@ -38,11 +38,12 @@ function renderBasic(field, pattern, defaultValue, resolver) {
|
|
|
38
38
|
}
|
|
39
39
|
case 'bigint':
|
|
40
40
|
case 'decimal':
|
|
41
|
+
case 'rate':
|
|
41
42
|
case 'time':
|
|
42
43
|
case 'date':
|
|
43
44
|
case 'datetime':
|
|
44
|
-
// Transmitted as string over HTTP: bigint/decimal keep full
|
|
45
|
-
// date/time serialize to string.
|
|
45
|
+
// Transmitted as string over HTTP: bigint/decimal/rate keep full
|
|
46
|
+
// precision, date/time serialize to string.
|
|
46
47
|
return def !== undefined ? `Type.String({ ${def} })` : 'Type.String()';
|
|
47
48
|
case 'boolean':
|
|
48
49
|
return def !== undefined ? `Type.Boolean({ ${def} })` : 'Type.Boolean()';
|
package/dist/utils.d.ts
CHANGED
|
@@ -2,3 +2,12 @@
|
|
|
2
2
|
export declare function toCamelCase(name: string): string;
|
|
3
3
|
/** snake_case → PascalCase: mer_id → MerId */
|
|
4
4
|
export declare function toPascalCase(snake: string): string;
|
|
5
|
+
import { SchemaBase } from './dsl.js';
|
|
6
|
+
import { FrontAppSchema } from './project.js';
|
|
7
|
+
/** A utility module bound to exactly one frontend app. */
|
|
8
|
+
export interface UtilsSchema extends SchemaBase {
|
|
9
|
+
type: 'utils';
|
|
10
|
+
/** The frontend app this utility module belongs to (shared instance from project.config). */
|
|
11
|
+
app: FrontAppSchema;
|
|
12
|
+
}
|
|
13
|
+
export declare function defineUtils(name: string, app: FrontAppSchema, description?: string): UtilsSchema;
|
package/dist/utils.js
CHANGED
package/docs/curd.md
CHANGED
|
@@ -1,111 +1,111 @@
|
|
|
1
|
-
# 管理端 CRUD 页面标准(CurdSchema)
|
|
2
|
-
|
|
3
|
-
CurdSchema 是**管理端专用**(`FrontAppSchema.type === 'admin'`)的 CRUD 页面标准:绑定一张实体表 + 一个管理端 app,描述列表页与新增/编辑/详情动作页生成所需的全部页面语义。一条 CurdSchema = 列表页(+ 动作页)的生成规格。
|
|
4
|
-
|
|
5
|
-
页面定义文件按实体组织:`{project}/pages/{entity}.curd.ts`(与 `schema/*.table.ts` 平级)。
|
|
6
|
-
|
|
7
|
-
**CurdSchema 只依赖 table schema(`Field` 实例),不挂钩 DTO(`DtoMessage`)**——DTO 由生成器按标准从 `columns` 推导。
|
|
8
|
-
|
|
9
|
-
## 定义
|
|
10
|
-
|
|
11
|
-
```ts
|
|
12
|
-
import { defineCurd } from '@pylonts/dsl';
|
|
13
|
-
|
|
14
|
-
export const orderCurd = defineCurd('order-curd', {
|
|
15
|
-
description: '订单管理',
|
|
16
|
-
app: webAdmin, // 所属管理端(project.config.ts 的 FrontAppSchema 共享实例)
|
|
17
|
-
table: order, // 绑定实体表(共享实例)
|
|
18
|
-
title: '订单管理',
|
|
19
|
-
section: '订单管理', // 必填:sidebar 分组名
|
|
20
|
-
actions: [defineAction('EXPORT', '导出订单')], // 额外操作按钮
|
|
21
|
-
actionPages: {
|
|
22
|
-
add: { mode: 'modal', columns: [order.columns.order_no, order.columns.mer_id] },
|
|
23
|
-
update: { mode: 'modal', columns: [order.columns.id, order.columns.order_no] },
|
|
24
|
-
detail: { mode: 'route', columns: [order.columns.id, order.columns.order_no, order.columns.amount] },
|
|
25
|
-
},
|
|
26
|
-
list: {
|
|
27
|
-
columns: [order.columns.id, order.columns.order_no, merchant.columns.name], // 可跨表
|
|
28
|
-
keyword: { columns: [order.columns.order_no] }, // 模糊搜索(本表字段)
|
|
29
|
-
orderBy: { column: order.columns.id, direction: 'desc' },
|
|
30
|
-
searchFields: [{ field: order.columns.mer_id }, { field: order.columns.order_no, op: 'like' }],
|
|
31
|
-
columnTitles: { order_no: '订单号', name: '商户名称' }, // Field.name → 文案
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## 字段
|
|
37
|
-
|
|
38
|
-
| 字段 | 类型 | 说明 |
|
|
39
|
-
|---|---|---|
|
|
40
|
-
| `app` | `FrontAppSchema` | 所属管理端(共享实例,`type` 必须为 `'admin'`) |
|
|
41
|
-
| `table` | `TableSchema` | 绑定实体表(共享实例) |
|
|
42
|
-
| `title` | `string` | 列表页中文标题 |
|
|
43
|
-
| `section` | `string` | **必填**:sidebar 分组名(`pylonts gen curd` 据此生成路由注册的 `section` 字段) |
|
|
44
|
-
| `actions?` | `ActionSchema[]` | 页面额外可执行动作(标准 CRUD 之外,如导出、审核) |
|
|
45
|
-
| `actionPages?` | `{ add? / update? / detail? }` | 动作页:`{ mode: 'modal' \| 'route'; columns: Field[] }` |
|
|
46
|
-
| `list` | `CurdListConfig` | 列表页配置(必填) |
|
|
47
|
-
|
|
48
|
-
### ActionPage
|
|
49
|
-
|
|
50
|
-
| 字段 | 类型 | 说明 |
|
|
51
|
-
|---|---|---|
|
|
52
|
-
| `mode` | `'modal' \| 'route'` | 弹窗或独立路由 |
|
|
53
|
-
| `columns` | `Field[]` | 该页面渲染的字段,**必填非空**——前端要显示的字段必须全部显式列出 |
|
|
54
|
-
|
|
55
|
-
### CurdListConfig
|
|
56
|
-
|
|
57
|
-
| 字段 | 类型 | 说明 |
|
|
58
|
-
|---|---|---|
|
|
59
|
-
| `columns` | `Field[]` | 列表列,**必填非空**——前端要显示的字段必须全部显式列出;可含跨表字段(见下) |
|
|
60
|
-
| `keyword?` | `{ columns: Field[] }` | 模糊搜索,columns 必须是**本表字段实例** |
|
|
61
|
-
| `orderBy` | `{ column: Field; direction: 'asc' \| 'desc' }` | 默认排序,**必填**,column 与 direction 都必填;column 必须是**本表字段实例** |
|
|
62
|
-
| `searchFields?` | `{ field: Field; op?: Operator }[]` | 搜索条件字段,op 默认 `'eq'`,可选 `eq/gt/gte/lt/lte/like/ne` |
|
|
63
|
-
| `columnTitles?` | `Record<string, string>` | 列标题覆盖:`Field.name` → 中文文案 |
|
|
64
|
-
|
|
65
|
-
## 跨表字段
|
|
66
|
-
|
|
67
|
-
`columns` / `searchFields` 里的 `Field` 实例可指向**本表或其他表**的列——列表列与搜索条件因此可以显示关联表字段(如订单列表显示商户名称):
|
|
68
|
-
|
|
69
|
-
```ts
|
|
70
|
-
list: {
|
|
71
|
-
columns: [order.columns.id, order.columns.order_no, merchant.columns.name], // 跨表:字段指向 merchant.name
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## 默认与校验
|
|
76
|
-
|
|
77
|
-
- `list.columns` / `actionPages.*.columns` **必填非空**(不允许省略、不允许空数组)——前端显示什么必须显式定义
|
|
78
|
-
- `list.orderBy` **必填**,`column` 与 `direction` 都必填(规格:默认主键 desc 由定义方显式写出)
|
|
79
|
-
- 运行时校验(`defineCurd`,仿 `defineTable` 强校验风格):
|
|
80
|
-
- `app.type` 必须为 `'admin'`,否则抛错
|
|
81
|
-
- 所有 `columns` 非空,否则抛错
|
|
82
|
-
- `list.keyword.columns` / `list.orderBy.column` 必须属于 `table`,否则抛错
|
|
83
|
-
- `list.columns` / `list.searchFields` 允许跨表,**不校验归属**
|
|
84
|
-
- 生成时校验(curd 生成器,`DtoSchemaGen.add` / `update`):
|
|
85
|
-
- 表配置 `autoIncrement` 或 `generator`(主键由服务端生成)时,`actionPages.add.columns` **不允许包含主键字段**,否则抛错——AddRequest 不携带服务端生成的主键
|
|
86
|
-
- `actionPages.update.columns` **必须包含主键字段**,否则抛错——UpdateRequest 靠主键定位记录
|
|
87
|
-
|
|
88
|
-
## DTO 推导(生成器约定)
|
|
89
|
-
|
|
90
|
-
DTO 由 curd 生成器从 `CurdSchema` 按标准命名推导,页面语义不持有 DTO 实例:
|
|
91
|
-
|
|
92
|
-
| DTO | 命名 | 字段来源 |
|
|
93
|
-
|---|---|---|
|
|
94
|
-
| Row | `{Pascal}Row` | `list.columns` |
|
|
95
|
-
| AddRequest | `{Pascal}AddRequest` | `actionPages.add.columns` |
|
|
96
|
-
| UpdateRequest | `{Pascal}UpdateRequest` | `actionPages.update.columns` |
|
|
97
|
-
| DetailRequest | `{Pascal}DetailRequest` | 主键 |
|
|
98
|
-
| DetailResponse | `{Pascal}DetailResponse` | `actionPages.detail.columns` |
|
|
99
|
-
|
|
100
|
-
## 与旧 PageConfig 的差异
|
|
101
|
-
|
|
102
|
-
| PageConfig(旧方案,已废弃) | CurdSchema |
|
|
103
|
-
|---|---|
|
|
104
|
-
| `module: string` | 由 `app` 推导(后端模块 == app 1:1) |
|
|
105
|
-
| `schema: 'bd'` 字符串 | `table: TableSchema` 实例(类型安全) |
|
|
106
|
-
| `operations: { label, action }` | `actions: ActionSchema[]` |
|
|
107
|
-
| `detail.mode` 单例 | `actionPages.detail.mode` |
|
|
108
|
-
| `forms.add / forms.update` | `actionPages.add / actionPages.update` |
|
|
109
|
-
| `keyword` / `orderBy` / `columnTitles` | `list.keyword` / `list.orderBy` / `list.columnTitles`(列改字段实例引用) |
|
|
110
|
-
| `naming` | 去掉(DTO 命名是生成器约定,非页面语义) |
|
|
1
|
+
# 管理端 CRUD 页面标准(CurdSchema)
|
|
2
|
+
|
|
3
|
+
CurdSchema 是**管理端专用**(`FrontAppSchema.type === 'admin'`)的 CRUD 页面标准:绑定一张实体表 + 一个管理端 app,描述列表页与新增/编辑/详情动作页生成所需的全部页面语义。一条 CurdSchema = 列表页(+ 动作页)的生成规格。
|
|
4
|
+
|
|
5
|
+
页面定义文件按实体组织:`{project}/pages/{entity}.curd.ts`(与 `schema/*.table.ts` 平级)。
|
|
6
|
+
|
|
7
|
+
**CurdSchema 只依赖 table schema(`Field` 实例),不挂钩 DTO(`DtoMessage`)**——DTO 由生成器按标准从 `columns` 推导。
|
|
8
|
+
|
|
9
|
+
## 定义
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { defineCurd } from '@pylonts/dsl';
|
|
13
|
+
|
|
14
|
+
export const orderCurd = defineCurd('order-curd', {
|
|
15
|
+
description: '订单管理',
|
|
16
|
+
app: webAdmin, // 所属管理端(project.config.ts 的 FrontAppSchema 共享实例)
|
|
17
|
+
table: order, // 绑定实体表(共享实例)
|
|
18
|
+
title: '订单管理',
|
|
19
|
+
section: '订单管理', // 必填:sidebar 分组名
|
|
20
|
+
actions: [defineAction('EXPORT', '导出订单')], // 额外操作按钮
|
|
21
|
+
actionPages: {
|
|
22
|
+
add: { mode: 'modal', columns: [order.columns.order_no, order.columns.mer_id] },
|
|
23
|
+
update: { mode: 'modal', columns: [order.columns.id, order.columns.order_no] },
|
|
24
|
+
detail: { mode: 'route', columns: [order.columns.id, order.columns.order_no, order.columns.amount] },
|
|
25
|
+
},
|
|
26
|
+
list: {
|
|
27
|
+
columns: [order.columns.id, order.columns.order_no, merchant.columns.name], // 可跨表
|
|
28
|
+
keyword: { columns: [order.columns.order_no] }, // 模糊搜索(本表字段)
|
|
29
|
+
orderBy: { column: order.columns.id, direction: 'desc' },
|
|
30
|
+
searchFields: [{ field: order.columns.mer_id }, { field: order.columns.order_no, op: 'like' }],
|
|
31
|
+
columnTitles: { order_no: '订单号', name: '商户名称' }, // Field.name → 文案
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 字段
|
|
37
|
+
|
|
38
|
+
| 字段 | 类型 | 说明 |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `app` | `FrontAppSchema` | 所属管理端(共享实例,`type` 必须为 `'admin'`) |
|
|
41
|
+
| `table` | `TableSchema` | 绑定实体表(共享实例) |
|
|
42
|
+
| `title` | `string` | 列表页中文标题 |
|
|
43
|
+
| `section` | `string` | **必填**:sidebar 分组名(`pylonts gen curd` 据此生成路由注册的 `section` 字段) |
|
|
44
|
+
| `actions?` | `ActionSchema[]` | 页面额外可执行动作(标准 CRUD 之外,如导出、审核) |
|
|
45
|
+
| `actionPages?` | `{ add? / update? / detail? }` | 动作页:`{ mode: 'modal' \| 'route'; columns: Field[] }` |
|
|
46
|
+
| `list` | `CurdListConfig` | 列表页配置(必填) |
|
|
47
|
+
|
|
48
|
+
### ActionPage
|
|
49
|
+
|
|
50
|
+
| 字段 | 类型 | 说明 |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `mode` | `'modal' \| 'route'` | 弹窗或独立路由 |
|
|
53
|
+
| `columns` | `Field[]` | 该页面渲染的字段,**必填非空**——前端要显示的字段必须全部显式列出 |
|
|
54
|
+
|
|
55
|
+
### CurdListConfig
|
|
56
|
+
|
|
57
|
+
| 字段 | 类型 | 说明 |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `columns` | `Field[]` | 列表列,**必填非空**——前端要显示的字段必须全部显式列出;可含跨表字段(见下) |
|
|
60
|
+
| `keyword?` | `{ columns: Field[] }` | 模糊搜索,columns 必须是**本表字段实例** |
|
|
61
|
+
| `orderBy` | `{ column: Field; direction: 'asc' \| 'desc' }` | 默认排序,**必填**,column 与 direction 都必填;column 必须是**本表字段实例** |
|
|
62
|
+
| `searchFields?` | `{ field: Field; op?: Operator }[]` | 搜索条件字段,op 默认 `'eq'`,可选 `eq/gt/gte/lt/lte/like/ne` |
|
|
63
|
+
| `columnTitles?` | `Record<string, string>` | 列标题覆盖:`Field.name` → 中文文案 |
|
|
64
|
+
|
|
65
|
+
## 跨表字段
|
|
66
|
+
|
|
67
|
+
`columns` / `searchFields` 里的 `Field` 实例可指向**本表或其他表**的列——列表列与搜索条件因此可以显示关联表字段(如订单列表显示商户名称):
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
list: {
|
|
71
|
+
columns: [order.columns.id, order.columns.order_no, merchant.columns.name], // 跨表:字段指向 merchant.name
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## 默认与校验
|
|
76
|
+
|
|
77
|
+
- `list.columns` / `actionPages.*.columns` **必填非空**(不允许省略、不允许空数组)——前端显示什么必须显式定义
|
|
78
|
+
- `list.orderBy` **必填**,`column` 与 `direction` 都必填(规格:默认主键 desc 由定义方显式写出)
|
|
79
|
+
- 运行时校验(`defineCurd`,仿 `defineTable` 强校验风格):
|
|
80
|
+
- `app.type` 必须为 `'admin'`,否则抛错
|
|
81
|
+
- 所有 `columns` 非空,否则抛错
|
|
82
|
+
- `list.keyword.columns` / `list.orderBy.column` 必须属于 `table`,否则抛错
|
|
83
|
+
- `list.columns` / `list.searchFields` 允许跨表,**不校验归属**
|
|
84
|
+
- 生成时校验(curd 生成器,`DtoSchemaGen.add` / `update`):
|
|
85
|
+
- 表配置 `autoIncrement` 或 `generator`(主键由服务端生成)时,`actionPages.add.columns` **不允许包含主键字段**,否则抛错——AddRequest 不携带服务端生成的主键
|
|
86
|
+
- `actionPages.update.columns` **必须包含主键字段**,否则抛错——UpdateRequest 靠主键定位记录
|
|
87
|
+
|
|
88
|
+
## DTO 推导(生成器约定)
|
|
89
|
+
|
|
90
|
+
DTO 由 curd 生成器从 `CurdSchema` 按标准命名推导,页面语义不持有 DTO 实例:
|
|
91
|
+
|
|
92
|
+
| DTO | 命名 | 字段来源 |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| Row | `{Pascal}Row` | `list.columns` |
|
|
95
|
+
| AddRequest | `{Pascal}AddRequest` | `actionPages.add.columns` |
|
|
96
|
+
| UpdateRequest | `{Pascal}UpdateRequest` | `actionPages.update.columns` |
|
|
97
|
+
| DetailRequest | `{Pascal}DetailRequest` | 主键 |
|
|
98
|
+
| DetailResponse | `{Pascal}DetailResponse` | `actionPages.detail.columns` |
|
|
99
|
+
|
|
100
|
+
## 与旧 PageConfig 的差异
|
|
101
|
+
|
|
102
|
+
| PageConfig(旧方案,已废弃) | CurdSchema |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `module: string` | 由 `app` 推导(后端模块 == app 1:1) |
|
|
105
|
+
| `schema: 'bd'` 字符串 | `table: TableSchema` 实例(类型安全) |
|
|
106
|
+
| `operations: { label, action }` | `actions: ActionSchema[]` |
|
|
107
|
+
| `detail.mode` 单例 | `actionPages.detail.mode` |
|
|
108
|
+
| `forms.add / forms.update` | `actionPages.add / actionPages.update` |
|
|
109
|
+
| `keyword` / `orderBy` / `columnTitles` | `list.keyword` / `list.orderBy` / `list.columnTitles`(列改字段实例引用) |
|
|
110
|
+
| `naming` | 去掉(DTO 命名是生成器约定,非页面语义) |
|
|
111
111
|
| DTO 引用(`request` / `fields` / `DtoFields`) | 去掉(DTO 由生成器推导,页面只依赖 table) |
|
package/docs/dto.md
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
# 定义 DTO(四种方向)
|
|
2
|
-
|
|
3
|
-
DTO 描述接口出入参。方向决定可选性规则,由各方向工厂设置;**规则统一只在 `field.optional === undefined` 时生效**(作者已显式设置的跳过):
|
|
4
|
-
|
|
5
|
-
| 构建器 | 方向 | 可选性规则 |
|
|
6
|
-
|---|---|---|
|
|
7
|
-
| `buildInput` | input | 按 DB 列规则:主键 → 必填;可空(未写 `optional` 或 `optional: true`)/ 有默认 → 可选;`optional: false` 无默认 → 必填 |
|
|
8
|
-
| `buildOutput` | output | 不设置(保持字段构建器/作者设置) |
|
|
9
|
-
| `buildQuery` | query | 全部可选 |
|
|
10
|
-
| `buildPk` | pk | 主键字段必填,其他字段可选 |
|
|
11
|
-
|
|
12
|
-
## 从表提取字段
|
|
13
|
-
|
|
14
|
-
```ts
|
|
15
|
-
import { buildInput, buildQuery, dtoField, from } from '@pylonts/dsl';
|
|
16
|
-
|
|
17
|
-
// 输入:新增订单
|
|
18
|
-
buildInput('OrderAddRequest', { ...from(order, [order.columns.mer_id, order.columns.amount]) });
|
|
19
|
-
|
|
20
|
-
// 输出:订单行
|
|
21
|
-
buildOutput('OrderRow', from(order, [order.columns.id, order.columns.order_no]));
|
|
22
|
-
|
|
23
|
-
// 查询:分页 + 过滤(query 字段恒为可选,.op() 声明比较操作符)
|
|
24
|
-
buildQuery('OrderPageQuery', {
|
|
25
|
-
keyword: dtoField(stringField({ maxLength: 32 })).setOperator('like'),
|
|
26
|
-
...from(order, [order.columns.mer_id]),
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
// 主键:按 id 取详情
|
|
30
|
-
buildPk('OrderDetailRequest', from(order, [order.columns.id]));
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
`from(table, fields)` 提取表字段包装为 DTO 字段,字段实例与表共享,`name/schema` 保持指向表。**DTO 字段名转 camelCase**(`mer_id` → `merId`),与 DB 列名(snake_case)分离。`from()` 本身不做任何可选性推断——推断在各方向工厂。
|
|
34
|
-
|
|
35
|
-
## 独立字段
|
|
36
|
-
|
|
37
|
-
不来自表的内联字段直接用 `dtoField(...)` 包装任意字段构建器,可加 `pattern`、`optional`、`operator`、`default`。
|
|
38
|
-
|
|
39
|
-
```ts
|
|
40
|
-
dtoField(stringField({ maxLength: 32 })).setOperator('like')
|
|
41
|
-
dtoField(intField()).setDefault(0) // TypeBox default 注解
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## 默认值
|
|
45
|
-
|
|
46
|
-
- `setDefault(v)` 设 DTO 层默认值,渲染为 TypeBox `default:` 注解(`Type.String({ default: 'PENDING' })`、`Type.Enum(OrderStatus, { default: OrderStatus.PENDING })`)。
|
|
47
|
-
- 枚举默认值必须是该枚举的成员值(value),渲染时解析为成员引用;非成员值直接报错。
|
|
48
|
-
- 未显式设置时,fallback 到字段构建器的 `default`(DB 默认值,string),`from()` 提取的字段自动带出。
|
|
49
|
-
|
|
50
|
-
## 继承基础 schema
|
|
51
|
-
|
|
52
|
-
```ts
|
|
53
|
-
buildQuery('OrderPageQuery', { ... })
|
|
54
|
-
.include({ from: '@pylonts/core', name: 'PageRequest' }); // 渲染 Type.Intersect([PageRequest, ...])
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## 关键语义
|
|
58
|
-
|
|
59
|
-
- **字段两层名**:`DtoField.name` 是接口字段名(DTO map key 反写);`field.name` 是数据库列名(表反写)。
|
|
60
|
-
- **可选性优先级**:DTO 层 `optional` 优先于字段层;query 方向所有字段强制可选。
|
|
61
|
-
- **HTTP 传 string**:bigint / decimal / date / time 在接口层渲染为 `Type.String()`,保证精度与序列化语义。
|
|
62
|
-
|
|
63
|
-
## 文件组织
|
|
64
|
-
|
|
65
|
-
- `dto_schema/{module}/*.dto.ts`:DTO 源文件,一个文件可导出多个 DtoMessage;`module` = `project.config.ts` 的 `apps.name`,另加 `common/` 放跨 app 共享 DTO。
|
|
66
|
-
- `pylonts gen dto` 按 module 逐个扫描:`dto_schema/{module}/*.dto.ts` → 生成 `dto/{module}/{Name}.dto.ts`。
|
|
1
|
+
# 定义 DTO(四种方向)
|
|
2
|
+
|
|
3
|
+
DTO 描述接口出入参。方向决定可选性规则,由各方向工厂设置;**规则统一只在 `field.optional === undefined` 时生效**(作者已显式设置的跳过):
|
|
4
|
+
|
|
5
|
+
| 构建器 | 方向 | 可选性规则 |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `buildInput` | input | 按 DB 列规则:主键 → 必填;可空(未写 `optional` 或 `optional: true`)/ 有默认 → 可选;`optional: false` 无默认 → 必填 |
|
|
8
|
+
| `buildOutput` | output | 不设置(保持字段构建器/作者设置) |
|
|
9
|
+
| `buildQuery` | query | 全部可选 |
|
|
10
|
+
| `buildPk` | pk | 主键字段必填,其他字段可选 |
|
|
11
|
+
|
|
12
|
+
## 从表提取字段
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { buildInput, buildQuery, dtoField, from } from '@pylonts/dsl';
|
|
16
|
+
|
|
17
|
+
// 输入:新增订单
|
|
18
|
+
buildInput('OrderAddRequest', { ...from(order, [order.columns.mer_id, order.columns.amount]) });
|
|
19
|
+
|
|
20
|
+
// 输出:订单行
|
|
21
|
+
buildOutput('OrderRow', from(order, [order.columns.id, order.columns.order_no]));
|
|
22
|
+
|
|
23
|
+
// 查询:分页 + 过滤(query 字段恒为可选,.op() 声明比较操作符)
|
|
24
|
+
buildQuery('OrderPageQuery', {
|
|
25
|
+
keyword: dtoField(stringField({ maxLength: 32 })).setOperator('like'),
|
|
26
|
+
...from(order, [order.columns.mer_id]),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// 主键:按 id 取详情
|
|
30
|
+
buildPk('OrderDetailRequest', from(order, [order.columns.id]));
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`from(table, fields)` 提取表字段包装为 DTO 字段,字段实例与表共享,`name/schema` 保持指向表。**DTO 字段名转 camelCase**(`mer_id` → `merId`),与 DB 列名(snake_case)分离。`from()` 本身不做任何可选性推断——推断在各方向工厂。
|
|
34
|
+
|
|
35
|
+
## 独立字段
|
|
36
|
+
|
|
37
|
+
不来自表的内联字段直接用 `dtoField(...)` 包装任意字段构建器,可加 `pattern`、`optional`、`operator`、`default`。
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
dtoField(stringField({ maxLength: 32 })).setOperator('like')
|
|
41
|
+
dtoField(intField()).setDefault(0) // TypeBox default 注解
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 默认值
|
|
45
|
+
|
|
46
|
+
- `setDefault(v)` 设 DTO 层默认值,渲染为 TypeBox `default:` 注解(`Type.String({ default: 'PENDING' })`、`Type.Enum(OrderStatus, { default: OrderStatus.PENDING })`)。
|
|
47
|
+
- 枚举默认值必须是该枚举的成员值(value),渲染时解析为成员引用;非成员值直接报错。
|
|
48
|
+
- 未显式设置时,fallback 到字段构建器的 `default`(DB 默认值,string),`from()` 提取的字段自动带出。
|
|
49
|
+
|
|
50
|
+
## 继承基础 schema
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
buildQuery('OrderPageQuery', { ... })
|
|
54
|
+
.include({ from: '@pylonts/core', name: 'PageRequest' }); // 渲染 Type.Intersect([PageRequest, ...])
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 关键语义
|
|
58
|
+
|
|
59
|
+
- **字段两层名**:`DtoField.name` 是接口字段名(DTO map key 反写);`field.name` 是数据库列名(表反写)。
|
|
60
|
+
- **可选性优先级**:DTO 层 `optional` 优先于字段层;query 方向所有字段强制可选。
|
|
61
|
+
- **HTTP 传 string**:bigint / decimal / date / time 在接口层渲染为 `Type.String()`,保证精度与序列化语义。
|
|
62
|
+
|
|
63
|
+
## 文件组织
|
|
64
|
+
|
|
65
|
+
- `dto_schema/{module}/*.dto.ts`:DTO 源文件,一个文件可导出多个 DtoMessage;`module` = `project.config.ts` 的 `apps.name`,另加 `common/` 放跨 app 共享 DTO。
|
|
66
|
+
- `pylonts gen dto` 按 module 逐个扫描:`dto_schema/{module}/*.dto.ts` → 生成 `dto/{module}/{Name}.dto.ts`。
|
|
67
67
|
- 枚举字段引用 `enums/` 下生成的枚举源文件,DTO 文件通过 `../../enums/{JsName}.enum` 导入。
|