openxiangda 1.0.34 → 1.0.36
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 +18 -0
- package/lib/cli.js +410 -1
- package/lib/workspace-init.js +1 -0
- package/openxiangda-skills/SKILL.md +5 -3
- package/openxiangda-skills/references/best-practices.md +11 -6
- package/openxiangda-skills/references/component-guide.md +10 -11
- 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/style-system.md +14 -18
- package/openxiangda-skills/references/troubleshooting.md +13 -13
- package/openxiangda-skills/references/workspace-state.md +9 -0
- package/openxiangda-skills/skills/openxiangda-form/SKILL.md +3 -3
- package/openxiangda-skills/skills/openxiangda-page/SKILL.md +2 -2
- 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 +114 -30
- 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 +114 -30
- package/packages/sdk/dist/runtime/index.mjs.map +1 -1
- package/packages/sdk/dist/styles/antd-theme.cjs +11 -3
- package/packages/sdk/dist/styles/antd-theme.cjs.map +1 -1
- package/packages/sdk/dist/styles/antd-theme.d.mts +2 -1
- package/packages/sdk/dist/styles/antd-theme.d.ts +2 -1
- package/packages/sdk/dist/styles/antd-theme.mjs +11 -3
- package/packages/sdk/dist/styles/antd-theme.mjs.map +1 -1
- package/packages/sdk/dist/styles/tailwind-preset.cjs +0 -1
- package/packages/sdk/dist/styles/tailwind-preset.cjs.map +1 -1
- package/packages/sdk/dist/styles/tailwind-preset.d.mts +0 -1
- package/packages/sdk/dist/styles/tailwind-preset.d.ts +0 -1
- package/packages/sdk/dist/styles/tailwind-preset.mjs +0 -1
- package/packages/sdk/dist/styles/tailwind-preset.mjs.map +1 -1
- package/packages/sdk/dist/styles/tokens.css +1 -0
- package/packages/sdk/src/build-source/scripts/build-forms.mjs +135 -50
- package/packages/sdk/src/build-source/scripts/build-pages.mjs +37 -10
- package/packages/sdk/src/build-source/scripts/register.mjs +2 -0
- package/packages/sdk/src/build-source/scripts/utils/form-api.mjs +1 -0
- package/packages/sdk/src/build-source/scripts/utils/load-config.mjs +3 -2
- package/packages/sdk/src/build-source/scripts/utils/register-payload.test.ts +2 -1
- package/packages/sdk/src/build-source/scripts/utils/tailwind-config.mjs +9 -7
- package/packages/sdk/src/build-source/scripts/utils/tailwind-config.test.ts +6 -4
- package/packages/sdk/src/build-source/src/cli.mjs +17 -0
- package/templates/sy-lowcode-app-workspace/app-workspace.config.ts +3 -3
- 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/postcss.config.cjs +0 -15
- package/templates/sy-lowcode-app-workspace/src/main.tsx +1 -12
- package/templates/sy-lowcode-app-workspace/src/shared/form-schema.ts +0 -1
|
@@ -43,7 +43,7 @@ Use a normal form plus:
|
|
|
43
43
|
|
|
44
44
|
- a `status` field
|
|
45
45
|
- user-facing responsibility fields such as personnel, department, enum, or
|
|
46
|
-
|
|
46
|
+
SDK-backed select fields
|
|
47
47
|
- hidden permission scope keys, when form permission groups require scalar
|
|
48
48
|
matching
|
|
49
49
|
- an action log form
|
|
@@ -109,7 +109,11 @@ For apps with dynamic roles:
|
|
|
109
109
|
- personnel and departments use platform personnel/department fields
|
|
110
110
|
- enum scopes use `SelectField` / `RadioField` with `options`
|
|
111
111
|
- class, college, project, customer, and similar maintained records use
|
|
112
|
-
`
|
|
112
|
+
`SelectField` with `optionSource.type: "linkedForm"` so the runtime queries
|
|
113
|
+
source form records through the SDK and builds dropdown options
|
|
114
|
+
- when the source form can have many records, set `remoteSearch: true`,
|
|
115
|
+
`searchFieldId`, and a reasonable `pageSize` so typing in the dropdown
|
|
116
|
+
triggers remote search
|
|
113
117
|
4. Add hidden derived scope keys only when platform form permission conditions
|
|
114
118
|
require scalar matching, for example:
|
|
115
119
|
- `collegeScopeKey`
|
|
@@ -129,12 +133,13 @@ groups or backend-side JS_CODE checks.
|
|
|
129
133
|
- Every visible form field should have a short, user-facing placeholder.
|
|
130
134
|
- Do not add tips to every field. Tips are only for special constraints,
|
|
131
135
|
unusual formats, compliance notes, or non-obvious business rules.
|
|
132
|
-
- Use select/radio controls for enums
|
|
133
|
-
|
|
136
|
+
- Use select/radio controls for enums. For values maintained by other forms, use
|
|
137
|
+
`SelectField` with SDK-backed `optionSource` options. Do not ask users to type
|
|
138
|
+
raw IDs.
|
|
134
139
|
- Hide permission scope keys, computed, sync, and developer/internal fields with
|
|
135
140
|
`behavior: "HIDDEN"` when they must exist in the schema. Scope keys should be
|
|
136
|
-
derived from visible select/
|
|
137
|
-
users.
|
|
141
|
+
derived from visible select/person/department fields, not typed by
|
|
142
|
+
users. For select-derived scalar keys, use `valueSync`.
|
|
138
143
|
- Do not add separate creator, updater, creator department, updater department,
|
|
139
144
|
created time, or updated time fields unless the user explicitly needs a
|
|
140
145
|
separate business field. The platform creates system metadata for every form.
|
|
@@ -102,19 +102,18 @@ export function CustomSelect({ options, className, ...rest }: CustomSelectProps)
|
|
|
102
102
|
return (
|
|
103
103
|
<Select
|
|
104
104
|
className={clsx('w-full rounded-md', className)}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
{...rest}
|
|
105
|
+
getPopupContainer={(trigger) => trigger.parentElement ?? document.body}
|
|
106
|
+
options={options}
|
|
107
|
+
{...rest}
|
|
109
108
|
/>
|
|
110
109
|
);
|
|
111
110
|
}
|
|
112
111
|
```
|
|
113
112
|
|
|
114
|
-
要点:
|
|
115
|
-
- 透传 `...rest`,保留 antd 全部 API。
|
|
116
|
-
- `
|
|
117
|
-
- `
|
|
113
|
+
要点:
|
|
114
|
+
- 透传 `...rest`,保留 antd 全部 API。
|
|
115
|
+
- `getPopupContainer` 解决弹层被裁切问题(详见第 7 节常见错误)。
|
|
116
|
+
- 默认 `cssIsolation: "none"` 时不要额外给弹层加 `sy-app-workspace`;只有 legacy `namespace/shadow` 页面才需要给弹层或容器补 namespace class。
|
|
118
117
|
- 使用 `w-full`、`rounded-md`、`text-slate-600`、`border-slate-200` 等 Tailwind 原生类作为默认基线;如果组件视觉需要精调,可以继续使用 Tailwind 任意值或局部 CSS。
|
|
119
118
|
|
|
120
119
|
### 4.2 错误:从头实现选择器
|
|
@@ -171,7 +170,7 @@ function BadSelect({ options }) {
|
|
|
171
170
|
</ConfigProvider>
|
|
172
171
|
```
|
|
173
172
|
|
|
174
|
-
4. **谨慎**:直接覆盖组件内部 class(`.ant-select-selector { ... }`)属于私有 API
|
|
173
|
+
4. **谨慎**:直接覆盖组件内部 class(`.ant-select-selector { ... }`)属于私有 API,升级风险较高。确实需要时必须限定在页面根类下;legacy 页面也可以兼容限定在 `.sy-app-workspace` 下。优先考虑 `ConfigProvider`、`className`、组件 props 或 CSS 变量是否能解决。
|
|
175
174
|
|
|
176
175
|
---
|
|
177
176
|
|
|
@@ -208,7 +207,7 @@ export function ActionButton(props) {
|
|
|
208
207
|
| 用第三方富文本(TinyMCE 等) | 用 `EditorField` |
|
|
209
208
|
| 自己写列表 + 分页 + 导出 | 用 `DataManagementList` |
|
|
210
209
|
| 常规组件大量写 `style={{ color: '#333', padding: 16 }}` | 优先 Tailwind 原生工具类、任意值或局部 CSS;动态值和少量精调 inline style 可接受 |
|
|
211
|
-
| Select / Date / Modal 弹层错位、被滚动容器裁切 |
|
|
210
|
+
| Select / Date / Modal 弹层错位、被滚动容器裁切 | 默认加 `getPopupContainer={(trigger) => trigger.parentElement}`;legacy `namespace/shadow` 页面才额外使用 `sy-app-workspace` |
|
|
212
211
|
| 不分端,PC 组件直接放移动端 | 按端拆页 + 端内用对应 UI 库 |
|
|
213
212
|
| 无作用域覆盖 `.ant-xxx` 内部 class | 优先用 `className`、CSS 变量或 `ConfigProvider` 主题;必要覆盖时限定到页面命名空间 |
|
|
214
213
|
| 在自定义组件中不透传 `...rest` / `ref` | 透传 props 与 `forwardRef`,保留底层组件全部能力 |
|
|
@@ -220,5 +219,5 @@ export function ActionButton(props) {
|
|
|
220
219
|
- [ ] 涉及人员 / 部门 / 文件 / 图片 / 富文本 / 签名 / 地图 / 列表 → 用了平台组件?
|
|
221
220
|
- [ ] 自定义组件 → 基于 antd / antd-mobile 包装并透传 props?
|
|
222
221
|
- [ ] 样式 → 默认用 Tailwind 原生类 / 任意值 / 局部 CSS;平台 token 只在主题兼容或平台组件需要时使用,且作用域收敛?
|
|
223
|
-
- [ ] 弹层 →
|
|
222
|
+
- [ ] 弹层 → 默认使用正常容器;只有 legacy 隔离页面才额外设置 `sy-app-workspace` 样式作用域?
|
|
224
223
|
- [ ] 多端 → PC 用 antd、Mobile 用 antd-mobile,业务逻辑共享?
|
|
@@ -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.
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
1. **业务页面默认写原生 Tailwind**:优先使用 `bg-white`、`border`、`border-slate-200`、`text-slate-600`、`shadow-sm`、`rounded-lg`、`p-4`、`gap-4`、`grid-cols-[240px_1fr]` 这类 Tailwind 原生类和任意值。
|
|
10
10
|
2. **不要强制平台 token**:不要把 `bg-container`、`text-secondary`、`rounded-form`、`shadow-card` 当默认示例或默认范式。历史项目或平台组件需要时可以兼容使用,但新业务页面不推荐主动依赖。
|
|
11
11
|
3. **不要直接套 shadcn token**:`bg-card`、`text-muted-foreground`、`text-foreground`、`bg-background` 等不是 Tailwind 原生类。除非项目已经在 `tailwind.config.cjs` 明确配置这些 token,否则必须改成 Tailwind 原生类或任意值。
|
|
12
|
-
4.
|
|
13
|
-
5. **
|
|
12
|
+
4. **默认不启用样式隔离**:新发布页面默认 `cssIsolation: "none"`,Tailwind 类按原样输出,不需要 `.sy-app-workspace` 前缀即可生效。
|
|
13
|
+
5. **legacy 隔离仅用于兼容**:历史页面或显式配置 `cssIsolation: "namespace" | "shadow"` 时,runtime 仍会保留 `.sy-app-workspace`、legacy portal 和旧样式前缀。
|
|
14
|
+
6. **CSS 作用域要收敛**:页面级 CSS 写在 `styles.css`,选择器优先限定在页面根类下,避免无意覆盖宿主平台或其他页面。
|
|
14
15
|
|
|
15
16
|
---
|
|
16
17
|
|
|
@@ -60,12 +61,12 @@ export function DashboardPage() {
|
|
|
60
61
|
### 2.4 局部 CSS
|
|
61
62
|
|
|
62
63
|
```css
|
|
63
|
-
.
|
|
64
|
+
.queue-booking-page {
|
|
64
65
|
min-height: 100%;
|
|
65
66
|
background: #f8fafc;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
.
|
|
69
|
+
.queue-booking-page .queue-booking-page__calendar-grid {
|
|
69
70
|
display: grid;
|
|
70
71
|
grid-template-columns: repeat(7, minmax(120px, 1fr));
|
|
71
72
|
gap: 12px;
|
|
@@ -113,11 +114,11 @@ module.exports = {
|
|
|
113
114
|
|
|
114
115
|
这个配置的作用:
|
|
115
116
|
|
|
116
|
-
- `openxiangdaPreset`
|
|
117
|
+
- `openxiangdaPreset` 保留平台组件 safelist、平台 token 兼容类和平台组件需要的工具类;不再强制 Tailwind 输出 `.sy-app-workspace` 前缀。
|
|
117
118
|
- `...openxiangdaContent` 确保平台组件内部 class 会被 Tailwind 扫描到。
|
|
118
119
|
- `blocklist` 避免 Tailwind 误生成日期字符串相关的异常类。
|
|
119
120
|
|
|
120
|
-
|
|
121
|
+
不要移除这些平台构建能力;自由写 Tailwind 原生类和保留平台组件兼容能力并不冲突。
|
|
121
122
|
|
|
122
123
|
---
|
|
123
124
|
|
|
@@ -160,21 +161,15 @@ body {
|
|
|
160
161
|
|
|
161
162
|
## 5. Ant Design 和弹层
|
|
162
163
|
|
|
163
|
-
PC 控件优先用 `antd`,移动端控件优先用 `antd-mobile
|
|
164
|
+
PC 控件优先用 `antd`,移动端控件优先用 `antd-mobile`。默认 `cssIsolation: "none"` 时使用 Ant Design 默认 `ant` class 和 `document.body` 弹层容器即可;只有 legacy `namespace/shadow` 页面才需要显式挂到当前隔离容器:
|
|
164
165
|
|
|
165
166
|
```tsx
|
|
166
|
-
<ConfigProvider
|
|
167
|
-
getPopupContainer={(trigger) =>
|
|
168
|
-
trigger?.closest('.sy-app-workspace') ??
|
|
169
|
-
(document.querySelector('.sy-app-workspace') as HTMLElement) ??
|
|
170
|
-
document.body
|
|
171
|
-
}
|
|
172
|
-
>
|
|
167
|
+
<ConfigProvider>
|
|
173
168
|
<App />
|
|
174
169
|
</ConfigProvider>
|
|
175
170
|
```
|
|
176
171
|
|
|
177
|
-
|
|
172
|
+
legacy 页面可使用 `getPopupContainer={(trigger) => trigger?.closest(".sy-app-workspace") ?? document.body}`。不要无作用域覆盖 `.ant-select-selector`、`.ant-modal` 等私有 class;确实需要覆盖时限定在页面根类下。
|
|
178
173
|
|
|
179
174
|
---
|
|
180
175
|
|
|
@@ -188,9 +183,10 @@ OpenXiangda 仍保留部分平台语义类和 CSS 变量,主要用于:
|
|
|
188
183
|
|
|
189
184
|
常见兼容类包括 `text-primary`、`text-secondary`、`bg-container`、`bg-layout`、`border-secondary`、`rounded-form`、`shadow-card` 等。AI 编写新业务页面时不要默认使用这些类;除非用户明确要求跟随平台变量主题,或当前项目已有一致的 token 体系。
|
|
190
185
|
|
|
191
|
-
|
|
186
|
+
如果需要统一主题,推荐把主题限制在应用根类下;兼容旧页面时也可以同时声明 `.sy-app-workspace`:
|
|
192
187
|
|
|
193
188
|
```css
|
|
189
|
+
.theme-brand,
|
|
194
190
|
.sy-app-workspace.theme-brand {
|
|
195
191
|
--sy-color-primary: #0f766e;
|
|
196
192
|
--sy-radius-md: 8px;
|
|
@@ -218,5 +214,5 @@ warning 不会阻断构建,但它通常意味着页面会出现“颜色、边
|
|
|
218
214
|
- [ ] 页面样式默认使用 Tailwind 原生类和任意值,而不是平台 token 类?
|
|
219
215
|
- [ ] 没有使用未配置的 shadcn token 类,例如 `bg-card`、`text-muted-foreground`、`text-foreground`?
|
|
220
216
|
- [ ] `tailwind.config.cjs` 保留 OpenXiangda preset、content 扫描和 blocklist?
|
|
221
|
-
- [ ] 业务 CSS
|
|
222
|
-
- [ ]
|
|
217
|
+
- [ ] 业务 CSS 限定在页面根类下,而不是全局裸覆盖?
|
|
218
|
+
- [ ] 只有 legacy `namespace/shadow` 页面才额外配置 `.sy-app-workspace` 弹层容器?
|
|
@@ -23,18 +23,18 @@
|
|
|
23
23
|
|
|
24
24
|
**症状**:Select / Dropdown / Popup / Modal 等组件的弹出层没有样式,直接跑到屏幕外,或呈现为无样式的裸 HTML。
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
**解决方案**:
|
|
29
|
-
1.
|
|
30
|
-
2.
|
|
31
|
-
3.
|
|
32
|
-
4.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
```tsx
|
|
36
|
-
import { useRef } from 'react';
|
|
37
|
-
import { ConfigProvider } from 'antd';
|
|
26
|
+
**根因**:新页面默认 `cssIsolation: "none"`,弹层使用 `document.body` 和默认 Ant Design 样式即可。只有历史页面显式启用 `cssIsolation: "namespace" | "shadow"` 时,弹层渲染到 `document.body` 才可能脱离 legacy 样式作用域;移动端 Popup 也可能因 namespace 未被继承而失去样式。
|
|
27
|
+
|
|
28
|
+
**解决方案**:
|
|
29
|
+
1. 新页面优先保持 `cssIsolation: "none"`,不要为了修弹层问题手动加 `.sy-app-workspace`。
|
|
30
|
+
2. legacy `shadow` 页面优先迁移为 `none`;无法迁移时可使用 `namespace` 兼容模式。
|
|
31
|
+
3. legacy 页面为 antd `ConfigProvider` 配置 `getPopupContainer` 指向页面根容器。
|
|
32
|
+
4. 对于 legacy antd-mobile 页面,确保 `Popup` / `Modal` 的 `getContainer` 指向正确容器,并按需补 namespace class。
|
|
33
|
+
|
|
34
|
+
**legacy 示例**:
|
|
35
|
+
```tsx
|
|
36
|
+
import { useRef } from 'react';
|
|
37
|
+
import { ConfigProvider } from 'antd';
|
|
38
38
|
|
|
39
39
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
40
40
|
|
|
@@ -188,7 +188,7 @@ export default defineConfig({
|
|
|
188
188
|
**解决方案**:
|
|
189
189
|
1. 在 `vite.config.ts` 的 `optimizeDeps.include` 中显式添加 `antd-mobile`,避免运行时多次预构建。
|
|
190
190
|
2. 调整 Tailwind preflight 策略,确保不会覆盖 antd-mobile 的基础样式(必要时关闭 preflight 或使用 `important` 选择器)。
|
|
191
|
-
3.
|
|
191
|
+
3. 仅 legacy `namespace/shadow` 页面需要确保 antd-mobile 组件渲染在 `sy-app-workspace` namespace 容器内,弹层 `getContainer` 指向该容器;新页面默认不需要。
|
|
192
192
|
|
|
193
193
|
**示例**:
|
|
194
194
|
```typescript
|
|
@@ -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.
|