cloud-web-corejs 1.0.277 → 1.0.279
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/package.json +3 -1
- package/src/components/formDialog/README.md +371 -0
- package/src/components/formDialog/actionButtons.js +90 -0
- package/src/components/formDialog/index.js +48 -0
- package/src/components/formDialog/index.vue +802 -0
- package/src/components/formDrawer/drawer.vue +722 -0
- package/src/components/formDrawer/index.js +50 -0
- package/src/views/user/menu/voc_list.vue +686 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloud-web-corejs",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.279",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vue-cli-service serve",
|
|
7
7
|
"lint": "eslint --ext .js,.vue src",
|
|
@@ -168,6 +168,8 @@
|
|
|
168
168
|
"src/components/xform",
|
|
169
169
|
"src/components/xhsPrint",
|
|
170
170
|
"src/components/langTag",
|
|
171
|
+
"src/components/formDialog",
|
|
172
|
+
"src/components/formDrawer",
|
|
171
173
|
"src/api",
|
|
172
174
|
"src/views/bd",
|
|
173
175
|
"src/views/support",
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# $formDialog 表单弹框
|
|
2
|
+
|
|
3
|
+
JS 调用式表单弹框:传入字段配置即可打开一个可编辑表单,确认后以 Promise 返回表单数据,无需为每个小表单单独编写 `.vue` 弹框组件。
|
|
4
|
+
|
|
5
|
+
- 组件位置:`src/components/formDialog/`
|
|
6
|
+
- 全局注册:`src/index.js` 中 `Vue.use(FormDialog)`,挂载为 `Vue.prototype.$formDialog`
|
|
7
|
+
- 示例页面:`src/views/support/form-dialog-demo/index.vue`(路由 `/support/form-dialog-demo`,11 个可运行示例)
|
|
8
|
+
|
|
9
|
+
两种模式:
|
|
10
|
+
|
|
11
|
+
1. **字段配置模式**(默认)——传 `fields` 数组,由本组件渲染 element 控件;
|
|
12
|
+
2. **xform 整表模式**——传 `formCode`(或 `formJson`),弹框内渲染设计器里配置好的完整 xform 表单,见「xform 整表模式」一节。
|
|
13
|
+
|
|
14
|
+
## 快速上手
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
const data = await this.$formDialog({
|
|
18
|
+
title: "新增分类",
|
|
19
|
+
fields: [
|
|
20
|
+
{ prop: "name", label: "名称", required: true },
|
|
21
|
+
{
|
|
22
|
+
prop: "type",
|
|
23
|
+
label: "类型",
|
|
24
|
+
type: "select",
|
|
25
|
+
options: [
|
|
26
|
+
{ label: "普通", value: 1 },
|
|
27
|
+
{ label: "重要", value: 2 },
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
{ prop: "enabled", label: "启用", type: "switch" },
|
|
31
|
+
{ prop: "remark", label: "备注", type: "textarea" },
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
// 点确定且校验通过 => data 为表单数据对象
|
|
35
|
+
// 取消 / 右上角关闭 => data 为 null(不使用 reject,无需 catch)
|
|
36
|
+
if (data !== null) {
|
|
37
|
+
// ...
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 顶层 options
|
|
42
|
+
|
|
43
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
44
|
+
|---|---|---|---|
|
|
45
|
+
| `title` | String | `""` | 弹框标题 |
|
|
46
|
+
| `width` | String | `"600px"` | 弹框宽度 |
|
|
47
|
+
| `labelWidth` | String | `"100px"` | 表单 label 宽度 |
|
|
48
|
+
| `model` | Object | `{}` | 初始值(编辑回显)。未提供的字段按类型补默认值 |
|
|
49
|
+
| `fields` | Array | `[]` | 字段配置,见下 |
|
|
50
|
+
| `rules` | Object | `{}` | 额外的 el-form 校验规则,与字段级 `required`/`rules` 合并 |
|
|
51
|
+
| `readonly` | Boolean | `false` | 只读查看模式:整表单禁用,底部只有"关闭"按钮 |
|
|
52
|
+
| `beforeConfirm` | Function | - | `(data) => Promise`。确认前钩子(常用于调保存接口),reject 则弹框不关闭;执行期间确定按钮 loading 防重复提交 |
|
|
53
|
+
| `formCode` / `formJson` | String / Object | - | 传入即切换为 **xform 整表模式**(`fields` 不再生效),见「xform 整表模式」一节 |
|
|
54
|
+
|
|
55
|
+
返回值:`Promise`。确认(校验通过、beforeConfirm 成功)resolve 表单数据浅拷贝;取消/关闭 resolve `null`。
|
|
56
|
+
|
|
57
|
+
## 字段配置(field)
|
|
58
|
+
|
|
59
|
+
### 通用属性
|
|
60
|
+
|
|
61
|
+
| 属性 | 类型 | 说明 |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `prop` | String | 字段名(必填,作为 model 的 key) |
|
|
64
|
+
| `label` | String | 标签文字 |
|
|
65
|
+
| `type` | String | 控件类型,缺省为 `input`,见下表 |
|
|
66
|
+
| `span` | Number | 栅格宽度(24 为一行一个,默认 24) |
|
|
67
|
+
| `required` | Boolean | 必填(自动生成"XX不能为空"规则) |
|
|
68
|
+
| `rules` | Array/Object | 字段级校验规则 |
|
|
69
|
+
| `placeholder` | String | 占位文案 |
|
|
70
|
+
| `disabled` | Boolean / `(model) => Boolean` | 禁用(支持函数联动) |
|
|
71
|
+
| `visible` | Boolean / `(model) => Boolean` | 显隐(支持函数联动;隐藏时不渲染、不参与校验) |
|
|
72
|
+
| `props` | Object | 透传给底层 element 控件的 props(如 `min`/`max`/`multiple`/`valueFormat` 等,可覆盖默认值) |
|
|
73
|
+
| `render` | `(h, model, field) => VNode` | 自定义渲染逃生口,内置类型覆盖不了时使用(配置了 render 则忽略 type) |
|
|
74
|
+
| `onChange` | `(value, model, field, dialog) => void` | 值变化回调,见「联动」 |
|
|
75
|
+
| `dependsOn` | Array\<String\> | 依赖字段列表,见「联动」 |
|
|
76
|
+
|
|
77
|
+
### 控件类型(type)
|
|
78
|
+
|
|
79
|
+
| type | 控件 | 值类型 | 说明 |
|
|
80
|
+
|---|---|---|---|
|
|
81
|
+
| `input`(默认) | el-input | String | |
|
|
82
|
+
| `textarea` | el-input type=textarea | String | 默认 3 行 |
|
|
83
|
+
| `number` | baseInputNumber | Number | 字段级 `scale`(小数位,默认 2,0=整数)、`negative`(允许负数);`min`/`max` 走 `props` |
|
|
84
|
+
| `select` | el-select | 任意 | 三种数据源见下节 |
|
|
85
|
+
| `date` | el-date-picker | String | value-format 默认 `yyyy-MM-dd` |
|
|
86
|
+
| `datetime` | el-date-picker | String | value-format 默认 `yyyy-MM-dd HH:mm:ss` |
|
|
87
|
+
| `daterange` | el-date-picker | Array | value-format 默认 `yyyy-MM-dd` |
|
|
88
|
+
| `switch` | el-switch | Boolean | |
|
|
89
|
+
| `radio` | el-radio-group | 任意 | 用 `options` |
|
|
90
|
+
| `checkbox` | el-checkbox-group | Array | 用 `options` |
|
|
91
|
+
| `search` | el-input + 搜索图标 | String | 通用搜索框样式(同 xform vabsearch),见「搜索框」 |
|
|
92
|
+
|
|
93
|
+
## 下拉数据源(select / radio / checkbox 通用)
|
|
94
|
+
|
|
95
|
+
三选一:
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
// 1. 静态值:数组(或函数,见「联动」)
|
|
99
|
+
{ prop: "type", type: "select", options: [{ label: "普通", value: 1 }, "直接字符串也可以"] }
|
|
100
|
+
|
|
101
|
+
// 2. 词汇:/user/common_attribute/listItems,显示 value、取值 sn(与 xform 约定一致)
|
|
102
|
+
{ prop: "sex", type: "select", dictCode: "SEX", dictData: { ... } }
|
|
103
|
+
|
|
104
|
+
// 3. 脚本编码:"formCode/scriptCode",先取服务名再请求 /{serviceName}/bd_api/{formCode}/{scriptCode}
|
|
105
|
+
{
|
|
106
|
+
prop: "org", type: "select",
|
|
107
|
+
scriptCode: "demoForm/getOptions",
|
|
108
|
+
scriptData: { ... }, // 请求参数,也支持 (model) => ({...})
|
|
109
|
+
labelField: "name", // 默认 "label"
|
|
110
|
+
valueField: "id", // 默认 "value"
|
|
111
|
+
// 或完全自定义解析:
|
|
112
|
+
// parseOptions: (res) => [{ label, value }],
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
远程选项加载中 select 显示 loading。脚本模式选项对象上带 `row`(原始行数据)。
|
|
117
|
+
|
|
118
|
+
## 搜索框(type: "search")
|
|
119
|
+
|
|
120
|
+
样式与 xform vabsearch 控件一致:输入框内右侧 `el-icon-search` 图标 + 悬停清除图标。输入框默认禁止手输(值由选择回填),传 `props: { editable: true }` 放开。
|
|
121
|
+
|
|
122
|
+
点击搜索图标的行为,三选一(优先级从上到下):
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
// 方式1:onSearch 完全自定义(自己开弹框、自己回写 model;model 是响应式的)
|
|
126
|
+
{
|
|
127
|
+
prop: "customerName", label: "客户", type: "search",
|
|
128
|
+
onSearch: async (model, field) => { /* ... */ },
|
|
129
|
+
onClear: (model, field) => { model.customerId = null; model.customerName = null; },
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 方式2:searchComponent —— 打开自定义 Vue 弹框组件(走 utils/componentDialog)
|
|
133
|
+
{
|
|
134
|
+
prop: "supplierName", label: "供应商", type: "search",
|
|
135
|
+
searchComponent: {
|
|
136
|
+
// 路径加载只收集 dialog/ 目录、*Dialog.vue、*Dialog/index.vue
|
|
137
|
+
componentPath: "views/xx/pickerDialog.vue",
|
|
138
|
+
// 也可直接传 component,或传 componentLoader: () => import("@/views/xx/picker.vue")
|
|
139
|
+
fieldMap: { supplierId: "id", supplierName: "name" },
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
// 组件契约:自带弹框(wrapper 默认 false)、确认时 $emit("confirm", rows)、
|
|
143
|
+
// 关闭时 $emit("update:visiable", false)。参考 demo 的 demoPickerDialog.vue
|
|
144
|
+
|
|
145
|
+
// 方式3:searchDialog —— 打开 xform 搜索弹框(searchFormDialog,按 formCode 渲染数据表格)
|
|
146
|
+
{
|
|
147
|
+
prop: "billName", label: "单据", type: "search",
|
|
148
|
+
searchDialog: {
|
|
149
|
+
formCode: "xxx", // 目标表单编码(需已配置搜索弹框)
|
|
150
|
+
multiple: false,
|
|
151
|
+
queryParam: { ... }, // 可选查询参数
|
|
152
|
+
rows: (model) => [...], // 可选回显已选行,支持函数
|
|
153
|
+
fieldMap: { billId: "id", billName: "_tt" },
|
|
154
|
+
// layoutType: "PC", // 默认 PC
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
选中回写规则(方式2/3 共用,优先级从上到下):
|
|
160
|
+
|
|
161
|
+
1. `onConfirm(model, rows, field)` —— 完全自定义
|
|
162
|
+
2. `fieldMap: { model字段: 行字段名 | (row, rows) => value }` —— 逐字段映射(函数形式可处理多选拼接)
|
|
163
|
+
3. 默认把首行中与 `prop` 同名(或 `showField` 指定)的字段写回本字段
|
|
164
|
+
|
|
165
|
+
清除图标触发 `onClear(model, field)`,不传则默认清空本字段;`clearable: false` 隐藏清除。
|
|
166
|
+
|
|
167
|
+
## 联动
|
|
168
|
+
|
|
169
|
+
四种机制可组合:
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
fields: [
|
|
173
|
+
{
|
|
174
|
+
prop: "province", label: "省份", type: "select", options: [...],
|
|
175
|
+
// 1. onChange:任意字段值变化时触发(含搜索框 fieldMap 回写、render 控件改 model)
|
|
176
|
+
// dialog 上可用:dialog.setFieldOptions(prop, options) 手动设选项
|
|
177
|
+
// dialog.reloadFieldOptions(prop) 按最新 model 重拉词汇/脚本选项
|
|
178
|
+
onChange: (val, model, field, dialog) => { ... },
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
prop: "city", label: "城市", type: "select",
|
|
182
|
+
// 2. dependsOn:依赖字段变化时自动清空自身值;
|
|
183
|
+
// 若选项来自 dictCode/scriptCode,会按最新 model 自动重新拉取
|
|
184
|
+
dependsOn: ["province"],
|
|
185
|
+
// 静态级联:options 支持函数,渲染时实时按 model 求值
|
|
186
|
+
options: (model) => cityMap[model.province] || [],
|
|
187
|
+
// 远程级联传参:dictData / scriptData 支持 (model) => ({...})
|
|
188
|
+
},
|
|
189
|
+
// 3. 显隐联动:隐藏时不渲染、不参与必填校验
|
|
190
|
+
{ prop: "taxNo", label: "税号", required: true, visible: (model) => model.needInvoice },
|
|
191
|
+
// 4. 禁用联动
|
|
192
|
+
{ prop: "reason", label: "原因", disabled: (model) => model.level !== 2 },
|
|
193
|
+
]
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
实现方式为深度监听 model + 快照 diff,链式联动(A 变 → B 清 → B 的 onChange)可用;避免在 onChange 里循环写值。
|
|
197
|
+
|
|
198
|
+
## xform 整表模式
|
|
199
|
+
|
|
200
|
+
传 `formCode`(或直接传 `formJson`)即进入整表模式:不再走 `fields` 渲染,而是按 `USER_PREFIX + /formTemplate/getByFormCode` 加载表单模板(与 `views/user/form/vform/render.vue` 同链路),弹框内用 `VFormRender` 渲染完整 xform 表单——业务控件、明细表、脚本事件等设计器能力全部可用。
|
|
201
|
+
|
|
202
|
+
```js
|
|
203
|
+
const data = await this.$formDialog({
|
|
204
|
+
formCode: "xxx", // 表单模板编码(与 formJson 二选一,formJson 直接传模板 JSON 对象)
|
|
205
|
+
// title: "自定义标题", // 缺省取表单模板名称
|
|
206
|
+
// width: "1000px", // 整表模式默认 1000px
|
|
207
|
+
model: { name: "初始值" }, // 初始数据,结构与 getFormData 一致(也可用 formData 字段名)
|
|
208
|
+
// readonly: true, // 只读:走 xform setReadMode,底部只有"关闭"
|
|
209
|
+
// optionData: {}, // 透传 VFormRender 的 option-data
|
|
210
|
+
// globalDsv: {}, // 透传 VFormRender 的 global-dsv
|
|
211
|
+
onReady: (formRef, dialog) => {
|
|
212
|
+
// 表单挂载后回调,formRef 即 VFormRender 实例:
|
|
213
|
+
// formRef.setFieldValue / getWidgetRef / disableForm(...) 等 API 均可用
|
|
214
|
+
},
|
|
215
|
+
beforeConfirm: (formData) => this.$http({ ... }), // 同字段模式,reject 不关闭
|
|
216
|
+
});
|
|
217
|
+
// data 为 xform getFormData 的完整表单数据(含明细表);取消/关闭仍为 null
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
说明:
|
|
221
|
+
|
|
222
|
+
- 确认时走 xform 自身的 `validateForm`(失败自动提示"必填项不能为空"并滚动定位到出错字段),通过后 resolve `getFormData` 的深拷贝。
|
|
223
|
+
- `VFormRender` 为异步组件(独立 chunk),只在首次打开整表模式时加载,不增加主包体积;加载前会自动执行 `loadExtension()` 注册 xform 扩展控件。
|
|
224
|
+
- 弹框规格与 xform 自己的 `dynamicDialogRender` 一致(`dialog-style list-dialog` + `.cont` 容器)。
|
|
225
|
+
- 整表模式下 `fields`/`rules`/联动等字段配置模式的参数不生效——表单结构、校验、联动都在设计器里配置。
|
|
226
|
+
|
|
227
|
+
## 其他说明
|
|
228
|
+
|
|
229
|
+
- 每次调用创建独立实例,支持叠开多层弹框(如在 `onSearch` 里再开一层 `$formDialog`,见 demo 示例 7/8)。
|
|
230
|
+
- 弹框规格与项目一致:`append-to-body`、禁点遮罩关闭、可拖拽(`v-el-drag-dialog`)、`dialog-style` 样式类。
|
|
231
|
+
- 实例挂在 `window.$vueRoot` 之下,弹框内可正常使用 `$t2`、`$http`、`$getBaseDicts` 等原型能力。
|
|
232
|
+
- 也可不经 Vue 实例调用:`import FormDialog from "@base/components/formDialog/index.js"; FormDialog.open({...})`。
|
|
233
|
+
- 数字框底层为 `baseInputNumber`(`v-limit-number` 指令限制小数位/负号,支持按键、选区替换与粘贴校验)。
|
|
234
|
+
|
|
235
|
+
## 自定义按钮(formConfig.actionButtons)
|
|
236
|
+
|
|
237
|
+
xform 整表模式下,底部按钮可在**表单设计器 → 表单设置 → 操作按钮**里配置(存模板 `formConfig.actionButtons`,随模板保存)。未配置则保持默认"取消/确定"。
|
|
238
|
+
|
|
239
|
+
```js
|
|
240
|
+
// 设计器里每个按钮的配置项
|
|
241
|
+
{
|
|
242
|
+
code: "submit", // 按钮标识(必填),resolve 时区分是哪个按钮
|
|
243
|
+
label: "提 交",
|
|
244
|
+
type: "primary", plain: false, icon: "el-icon-check",
|
|
245
|
+
authName: "", // 权限唯一名称,完整权限编码 = formCode:authName
|
|
246
|
+
needValidate: true, // 点击前先 validateForm
|
|
247
|
+
closeAfter: true, // 脚本成功后自动收口并关闭
|
|
248
|
+
visible: "...", // 显隐脚本:visible(button),返回 false 隐藏
|
|
249
|
+
onClick: "...", // 点击脚本:onClick(formData, dialog, done, selection)
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
规则:
|
|
254
|
+
|
|
255
|
+
- **权限**:禁用清单机制(`dispermissions`)——配了 `authName` 且 `formCode:authName` 被禁用则不渲染;未配 `authName` 默认放行。与 visible 脚本是 AND 关系。
|
|
256
|
+
- **onClick 脚本**:`this` = VFormRender 实例(`getFormData`/`getWidgetRef`/`$http` 等可用);`done(data)` 结算 Promise(缺省 data 取表单数据);返回 `false` 或 reject 的 Promise 不关闭;未显式调 `done` 且 `closeAfter` 时自动以表单数据收口。执行期间按钮 loading 防重复提交。
|
|
257
|
+
- **resolve 语义**:配置了自定义按钮后 resolve `{ action: code, data }`;默认按钮仍是 `data`;取消/关闭仍是 `null`(存量调用不受影响)。
|
|
258
|
+
- 调用方可传 `buttons: [...]`(同结构)覆盖模板配置;readonly 模式只显示"关闭"。
|
|
259
|
+
- 弹框/抽屉共用同一套配置与语义(见下节 $formDrawer)。
|
|
260
|
+
- **三个壳都吃这份配置**:全局 `$formDialog` / `$formDrawer`,以及表单脚本内部弹框通道
|
|
261
|
+
`openFormDialog`(`xform/form-designer/form-widget/dialog/formDialog.vue`)。
|
|
262
|
+
一份模板配置,三处生效。内部通道收口时回调 `option.onAction(payload, formRef, vm)`
|
|
263
|
+
(不传则仅按 `closeAfter` 关闭,不再走 `option.confirm`——按钮自身的 onClick 脚本即业务处理)。
|
|
264
|
+
> `openFormDrawer` 曾有一个同目录的内部抽屉壳,因零调用方且样式选择器写成 `.el-dialog__body`
|
|
265
|
+
> (el-drawer 下不匹配、内容区没高度)已删除;该方法现委托给全局 `$formDrawer`,
|
|
266
|
+
> 调用签名保持兼容,但 `bodyConfig` / `footerConfig` / `multiple` / `onAction` 四个字段不再生效。
|
|
267
|
+
|
|
268
|
+
## $formDrawer 表单抽屉
|
|
269
|
+
|
|
270
|
+
el-drawer 形态的 formCode 承载壳,全局方法 `this.$formDrawer(options)`(`src/components/formDrawer/`,`Vue.use(FormDrawer)` 注册)。仅支持 xform 整表模式(必传 `formCode`),**不支持 H5**。
|
|
271
|
+
|
|
272
|
+
```js
|
|
273
|
+
const result = await this.$formDrawer({
|
|
274
|
+
formCode: "xxx", // 必填
|
|
275
|
+
// title: "自定义标题", // 缺省取模板名称
|
|
276
|
+
// size: "62%", direction: "rtl", // el-drawer 外观,drawerConfig 可整体透传
|
|
277
|
+
// dataId: row.id, // 详情单据 id —— 详情场景必传,xform 靠它加载单据数据
|
|
278
|
+
// model: {...}, // 初始表单数据(新增态/自定义数据时用)
|
|
279
|
+
// param: {...}, // 透传 VFormRender 的 param(只给模板脚本用,不参与请求)
|
|
280
|
+
// queryParam: {...}, // 合并进目标表单查询表格的请求参数(host:"list" 下不生效),见下
|
|
281
|
+
// { warehouseId: 7 } // 平铺参数:给后台脚本读(formScriptParam 那套约定)
|
|
282
|
+
// { condition: { status: 1, type: ["A","B"] } } // 结构化条件:追加进查询区组装的 condition,同字段以此为准
|
|
283
|
+
// conditionParam: { objId, objTypeCode, taskId }, // 流程/权限上下文
|
|
284
|
+
// fillHeight: true, // 强制内容区撑满(列表型模板默认自动开启)
|
|
285
|
+
// host: "list", // 用列表宿主页承载,带「常规(详情)/列表」页签,见下
|
|
286
|
+
// plain: true, // 详情素身模式:去标题栏、去模块导航,只留模块
|
|
287
|
+
// select: { // 查询表格选择模式(列表型模板)
|
|
288
|
+
// multiple: true, rows: [...], fieldKey: "id",
|
|
289
|
+
// queryParam: {...}, onConfirm: (rows, formRef, drawer) => {},
|
|
290
|
+
// },
|
|
291
|
+
// buttons: [...], // 覆盖模板 actionButtons
|
|
292
|
+
// showFooter: false, readonly: true, onReady, beforeConfirm,
|
|
293
|
+
});
|
|
294
|
+
// 默认按钮:select 模式 resolve 选中行数组,表单模式 resolve 表单数据;
|
|
295
|
+
// 自定义按钮 resolve {action, data};取消/关闭 resolve null
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
说明:
|
|
299
|
+
|
|
300
|
+
- **select 模式**:行为对齐 xform 的 searchFormDialog——行点选/全选、单选双击直接确认、多选底部已选清单;`fieldKey` 缺省取模板 `searchDialogUniqueField`。自定义按钮脚本的 `selection` 参数即当前选中行。
|
|
301
|
+
- **plain 模式**:详情模板"只有一个个模块"——不渲染单据标题栏与模块导航(走 VFormRender 的 `plainDetailMode`,是渲染时覆盖,不改模板配置)。约定:抽屉不服务流程表单(wfEnabled)。
|
|
302
|
+
详情容器另有两个**模板级**开关(设计器选中详情容器后的属性面板),与 plain 是"任一命中即隐藏"的叠加关系:`hideHeader` 整条标题栏不渲染、`hideNav` 隐藏模块导航。详情页本身也要素身时用模板开关,只在抽屉里素身时用 `plain`。
|
|
303
|
+
- **`host: "list"` 列表宿主模式**:抽屉里要"从列表点进详情"时用。此模式不再直接渲染单个表单,
|
|
304
|
+
而是承载列表宿主页 `views/user/form/view/list.vue`,于是「常规(详情)」「列表」两个页签、其他自动页签、
|
|
305
|
+
多标签编辑、翻单等行为与整页完全一致——点行进详情走的是模板自己的 `editFormCode`,无需额外接线。
|
|
306
|
+
|
|
307
|
+
```js
|
|
308
|
+
this.$formDrawer({ formCode: "purchaseOrderList", host: "list", param: { status: 1 } });
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
与普通模式的差别:默认 `size` 为 85%(要容纳详情);底部 footer 默认**不显示**(列表页自带按钮区),
|
|
312
|
+
显式传 `showFooter: true` 才出;`plain` / `select` / `actionButtons` 在此模式下不生效——
|
|
313
|
+
抽屉只作视口,页内行为全部由宿主页负责。抽屉本身仍可 `await`,取消/关闭 resolve `null`。
|
|
314
|
+
|
|
315
|
+
表单脚本内部弹框通道 `openFormDialog` 同样支持 `host: "list"`。
|
|
316
|
+
弹框形态嵌套层级更深(表单 → 弹框 → 列表页 → 详情),建议配 `dialogConfig: { fullscreen: true }`
|
|
317
|
+
或足够大的 width。抽屉形态直接调全局 `this.$formDrawer({ host: "list" })`
|
|
318
|
+
(`openFormDrawer` 也行,它就是这个方法的薄委托)。
|
|
319
|
+
- **详情必传 `dataId`**:xform 的 `loadDefaultEntityData` 要 `formConfig.entity && dataId && 脚本编码` 三者齐全才发请求加载单据数据;只传 `conditionParam` 不会加载,表单会是空的。
|
|
320
|
+
- **样式上下文**:抽屉内容区套了 `<div id="containt" class="designer-view">`,与 `views/user/form/vform/render.vue`、xform formDialog 一致。全站渲染态样式都挂在这两个钩子上(`style.scss` 的 `#containt .detail-wrap{...}` 与 `.preview-dialog,.designer-view{...}`),不套这层,详情场景会丢:头部右侧按钮的 inline-block(竖排换行)、`.d-item` 右侧 112px 的导航占位(模块导航压住数据表格)、`.detail-wrap` 的 `position:relative`(导航跑去锚 `.el-drawer`,被裁在抽屉外)、模块边框与内边距。
|
|
321
|
+
- **表格铺满高度**:查询表格的 vxe-grid 用 `height="auto"`,按父元素实测高度计算,父链没有确定高度就会塌成一小块。抽屉不在全局 `.dialog-style .el-dialog__body .data-table_height` 选择器下,故由抽屉自己补高度链:模板顶层只有一个查询表格(`data-table` + `isQueryTable`)时自动开启;模板结构特殊(表格嵌在 tab/栅格里,或表单+表格混排)时用 `fillHeight: true/false` 手工指定,`true` 时被撑满的是渲染器根节点与 `.render-form`,表格外层容器仍需自身有高度。
|
|
322
|
+
- 壳与 xform 渲染器均为懒加载(首次调用才加载),xform chunk 与 $formDialog 共享。
|
|
323
|
+
|
|
324
|
+
## 设计器入口:点击绑定事件
|
|
325
|
+
|
|
326
|
+
不写脚本也能打开这两个壳。设计器里选中按钮 / 链接 / 下拉项 / 搜索按钮 → 高级属性 →
|
|
327
|
+
**点击绑定事件** → 「打开表单弹框」或「打开表单抽屉」,下方展开「表单弹框/抽屉配置」
|
|
328
|
+
(存模板 JSON 的 `options.formHostConfig`,弹框与抽屉共用一份,互切不丢配置)。
|
|
329
|
+
|
|
330
|
+
| 配置项 | 说明 |
|
|
331
|
+
|---|---|
|
|
332
|
+
| 表单编码 / 表单编码脚本 | 脚本优先,参数 `(dataId, formCode, rowData)` |
|
|
333
|
+
| 以列表页承载 | 即 `host: "list"` |
|
|
334
|
+
| 打开方式 | 新增(不传 dataId)/ 详情 |
|
|
335
|
+
| 单据ID来源 | 当前控件值 / 指定表单字段 / 当前表格行字段 / 脚本。按钮类控件缺省用行字段(表格操作列场景) |
|
|
336
|
+
| 查询参数 | 脚本返回对象 → 壳的 `queryParam`,合并进目标表单查询表格的请求参数(见下「查询参数与 condition」)。「以列表页承载」时不生效,该项在编辑器里也随之隐藏 |
|
|
337
|
+
| 自定义参数 | 脚本返回对象 → 壳的 `param`,只透传给目标模板的脚本(`this.param`),不参与任何请求 |
|
|
338
|
+
| 只读 | 抽屉走 `readonly`(只读态、底部仅「关闭」);弹框走 `disabledMode`(整表禁用、底部按钮不变) |
|
|
339
|
+
| 形态 | 弹框:宽度 / 全屏;抽屉:素身详情 / 尺寸 / 弹出方向 |
|
|
340
|
+
| 关闭后事件 | 参数 `(formRef, result)`。`result` 即壳的收口值——默认按钮是表单数据、配置按钮是 `{action, data}`、取消/关闭是 `null`,脚本据此决定要不要刷新宿主表格。抽屉侧 `formRef` 为 `null` |
|
|
341
|
+
|
|
342
|
+
### 查询参数与 condition
|
|
343
|
+
|
|
344
|
+
查询表格发给后台的请求体是 `{ condition: [{ field, value, filter }...], current, size, searchCount, ...平铺参数 }`——
|
|
345
|
+
`condition` 由查询区字段按控件类型自动组装(文本 like、日期 between、多选 in、单选 eq),平铺参数则给后台脚本读。
|
|
346
|
+
宿主注入的 `queryParam` 按键分两路合并(`xform/utils/queryParamUtil.js`,PC 与 H5 两条查询链路共用):
|
|
347
|
+
|
|
348
|
+
| 写法 | 结果 |
|
|
349
|
+
|---|---|
|
|
350
|
+
| `{ warehouseId: 7 }` | 平铺覆盖,与改造前一致 |
|
|
351
|
+
| `{ condition: { status: 1, type: ["A","B"] } }` | 对象简写 → `[{field:"status",value:1,filter:"eq"}, {field:"type",value:["A","B"],filter:"in"}]`,**追加**到查询区条件之后 |
|
|
352
|
+
| `{ condition: { createTime: { value: ["2026-01-01","2026-12-31"], filter: "between" } } }` | 需要指定比较符时用 `{value, filter}` 形式 |
|
|
353
|
+
| `{ condition: [{ field:"status", value:1, filter:"eq" }] }` | 原生数组,`filter` 可省(数组值补 `in`,其余补 `eq`) |
|
|
354
|
+
|
|
355
|
+
同一个 `field` 上宿主条件覆盖查询区输入(先剔旧再追加),不会撞成两条。
|
|
356
|
+
`condition` 不再整体覆盖——此前带 `condition` 的 `queryParam` 会把用户当场输入的查询条件一并清掉。
|
|
357
|
+
注意值对象只有带 `value` 键才被当作 `{value, filter}` 解析,业务对象(如 `{ org: { id, name } }`)整体作为值传下去。
|
|
358
|
+
|
|
359
|
+
底部按钮不在这里配——由目标表单模板自己的 `formConfig.actionButtons` 决定(就是上一节那份配置)。
|
|
360
|
+
该动作**不做数据回填、不做选择模式**:要回填/选行请用「打开选择弹框」。
|
|
361
|
+
实现在 `xform/form-designer/setting-panel/property-editor/field-button/clickBindActions.js`
|
|
362
|
+
(纯 JS 动作注册表,运行时与设计器共用),方案见
|
|
363
|
+
`docs/点击绑定事件重构与表单弹框抽屉动作方案.md`。
|
|
364
|
+
|
|
365
|
+
## 与既有设施的分工
|
|
366
|
+
|
|
367
|
+
| 场景 | 用什么 |
|
|
368
|
+
|---|---|
|
|
369
|
+
| 轻量配置化表单(增改查小弹框) | `$formDialog`(本组件) |
|
|
370
|
+
| 复杂业务表单弹框(已有/需要专用 .vue) | `openComponentDialog`(`src/utils/componentDialog.js`) |
|
|
371
|
+
| 按 xform 表单模板渲染的完整表单 | xform(`VFormRender` / `searchFormDialog`) |
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* formConfig.actionButtons 公共逻辑:formDialog / formDrawer 共用。
|
|
3
|
+
* 配置结构(存表单模板 formViewContent 的 formConfig 上,设计器"操作按钮"面板维护):
|
|
4
|
+
* { code, label, type, plain, icon, authName, needValidate, closeAfter, visible, onClick }
|
|
5
|
+
* 语义约定见 docs/formDrawer抽屉与formDialog按钮配置改造方案.md §3.1。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** 调用方 buttons 覆盖模板配置;两者都没有则返回 null(壳回退默认取消/确定) */
|
|
9
|
+
export function resolveActionButtons(callerButtons, formConfig) {
|
|
10
|
+
const raw
|
|
11
|
+
= (Array.isArray(callerButtons) && callerButtons.length && callerButtons)
|
|
12
|
+
|| (formConfig
|
|
13
|
+
&& Array.isArray(formConfig.actionButtons)
|
|
14
|
+
&& formConfig.actionButtons.length
|
|
15
|
+
&& formConfig.actionButtons)
|
|
16
|
+
|| null;
|
|
17
|
+
if (!raw) return null;
|
|
18
|
+
return raw.map((btn, index) => ({
|
|
19
|
+
needValidate: false,
|
|
20
|
+
closeAfter: true,
|
|
21
|
+
...btn,
|
|
22
|
+
code: btn.code || `button_${index}`,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 按钮权限:denylist——formCode:authName 在 dispermissions 中则无权;未配 authName 放行 */
|
|
27
|
+
export function hasButtonPermission(btn, formCode, vm) {
|
|
28
|
+
if (!btn.authName || !formCode) return true;
|
|
29
|
+
const dispermissions
|
|
30
|
+
= (vm && vm.$store && vm.$store.getters.dispermissions) || [];
|
|
31
|
+
return !dispermissions.includes(`${formCode}:${btn.authName}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** visible 脚本:handleCustomEvent 执行(this = 表单实例),显式返回 false 才隐藏 */
|
|
35
|
+
export function isButtonVisible(btn, formRef) {
|
|
36
|
+
if (!btn.visible) return true;
|
|
37
|
+
if (!formRef) return true; // 表单未挂载前先渲染,挂载后重渲染时再求值
|
|
38
|
+
return formRef.handleCustomEvent(btn.visible, ["button"], [btn]) !== false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 执行按钮:needValidate 校验 → onClick 脚本 → done/closeAfter 收口。
|
|
43
|
+
* 脚本签名 onClick(formData, dialog, done, selection),this = VFormRender 实例。
|
|
44
|
+
* 返回 false / reject 均不关闭;未显式调 done 且 closeAfter 时自动以表单数据收口。
|
|
45
|
+
* @returns {Promise} 完成(无论是否收口)后 resolve,供壳侧解除按钮 loading
|
|
46
|
+
*/
|
|
47
|
+
export function runActionButton(btn, { formRef, host, getSelection, onSettle }) {
|
|
48
|
+
if (!formRef) return Promise.resolve();
|
|
49
|
+
|
|
50
|
+
const validate = () =>
|
|
51
|
+
new Promise((resolve) => {
|
|
52
|
+
if (!btn.needValidate) return resolve(true);
|
|
53
|
+
formRef.validateForm((valid) => resolve(!!valid));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const getFormData = () =>
|
|
57
|
+
host.$baseLodash
|
|
58
|
+
? host.$baseLodash.cloneDeep(formRef.getFormData(false))
|
|
59
|
+
: formRef.getFormData(false);
|
|
60
|
+
|
|
61
|
+
return validate().then((valid) => {
|
|
62
|
+
if (!valid) return;
|
|
63
|
+
let doneCalled = false;
|
|
64
|
+
const done = (data) => {
|
|
65
|
+
doneCalled = true;
|
|
66
|
+
onSettle(
|
|
67
|
+
{ action: btn.code, data: data === undefined ? getFormData() : data },
|
|
68
|
+
btn.closeAfter !== false
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
const selection
|
|
72
|
+
= typeof getSelection === "function" ? getSelection() || [] : [];
|
|
73
|
+
const result = formRef.handleCustomEvent(
|
|
74
|
+
btn.onClick,
|
|
75
|
+
["formData", "dialog", "done", "selection"],
|
|
76
|
+
[formRef.getFormData(false), host, done, selection]
|
|
77
|
+
);
|
|
78
|
+
if (result === false) return;
|
|
79
|
+
return Promise.resolve(result)
|
|
80
|
+
.then(() => {
|
|
81
|
+
// 脚本成功且未手动收口:closeAfter 时自动以表单数据收口,否则保持打开
|
|
82
|
+
if (!doneCalled && btn.closeAfter !== false) {
|
|
83
|
+
onSettle({ action: btn.code, data: getFormData() }, true);
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
.catch(() => {
|
|
87
|
+
// 脚本 reject:不关闭,由脚本自行提示错误
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const modules = {};
|
|
2
|
+
import vue from "vue";
|
|
3
|
+
|
|
4
|
+
// 壳组件懒加载:首屏只留本入口几十行,index.vue(含 xform 渲染链路)随首次调用进独立 chunk
|
|
5
|
+
let FormDialogCtor = null;
|
|
6
|
+
|
|
7
|
+
function loadCtor() {
|
|
8
|
+
if (FormDialogCtor) return Promise.resolve(FormDialogCtor);
|
|
9
|
+
return import(
|
|
10
|
+
/* webpackChunkName: "form-dialog" */ "./index.vue"
|
|
11
|
+
).then((m) => {
|
|
12
|
+
FormDialogCtor = vue.extend(m.default);
|
|
13
|
+
return FormDialogCtor;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* JS 调用打开表单弹框。每次调用创建独立实例,支持叠开多层。
|
|
19
|
+
* 确定(校验通过)resolve 表单数据,取消/关闭 resolve(null),不使用 reject。
|
|
20
|
+
* 配置了自定义按钮(formConfig.actionButtons / options.buttons)时 resolve {action, data}。
|
|
21
|
+
*/
|
|
22
|
+
function open(options = {}) {
|
|
23
|
+
return loadCtor().then((Ctor) => {
|
|
24
|
+
const parent
|
|
25
|
+
= typeof window !== "undefined" && window.$vueRoot ? window.$vueRoot : null;
|
|
26
|
+
const instanceOptions = {};
|
|
27
|
+
if (parent) {
|
|
28
|
+
instanceOptions.parent = parent;
|
|
29
|
+
if (parent._i18n) {
|
|
30
|
+
instanceOptions.i18n = parent._i18n;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const instance = new Ctor(instanceOptions);
|
|
34
|
+
instance.$mount();
|
|
35
|
+
document.body.appendChild(instance.$el);
|
|
36
|
+
return instance.open(options);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 定义插件对象
|
|
41
|
+
// vue的install方法,用于定义vue插件
|
|
42
|
+
modules.install = function (Vue) {
|
|
43
|
+
// 在Vue的原型上添加实例方法,以全局调用
|
|
44
|
+
Vue.prototype.$formDialog = open;
|
|
45
|
+
};
|
|
46
|
+
modules.open = open;
|
|
47
|
+
|
|
48
|
+
export default modules;
|