openxiangda 1.0.34 → 1.0.35
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/README.md +16 -0
- package/lib/cli.js +409 -0
- package/lib/workspace-init.js +1 -0
- package/openxiangda-skills/SKILL.md +4 -2
- package/openxiangda-skills/references/best-practices.md +11 -6
- package/openxiangda-skills/references/connector-resources.md +3 -0
- package/openxiangda-skills/references/data-views.md +217 -0
- package/openxiangda-skills/references/forms/component-registry.md +4 -3
- package/openxiangda-skills/references/forms/form-schema.md +31 -2
- package/openxiangda-skills/references/pages/page-sdk.md +43 -0
- package/openxiangda-skills/references/workspace-state.md +9 -0
- package/openxiangda-skills/skills/openxiangda-form/SKILL.md +3 -3
- package/openxiangda-skills/skills/openxiangda-permission-settings/SKILL.md +1 -1
- package/package.json +1 -1
- package/packages/sdk/dist/components/index.cjs +944 -765
- package/packages/sdk/dist/components/index.cjs.map +1 -1
- package/packages/sdk/dist/components/index.d.mts +18 -2
- package/packages/sdk/dist/components/index.d.ts +18 -2
- package/packages/sdk/dist/components/index.mjs +938 -761
- package/packages/sdk/dist/components/index.mjs.map +1 -1
- package/packages/sdk/dist/runtime/index.cjs +47 -0
- package/packages/sdk/dist/runtime/index.cjs.map +1 -1
- package/packages/sdk/dist/runtime/index.d.mts +18 -1
- package/packages/sdk/dist/runtime/index.d.ts +18 -1
- package/packages/sdk/dist/runtime/index.mjs +47 -0
- package/packages/sdk/dist/runtime/index.mjs.map +1 -1
- package/packages/sdk/src/build-source/scripts/utils/form-api.mjs +1 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/decision-guide.md +4 -3
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/role-governance/permissions.test.ts +1 -1
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/app-role/schema.ts +36 -18
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/service-ticket/schema.ts +36 -18
- package/templates/sy-lowcode-app-workspace/src/shared/form-schema.ts +0 -1
|
@@ -10,6 +10,7 @@ Common folders:
|
|
|
10
10
|
- `menus`
|
|
11
11
|
- `workflows`
|
|
12
12
|
- `automations`
|
|
13
|
+
- `data-views`
|
|
13
14
|
- `permissions/page-groups`
|
|
14
15
|
- `permissions/form-groups`
|
|
15
16
|
- `settings/forms`
|
|
@@ -27,6 +28,8 @@ Connector manifests use stable `code` values. The platform maps connector `code`
|
|
|
27
28
|
|
|
28
29
|
Notification manifests live under `src/resources/notifications/` and contain `templates` plus `typeConfigs`. See `notifications.md` before generating reminders or message templates.
|
|
29
30
|
|
|
31
|
+
Data view manifests live under `src/resources/data-views/` and define read-only materialized views for repeated multi-form joins. Use them for joined list/report/lookup sources where refresh lag is acceptable. Do not use them for single-form CRUD, simple linkedForm selects, real-time writes, or write-back. See `data-views.md` before generating one.
|
|
32
|
+
|
|
30
33
|
```json
|
|
31
34
|
{
|
|
32
35
|
"code": "crm",
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Data View Resources
|
|
2
|
+
|
|
3
|
+
Data views are OpenXiangda-managed read-only PostgreSQL materialized views. They turn repeated multi-form joins into a published resource under `src/resources/data-views/`.
|
|
4
|
+
|
|
5
|
+
Use a data view when the app needs read-only joined data from multiple forms, such as:
|
|
6
|
+
|
|
7
|
+
- Ticket list with customer name, owner, SLA, and status fields.
|
|
8
|
+
- Order list with product, customer, and payment fields.
|
|
9
|
+
- Project dashboard combining project, member, task, and risk forms.
|
|
10
|
+
- Reusable lookup or report data consumed by several pages or automations.
|
|
11
|
+
- Large list pages where repeated client-side cross-form joins would be slow or inconsistent.
|
|
12
|
+
|
|
13
|
+
Do not use a data view for:
|
|
14
|
+
|
|
15
|
+
- Single-form CRUD. Use `sdk.form.advancedSearch`, form pages, or `DataManagementList`.
|
|
16
|
+
- Simple one-form dropdown options. Use `SelectField` with `optionSource.type: "linkedForm"`.
|
|
17
|
+
- Writes or write-back. Data views are read-only.
|
|
18
|
+
- Strong real-time views after every form update. Data views update after manual or scheduled refresh.
|
|
19
|
+
- Raw SQL, incremental refresh, source-table trigger refresh, or group-by aggregation. v1 does not support them.
|
|
20
|
+
|
|
21
|
+
## Authoring
|
|
22
|
+
|
|
23
|
+
Place manifests in `src/resources/data-views/*.json`. Use logical `formCode` values in source files. The CLI resolves them to the current profile's `formUuid` during `resource publish`.
|
|
24
|
+
|
|
25
|
+
Minimal example:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"code": "ticket_with_customer",
|
|
30
|
+
"name": "Ticket With Customer",
|
|
31
|
+
"base": { "formCode": "service_ticket", "alias": "ticket" },
|
|
32
|
+
"joins": [
|
|
33
|
+
{
|
|
34
|
+
"type": "left",
|
|
35
|
+
"formCode": "customer",
|
|
36
|
+
"alias": "customer",
|
|
37
|
+
"on": [
|
|
38
|
+
{
|
|
39
|
+
"left": "ticket.customer.value",
|
|
40
|
+
"op": "=",
|
|
41
|
+
"right": "customer.form_instance_id"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"select": [
|
|
47
|
+
{ "field": "ticket.form_instance_id", "as": "ticketId" },
|
|
48
|
+
{ "field": "ticket.title", "as": "ticketTitle" },
|
|
49
|
+
{ "field": "customer.name", "as": "customerName" }
|
|
50
|
+
],
|
|
51
|
+
"indexes": [{ "fields": ["ticketId"], "unique": true }],
|
|
52
|
+
"refresh": { "mode": "scheduled", "cron": "0 */10 * * * *" },
|
|
53
|
+
"permissionGroups": [
|
|
54
|
+
{
|
|
55
|
+
"code": "ticket_query",
|
|
56
|
+
"name": "Ticket Query",
|
|
57
|
+
"roles": ["manager"],
|
|
58
|
+
"operations": ["query"]
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Field reference rules:
|
|
65
|
+
|
|
66
|
+
- Use `alias.field`, for example `ticket.title`.
|
|
67
|
+
- Use system fields directly, such as `form_instance_id`, `created_at`, `updated_at`, `created_by`, and `tenant_id`.
|
|
68
|
+
- For option-like JSON fields that store `{ label, value }`, use `.value` for joins and `.label` for display when needed.
|
|
69
|
+
- Every `select` item must have an explicit output alias in `as`.
|
|
70
|
+
- Runtime `fields`, `filters`, `order`, indexes, field permissions, and row permissions reference output aliases, not source field references.
|
|
71
|
+
|
|
72
|
+
Join rules:
|
|
73
|
+
|
|
74
|
+
- v1 supports `left` and `inner`.
|
|
75
|
+
- Join operators are `=`, `!=`, `<>`, `>`, `>=`, `<`, `<=`.
|
|
76
|
+
- The platform automatically constrains sources to the same tenant.
|
|
77
|
+
|
|
78
|
+
Filters:
|
|
79
|
+
|
|
80
|
+
- Definition `where` filters source rows before materialization and uses source references such as `ticket.status`.
|
|
81
|
+
- Runtime query filters use output aliases such as `ticketTitle`.
|
|
82
|
+
- Supported operators include `=`, `!=`, `<>`, `>`, `>=`, `<`, `<=`, `contains`, `notContains`, `in`, `isEmpty`, and `isNotEmpty`, with aliases such as `eq`, `neq`, `gte`, `lte`, `like`, `is_null`, and `is_not_null`.
|
|
83
|
+
|
|
84
|
+
Refresh:
|
|
85
|
+
|
|
86
|
+
- `manual` means refresh only when an administrator runs refresh or the resource is recreated.
|
|
87
|
+
- `scheduled` uses cron, for example `0 */10 * * * *`.
|
|
88
|
+
- Query results may be stale. Show or inspect `lastRefreshedAt` when freshness matters.
|
|
89
|
+
|
|
90
|
+
Indexes:
|
|
91
|
+
|
|
92
|
+
- Index output aliases that pages filter or sort by.
|
|
93
|
+
- Use `unique: true` only when the output field combination is truly unique.
|
|
94
|
+
|
|
95
|
+
## Permissions
|
|
96
|
+
|
|
97
|
+
Management APIs require `app:data-view:manage`.
|
|
98
|
+
|
|
99
|
+
Runtime page queries use data view permission groups unless the user has app manage or data view manage runtime bypass permission.
|
|
100
|
+
|
|
101
|
+
Permission group behavior:
|
|
102
|
+
|
|
103
|
+
- `operations` can include `query` and `refresh`; omitted operations default to `query`.
|
|
104
|
+
- Empty or omitted `roles` match all logged-in users.
|
|
105
|
+
- Missing or empty `fieldPermissions` means all output fields are visible.
|
|
106
|
+
- When multiple permission groups match, field permissions are most permissive: a field is visible if any matched group allows it.
|
|
107
|
+
- `dataPermission` is a row condition over output aliases.
|
|
108
|
+
- Multiple matched row conditions are ORed.
|
|
109
|
+
- If any matched group has no row condition, rows are unrestricted.
|
|
110
|
+
|
|
111
|
+
## Commands
|
|
112
|
+
|
|
113
|
+
Use the standard resource workflow:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
openxiangda resource validate --profile dev
|
|
117
|
+
openxiangda resource plan --profile dev
|
|
118
|
+
openxiangda resource publish --profile dev
|
|
119
|
+
openxiangda resource pull --profile dev
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Diagnostic commands:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
openxiangda data-view list --profile dev
|
|
126
|
+
openxiangda data-view status ticket_with_customer --profile dev
|
|
127
|
+
openxiangda data-view refresh ticket_with_customer --profile dev
|
|
128
|
+
openxiangda data-view query ticket_with_customer --profile dev --fields ticketId,customerName
|
|
129
|
+
openxiangda data-view query ticket_with_customer --profile dev --query-json query.json
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Runtime SDK
|
|
133
|
+
|
|
134
|
+
Direct query:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const response = await sdk.dataView.query("ticket_with_customer", {
|
|
138
|
+
fields: ["ticketId", "ticketTitle", "customerName"],
|
|
139
|
+
filters: [
|
|
140
|
+
{ field: "customerName", operator: "contains", value: keyword },
|
|
141
|
+
],
|
|
142
|
+
order: [{ field: "ticketTitle", isAsc: "y" }],
|
|
143
|
+
currentPage: 1,
|
|
144
|
+
pageSize: 20,
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The response data is:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
{
|
|
152
|
+
data: unknown[]
|
|
153
|
+
totalCount: number
|
|
154
|
+
currentPage: number
|
|
155
|
+
pageSize: number
|
|
156
|
+
lastRefreshedAt?: string | null
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Page data source descriptor:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
export default {
|
|
164
|
+
dataSources: [
|
|
165
|
+
{
|
|
166
|
+
key: "tickets",
|
|
167
|
+
type: "dataView.query",
|
|
168
|
+
code: "ticket_with_customer",
|
|
169
|
+
fields: ["ticketId", "ticketTitle", "customerName"],
|
|
170
|
+
defaultFilter: [
|
|
171
|
+
{ field: "ticketTitle", operator: "isNotEmpty" }
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const response = await sdk.dataSource.run("tickets", {
|
|
178
|
+
filters: [
|
|
179
|
+
{ field: "customerName", operator: "contains", value: keyword }
|
|
180
|
+
],
|
|
181
|
+
pageSize: 20,
|
|
182
|
+
})
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Common Patterns
|
|
186
|
+
|
|
187
|
+
List page:
|
|
188
|
+
|
|
189
|
+
- Define one data view for the list row shape.
|
|
190
|
+
- Select only fields shown in the table, filters, and row actions.
|
|
191
|
+
- Add indexes for common filters and sorts.
|
|
192
|
+
- Query with `sdk.dataView.query`.
|
|
193
|
+
|
|
194
|
+
Dashboard:
|
|
195
|
+
|
|
196
|
+
- Use one or more data views as read-optimized sources.
|
|
197
|
+
- Keep refresh scheduled.
|
|
198
|
+
- Show `lastRefreshedAt` when business users care about freshness.
|
|
199
|
+
|
|
200
|
+
Reusable lookup:
|
|
201
|
+
|
|
202
|
+
- Use a data view if the display label depends on multiple forms.
|
|
203
|
+
- Keep the output small, for example `id`, `label`, `status`, and `owner`.
|
|
204
|
+
- For simple one-form lookup, keep using linkedForm instead.
|
|
205
|
+
|
|
206
|
+
Automation diagnosis:
|
|
207
|
+
|
|
208
|
+
- Query the data view with `openxiangda data-view query --query-json`.
|
|
209
|
+
- Refresh manually after bulk imports or test data resets.
|
|
210
|
+
|
|
211
|
+
## Troubleshooting
|
|
212
|
+
|
|
213
|
+
- `formCode 未绑定`: publish or bind the source forms first so `.openxiangda/state.json` has their `formUuid`.
|
|
214
|
+
- Empty joined fields: check whether the source field stores a scalar or `{ label, value }`; linked/select fields usually need `.value`.
|
|
215
|
+
- Query field rejected: runtime filters and `fields` must use output aliases.
|
|
216
|
+
- User sees no data: inspect role codes, permission groups, `fieldPermissions`, and `dataPermission`.
|
|
217
|
+
- Data is stale: check `lastRefreshedAt`, scheduled cron, and refresh status.
|
|
@@ -21,7 +21,6 @@ Common field components:
|
|
|
21
21
|
| `ImageField` | Image upload |
|
|
22
22
|
| `AddressField` | Address |
|
|
23
23
|
| `CascadeSelectField` | Cascading select |
|
|
24
|
-
| `AssociationFormField` | Select records from another form when a linked-form picker is required |
|
|
25
24
|
| `LocationField` | Location |
|
|
26
25
|
| `EditorField` | Rich text |
|
|
27
26
|
| `JSONField` | Structured JSON |
|
|
@@ -35,8 +34,10 @@ Rules:
|
|
|
35
34
|
- Avoid generated random field IDs after initial creation.
|
|
36
35
|
- Use field-level `rules` for validation. Required fields should set both `required: true` and `rules: [{ required: true, message: "..." }]` when a custom message is needed.
|
|
37
36
|
- For option components (`SelectField`, `MultiSelectField`, `RadioField`, `CheckboxField`, `CascadeSelectField`), always provide an `options` array. Use `options: [{ value: "stable_code", label: "显示名" }]`; if options will be loaded elsewhere later, still emit `options: []` so the runtime does not crash on `options.map(...)`.
|
|
38
|
-
- Do not rely on custom `relation` metadata alone for normal form pages. A `SelectField` with `relation` but no `options` can white-screen with `Cannot read properties of undefined (reading 'map')`. Use static `options`, `
|
|
39
|
-
- Do not model business enums or data-source references as free text IDs. Use `SelectField` / `RadioField` with `options` for enums, and
|
|
37
|
+
- Do not rely on custom `relation` metadata alone for normal form pages. A `SelectField` with `relation` but no `options` can white-screen with `Cannot read properties of undefined (reading 'map')`. Use static `options`, or use `SelectField` with `optionSource.type: "linkedForm"` so the runtime queries source form records through the SDK.
|
|
38
|
+
- Do not model business enums or data-source references as free text IDs. Use `SelectField` / `RadioField` with `options` for enums. For records maintained by another form, use `SelectField` with `options: []` and `optionSource.linkedForm`; set `remoteSearch: true`, `searchFieldId`, and a modest `pageSize` when the source data can be large.
|
|
39
|
+
- If a selected option value must also be stored as a hidden scalar key for permission conditions, add `valueSync: [{ targetFieldId: "scopeKey", valuePath: "value" }]` to the visible `SelectField`.
|
|
40
|
+
- `AssociationFormField` is kept only for backward compatibility. Do not use it for new form pages.
|
|
40
41
|
- For `NumberField`, prefer explicit `min`, `max`, `precision`, and `unit` when the business meaning is constrained.
|
|
41
42
|
- For `DateField`, use `mode: "date"` or `mode: "datetime"` and a stable `format` such as `YYYY-MM-DD` or `YYYY-MM-DD HH:mm`.
|
|
42
43
|
- For `CascadeDateField`, store a date range and document whether the value is inclusive.
|
|
@@ -59,8 +59,8 @@ export default defineFormSchema({
|
|
|
59
59
|
- Each visible field should include a concise user-facing `placeholder`. Hidden/internal fields do not need placeholders.
|
|
60
60
|
- Use `tips` only for special constraints, unusual formats, compliance notes, or non-obvious business rules. Do not add tips to every field.
|
|
61
61
|
- Use `SelectField` / `RadioField` for enum values and always provide `options`.
|
|
62
|
-
- Use `
|
|
63
|
-
- Keep only user-needed fields visible. If a scalar key is needed for permissions, synchronization, computed state, or internal logic, derive it from a visible select/
|
|
62
|
+
- Use `SelectField` with `optionSource.type: "linkedForm"` for values maintained in another form or data source, such as class, college, customer, project, category, or asset. The runtime queries source form data through the SDK and builds `{ label, value }` options. For large source forms, set `remoteSearch: true`, an explicit `searchFieldId`, and a modest `pageSize` so typing in the dropdown triggers remote search. Do not use `AssociationFormField` for new form pages, and do not ask users to type raw IDs in `TextField`.
|
|
63
|
+
- Keep only user-needed fields visible. If a scalar key is needed for permissions, synchronization, computed state, or internal logic, derive it from a visible select/person/department field and keep it in the schema with `behavior: "HIDDEN"`. For `SelectField`, use `valueSync` when the selected option value should be copied into a hidden scalar field.
|
|
64
64
|
- Do not create duplicate platform system fields such as creator, updater, creator department, updater department, created time, or updated time unless the user explicitly needs a distinct business field. The platform already creates system metadata for every form.
|
|
65
65
|
- Labels, placeholders, tips, section titles, descriptions, and empty states must be end-user-facing. Do not write developer-facing implementation explanations into visible form copy.
|
|
66
66
|
- Use platform-supported field components from `component-registry.md`.
|
|
@@ -68,6 +68,35 @@ export default defineFormSchema({
|
|
|
68
68
|
- Put validation rules on fields as `field.rules`. Do not put validation objects in top-level `schema.rules`.
|
|
69
69
|
- For option components (`SelectField`, `MultiSelectField`, `RadioField`, `CheckboxField`, `CascadeSelectField`), always include `options`. If a linked lookup is not ready, use `options: []` instead of omitting it.
|
|
70
70
|
|
|
71
|
+
Linked form dropdown example:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
{
|
|
75
|
+
fieldId: "college",
|
|
76
|
+
componentName: "SelectField",
|
|
77
|
+
label: "所属学院",
|
|
78
|
+
placeholder: "请选择所属学院",
|
|
79
|
+
showSearch: true,
|
|
80
|
+
allowClear: true,
|
|
81
|
+
options: [],
|
|
82
|
+
valueSync: [{ targetFieldId: "collegeScopeKey", valuePath: "value" }],
|
|
83
|
+
optionSource: {
|
|
84
|
+
type: "linkedForm",
|
|
85
|
+
linkedForm: {
|
|
86
|
+
formUuid: "FORM_COLLEGE_PROFILE",
|
|
87
|
+
fieldId: "collegeName",
|
|
88
|
+
labelFieldId: "collegeName",
|
|
89
|
+
valueFieldId: "collegeCode",
|
|
90
|
+
searchFieldId: "collegeName",
|
|
91
|
+
sortField: "collegeName",
|
|
92
|
+
pageSize: 20,
|
|
93
|
+
remoteSearch: true,
|
|
94
|
+
deduplicate: true,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
71
100
|
## Effects vs Validation
|
|
72
101
|
|
|
73
102
|
Top-level `schema.rules` is reserved for `FormEffect[]`, not validation. A `FormEffect` must have both `when` and `then`:
|
|
@@ -6,6 +6,8 @@ Guidelines:
|
|
|
6
6
|
|
|
7
7
|
- Do not hardcode `/openxiangda-api` calls inside end-user page components unless the page is explicitly an admin tool.
|
|
8
8
|
- Prefer SDK modules for form data, user context, permissions, and platform navigation.
|
|
9
|
+
- Use `sdk.dataView.query` for published read-only multi-form data views. Data view runtime queries can filter, sort, paginate, and select only output aliases.
|
|
10
|
+
- Use `sdk.dataSource.run()` with a page data source descriptor when the page config should own the data view code, default fields, or default filters.
|
|
9
11
|
- Use `sdk.connector.invoke`, `sdk.connector.call("connector.api")`, or `sdk.connector.download` for external services. The SDK calls the platform runtime connector endpoint; it must not call third-party domains directly.
|
|
10
12
|
- Use `sdk.notification.sendByType` and `batchSendByType` for reusable business messages. Custom notification types must be declared in `src/resources/notifications/` and published with `openxiangda resource publish`.
|
|
11
13
|
- For the current user's department hierarchy, use `sdk.department.getCurrentUserParentDepartments()`; do not hardcode `GET /department/:id/parentDepartments` in page code.
|
|
@@ -13,3 +15,44 @@ Guidelines:
|
|
|
13
15
|
- Treat user context and tenant context as runtime-provided values.
|
|
14
16
|
|
|
15
17
|
When the SDK lacks a capability, document the fallback and keep it isolated.
|
|
18
|
+
|
|
19
|
+
Data view query:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
const response = await sdk.dataView.query("ticket_with_customer", {
|
|
23
|
+
fields: ["ticketId", "ticketTitle", "customerName"],
|
|
24
|
+
filters: [
|
|
25
|
+
{ field: "customerName", operator: "contains", value: keyword },
|
|
26
|
+
],
|
|
27
|
+
order: [{ field: "ticketTitle", isAsc: "y" }],
|
|
28
|
+
currentPage: 1,
|
|
29
|
+
pageSize: 20,
|
|
30
|
+
})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Data view data source descriptor:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
export default {
|
|
37
|
+
dataSources: [
|
|
38
|
+
{
|
|
39
|
+
key: "tickets",
|
|
40
|
+
type: "dataView.query",
|
|
41
|
+
code: "ticket_with_customer",
|
|
42
|
+
fields: ["ticketId", "ticketTitle", "customerName"],
|
|
43
|
+
defaultFilter: [
|
|
44
|
+
{ field: "ticketTitle", operator: "isNotEmpty" }
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const response = await sdk.dataSource.run("tickets", {
|
|
51
|
+
filters: [
|
|
52
|
+
{ field: "customerName", operator: "contains", value: keyword }
|
|
53
|
+
],
|
|
54
|
+
pageSize: 20,
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Data view filters and fields use output aliases such as `customerName`, not source references such as `customer.name`. If the page only needs one form, prefer `sdk.form.advancedSearch`. If the page only needs a simple one-form dropdown, prefer linkedForm options.
|
|
@@ -31,6 +31,13 @@ Tokens never belong in the project. User tokens live in `~/.openxiangda/profiles
|
|
|
31
31
|
"menus": {},
|
|
32
32
|
"roles": {},
|
|
33
33
|
"connectors": {},
|
|
34
|
+
"dataViews": {
|
|
35
|
+
"ticket_with_customer": {
|
|
36
|
+
"dataViewId": "DV_XXX",
|
|
37
|
+
"materializedViewName": "mv_dv_abc123",
|
|
38
|
+
"status": "active"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
34
41
|
"notifications": {
|
|
35
42
|
"templates": {},
|
|
36
43
|
"typeConfigs": {}
|
|
@@ -52,6 +59,8 @@ Tokens never belong in the project. User tokens live in `~/.openxiangda/profiles
|
|
|
52
59
|
- Do not search platform apps or reuse similar names unless the user explicitly asks to reuse an existing app or provides an `appType`.
|
|
53
60
|
- Local resource keys are logical codes.
|
|
54
61
|
- Live IDs and lightweight runtime aliases are nested under the profile that produced them.
|
|
62
|
+
- `resources.dataViews` is keyed by data view `code` and stores only profile-local platform metadata such as `dataViewId`, `materializedViewName`, and last known `status`.
|
|
63
|
+
- Data view definitions, refresh config, permissions, and source `formCode` references belong in `src/resources/data-views/*.json`, not in state.
|
|
55
64
|
- Do not store business configuration or secrets in `.openxiangda/state.json`; store those in `src/resources/`.
|
|
56
65
|
- A prod publish must not read dev IDs.
|
|
57
66
|
- Before publishing to another platform, run `openxiangda workspace bind --profile <name> --app-type <APP_XXX>`.
|
|
@@ -77,12 +77,12 @@ Read these references only when writing or reviewing schema:
|
|
|
77
77
|
- Every visible field should have a concise, user-facing `placeholder` that tells the user what to enter or select.
|
|
78
78
|
- Use `tips` sparingly. Add tips only for special constraints, unusual formats, or non-obvious business rules; ordinary fields should not have tips.
|
|
79
79
|
- Use `SelectField` or `RadioField` for enumerable business values. Do not model enums as `TextField` values that users must type manually.
|
|
80
|
-
-
|
|
81
|
-
- Display only fields the user needs to interact with. Permission scope keys, computed fields, sync fields, and developer/internal fields should normally be derived from visible select/
|
|
80
|
+
- When a value is maintained by another form or data source, such as class, college, customer, project, category, or asset, use `SelectField` with `optionSource.type: "linkedForm"`. The runtime queries the source form through the SDK, converts records to `{ label, value }` options, and can use `remoteSearch: true` plus `searchFieldId` for large datasets. Do not use `AssociationFormField` for new form work, and do not make users maintain raw ID text fields for those values.
|
|
81
|
+
- Display only fields the user needs to interact with. Permission scope keys, computed fields, sync fields, and developer/internal fields should normally be derived from visible select/person/department fields and stay in the schema with `behavior: "HIDDEN"` instead of appearing on the form page. For select-derived scalar keys, use `valueSync`.
|
|
82
82
|
- Do not create separate fields for platform system metadata such as creator, updater, creator department, updater department, created time, or updated time unless the user explicitly asks for a separate business concept. The platform creates those system fields for every form.
|
|
83
83
|
- All labels, placeholders, tips, section titles, empty states, and helper text must be end-user-facing business copy. Do not write developer explanations or implementation notes into visible page copy.
|
|
84
84
|
- For business lifecycles, model status with normal form fields (`status`, owner/responsibility fields, action-log relation fields) unless the scenario has real approval tasks. Do not create workflow forms for ordinary status transitions.
|
|
85
|
-
- For permission isolation, collect user-facing ownership with select, personnel,
|
|
85
|
+
- For permission isolation, collect user-facing ownership with select, personnel, or department fields. If form permission groups require scalar matching, add hidden derived keys such as `collegeScopeKey`, `classScopeKey`, `ownerDeptScopeKey`, and `ownerUserScopeKey`; never expose those keys as editable text inputs.
|
|
86
86
|
- For multi-platform publishing, create or bind the form separately for each profile.
|
|
87
87
|
- Do not copy `formUuid` from dev to prod unless the target platform explicitly already uses that ID.
|
|
88
88
|
- Keep `schema.ts` and `page.tsx` as the source for fields/layout/rules and presentation; generated build output is not the source of truth.
|
|
@@ -34,7 +34,7 @@ Use role codes in permission group JSON. Bind existing role IDs only inside the
|
|
|
34
34
|
openxiangda permission role-bind sales --role-id <id> --profile dev
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
For dynamic multi-role apps, do not hardcode role behavior only in page code. Create a role maintenance form and sync it to platform roles with automation / JS_CODE. Visible scope fields should be maintainable controls: personnel/department fields for people and orgs, `SelectField` / `RadioField` for enums, and `
|
|
37
|
+
For dynamic multi-role apps, do not hardcode role behavior only in page code. Create a role maintenance form and sync it to platform roles with automation / JS_CODE. Visible scope fields should be maintainable controls: personnel/department fields for people and orgs, `SelectField` / `RadioField` for enums, and `SelectField` with `optionSource.type: "linkedForm"` for records maintained by other forms. When the source form can have many records, set `remoteSearch: true` and `searchFieldId` so typing in the dropdown triggers an SDK query instead of loading every record. If form permission groups need scalar matching, derive hidden keys such as `collegeScopeKey`, `classScopeKey`, `ownerDeptScopeKey`, `ownerUserScopeKey`, and `roleCode`; do not expose raw ID text fields to users. Use page permission groups for entry visibility and form permission groups with condition-based data permissions for real data isolation.
|
|
38
38
|
|
|
39
39
|
## Page Permission Groups
|
|
40
40
|
|