@qingflow-tech/qingflow-cli 1.1.50-beta.2 → 1.1.50-beta.3
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 +3 -3
- package/package.json +1 -1
- package/pyproject.toml +7 -2
- package/skills/qingflow-cli/SKILL.md +74 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md +3 -9
- package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +1 -0
- package/skills/qingflow-cli/reference/builder/50-views.md +176 -190
- package/skills/qingflow-cli/reference/builder/README.md +1 -1
- package/skills/qingflow-cli/reference/builder/reference/app-delivery-sop.md +3 -3
- package/skills/qingflow-cli/reference/examples/README.md +1 -1
- package/skills/qingflow-cli/reference/examples/views/views_apply_v1alpha1.example.json +14 -0
- package/skills/qingflow-cli/reference/examples/views/views_upsert_table_minimal.example.json +30 -36
- package/src/qingflow_mcp/builder_facade/models.py +208 -17
- package/src/qingflow_mcp/builder_facade/schemas/views/manifest.json +43 -0
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/complete.json +1193 -0
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/button-config.json +53 -0
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/data-permission.json +60 -0
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/display.json +71 -0
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/operation-permission.json +48 -0
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/query-condition.json +47 -0
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/summary.json +274 -0
- package/src/qingflow_mcp/builder_facade/service.py +1427 -410
- package/src/qingflow_mcp/builder_facade/views_extract.py +572 -0
- package/src/qingflow_mcp/builder_facade/views_schema.py +400 -0
- package/src/qingflow_mcp/cli/commands/builder.py +72 -53
- package/src/qingflow_mcp/public_surface.py +3 -0
- package/src/qingflow_mcp/response_trim.py +11 -2
- package/src/qingflow_mcp/server_app_builder.py +32 -8
- package/src/qingflow_mcp/solution/executor.py +93 -7
- package/src/qingflow_mcp/tools/ai_builder_tools.py +211 -393
- package/src/qingflow_mcp/tools/solution_tools.py +2 -0
- package/src/qingflow_mcp/tools/view_tools.py +0 -6
- package/src/qingflow_mcp/tools/view_writer.py +44 -0
- package/skills/qingflow-cli/reference/examples/views/views_batch_full.example.json +0 -159
- package/skills/qingflow-cli/reference/views_upsert_table_minimal.example.json +0 -42
|
@@ -1,38 +1,52 @@
|
|
|
1
1
|
# Builder Views
|
|
2
2
|
|
|
3
|
-
Read this when the task is about table/card/board/gantt views, fixed filters, query panels, view sorting, associated report/view display on a view, or repairing existing views.
|
|
3
|
+
Read this when the task is about table/card/board/gantt/hierarchy views, fixed filters, query panels, view sorting, view data permissions, associated report/view display on a view, or repairing existing views.
|
|
4
4
|
|
|
5
5
|
## Scope
|
|
6
6
|
|
|
7
|
-
Responsible for: `builder views apply --
|
|
7
|
+
Responsible for: `builder views apply --app-key APP_KEY --view-file PATH` (the **only** creation entry), one view declaration, `columns`, `filters`, `query_conditions`, `action_buttons`, and view readback.
|
|
8
8
|
|
|
9
9
|
Not responsible for: creating associated report resources or maintaining custom button bodies outside the view design. New business views must include `query_conditions`; ordinary business shortcut buttons are required and are declared only through `action_buttons` on the target view.
|
|
10
10
|
|
|
11
11
|
## Main chain
|
|
12
12
|
|
|
13
13
|
```text
|
|
14
|
-
app get -> app get fields/views -> views
|
|
14
|
+
app get -> app get fields/views -> views schema -> prepare one view -> views validate -> views apply -> app get/views readback
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
## 版本与 schema(views v1alpha1)
|
|
18
|
+
|
|
19
|
+
视图声明是**版本化契约**(`apiVersion: builder.qingflow.com/v1alpha1`)。`builder views apply` 是视图创建的**唯一入口**(旧命令式 `view_create` 已删除;不要再寻找或使用任何“直接创建视图”的命令/工具)。
|
|
20
|
+
|
|
21
|
+
- **两级 schema**:**summary**(最简版:类型/名称/数据展示内容 + `data_permission` 数据权限形状)与 **complete**(完整版:visibility 可见性授权、`filters`、`sort`、`query_conditions`、`action_buttons`/`buttons`、`associated_resources`、`card`/`gantt`/`hierarchy` 类型专属配置、`display`/`record`/`share`)。字段级定义逐字段带 `x-backend` 后端映射。
|
|
22
|
+
- **读 schema**:`qingflow --json builder views schema` 默认返回 `v1alpha1/summary.json`;需要完整可写配置时显式传 `--schema-kind complete`。MCP 对应 `app_views_schema`。
|
|
23
|
+
- **Schema-first 写入**:先用 `builder views schema --schema-version VERSION --schema-kind complete` 读取完整 schema;将相同版本显式传给 `validate --schema-version VERSION` 和 `apply --schema-version VERSION`。`validate` 也可从声明信封读取 `schema_version`,但推荐显式传版本;apply 的版本校验必须用 `--schema-version`。`validate` 接受一个直接视图声明对象,不接受 `upsert_view` 包装或批量 envelope。
|
|
24
|
+
- **说明字段**:视图 Schema 使用语义化的 `*Description` 自定义 key(如 `dataPermissionDescription`)承载说明,加载和引用展开时原样保留;读取说明的工具应识别这些 key。标准校验器忽略这些自定义字段,仍按 `type`、`enum`、`required` 等关键字校验;`default` 是标准注解,不会自动填值。`*Description` 不是提交视图声明时的业务字段。
|
|
25
|
+
- **离线校验**:`qingflow --json builder views validate --file PATH`(`-` 支持 stdin),不访问后端;返回 `ok`/`issues[]`(带 JSON pointer path)。
|
|
26
|
+
- **读 canonical 声明**:`qingflow --json builder views get --app-key APP --schema-version VERSION --schema-kind summary`,返回指定版本的 summary 声明数组(字段名反解);完整可写配置用 `--schema-kind complete` 并检查 `config_read_errors[].gaps`。
|
|
27
|
+
- **完整声明的提取边界**:`views get --schema-kind complete` 优先读取 `buttonConfigVO` 中的完整按钮绑定。公式过滤、无法解析的过滤规则或按钮 ID 会使该视图返回 `VIEW_NOT_EXTRACTABLE`(见 `details.config_read_errors[].gaps`),不会生成删减后的可写声明。修改这类视图的其他属性应使用局部 patch。更新现有视图时,已配置的系统字段原样保留,不补默认值、不重算序号;读接口生成的未配置占位对象不会写回,不会因此新增系统字段配置。
|
|
28
|
+
- **版本协商**:apply 显式传 `--schema-version v1alpha1` 时启用版本化校验:非法声明返回 `INVALID_SPEC` 且**不写**;响应信封回显 `schemaVersion`。apply 不传时行为与旧版一致(别名宽容,零破坏)。
|
|
29
|
+
- **complete 档新能力速览**:`data_permission`(`{type: config|custom, scope: all|oneself|dept|subordinate|custom}`;`scope=custom` 的规则明细写 `filters`);`sort`(`[{que_id, ascending}]`,空数组=恢复默认排序);`hierarchy` 类型(须 `hierarchy.title_que_id/children_que_id`,可选 `root_filter`);`card`/`gantt`/`display`/`record`/`share` 配置组。summary 档仅四公开类型(table/card/board/gantt)。
|
|
30
|
+
|
|
17
31
|
## Update existing views
|
|
18
32
|
|
|
19
|
-
- Prefer
|
|
33
|
+
- Prefer `--patch-views-file` for small changes to an existing view.
|
|
20
34
|
- Use raw `view_key` from `app_get.views[].view_key`; do not use record-data `custom:<view_key>` prefixes.
|
|
21
|
-
- Do not send a partial `
|
|
35
|
+
- Do not send a partial `--view-file` object to update a complex existing view; use `--patch-views-file` with `set`.
|
|
22
36
|
- Verify filters, query conditions, and associated resources separately before reporting success.
|
|
23
37
|
|
|
24
38
|
## Detailed contract notes
|
|
25
39
|
|
|
26
|
-
稳定命令:**`qingflow builder views apply --
|
|
40
|
+
稳定命令:**`qingflow builder views apply --app-key APP_KEY --view-file PATH`**(别名 **`qingflow build views apply`**)。`--view-file` 是一个直接的 JSON 对象,只声明一个新建或完整更新的视图。
|
|
27
41
|
|
|
28
42
|
> **权限**:视图**新建**走后端 `createViewgraphConfig`,需要同时满足 **视图管理 / ViewManagementAuth**(`beingViewManageStatus`)和 **数据管理 / DataManageAuth**;视图**更新/删除**只走 **ViewManagementAuth**。未开启高级应用权限时 `beingViewManageStatus` 等价回落到 **DataManageAuth**。失败时常见 **40002 / 40161 / 编辑锁**,参见主技能与 **ADMIN** 速查。
|
|
29
43
|
|
|
30
|
-
> **默认系统视图**:`全部数据`、`我的数据`、`我发起的`、`待办`、`已办`、`抄送` 等由轻流自带,**不要新建同名业务视图**。新建视图必须用业务名,例如 `项目台账视图`、`客户跟进视图`、`逾期任务看板`。如果确实要调整默认系统视图,只能从 `app get views` 取已存在的 raw **`view_key`**
|
|
44
|
+
> **默认系统视图**:`全部数据`、`我的数据`、`我发起的`、`待办`、`已办`、`抄送` 等由轻流自带,**不要新建同名业务视图**。新建视图必须用业务名,例如 `项目台账视图`、`客户跟进视图`、`逾期任务看板`。如果确实要调整默认系统视图,只能从 `app get views` 取已存在的 raw **`view_key`** 后通过 `--patch-views-file` 更新。
|
|
31
45
|
|
|
32
46
|
> **契约**(权威键名、枚举、`execution_notes`、示例):
|
|
33
47
|
> `qingflow --json builder contract --tool-name app_views_apply`
|
|
34
48
|
> (`--tool-name` 为契约索引,用于拉取 **`allowed_keys` / 别名 / 示例**。)
|
|
35
|
-
> 契约示例若带 `"profile"`,不要复制到 `--
|
|
49
|
+
> 契约示例若带 `"profile"` 或 `"app_key"`,不要复制到 `--view-file`;CLI 分别用根级 `qingflow --profile …` 和 `--app-key` 指定。
|
|
36
50
|
|
|
37
51
|
---
|
|
38
52
|
|
|
@@ -42,10 +56,11 @@ app get -> app get fields/views -> views apply with required query_conditions an
|
|
|
42
56
|
|------|------|-----|
|
|
43
57
|
| ① 读应用地图 | 先看 compact views/charts/buttons/associated resources | `qingflow --json builder app get --app-key <APP_KEY>` |
|
|
44
58
|
| ② 读字段/视图 | 确认 **`columns`** / `query_conditions.rows` 可用字段、核对 **`view_key`** | `qingflow --json builder app get --app-key <APP_KEY> fields` / `qingflow --json builder app get --app-key <APP_KEY> views` |
|
|
45
|
-
| ③
|
|
46
|
-
| ④
|
|
47
|
-
| ⑤
|
|
48
|
-
| ⑥
|
|
59
|
+
| ③ 读取 schema | 固定版本;按需求读取 summary 或 complete | `qingflow --json builder views schema --schema-version v1alpha1 --schema-kind complete` |
|
|
60
|
+
| ④ 准备并校验声明 | 新建或完整更新写一个视图对象;先离线校验;局部修改写单项 patch | `qingflow --json builder views validate --file PATH --schema-version v1alpha1` |
|
|
61
|
+
| ⑤ 应用视图操作 | 将同一版本传给 apply;一次只提交一个视图操作 | `qingflow builder views apply --app-key … --view-file PATH --schema-version v1alpha1` |
|
|
62
|
+
| ⑥ 配置查询面板和快捷按钮 | 新建业务视图必须写 `query_conditions` 和 `action_buttons`,按钮同时覆盖 `list` 和 `detail` 位置 | `builder views apply` |
|
|
63
|
+
| ⑦ 需要上线时 | 发布侧校验 | `qingflow builder publish verify --app-key …` |
|
|
49
64
|
|
|
50
65
|
**说明**:`views apply` 的 **`--publish`** 对应契约里的 **`publish`**;新建业务视图必须包含 `query_conditions` 和 `action_buttons` 并使用 `publish=true`,因为底层按钮写入会自动发布。只有删除、纯维护补丁、或用户明确要求的只读/归档视图,才允许不带按钮;只读/归档视图仍应配置 `query_conditions`。
|
|
51
66
|
|
|
@@ -55,14 +70,17 @@ app get -> app get fields/views -> views apply with required query_conditions an
|
|
|
55
70
|
|
|
56
71
|
```bash
|
|
57
72
|
qingflow builder views apply \
|
|
58
|
-
--
|
|
73
|
+
--app-key APP_KEY \
|
|
74
|
+
--view-file PATH \
|
|
59
75
|
[--publish | --no-publish]
|
|
60
76
|
```
|
|
61
77
|
|
|
62
|
-
- **`--
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
78
|
+
- **`--view-file`**:单个 JSON 对象,用于新建或完整更新一个视图;命令每次只接受一个。
|
|
79
|
+
- **`--patch-views-file`**:JSON 数组,用于已有视图的局部更新;每次最多传一项,包含 `view_key` 或唯一 `name`,以及 `set`/`unset`。
|
|
80
|
+
- **`--remove-views-file`**:JSON 数组,用于删除一个视图;每次最多传一个 raw `view_key` 或唯一名称。
|
|
81
|
+
- 每次调用只能选择一种写入模式:`--view-file`、`--patch-views-file`、`--remove-views-file` 三者不能混用;每次只处理一个应用中的一个视图操作。
|
|
82
|
+
- **不支持批量创建**:不要传 `upsert_views`、`views`、`apps` 等批量声明,也不要在 patch/remove 文件中放多项;多个视图须逐个调用 `builder views apply`,每次单独读回确认。
|
|
83
|
+
- **`--app-key`**:每次视图写入必须指定一个应用;CLI 不提供跨应用或同次多视图写入。
|
|
66
84
|
- 输出默认直接给 stdout,前端可直接读取;需要留档时用 `qingflow builder views apply … | tee tmp/builder_views_apply.json`。
|
|
67
85
|
|
|
68
86
|
---
|
|
@@ -71,10 +89,10 @@ qingflow builder views apply \
|
|
|
71
89
|
|
|
72
90
|
| 文件 | 用途 |
|
|
73
91
|
|------|------|
|
|
74
|
-
| [views_upsert_table_minimal.example.json](../examples/views/views_upsert_table_minimal.example.json) |
|
|
75
|
-
| [
|
|
92
|
+
| [views_upsert_table_minimal.example.json](../examples/views/views_upsert_table_minimal.example.json) | 单视图表格示例,可直接作为 `--view-file` |
|
|
93
|
+
| [views_apply_v1alpha1.example.json](../examples/views/views_apply_v1alpha1.example.json) | 版本化单视图字段示例;作为 `--view-file` 使用时加 `--schema-version v1alpha1` |
|
|
76
94
|
|
|
77
|
-
|
|
95
|
+
复制到具体应用前替换 **`columns`**;应用由命令的 **`--app-key`** 指定。**`name`** 重名时会更新已有同名视图而非新建(见 **§4**)。
|
|
78
96
|
|
|
79
97
|
---
|
|
80
98
|
|
|
@@ -84,12 +102,11 @@ CLI 将参数与文件内容拼装为服务层载荷,**顶层键**如下:
|
|
|
84
102
|
|
|
85
103
|
| 键 | 说明 |
|
|
86
104
|
|----|------|
|
|
87
|
-
| **`
|
|
88
|
-
| **`
|
|
89
|
-
| **`
|
|
90
|
-
| **`
|
|
91
|
-
| **`
|
|
92
|
-
| **`views[].set` / `unset`** | `operation="patch"` 时使用 |
|
|
105
|
+
| **`app_key`** | 命令的必填参数,定位单个应用 |
|
|
106
|
+
| **`upsert_view`** | 来自 `--view-file` 的一个对象;创建用 `name`,完整更新优先带 raw `view_key` |
|
|
107
|
+
| **`upsert_view.query_conditions`** | 新建业务视图必填;配置前端查询面板字段布局 |
|
|
108
|
+
| **`patch_views`** | 来自 `--patch-views-file` 的数组;每次最多一项,使用 `view_key`/`name` 和 `set`/`unset` |
|
|
109
|
+
| **`remove_views`** | 来自 `--remove-views-file` 的数组;每次最多一项,使用 raw `view_key` 或唯一名称 |
|
|
93
110
|
|
|
94
111
|
**别名**(契约 `aliases` 节选):**`fields` / `column_names` → `columns`**;**`view_key` / `viewKey`**;**`tableView`→`table`**、**`kanban`→`board`** 等;**`filter_rules`→`filters`**;**`startField`→`start_field`** 等。
|
|
95
112
|
|
|
@@ -99,15 +116,15 @@ CLI 将参数与文件内容拼装为服务层载荷,**顶层键**如下:
|
|
|
99
116
|
|
|
100
117
|
服务逻辑概要:
|
|
101
118
|
|
|
102
|
-
- **`
|
|
103
|
-
- **`
|
|
119
|
+
- **`remove_views`**:优先传 raw **`view_key`**;也可传唯一视图显示名 `name`,显示名重名则 **`AMBIGUOUS_VIEW`**。
|
|
120
|
+
- **`upsert_view`**:
|
|
104
121
|
- 若提供 **`view_key`**:对该视图做 **更新**(含类型变更等,仍受后端约束)。
|
|
105
122
|
- 未提供 **`view_key`**:若 **`name` 唯一** 匹配已有视图 → **更新**;否则 → **创建**。
|
|
106
|
-
- **`
|
|
123
|
+
- **`patch_views`**:对已有视图做局部参数替换;必须有 `view_key` 或唯一 `name`,并写 `set` / `unset`。
|
|
107
124
|
|
|
108
|
-
响应中的 **`verification.
|
|
125
|
+
响应中的 **`verification.view.status`** 会出现 **`created` / `updated`** 等;**`view_result`** 给出本次唯一视图操作结果,**`views_diff`** 仅保留兼容摘要。
|
|
109
126
|
|
|
110
|
-
**`partial_success
|
|
127
|
+
**`partial_success`**:单个视图写入已执行,但读回或附属配置校验未完全通过;务必读 **`view_result`**、**`warnings`** 与 **`verification`**。
|
|
111
128
|
|
|
112
129
|
**系统默认名禁止新建**:未提供 `view_key` 的 `upsert` 不要使用 `全部数据`、`我的数据`、`我发起的`、`待办`、`已办`、`抄送` 这类默认系统视图名。它们不是业务视图模板,而是平台默认对象;新业务视图用具体业务名。
|
|
113
130
|
|
|
@@ -119,12 +136,12 @@ CLI 将参数与文件内容拼装为服务层载荷,**顶层键**如下:
|
|
|
119
136
|
|
|
120
137
|
| `type` | 列/字段要点 | 类型专属键 |
|
|
121
138
|
|--------|----------------|------------|
|
|
122
|
-
| **`table`** | **`columns`**(业务字段
|
|
139
|
+
| **`table`** | **`columns`**(业务字段 **queId**,或已有系统字段 **queId**) | — |
|
|
123
140
|
| **`card`** | 同 table | — |
|
|
124
141
|
| **`board`**(看板) | 列;且须 **`group_by`**(分组字段名) | **`group_by`** |
|
|
125
142
|
| **`gantt`** | 列;须 **`start_field`**、**`end_field`**(通常为日期类字段名) | 可选 **`title_field`** |
|
|
126
143
|
|
|
127
|
-
实现侧校验:对 **table/card
|
|
144
|
+
实现侧校验:对 **table/card**,`columns` 至少包含一列;已知系统字段在读取已有视图时原样透传,不要求出现在表单业务字段索引中。未知系统字段仍会报告覆盖缺口。
|
|
128
145
|
|
|
129
146
|
---
|
|
130
147
|
|
|
@@ -136,9 +153,9 @@ CLI 将参数与文件内容拼装为服务层载荷,**顶层键**如下:
|
|
|
136
153
|
|
|
137
154
|
| 目标场景 | 写到哪里 | 推荐写法 | 不要这样做 |
|
|
138
155
|
| --- | --- | --- | --- |
|
|
139
|
-
| 视图打开时固定筛选数据 | `filters` | `{"
|
|
156
|
+
| 视图打开时固定筛选数据 | `filters` | `{"que_id":101,"operator":"eq","value":"进行中"}` | 不要写 `judgeType` / `judgeValues`;不要把它放进 `query_conditions` |
|
|
140
157
|
| 前端查询面板显示哪些字段 | `query_conditions.rows` | `[["客户名称", "负责人"], ["创建时间"]]` | 不要写筛选值;不要把它当 OR 条件 |
|
|
141
|
-
| 按钮显示条件 | `action_buttons[].visible_when` | `{"
|
|
158
|
+
| 按钮显示条件 | `action_buttons[].visible_when` | `{"que_id":101,"operator":"eq","value":"已完工"}` | 不要手写后端判断符 |
|
|
142
159
|
| 当前记录创建下游记录时传值 | `field_mappings` | `{"source_field":"数据ID","target_field":"关联工单"}` | 不要放在 `header` 按钮;`header` 没有当前记录上下文 |
|
|
143
160
|
| 关联视图/关联报表按当前记录过滤 | `associated-resource apply.match_mappings` | `{"target_field":"关联客户","operator":"eq","source_field":"数据ID"}` | 不要用 `filters` 或 `query_conditions` 代替 |
|
|
144
161
|
| 关联视图/关联报表静态筛选 | `match_mappings` | `{"target_field":"状态","operator":"eq","value":"有效"}` | 不要混用 raw `match_rules` |
|
|
@@ -147,17 +164,17 @@ CLI 将参数与文件内容拼装为服务层载荷,**顶层键**如下:
|
|
|
147
164
|
|
|
148
165
|
### 6.1 `filters`
|
|
149
166
|
|
|
150
|
-
- 项形状:**`
|
|
167
|
+
- 项形状:**`que_id`**、**`operator`**、**`values`**(或契约允许的 **`value`** 别名)。
|
|
151
168
|
- **`operator`**:统一公共语义,推荐 `eq` / `neq` / `in` / `contains` / `gte` / `lte` / `is_empty` / `not_empty`;兼容 `equal` / `equals` / `=` / `!=` / `any_of` / `one_of` / `empty` 等别名(以契约为准)。
|
|
152
169
|
- **`in` 等多值**:传 **列表**;**`value`** 为列表时可作别名。
|
|
153
170
|
- **`value` 按字段类型写**:文本/数字/日期直接写业务值;单选/多选/是否优先写选项文本,也支持 option id;成员/部门/关联记录固定筛选优先写唯一 id 或 `{id,value}`。完整规则见 **[reference/match-rules.md](./reference/match-rules.md)**。
|
|
154
171
|
- **语义**:固定筛选条件,打开视图即生效。不要把前端查询栏字段写进 `filters`。
|
|
155
|
-
- **写入 / 读回语义**:智能体始终写 `
|
|
172
|
+
- **写入 / 读回语义**:智能体始终写 `que_id + operator + value/values`;CLI 内部自动转换目标协议,读回也会还原成 `operator/value` 语义。不要手写或解释后端判断符。
|
|
156
173
|
|
|
157
174
|
示例:
|
|
158
175
|
|
|
159
176
|
```json
|
|
160
|
-
{"
|
|
177
|
+
{"que_id": 101, "operator": "eq", "value": "进行中"}
|
|
161
178
|
```
|
|
162
179
|
|
|
163
180
|
### 6.2 `query_conditions`
|
|
@@ -176,44 +193,38 @@ CLI 将参数与文件内容拼装为服务层载荷,**顶层键**如下:
|
|
|
176
193
|
|
|
177
194
|
```json
|
|
178
195
|
{
|
|
179
|
-
"
|
|
196
|
+
"name": "客户查询视图",
|
|
197
|
+
"type": "table",
|
|
198
|
+
"columns": [101, 102, 103, 104],
|
|
199
|
+
"filters": [
|
|
200
|
+
{"que_id": 103, "operator": "eq", "value": "有效"}
|
|
201
|
+
],
|
|
202
|
+
"query_conditions": {
|
|
203
|
+
"enabled": true,
|
|
204
|
+
"exact": false,
|
|
205
|
+
"hide_before_query": false,
|
|
206
|
+
"rows": [["客户名称", "负责人"], ["创建时间"]]
|
|
207
|
+
},
|
|
208
|
+
"action_buttons": [
|
|
180
209
|
{
|
|
181
|
-
"
|
|
182
|
-
"
|
|
183
|
-
"
|
|
184
|
-
"
|
|
185
|
-
|
|
186
|
-
"filters": [
|
|
187
|
-
{"field_name": "客户状态", "operator": "eq", "value": "有效"}
|
|
210
|
+
"text": "行内创建跟进",
|
|
211
|
+
"action": "add_data",
|
|
212
|
+
"target_app_key": "FOLLOWUP_APP_KEY",
|
|
213
|
+
"field_mappings": [
|
|
214
|
+
{"source_field": "数据ID", "target_field": "关联客户"}
|
|
188
215
|
],
|
|
189
|
-
"
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
"
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
{"source_field": "数据ID", "target_field": "关联客户"}
|
|
202
|
-
],
|
|
203
|
-
"default_values": {"跟进状态": "待跟进"},
|
|
204
|
-
"placement": "list"
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
"text": "详情创建跟进",
|
|
208
|
-
"action": "add_data",
|
|
209
|
-
"target_app_key": "FOLLOWUP_APP_KEY",
|
|
210
|
-
"field_mappings": [
|
|
211
|
-
{"source_field": "数据ID", "target_field": "关联客户"}
|
|
212
|
-
],
|
|
213
|
-
"default_values": {"跟进状态": "待跟进"},
|
|
214
|
-
"placement": "detail"
|
|
215
|
-
}
|
|
216
|
-
]
|
|
216
|
+
"default_values": {"跟进状态": "待跟进"},
|
|
217
|
+
"placement": "list"
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"text": "详情创建跟进",
|
|
221
|
+
"action": "add_data",
|
|
222
|
+
"target_app_key": "FOLLOWUP_APP_KEY",
|
|
223
|
+
"field_mappings": [
|
|
224
|
+
{"source_field": "数据ID", "target_field": "关联客户"}
|
|
225
|
+
],
|
|
226
|
+
"default_values": {"跟进状态": "待跟进"},
|
|
227
|
+
"placement": "detail"
|
|
217
228
|
}
|
|
218
229
|
]
|
|
219
230
|
}
|
|
@@ -236,47 +247,41 @@ CLI 将参数与文件内容拼装为服务层载荷,**顶层键**如下:
|
|
|
236
247
|
|
|
237
248
|
```json
|
|
238
249
|
{
|
|
239
|
-
"
|
|
250
|
+
"name": "生产工单执行视图",
|
|
251
|
+
"type": "table",
|
|
252
|
+
"columns": ["工单编号", "产品", "状态", "负责人"],
|
|
253
|
+
"filters": [
|
|
254
|
+
{"que_id": 101, "operator": "in", "values": ["生产中", "已完工"]}
|
|
255
|
+
],
|
|
256
|
+
"query_conditions": {
|
|
257
|
+
"enabled": true,
|
|
258
|
+
"exact": false,
|
|
259
|
+
"hide_before_query": false,
|
|
260
|
+
"rows": [["工单编号", "状态"], ["负责人"]]
|
|
261
|
+
},
|
|
262
|
+
"action_buttons": [
|
|
240
263
|
{
|
|
241
|
-
"
|
|
242
|
-
"
|
|
243
|
-
"
|
|
244
|
-
"
|
|
245
|
-
|
|
246
|
-
"filters": [
|
|
247
|
-
{"field_name": "状态", "operator": "in", "values": ["生产中", "已完工"]}
|
|
264
|
+
"text": "行内创建质检单",
|
|
265
|
+
"action": "add_data",
|
|
266
|
+
"target_app_key": "QUALITY_APP",
|
|
267
|
+
"field_mappings": [
|
|
268
|
+
{"source_field": "数据ID", "target_field": "关联工单"}
|
|
248
269
|
],
|
|
249
|
-
"
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
"
|
|
253
|
-
"rows": [["工单编号", "状态"], ["负责人"]]
|
|
254
|
-
},
|
|
255
|
-
"action_buttons": [
|
|
256
|
-
{
|
|
257
|
-
"text": "行内创建质检单",
|
|
258
|
-
"action": "add_data",
|
|
259
|
-
"target_app_key": "QUALITY_APP",
|
|
260
|
-
"field_mappings": [
|
|
261
|
-
{"source_field": "数据ID", "target_field": "关联工单"}
|
|
262
|
-
],
|
|
263
|
-
"default_values": {"处理状态": "待处理"},
|
|
264
|
-
"placement": "list",
|
|
265
|
-
"visible_when": [
|
|
266
|
-
{"field_name": "状态", "operator": "eq", "value": "已完工"}
|
|
267
|
-
]
|
|
268
|
-
},
|
|
269
|
-
{
|
|
270
|
-
"text": "详情创建返工单",
|
|
271
|
-
"action": "add_data",
|
|
272
|
-
"target_app_key": "REWORK_APP",
|
|
273
|
-
"field_mappings": [
|
|
274
|
-
{"source_field": "数据ID", "target_field": "关联工单"}
|
|
275
|
-
],
|
|
276
|
-
"default_values": {"返工状态": "待处理"},
|
|
277
|
-
"placement": "detail"
|
|
278
|
-
}
|
|
270
|
+
"default_values": {"处理状态": "待处理"},
|
|
271
|
+
"placement": "list",
|
|
272
|
+
"visible_when": [
|
|
273
|
+
{"que_id": 101, "operator": "eq", "value": "已完工"}
|
|
279
274
|
]
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
"text": "详情创建返工单",
|
|
278
|
+
"action": "add_data",
|
|
279
|
+
"target_app_key": "REWORK_APP",
|
|
280
|
+
"field_mappings": [
|
|
281
|
+
{"source_field": "数据ID", "target_field": "关联工单"}
|
|
282
|
+
],
|
|
283
|
+
"default_values": {"返工状态": "待处理"},
|
|
284
|
+
"placement": "detail"
|
|
280
285
|
}
|
|
281
286
|
]
|
|
282
287
|
}
|
|
@@ -286,8 +291,6 @@ CLI 将参数与文件内容拼装为服务层载荷,**顶层键**如下:
|
|
|
286
291
|
|
|
287
292
|
```json
|
|
288
293
|
{
|
|
289
|
-
"operation": "patch",
|
|
290
|
-
"app_key": "APP_KEY",
|
|
291
294
|
"view_key": "RAW_VIEW_KEY",
|
|
292
295
|
"set": {
|
|
293
296
|
"action_buttons": [
|
|
@@ -325,7 +328,7 @@ CLI 将参数与文件内容拼装为服务层载荷,**顶层键**如下:
|
|
|
325
328
|
- `publish=false` + `action_buttons` 会被阻断为 `VIEW_ACTION_BUTTONS_REQUIRE_PUBLISH`。
|
|
326
329
|
- 视图内按钮写智能体语义:`action: "add_data"` 或 `action: "link"`。不要手写底层触发名。
|
|
327
330
|
- 响应里同时看 `verification.views_verified`、`verification.action_buttons_verified`、`verification.view_button_bindings_verified`。
|
|
328
|
-
- 按钮失败但视图成功时是 `partial_success`;先按 `suggested_next_call`
|
|
331
|
+
- 按钮失败但视图成功时是 `partial_success`;先按 `suggested_next_call` 用 `patch_views` 重试失败视图。 如果回读里目标视图已有 `CUSTOM` 按钮、文本/位置/触发类型正确,不要重建视图或重复创建按钮。
|
|
329
332
|
|
|
330
333
|
`default_values` 是写入**目标应用新增表单**的静态默认值,必须按目标应用 schema 写:
|
|
331
334
|
|
|
@@ -333,7 +336,7 @@ CLI 将参数与文件内容拼装为服务层载荷,**顶层键**如下:
|
|
|
333
336
|
- 单选/多选字段的默认值必须来自目标字段 `options` 的**原始文本或 option id**;不要用业务近义词。例如 schema 选项是 `每月`、`中`,就不要写 `月检`、`普通`。
|
|
334
337
|
- 成员/部门/关联字段默认值按 record insert 的写法处理;不确定候选或目标记录时,先只写 `field_mappings`,不要编造 `default_values`。
|
|
335
338
|
- `field_mappings[].target_field` 和 `default_values` 的 key 都是**目标应用字段名**,不是当前视图字段名。
|
|
336
|
-
- CLI 会在按钮写入前校验默认值;非法值会返回 `CUSTOM_BUTTON_DEFAULT_VALUE_UNSUPPORTED`。这时视图可能已经创建成功,只需要用 `
|
|
339
|
+
- CLI 会在按钮写入前校验默认值;非法值会返回 `CUSTOM_BUTTON_DEFAULT_VALUE_UNSUPPORTED`。这时视图可能已经创建成功,只需要用 `patch_views` + raw `view_key` 修正 `action_buttons.default_values`,不要重建视图或应用。
|
|
337
340
|
|
|
338
341
|
正确示例:
|
|
339
342
|
|
|
@@ -386,10 +389,10 @@ CLI 将参数与文件内容拼装为服务层载荷,**顶层键**如下:
|
|
|
386
389
|
|
|
387
390
|
## 7. 删除视图
|
|
388
391
|
|
|
389
|
-
- 主路径使用
|
|
392
|
+
- 主路径使用 `--remove-views-file`;元素是 raw **`view_key`** 或唯一视图显示名,应用通过 `--app-key` 指定。
|
|
390
393
|
- 优先用 **`view_key`** 删除;显示名重名会导致 **`AMBIGUOUS_VIEW`**,需先在 **`app get views`** 里确认。
|
|
391
394
|
- DELETE 发出后,工具会用单个 **`view_key`** 回读验证,而不是再读全量视图列表判断删除结果。
|
|
392
|
-
- 删除结果看 **`verification.
|
|
395
|
+
- 删除结果看 **`verification.view.delete_executed`**、**`readback_status`**、**`safe_to_retry_delete`**:
|
|
393
396
|
- **`readback_status=deleted`**:删除已验证。
|
|
394
397
|
- **`readback_status=unavailable`** 或 **`still_exists`**:删除请求已发出但回读未完全确认,**不要盲目重复删除**;稍后用 `builder app get views` 或对应视图详情确认。
|
|
395
398
|
|
|
@@ -401,21 +404,15 @@ CLI 将参数与文件内容拼装为服务层载荷,**顶层键**如下:
|
|
|
401
404
|
|
|
402
405
|
```json
|
|
403
406
|
{
|
|
404
|
-
"
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
"selectors": {},
|
|
414
|
-
"external_mode": "not",
|
|
415
|
-
"external_selectors": {}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
]
|
|
407
|
+
"name": "项目台账视图",
|
|
408
|
+
"type": "table",
|
|
409
|
+
"columns": [101],
|
|
410
|
+
"visibility": {
|
|
411
|
+
"mode": "workspace",
|
|
412
|
+
"selectors": {},
|
|
413
|
+
"external_mode": "not",
|
|
414
|
+
"external_selectors": {}
|
|
415
|
+
}
|
|
419
416
|
}
|
|
420
417
|
```
|
|
421
418
|
|
|
@@ -423,70 +420,58 @@ CLI 将参数与文件内容拼装为服务层载荷,**顶层键**如下:
|
|
|
423
420
|
|
|
424
421
|
```json
|
|
425
422
|
{
|
|
426
|
-
"
|
|
423
|
+
"name": "项目甘特图",
|
|
424
|
+
"type": "gantt",
|
|
425
|
+
"columns": [101, 102, 103],
|
|
426
|
+
"start_que_id": 102,
|
|
427
|
+
"end_que_id": 103,
|
|
428
|
+
"title_que_id": 101,
|
|
429
|
+
"filters": [
|
|
427
430
|
{
|
|
428
|
-
"
|
|
429
|
-
"
|
|
430
|
-
"
|
|
431
|
-
"type": "gantt",
|
|
432
|
-
"columns": ["项目名称", "开始日期", "结束日期"],
|
|
433
|
-
"start_field": "开始日期",
|
|
434
|
-
"end_field": "结束日期",
|
|
435
|
-
"title_field": "项目名称",
|
|
436
|
-
"filters": [
|
|
437
|
-
{
|
|
438
|
-
"field_name": "状态",
|
|
439
|
-
"operator": "eq",
|
|
440
|
-
"value": "进行中"
|
|
441
|
-
}
|
|
442
|
-
]
|
|
431
|
+
"que_id": 101,
|
|
432
|
+
"operator": "eq",
|
|
433
|
+
"value": "进行中"
|
|
443
434
|
}
|
|
444
435
|
]
|
|
445
436
|
}
|
|
446
437
|
```
|
|
447
438
|
|
|
448
|
-
**Table + 筛选 + 查询条件 +
|
|
439
|
+
**Table + 筛选 + 查询条件 + 快捷按钮(单条 `--view-file`)**:
|
|
449
440
|
|
|
450
441
|
```json
|
|
451
442
|
{
|
|
452
|
-
"
|
|
443
|
+
"name": "客户查询视图",
|
|
444
|
+
"type": "table",
|
|
445
|
+
"columns": [101, 102, 103, 104],
|
|
446
|
+
"filters": [
|
|
447
|
+
{"que_id": 103, "operator": "eq", "value": "有效"}
|
|
448
|
+
],
|
|
449
|
+
"query_conditions": {
|
|
450
|
+
"enabled": true,
|
|
451
|
+
"exact": false,
|
|
452
|
+
"hide_before_query": false,
|
|
453
|
+
"rows": [["客户名称", "负责人"], ["创建时间"]]
|
|
454
|
+
},
|
|
455
|
+
"action_buttons": [
|
|
453
456
|
{
|
|
454
|
-
"
|
|
455
|
-
"
|
|
456
|
-
"
|
|
457
|
-
"
|
|
458
|
-
|
|
459
|
-
"filters": [
|
|
460
|
-
{"field_name": "客户状态", "operator": "eq", "value": "有效"}
|
|
457
|
+
"text": "行内创建跟进",
|
|
458
|
+
"action": "add_data",
|
|
459
|
+
"target_app_key": "FOLLOWUP_APP_KEY",
|
|
460
|
+
"field_mappings": [
|
|
461
|
+
{"source_field": "数据ID", "target_field": "关联客户"}
|
|
461
462
|
],
|
|
462
|
-
"
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
"
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
{"source_field": "数据ID", "target_field": "关联客户"}
|
|
475
|
-
],
|
|
476
|
-
"default_values": {"跟进状态": "待跟进"},
|
|
477
|
-
"placement": "list"
|
|
478
|
-
},
|
|
479
|
-
{
|
|
480
|
-
"text": "详情创建跟进",
|
|
481
|
-
"action": "add_data",
|
|
482
|
-
"target_app_key": "FOLLOWUP_APP_KEY",
|
|
483
|
-
"field_mappings": [
|
|
484
|
-
{"source_field": "数据ID", "target_field": "关联客户"}
|
|
485
|
-
],
|
|
486
|
-
"default_values": {"跟进状态": "待跟进"},
|
|
487
|
-
"placement": "detail"
|
|
488
|
-
}
|
|
489
|
-
]
|
|
463
|
+
"default_values": {"跟进状态": "待跟进"},
|
|
464
|
+
"placement": "list"
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
"text": "详情创建跟进",
|
|
468
|
+
"action": "add_data",
|
|
469
|
+
"target_app_key": "FOLLOWUP_APP_KEY",
|
|
470
|
+
"field_mappings": [
|
|
471
|
+
{"source_field": "数据ID", "target_field": "关联客户"}
|
|
472
|
+
],
|
|
473
|
+
"default_values": {"跟进状态": "待跟进"},
|
|
474
|
+
"placement": "detail"
|
|
490
475
|
}
|
|
491
476
|
]
|
|
492
477
|
}
|
|
@@ -508,11 +493,12 @@ qingflow --json builder app get --app-key ead8ims5i401 views
|
|
|
508
493
|
|
|
509
494
|
```bash
|
|
510
495
|
qingflow builder views apply \
|
|
496
|
+
--app-key ead8ims5i401 \
|
|
511
497
|
--publish \
|
|
512
|
-
--
|
|
498
|
+
--view-file ./view.json
|
|
513
499
|
```
|
|
514
500
|
|
|
515
|
-
**结果摘要**(示例视图名 **`示例业务操作视图_CLI模板`**):`status: success`,`verification.
|
|
501
|
+
**结果摘要**(示例视图名 **`示例业务操作视图_CLI模板`**):`status: success`,`verification.view.status: created`,返回 **`view_key`**(如 **`ecdk22v65c02`**),并验证 `query_conditions` 与 `action_buttons`。
|
|
516
502
|
|
|
517
503
|
---
|
|
518
504
|
|
|
@@ -524,7 +510,7 @@ qingflow builder views apply \
|
|
|
524
510
|
| 重名视图 / 删错 | **`app get views`**;更新时带 **`view_key`** |
|
|
525
511
|
| 过滤器未生效疑虑 | 看响应 **`verification.view_filters_verified`**;必要时再 **`app get views`** |
|
|
526
512
|
| 查询条件未生效疑虑 | 看响应 **`verification.view_query_conditions_verified`**;不要把 `query_conditions` 当 `filters` |
|
|
527
|
-
| 后端 **40038 / Object not exist** | 先读失败项 **`details.
|
|
513
|
+
| 后端 **40038 / Object not exist** | 先读失败项 **`details.view_result.details.field_level_diagnostics`** 与 **`recommended_minimal_retry`**。不要删除字段、不要重建应用;先按最小列重试,再逐步加非关键列/筛选。 |
|
|
528
514
|
| 按钮/关联资源配置 | 常规业务按钮随视图写 **`action_buttons`**;关联视图/报表走 **`builder associated-resource apply`** |
|
|
529
515
|
| 部分失败 | 读 **`views_diff.failed`** 与契约 **execution_notes** 中 **partial_success** |
|
|
530
516
|
| 权限/锁 | 同 [publish verify](./99-publish-verify.md) 与 [app delivery reference](./reference/app-delivery-sop.md) |
|
|
@@ -533,7 +519,7 @@ qingflow builder views apply \
|
|
|
533
519
|
|
|
534
520
|
## 11. 交叉引用
|
|
535
521
|
|
|
536
|
-
- [views_upsert_table_minimal.example.json](../examples/views/views_upsert_table_minimal.example.json)
|
|
522
|
+
- [views_upsert_table_minimal.example.json](../examples/views/views_upsert_table_minimal.example.json)(单视图示例,可直接作为 `--view-file` 使用)
|
|
537
523
|
- [SKILL.md](../../SKILL.md):builder 直接输出、鉴权顺序。
|
|
538
524
|
- [99-publish-verify.md](./99-publish-verify.md):发布与回读。
|
|
539
525
|
- [reference/app-delivery-sop.md](./reference/app-delivery-sop.md):系统列与历史交付细节。
|
|
@@ -38,7 +38,7 @@ Rules:
|
|
|
38
38
|
- Portal chart sections use one `chart` field: `{"chart_id":"..."}` to reuse an existing QingBI chart, or `{"app_key":"...","name":"...","chart_type":"...",...}` to create/update the needed chart inline.
|
|
39
39
|
- Buttons are part of the view design: declare them in the target view with `action_buttons`.
|
|
40
40
|
- Update instructions live inside the resource document. Do not use `update-*` as a separate main route.
|
|
41
|
-
- Batch read, batch write, and patch are resource-specific variants, not separate workflows.
|
|
41
|
+
- Batch read, batch write, and patch are resource-specific variants, not separate workflows. View writes are single-operation: `builder views apply` handles one view per call; consult [50-views.md](./50-views.md) before preparing multiple view changes.
|
|
42
42
|
|
|
43
43
|
## File responsibilities
|
|
44
44
|
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
这些不是独立流程,而是读、写、改三个动作的多资源形态:
|
|
42
42
|
|
|
43
43
|
- **批量读**:读多个应用时优先用 `--app-keys`,例如 `builder app get --app-keys a,b fields`、`builder app get --app-keys a,b views`、`builder button get --app-keys a,b`、`builder associated-resource get --app-keys a,b`、`builder publish verify --app-keys a,b`。读结果同时看成功项和 `errors[]`,不要因单个应用失败否定全批。
|
|
44
|
-
-
|
|
44
|
+
- **批量写**:仅在资源契约明确支持时使用批量文件;视图例外,`builder views apply` 每次只提交一个视图的一种操作,不支持多视图批量创建或把 upsert/patch/remove 混在一次调用中。应用表单不走 `apps-file`,而是每个应用独立提交完整 AppForm 声明。
|
|
45
45
|
- **局部改**:只改已存在资源的少量参数时用对应 patch 文件;`patch_*` 内只写目标定位字段和 `set/unset`,不要用不完整 `upsert_*` 伪装局部更新。
|
|
46
46
|
|
|
47
47
|
---
|
|
@@ -374,14 +374,14 @@ qingflow builder flow apply \
|
|
|
374
374
|
|
|
375
375
|
后端很多保存接口仍是整份配置保存,但新版 CLI/MCP 对外提供 **public partial patch**:
|
|
376
376
|
|
|
377
|
-
- 已有视图:`builder views apply --views-file
|
|
377
|
+
- 已有视图:`builder views apply --app-key APP_KEY --patch-views-file PATH`,文件只放一项,写 `view_key`/`name` 与 `set`/`unset`
|
|
378
378
|
- 已有按钮:`builder button apply --patch-buttons-file`
|
|
379
379
|
- 已有关联资源:`builder associated-resource apply --patch-resources-file`
|
|
380
380
|
- 已有报表:`builder charts apply --patch-file`
|
|
381
381
|
- 已有流程节点:`builder flow apply --patch-nodes-file`
|
|
382
382
|
- 已有门户组件:`builder portal apply --patch-sections-file`
|
|
383
383
|
|
|
384
|
-
局部更新的语义是:你只写要替换的参数,工具内部读取当前配置、补齐后端必填字段,再整份保存。每个 patch 项直接使用对象真实定位字段 + `set` / 可选 `unset`,不要写字面量 `selector` key。示例:`{"
|
|
384
|
+
局部更新的语义是:你只写要替换的参数,工具内部读取当前配置、补齐后端必填字段,再整份保存。每个 patch 项直接使用对象真实定位字段 + `set` / 可选 `unset`,不要写字面量 `selector` key。示例:`{"view_key":"VIEW_KEY","set":{"query_conditions":{...}}}`、`{"button_id": 1001, "set": {"button_text": "新名称"}}`、`{"associated_item_id": 123, "set": {"match_mappings": [...]}}`、`{"chart_id": 456, "set": {"name": "新报表名"}}`、`{"id": "node_1", "set": {"name": "主管审批"}}`、`{"chart": {"chart_key": "xxx"}, "set": {"rows": 7}}`。
|
|
385
385
|
|
|
386
386
|
`upsert_*` 用于创建或提供完整目标配置;不要只给 `name/type/query_conditions` 这类局部片段然后期待后端自动合并。
|
|
387
387
|
|