openxiangda 1.0.0
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 +58 -0
- package/bin/openxiangda.js +11 -0
- package/lib/cli.js +2423 -0
- package/lib/config.js +121 -0
- package/lib/http.js +47 -0
- package/lib/skills.js +371 -0
- package/lib/utils.js +87 -0
- package/lib/workspace-init.js +139 -0
- package/openxiangda-skills/SKILL.md +128 -0
- package/openxiangda-skills/references/architecture-patterns.md +242 -0
- package/openxiangda-skills/references/automation-v3.md +129 -0
- package/openxiangda-skills/references/component-guide.md +198 -0
- package/openxiangda-skills/references/forms/component-registry.md +53 -0
- package/openxiangda-skills/references/forms/form-schema.md +109 -0
- package/openxiangda-skills/references/forms/layout-and-rules.md +24 -0
- package/openxiangda-skills/references/openxiangda-api.md +466 -0
- package/openxiangda-skills/references/pages/page-sdk.md +13 -0
- package/openxiangda-skills/references/pages/publish-flow.md +36 -0
- package/openxiangda-skills/references/pages/workspace-structure.md +38 -0
- package/openxiangda-skills/references/permissions-settings.md +147 -0
- package/openxiangda-skills/references/platform-data-model.md +305 -0
- package/openxiangda-skills/references/style-system.md +492 -0
- package/openxiangda-skills/references/troubleshooting.md +246 -0
- package/openxiangda-skills/references/workflow-v3.md +105 -0
- package/openxiangda-skills/references/workspace-state.md +45 -0
- package/openxiangda-skills/skills/openxiangda-app/SKILL.md +64 -0
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +143 -0
- package/openxiangda-skills/skills/openxiangda-form/SKILL.md +76 -0
- package/openxiangda-skills/skills/openxiangda-inspect/SKILL.md +40 -0
- package/openxiangda-skills/skills/openxiangda-page/SKILL.md +62 -0
- package/openxiangda-skills/skills/openxiangda-permission-settings/SKILL.md +95 -0
- package/openxiangda-skills/skills/openxiangda-workflow-automation/SKILL.md +97 -0
- package/package.json +126 -0
- package/packages/sdk/bin/lowcode-workspace.mjs +4 -0
- package/packages/sdk/dist/build/index.cjs +33 -0
- package/packages/sdk/dist/build/index.cjs.map +1 -0
- package/packages/sdk/dist/build/index.d.mts +40 -0
- package/packages/sdk/dist/build/index.d.ts +40 -0
- package/packages/sdk/dist/build/index.mjs +8 -0
- package/packages/sdk/dist/build/index.mjs.map +1 -0
- package/packages/sdk/dist/components/index.cjs +18700 -0
- package/packages/sdk/dist/components/index.cjs.map +1 -0
- package/packages/sdk/dist/components/index.d.mts +2094 -0
- package/packages/sdk/dist/components/index.d.ts +2094 -0
- package/packages/sdk/dist/components/index.mjs +18649 -0
- package/packages/sdk/dist/components/index.mjs.map +1 -0
- package/packages/sdk/dist/runtime/index.cjs +1469 -0
- package/packages/sdk/dist/runtime/index.cjs.map +1 -0
- package/packages/sdk/dist/runtime/index.d.mts +831 -0
- package/packages/sdk/dist/runtime/index.d.ts +831 -0
- package/packages/sdk/dist/runtime/index.mjs +1420 -0
- package/packages/sdk/dist/runtime/index.mjs.map +1 -0
- package/packages/sdk/dist/styles/antd-theme.cjs +60 -0
- package/packages/sdk/dist/styles/antd-theme.cjs.map +1 -0
- package/packages/sdk/dist/styles/antd-theme.d.mts +5 -0
- package/packages/sdk/dist/styles/antd-theme.d.ts +5 -0
- package/packages/sdk/dist/styles/antd-theme.mjs +35 -0
- package/packages/sdk/dist/styles/antd-theme.mjs.map +1 -0
- package/packages/sdk/dist/styles/tailwind-preset.cjs +2641 -0
- package/packages/sdk/dist/styles/tailwind-preset.cjs.map +1 -0
- package/packages/sdk/dist/styles/tailwind-preset.d.mts +75 -0
- package/packages/sdk/dist/styles/tailwind-preset.d.ts +75 -0
- package/packages/sdk/dist/styles/tailwind-preset.mjs +2618 -0
- package/packages/sdk/dist/styles/tailwind-preset.mjs.map +1 -0
- package/packages/sdk/dist/styles/tokens.css +73 -0
- package/packages/sdk/src/build-source/README.md +9 -0
- package/packages/sdk/src/build-source/bin/lowcode-workspace.mjs +7 -0
- package/packages/sdk/src/build-source/package.json +34 -0
- package/packages/sdk/src/build-source/scripts/build-forms.mjs +824 -0
- package/packages/sdk/src/build-source/scripts/build-forms.runtime-entry.test.ts +18 -0
- package/packages/sdk/src/build-source/scripts/build-pages.mjs +793 -0
- package/packages/sdk/src/build-source/scripts/build-workspace.mjs +64 -0
- package/packages/sdk/src/build-source/scripts/publish-all.mjs +127 -0
- package/packages/sdk/src/build-source/scripts/publish-oss.mjs +149 -0
- package/packages/sdk/src/build-source/scripts/register-bundle.mjs +1 -0
- package/packages/sdk/src/build-source/scripts/register.mjs +329 -0
- package/packages/sdk/src/build-source/scripts/sync-schema.mjs +301 -0
- package/packages/sdk/src/build-source/scripts/utils/form-api.mjs +639 -0
- package/packages/sdk/src/build-source/scripts/utils/form-api.test.ts +244 -0
- package/packages/sdk/src/build-source/scripts/utils/form-runtime-assets.mjs +57 -0
- package/packages/sdk/src/build-source/scripts/utils/form-runtime-assets.test.ts +135 -0
- package/packages/sdk/src/build-source/scripts/utils/incremental.mjs +210 -0
- package/packages/sdk/src/build-source/scripts/utils/load-config.mjs +257 -0
- package/packages/sdk/src/build-source/scripts/utils/load-config.test.ts +44 -0
- package/packages/sdk/src/build-source/scripts/utils/mime-types.mjs +70 -0
- package/packages/sdk/src/build-source/scripts/utils/namespace-css.mjs +61 -0
- package/packages/sdk/src/build-source/scripts/utils/oss-client.mjs +128 -0
- package/packages/sdk/src/build-source/scripts/utils/pages.mjs +80 -0
- package/packages/sdk/src/build-source/scripts/utils/progress.mjs +57 -0
- package/packages/sdk/src/build-source/scripts/utils/register-payload.mjs +89 -0
- package/packages/sdk/src/build-source/scripts/utils/register-payload.test.ts +76 -0
- package/packages/sdk/src/build-source/scripts/utils/runtime-css-check.mjs +44 -0
- package/packages/sdk/src/build-source/scripts/utils/runtime-css-check.test.ts +54 -0
- package/packages/sdk/src/build-source/scripts/utils/schema-transform.mjs +130 -0
- package/packages/sdk/src/build-source/scripts/utils/schema-transform.test.ts +141 -0
- package/packages/sdk/src/build-source/scripts/utils/tailwind-config.mjs +227 -0
- package/packages/sdk/src/build-source/scripts/utils/tailwind-config.test.ts +187 -0
- package/packages/sdk/src/build-source/src/cli.mjs +679 -0
- package/templates/sy-lowcode-app-workspace/app-workspace.config.ts +34 -0
- package/templates/sy-lowcode-app-workspace/examples/forms/customer/page.tsx +1 -0
- package/templates/sy-lowcode-app-workspace/examples/forms/customer/schema.ts +35 -0
- package/templates/sy-lowcode-app-workspace/index.html +12 -0
- package/templates/sy-lowcode-app-workspace/package.json +49 -0
- package/templates/sy-lowcode-app-workspace/postcss.config.cjs +6 -0
- package/templates/sy-lowcode-app-workspace/scripts/build-js-code.mjs +100 -0
- package/templates/sy-lowcode-app-workspace/src/dev/App.tsx +26 -0
- package/templates/sy-lowcode-app-workspace/src/forms/.gitkeep +1 -0
- package/templates/sy-lowcode-app-workspace/src/forms/README.md +48 -0
- package/templates/sy-lowcode-app-workspace/src/index.css +28 -0
- package/templates/sy-lowcode-app-workspace/src/js-code-nodes/.gitkeep +1 -0
- package/templates/sy-lowcode-app-workspace/src/js-code-nodes/types.d.ts +3 -0
- package/templates/sy-lowcode-app-workspace/src/main.tsx +36 -0
- package/templates/sy-lowcode-app-workspace/src/pages/.gitkeep +1 -0
- package/templates/sy-lowcode-app-workspace/src/shared/form-schema.ts +128 -0
- package/templates/sy-lowcode-app-workspace/src/types/app-workspace.types.ts +31 -0
- package/templates/sy-lowcode-app-workspace/tailwind.config.cjs +30 -0
- package/templates/sy-lowcode-app-workspace/tsconfig.app.json +24 -0
- package/templates/sy-lowcode-app-workspace/tsconfig.js-code-nodes.json +15 -0
- package/templates/sy-lowcode-app-workspace/tsconfig.json +7 -0
- package/templates/sy-lowcode-app-workspace/tsconfig.node.json +10 -0
- package/templates/sy-lowcode-app-workspace/vite.config.ts +32 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# 平台数据模型参考(Platform Data Model)
|
|
2
|
+
|
|
3
|
+
> 一句话总结:OpenXiangda 平台按表单 schema 字段直接建表(每个字段对应一个数据库列),简单字段使用原生类型存储,选择类/复杂字段使用 JSON 列存储 `{label, value}` 结构;**提交什么格式就存什么格式,查询时原样返回**,AI Agent 在开发时**不要做任何格式翻译**。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. 核心原则
|
|
8
|
+
|
|
9
|
+
### 1.1 按字段建表 + JSON 列混合存储
|
|
10
|
+
|
|
11
|
+
- 平台按表单 schema **直接建表**:每个表单对应一张 PostgreSQL 数据表,每个字段对应一个数据库列。
|
|
12
|
+
- **简单字段**(文本、数字、日期等)使用原生 SQL 类型(`varchar`、`numeric`、`date` 等)存储。
|
|
13
|
+
- **复杂字段**(选择、人员、部门、附件、子表单等)使用 `json`/`jsonb` 列存储,直接保存完整的结构化对象。
|
|
14
|
+
- 数据库不做任何字典翻译或额外规范化。
|
|
15
|
+
- 虽然底层存储类型不同,但对 API 调用者来说**行为一致**:提交什么格式,查询就返回什么格式。
|
|
16
|
+
|
|
17
|
+
### 1.2 "存什么返什么"的设计哲学
|
|
18
|
+
|
|
19
|
+
- **写入端**:前端/Agent 提交什么格式,数据库就存什么格式。
|
|
20
|
+
- **读取端**:API 查询返回的就是原始 JSON 结构,**不会**额外执行字典翻译、用户名补全或部门名拼接。
|
|
21
|
+
- **展示端**:选择类字段的中文名(label)已经随数据写入,前端直接取 `item.label` 即可,**禁止**再次基于 `value` 去查字典。
|
|
22
|
+
- **操作端**:业务逻辑、过滤、查询条件统一以 `item.value` 为准(稳定、不随显示文案变化)。
|
|
23
|
+
|
|
24
|
+
> 这意味着:label 是"快照",value 是"语义键"。一旦字段值落库,label 与 value 一起被冻结,后续即便选项字典改名也不会影响历史数据展示。
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 2. 字段类型与存储格式对照表
|
|
29
|
+
|
|
30
|
+
下表是 AI Agent 开发时必须遵守的字段映射规范。**所有"存储格式"列即为"提交格式",也即"查询返回格式",三者完全一致**。
|
|
31
|
+
|
|
32
|
+
| 字段类型 | 组件名 | 存储格式(=提交格式=查询返回格式) | 展示方式 | 操作方式 |
|
|
33
|
+
|---|---|---|---|---|
|
|
34
|
+
| 单行文本 | `TextField` | `"张三"` (string) | 直接渲染字符串 | 直接比较字符串 |
|
|
35
|
+
| 多行文本 | `TextAreaField` | `"第一行\n第二行"` (string,可含 `\n`) | `white-space: pre-wrap` 渲染 | 直接比较字符串 |
|
|
36
|
+
| 数字 | `NumberField` | `100` 或 `100.5` (number) | 直接渲染,可格式化千分位 | 数值比较 |
|
|
37
|
+
| 日期 | `DateField` | `"2026-05-29"` (ISO 字符串) | 格式化为本地日期 | 字符串比较或 dayjs 解析 |
|
|
38
|
+
| 日期范围 | `DateRangeField` | `["2026-05-01", "2026-05-31"]` (string[]) | `start ~ end` | `value[0]` / `value[1]` |
|
|
39
|
+
| 单选 | `SelectField` (单选) | `{"label": "选项A", "value": "a"}` | `value.label` | `value.value` |
|
|
40
|
+
| 多选 | `SelectField` (多选) | `[{"label":"A","value":"a"},{"label":"B","value":"b"}]` | `items.map(i => i.label).join(', ')` | `items.some(i => i.value === 'a')` |
|
|
41
|
+
| 单选按钮 | `RadioField` | `{"label": "是", "value": "yes"}` | `value.label` | `value.value` |
|
|
42
|
+
| 复选框 | `CheckboxField` | `[{"label":"A","value":"a"}]` (始终数组) | `items.map(i => i.label).join(', ')` | `items.some(i => i.value === 'a')` |
|
|
43
|
+
| 人员选择(单) | `UserSelectField` (单选) | `{"label": "张三", "value": "user_001"}` | `value.label` | `value.value` 作为 userId |
|
|
44
|
+
| 人员选择(多) | `UserSelectField` (多选) | `[{"label":"张三","value":"user_001"},{"label":"李四","value":"user_002"}]` | `items.map(i => i.label).join(', ')` | `items.map(i => i.value)` 作为 userId 列表 |
|
|
45
|
+
| 部门选择 | `DepartmentSelectField` | 单选 `{"label": "研发部", "value": "dept_001"}`;多选 `[{...}, ...]` | `value.label` 或 `items.map(i => i.label)` | `value.value` / `items.map(i => i.value)` |
|
|
46
|
+
| 附件 | `AttachmentField` | `[{"name":"文件.pdf","url":"https://...","size":1024,"type":"application/pdf"}]` | 文件名+下载链接列表 | 通过 `url` 下载 |
|
|
47
|
+
| 图片 | `ImageField` | `[{"name":"图片.png","url":"https://...","thumbUrl":"https://..."}]` | 缩略图网格 | 通过 `url` 预览 |
|
|
48
|
+
| 子表单 | `SubFormField` | `[{字段A: ..., 字段B: ...}, {...}]` (对象数组) | 渲染为子行表格 | 遍历每行,按字段 code 取值 |
|
|
49
|
+
| 位置 | `LocationField` | `{"address":"杭州市xxx","lng":120.12,"lat":30.27}` | `value.address` | `value.lng`/`value.lat` 用于地图 |
|
|
50
|
+
| 富文本 | `EditorField` | `"<p>富文本内容</p>"` (HTML 字符串) | `dangerouslySetInnerHTML` | 一般不参与查询过滤 |
|
|
51
|
+
|
|
52
|
+
### 2.1 选择类字段统一约定
|
|
53
|
+
|
|
54
|
+
> 所有"选择类"字段(`SelectField` / `RadioField` / `CheckboxField` / `UserSelectField` / `DepartmentSelectField`)共享**同一种结构**:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
type Option = { label: string; value: string };
|
|
58
|
+
|
|
59
|
+
// 单选 / 单值
|
|
60
|
+
type SingleSelectValue = Option;
|
|
61
|
+
|
|
62
|
+
// 多选 / 多值
|
|
63
|
+
type MultiSelectValue = Option[];
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- **单选/多选的判定**取决于字段配置 `multiple: boolean`,而非字段类型本身。
|
|
67
|
+
- **多选字段始终为数组**,即使用户只勾选了一项,也是 `[{label, value}]`,**不要降级为对象**。
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 3. 提交数据示例
|
|
72
|
+
|
|
73
|
+
下面是一份完整的表单提交 payload(前端调用 `submitFormData` 时传入的 `formData` 字段):
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"name": "张三",
|
|
78
|
+
"remark": "第一行备注\n第二行备注",
|
|
79
|
+
"amount": 1280.50,
|
|
80
|
+
"applyDate": "2026-05-29",
|
|
81
|
+
"tripRange": ["2026-06-01", "2026-06-05"],
|
|
82
|
+
"department": { "label": "研发部", "value": "dept_001" },
|
|
83
|
+
"priority": { "label": "高", "value": "high" },
|
|
84
|
+
"tags": [
|
|
85
|
+
{ "label": "紧急", "value": "urgent" },
|
|
86
|
+
{ "label": "重要", "value": "important" }
|
|
87
|
+
],
|
|
88
|
+
"isAgree": { "label": "同意", "value": "yes" },
|
|
89
|
+
"hobbies": [
|
|
90
|
+
{ "label": "阅读", "value": "reading" }
|
|
91
|
+
],
|
|
92
|
+
"owner": { "label": "张三", "value": "user_001" },
|
|
93
|
+
"ccList": [
|
|
94
|
+
{ "label": "李四", "value": "user_002" },
|
|
95
|
+
{ "label": "王五", "value": "user_003" }
|
|
96
|
+
],
|
|
97
|
+
"attachments": [
|
|
98
|
+
{
|
|
99
|
+
"name": "合同.pdf",
|
|
100
|
+
"url": "https://cdn.openxiangda.com/files/contract.pdf",
|
|
101
|
+
"size": 204800,
|
|
102
|
+
"type": "application/pdf"
|
|
103
|
+
}
|
|
104
|
+
],
|
|
105
|
+
"photos": [
|
|
106
|
+
{
|
|
107
|
+
"name": "现场.png",
|
|
108
|
+
"url": "https://cdn.openxiangda.com/files/site.png",
|
|
109
|
+
"thumbUrl": "https://cdn.openxiangda.com/files/site_thumb.png"
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
"items": [
|
|
113
|
+
{ "itemName": "笔记本", "itemQty": 2, "itemPrice": 5000 },
|
|
114
|
+
{ "itemName": "鼠标", "itemQty": 5, "itemPrice": 100 }
|
|
115
|
+
],
|
|
116
|
+
"address": {
|
|
117
|
+
"address": "杭州市西湖区文三路 100 号",
|
|
118
|
+
"lng": 120.1234,
|
|
119
|
+
"lat": 30.2741
|
|
120
|
+
},
|
|
121
|
+
"description": "<p>这是<strong>富文本</strong>内容</p>"
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 4. 查询数据示例(与提交一致)
|
|
128
|
+
|
|
129
|
+
调用 `getFormDataDetail` 等单条查询 API 后,返回的 `formData` 与提交时**完全一致**,无任何字段被翻译或重组:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"id": "rec_2026052900001",
|
|
134
|
+
"formCode": "expense_apply",
|
|
135
|
+
"createdAt": "2026-05-29T10:23:11Z",
|
|
136
|
+
"updatedAt": "2026-05-29T10:23:11Z",
|
|
137
|
+
"creator": { "label": "张三", "value": "user_001" },
|
|
138
|
+
"formData": {
|
|
139
|
+
"name": "张三",
|
|
140
|
+
"amount": 1280.50,
|
|
141
|
+
"applyDate": "2026-05-29",
|
|
142
|
+
"department": { "label": "研发部", "value": "dept_001" },
|
|
143
|
+
"priority": { "label": "高", "value": "high" },
|
|
144
|
+
"tags": [
|
|
145
|
+
{ "label": "紧急", "value": "urgent" },
|
|
146
|
+
{ "label": "重要", "value": "important" }
|
|
147
|
+
],
|
|
148
|
+
"ccList": [
|
|
149
|
+
{ "label": "李四", "value": "user_002" },
|
|
150
|
+
{ "label": "王五", "value": "user_003" }
|
|
151
|
+
],
|
|
152
|
+
"attachments": [
|
|
153
|
+
{ "name": "合同.pdf", "url": "https://cdn.openxiangda.com/files/contract.pdf", "size": 204800, "type": "application/pdf" }
|
|
154
|
+
],
|
|
155
|
+
"items": [
|
|
156
|
+
{ "itemName": "笔记本", "itemQty": 2, "itemPrice": 5000 },
|
|
157
|
+
{ "itemName": "鼠标", "itemQty": 5, "itemPrice": 100 }
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
> 注意:`formData` 是 API 返回的聚合 JSON 对象(键为字段 code,值为各字段存储值),与提交时的结构**字字相同**。底层实现是按字段建表(简单字段原生类型列 + 复杂字段 json/jsonb 列),而非所有数据存在单一 JSONB 列中。AI Agent 不要假设后端会做字典补全。
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## 5. 数据管理列表 API 返回格式
|
|
168
|
+
|
|
169
|
+
`advancedSearchDataManagement` 是数据列表查询 API,用于分页、筛选、排序。其返回结构如下:
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"code": 200,
|
|
174
|
+
"message": "ok",
|
|
175
|
+
"data": {
|
|
176
|
+
"total": 128,
|
|
177
|
+
"page": 1,
|
|
178
|
+
"pageSize": 20,
|
|
179
|
+
"list": [
|
|
180
|
+
{
|
|
181
|
+
"id": "rec_2026052900001",
|
|
182
|
+
"formCode": "expense_apply",
|
|
183
|
+
"createdAt": "2026-05-29T10:23:11Z",
|
|
184
|
+
"updatedAt": "2026-05-29T10:23:11Z",
|
|
185
|
+
"creator": { "label": "张三", "value": "user_001" },
|
|
186
|
+
"formData": {
|
|
187
|
+
"name": "张三",
|
|
188
|
+
"amount": 1280.50,
|
|
189
|
+
"department": { "label": "研发部", "value": "dept_001" },
|
|
190
|
+
"priority": { "label": "高", "value": "high" },
|
|
191
|
+
"tags": [
|
|
192
|
+
{ "label": "紧急", "value": "urgent" }
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"id": "rec_2026052900002",
|
|
198
|
+
"formCode": "expense_apply",
|
|
199
|
+
"createdAt": "2026-05-29T11:05:42Z",
|
|
200
|
+
"updatedAt": "2026-05-29T11:05:42Z",
|
|
201
|
+
"creator": { "label": "李四", "value": "user_002" },
|
|
202
|
+
"formData": {
|
|
203
|
+
"name": "李四",
|
|
204
|
+
"amount": 320.00,
|
|
205
|
+
"department": { "label": "市场部", "value": "dept_002" },
|
|
206
|
+
"priority": { "label": "普通", "value": "normal" },
|
|
207
|
+
"tags": []
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### 5.1 字段访问示例
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
// 渲染列表表格
|
|
219
|
+
list.map(row => ({
|
|
220
|
+
id: row.id,
|
|
221
|
+
申请人: row.formData.name, // 纯字符串
|
|
222
|
+
金额: row.formData.amount, // number
|
|
223
|
+
部门: row.formData.department?.label ?? '-', // 选择类直接取 label
|
|
224
|
+
优先级: row.formData.priority?.label ?? '-',
|
|
225
|
+
标签: row.formData.tags?.map(t => t.label).join(', ') ?? '',
|
|
226
|
+
创建人: row.creator.label,
|
|
227
|
+
}));
|
|
228
|
+
|
|
229
|
+
// 构造筛选条件(用 value,不用 label)
|
|
230
|
+
const filter = {
|
|
231
|
+
'formData.priority.value': 'high',
|
|
232
|
+
'formData.tags.value': { $in: ['urgent'] },
|
|
233
|
+
};
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## 6. AI 开发注意事项(关键规则)
|
|
239
|
+
|
|
240
|
+
> 以下规则按重要程度排序。AI Agent 写代码前请逐条核对。
|
|
241
|
+
|
|
242
|
+
1. **展示选择类字段值时,直接取 `item.label`**,**不要**做 `options.find(o => o.value === item.value)?.label` 这种"翻译"操作——label 已经在字段里了。
|
|
243
|
+
2. **操作(查询/过滤/比较)时一律用 `item.value`**:value 是稳定 ID,label 可能因字典调整而失真。
|
|
244
|
+
3. **提交表单时必须组装完整 `{label, value}` 结构**:仅传 value 会导致历史展示丢失中文名。
|
|
245
|
+
4. **多选字段始终是数组**,即便只有一项也写成 `[{label, value}]`;空值用 `[]`,不要用 `null`。
|
|
246
|
+
5. **`UserSelectField` / `DepartmentSelectField` 与普通 `SelectField` 的格式完全一致**,唯一区别是 `value` 语义为 userId / departmentId。
|
|
247
|
+
6. **附件 / 图片字段始终是数组**:单文件场景也是 `[{name, url, ...}]`,没有则 `[]`。
|
|
248
|
+
7. **子表单 `SubFormField` 是对象数组**,每个对象代表一行,键为子字段 code。读取时遍历数组,按字段 code 访问;空子表单为 `[]`。
|
|
249
|
+
8. **不要尝试"翻译"或"规范化" label/value**:数据库存的就是最终展示格式,前后端皆然。任何二次翻译都是 bug 来源。
|
|
250
|
+
9. **流程表单的数据格式与普通表单一致**:`formData` 结构与普通表单一致,流程节点信息走另外的字段,不影响业务字段结构。
|
|
251
|
+
10. **日期字段是字符串而非 Date 对象**:`DateField` 用 ISO 日期串 `"YYYY-MM-DD"`,`DateRangeField` 是 `[startISO, endISO]`,序列化时不要 `JSON.stringify(new Date())`。
|
|
252
|
+
11. **数字字段是原生 number**,不是字符串;金额建议在前端格式化展示,不要在存储时加货币符号。
|
|
253
|
+
12. **富文本是 HTML 字符串**,渲染需用 `dangerouslySetInnerHTML`,并自行处理 XSS 过滤;不要把它当作 markdown。
|
|
254
|
+
13. **位置字段 `LocationField` 是对象**,含 `address` / `lng` / `lat`,地图渲染用 `lng`/`lat`,列表显示用 `address`。
|
|
255
|
+
14. **空值约定**:未填写的字段在 `formData` 中可能**缺失键**,也可能为 `null`;读取时统一用可选链 `?.` 与默认值兜底。
|
|
256
|
+
15. **不要修改返回的 `formData` 引用**:列表场景下若需做格式化展示,请生成新对象,避免污染原始数据。
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## 附录:TypeScript 类型定义参考
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
// 选项类(含人员/部门)
|
|
264
|
+
export interface Option {
|
|
265
|
+
label: string;
|
|
266
|
+
value: string;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// 附件
|
|
270
|
+
export interface AttachmentItem {
|
|
271
|
+
name: string;
|
|
272
|
+
url: string;
|
|
273
|
+
size?: number;
|
|
274
|
+
type?: string;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 图片
|
|
278
|
+
export interface ImageItem {
|
|
279
|
+
name: string;
|
|
280
|
+
url: string;
|
|
281
|
+
thumbUrl?: string;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// 位置
|
|
285
|
+
export interface LocationValue {
|
|
286
|
+
address: string;
|
|
287
|
+
lng: number;
|
|
288
|
+
lat: number;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 子表单一行(结构由具体子字段决定)
|
|
292
|
+
export type SubFormRow = Record<string, unknown>;
|
|
293
|
+
|
|
294
|
+
// 一条表单数据
|
|
295
|
+
export interface FormDataRecord {
|
|
296
|
+
id: string;
|
|
297
|
+
formCode: string;
|
|
298
|
+
createdAt: string;
|
|
299
|
+
updatedAt: string;
|
|
300
|
+
creator: Option;
|
|
301
|
+
formData: Record<string, unknown>; // 字段 code -> 上述任一格式
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
> 记住一句话:**按字段建表(简单字段原生类型 + 复杂字段 JSON 列)+ label/value 快照 + 不翻译**,这是 OpenXiangda 数据模型最重要的三个关键词。
|