cloud-web-corejs 1.1.0-dev.22 → 1.1.0-dev.24

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.
Files changed (30) hide show
  1. package/package.json +1 -1
  2. package/src/components/bizTab/BizTabListPage.vue +66 -33
  3. package/src/components/bizTab/README.md +3 -1
  4. package/src/components/mobile/wf/content.vue +27 -3
  5. package/src/components/table/index.js +5 -2
  6. package/src/components/table/plugins/cell-area/index.js +10 -3
  7. package/src/components/wf/wfStartDialog.vue +1 -0
  8. package/src/components/wf/wfUtil.js +47 -29
  9. package/src/components/wf/wfVisibility.js +6 -0
  10. package/src/components/xform/form-designer/form-widget/container-widget/data-table-mixin.js +7 -0
  11. package/src/components/xform/form-designer/form-widget/container-widget/data-table-widget.vue +3 -1
  12. package/src/components/xform/form-designer/form-widget/field-widget/fieldMixin.js +25 -1
  13. package/src/components/xform/form-designer/setting-panel/form-setting.vue +14 -2
  14. package/src/components/xform/form-designer/setting-panel/property-editor/container-data-table/data-table-editor.vue +318 -12
  15. package/src/components/xform/form-designer/setting-panel/property-editor/container-data-table/table-column-dialog.vue +23 -7
  16. package/src/components/xform/form-designer/widget-panel/widgetsConfig.js +48 -0
  17. package/src/components/xform/form-render/container-item/data-table-item.vue +8 -6
  18. package/src/components/xform/form-render/container-item/data-table-mixin.js +584 -24
  19. package/src/components/xform/form-render/container-item/detail-h5-item.vue +26 -16
  20. package/src/components/xform/form-render/container-item/detail-item.vue +350 -446
  21. package/src/components/xform/form-render/fieldControlMixin.js +2 -0
  22. package/src/components/xform/form-render/indexMixin.js +25 -15
  23. package/src/components/xform/mixins/businessLock.js +59 -0
  24. package/src/components/xform/mixins/wfStart.js +103 -0
  25. package/src/components/xform/mixins/wfVisibility.js +33 -0
  26. package/src/components/xform/styles/h5.scss +6 -1
  27. package/src/components/xform/utils/util.js +2 -0
  28. package/src/views/user/home/index.vue +1 -1
  29. package/src/views/user/home/net/distributor.vue +1015 -0
  30. package/src/views/user/home/net/index.vue +61 -0
@@ -1,446 +1,350 @@
1
- <!--
2
- /**
3
- * author: vformAdmin
4
- * email: vdpadmin@163.com
5
- * website: https://www.vform666.com
6
- * date: 2021.08.18
7
- * remark: 如果要分发VForm源码,需在本文件顶部保留此文件头信息!!
8
- */
9
- -->
10
-
11
- <template>
12
- <container-item-wrapper :widget="widget">
13
- <div
14
- class="detail-wrap grid-container"
15
- :class="[customClass]"
16
- :ref="widget.id"
17
- >
18
- <div class="d-header clearfix" v-if="showHeader">
19
- <div class="fl">
20
- <i class="el-icon-info" />
21
- {{ getI18nLabel(widget.options.label) }}
22
- </div>
23
- <!-- <div class="fr">
24
- <el-button type="primary" plain class="button-sty" icon="el-icon-refresh-right" @click="reload">重置
25
- </el-button>
26
- <el-button type="primary" class="button-sty" icon="el-icon-check" @click="saveData" v-if="!wfInfo.id">保存
27
- </el-button>
28
- </div>-->
29
- <div class="fr">
30
- <template v-if="billNav">
31
- <el-button
32
- class="button-sty"
33
- size="mini"
34
- icon="el-icon-arrow-left"
35
- :disabled="!billNav.canPrev"
36
- @click="navBill(-1)"
37
- >上一单</el-button
38
- >
39
- <el-button
40
- class="button-sty"
41
- size="mini"
42
- :disabled="!billNav.canNext"
43
- @click="navBill(1)"
44
- >下一单<i class="el-icon-arrow-right el-icon--right"></i
45
- ></el-button>
46
- </template>
47
- <el-button
48
- v-if="showSaveSubmitBtn()"
49
- class="button-sty"
50
- type="success"
51
- icon="el-icon-video-play"
52
- @click="saveAndSubmitWfHandle()"
53
- >{{ saveSubmitButtonName }}</el-button
54
- >
55
- <el-button
56
- v-if="showWfStartBtn()"
57
- class="button-sty yyy"
58
- type="success"
59
- icon="el-icon-video-play"
60
- @click="startWf"
61
- >{{ wfStartButtonName }}</el-button
62
- >
63
- <template v-for="(subWidget, swIdx) in widget.widgetList">
64
- <template v-if="'container' === subWidget.category">
65
- <component
66
- :is="subWidget.type + '-item'"
67
- :widget="subWidget"
68
- :key="swIdx"
69
- :parent-list="widget.widgetList"
70
- :index-of-parent-list="swIdx"
71
- :parent-widget="widget"
72
- >
73
- <!-- 递归传递插槽!!! -->
74
- <template
75
- v-for="slot in Object.keys($scopedSlots)"
76
- v-slot:[slot]="scope"
77
- >
78
- <slot :name="slot" v-bind="scope" />
79
- </template>
80
- </component>
81
- </template>
82
- <template v-else>
83
- <component
84
- :is="subWidget.type + '-widget'"
85
- :field="subWidget"
86
- :designer="null"
87
- :key="swIdx"
88
- :parent-list="widget.widgetList"
89
- :index-of-parent-list="swIdx"
90
- :parent-widget="widget"
91
- >
92
- <!-- 递归传递插槽!!! -->
93
- <template
94
- v-for="slot in Object.keys($scopedSlots)"
95
- v-slot:[slot]="scope"
96
- >
97
- <slot :name="slot" v-bind="scope" />
98
- </template>
99
- </component>
100
- </template>
101
- </template>
102
- </div>
103
- </div>
104
- <!-- 正文区:panes 是通用容器列表,既可直接挂 detail-pane(模块),也可挂 tab 等其它容器。
105
- baseTabs 的锚点导航靠 getTabs() 探测 slot 子级:detail-pane 带 tabPaneGrade=2 会被收进
106
- navList;tab 等容器两个条件都不满足,被静默跳过——即标签内的模块不进外层导航,由标签内
107
- 自己的 detail-plain 负责(见 docs/详情模块标签承载方案.md)。 -->
108
- <baseTabs :showNav="!widget.options.hideNav && !plainMode">
109
- <template v-for="(paneWidget, index) in widget.panes">
110
- <detail-pane-widget
111
- v-if="paneWidget.type === 'detail-pane'"
112
- :key="paneWidget.id || index"
113
- :widget="paneWidget"
114
- :parent-list="widget.panes"
115
- :index-of-parent-list="index"
116
- :parent-widget="widget"
117
- :col-height="widget.options.colHeight"
118
- tabRef="baseTabRef"
119
- :tabPaneGrade="2"
120
- ></detail-pane-widget>
121
- <component
122
- v-else-if="'container' === paneWidget.category"
123
- :is="paneWidget.type + '-item'"
124
- :key="paneWidget.id || index"
125
- :widget="paneWidget"
126
- :parent-list="widget.panes"
127
- :index-of-parent-list="index"
128
- :parent-widget="widget"
129
- >
130
- <!-- 递归传递插槽!!! -->
131
- <template
132
- v-for="slot in Object.keys($scopedSlots)"
133
- v-slot:[slot]="scope"
134
- >
135
- <slot :name="slot" v-bind="scope" />
136
- </template>
137
- </component>
138
- <component
139
- v-else
140
- :is="paneWidget.type + '-widget'"
141
- :key="paneWidget.id || index"
142
- :field="paneWidget"
143
- :designer="null"
144
- :parent-list="widget.panes"
145
- :index-of-parent-list="index"
146
- :parent-widget="widget"
147
- >
148
- <!-- 递归传递插槽!!! -->
149
- <template
150
- v-for="slot in Object.keys($scopedSlots)"
151
- v-slot:[slot]="scope"
152
- >
153
- <slot :name="slot" v-bind="scope" />
154
- </template>
155
- </component>
156
- </template>
157
- </baseTabs>
158
- </div>
159
- </container-item-wrapper>
160
- </template>
161
-
162
- <script>
163
- import emitter from "../../../../components/xform/utils/emitter";
164
- import i18n from "../../../../components/xform/utils/i18n";
165
- import refMixin from "../../../../components/xform/form-render/refMixin";
166
- import ContainerItemWrapper from "./container-item-wrapper";
167
- import containerItemMixin from "./containerItemMixin";
168
- import detailPaneWidget from "../../../../components/xform/form-render/container-item/detail-pane-item";
169
- import FieldComponents from "../../../../components/xform/form-designer/form-widget/field-widget/index";
170
-
171
- import { initWf, openStartWfDialog } from "../../../../components/wf/wfUtil";
172
- import {
173
- getWfButtonNames,
174
- loadWfButtonNames,
175
- DEFAULT_SUBMIT_BTN,
176
- DEFAULT_SAVE_SUBMIT_BTN,
177
- } from "../../../../components/wf/wfButtonName";
178
- import { loadWfAutoConfirm } from "../../../../components/wf/wfAutoConfirm";
179
- import { loadWfUserRange } from "../../../../components/wf/wfUserRange";
180
- import settingConfig from "@/settings.js";
181
-
182
- export default {
183
- name: "detail-item",
184
- componentName: "ContainerItem",
185
- mixins: [emitter, i18n, refMixin, containerItemMixin],
186
- components: {
187
- ContainerItemWrapper,
188
- detailPaneWidget,
189
- ...FieldComponents,
190
- },
191
- props: {
192
- widget: Object,
193
- },
194
- inject: ["refList", "sfRefList", "globalModel", "previewState"],
195
- created() {
196
- this.initRefList();
197
- this.handleOnCreated();
198
- //只有流程表单才需要按钮名/自动确认开关,避免给普通详情页平白多一次请求(模块内全局只发一次)
199
- if (this.getFormRef()?.formConfig?.wfEnabled) {
200
- loadWfButtonNames(this);
201
- loadWfAutoConfirm(this);
202
- loadWfUserRange(this);
203
- }
204
- },
205
- mounted() {
206
- this.handleOnMounted();
207
- this.$nextTick(() => {
208
- this.createWf();
209
- });
210
- },
211
- beforeDestroy() {
212
- this.unregisterFromRefList();
213
- },
214
- data() {
215
- return {
216
- wfInfo: {},
217
- wfReady: false, // 流程状态是否已就绪(callback 返回后)
218
- wfHasStarted: false, // 流程是否已启动(已启动则不显示启动按钮)
219
- wfStartOption: null, // 启动流程所需 option(createWf 构造)
220
- wfStartCtx: null, // openStartWfDialog 的执行上下文(与 initWf 一致)
221
- };
222
- },
223
- computed: {
224
- // 素身详情模式(宿主 VFormRender 传 plainDetailMode):去标题栏、去模块导航,只留模块
225
- plainMode() {
226
- let formRef = this.getFormRef && this.getFormRef();
227
- return !!(formRef && formRef.plainDetailMode);
228
- },
229
- // 标题栏:模板配 hideHeader 或宿主素身模式,两者任一命中即不渲染
230
- showHeader() {
231
- return !this.plainMode && !this.widget.options.hideHeader;
232
- },
233
- // 按钮名取值链:后台 param18 → 全局 settings(老部署兼容) → 默认。
234
- // 只能用 $t1(找不到 key 原样返回):$t2 的第二参在 i18n 命中 key 时会把自定义名整个覆盖掉。
235
- wfStartButtonName() {
236
- return (
237
- getWfButtonNames().submitBtn ||
238
- settingConfig.wfStartButtonName ||
239
- this.$t1(DEFAULT_SUBMIT_BTN)
240
- );
241
- },
242
- saveSubmitButtonName() {
243
- return (
244
- getWfButtonNames().saveSubmitBtn || this.$t1(DEFAULT_SAVE_SUBMIT_BTN)
245
- );
246
- },
247
- // 「上一单/下一单」翻单按钮状态:详情表单开启 billNavEnabled 且宿主列表有翻单快照时才显示。
248
- // detail-item 在每次翻单时随详情表单重挂载,故此处按当前挂载时机取一次即可。
249
- billNav() {
250
- let formRef = this.getFormRef && this.getFormRef();
251
- if (
252
- !formRef ||
253
- !formRef.formConfig ||
254
- !formRef.formConfig.billNavEnabled
255
- ) {
256
- return null;
257
- }
258
- let host = formRef.getListPageRef && formRef.getListPageRef();
259
- if (!host || typeof host.getActiveBillNavState !== "function")
260
- return null;
261
- let state = host.getActiveBillNavState();
262
- if (!state) return null;
263
- return {
264
- canPrev: state.index > 0,
265
- canNext: state.index < state.rows.length - 1,
266
- };
267
- },
268
- },
269
- methods: {
270
- // 解析流程启动信息:优先取表单实例(indexMixin,xform 主链路)暴露的,回退到本组件 createWf(旧路径)。
271
- // 用方法实时求值,避免 computed 缓存导致早于 wf 就绪时求值后不再更新。
272
- wfStart() {
273
- let formRef = this.getFormRef && this.getFormRef();
274
- if (formRef && formRef.wfStartOption) {
275
- return {
276
- ctx: formRef.wfStartCtx || formRef,
277
- option: formRef.wfStartOption,
278
- hasWf: !!formRef.hasWf,
279
- };
280
- }
281
- if (this.wfReady && this.wfStartOption && this.wfStartCtx) {
282
- return {
283
- ctx: this.wfStartCtx,
284
- option: this.wfStartOption,
285
- hasWf: this.wfHasStarted,
286
- };
287
- }
288
- return null;
289
- },
290
- // 流程启动按钮:有启动信息、需要按钮(showStartBtn 未显式关闭)、未启动、非预览态才显示。
291
- // 声明式渲染,替代 wfUtil DOM 强制注入。
292
- showWfStartBtn() {
293
- let ws = this.wfStart();
294
- if (!ws || this.previewState) return false;
295
- let opt = ws.option || {};
296
- if (opt.showStartBtn === false || ws.hasWf) return false;
297
- return this.evalWfStartBtnShow();
298
- },
299
- /**
300
- * 「提交流程」按钮显隐脚本(formConfig.wfStartBtnShow):return false 隐藏,不配则显示。
301
- * 在渲染期求值,好处是随表单数据自动更新,代价是脚本会被反复执行——所以:
302
- * 1) 编译结果按脚本原文缓存,避免每次渲染都 new Function;
303
- * 2) 编译/执行异常一律放行(返回 true),不能因为脚本写错就把提交入口挡死;
304
- * 3) formData 取轻量的 formRef.formData,不走 getSysFormData/getRealFormData 那套重加工。
305
- * 脚本里务必只读数据、返回布尔,改数据会引发渲染循环。
306
- */
307
- evalWfStartBtnShow() {
308
- let formRef = this.getFormRef && this.getFormRef();
309
- let src = formRef && formRef.formConfig && formRef.formConfig.wfStartBtnShow;
310
- if (!src) return true;
311
- let cache = this._wfStartShowFn; //挂实例属性即可,不需要响应式
312
- if (!cache || cache.src !== src) {
313
- try {
314
- cache = {
315
- src,
316
- fn: new Function("dataId", "formCode", "formData", src),
317
- };
318
- } catch (error) {
319
- console.error("wfStartBtnShow 脚本编译失败", error);
320
- return true;
321
- }
322
- this._wfStartShowFn = cache;
323
- }
324
- try {
325
- let ret = cache.fn.call(
326
- formRef,
327
- formRef.dataId,
328
- formRef.reportTemplate && formRef.reportTemplate.formCode,
329
- formRef.formData
330
- );
331
- return ret !== false;
332
- } catch (error) {
333
- console.error("wfStartBtnShow 脚本执行异常", error);
334
- return true;
335
- }
336
- },
337
- // 「保存并提交流程」内建按钮:表单设置里 saveSubmitEnabled 开启后渲染,只在新增页显示。
338
- // 与 showWfStartBtn 一样用方法而非 computed:流程状态是异步就绪的,computed 缓存会漏更新。
339
- showSaveSubmitBtn() {
340
- if (this.previewState) return false;
341
- let formRef = this.getFormRef && this.getFormRef();
342
- let formConfig = formRef && formRef.formConfig;
343
- if (!formConfig || !formConfig.wfEnabled || !formConfig.saveSubmitEnabled)
344
- return false;
345
- if (formRef.dataId) return false; //只有新增页(编辑态一律不显示)
346
- let wfParam = formRef.wfParam || {};
347
- //流程已启动不显示(与 save_submit_wf_button-widget 的 isWfStarted 同口径)
348
- if (formRef.hasWf || wfParam.hasWf || wfParam.wfInfo?.procInstId)
349
- return false;
350
- return true;
351
- },
352
- // 声明式触发流程启动(替代原注入按钮的点击),复用 wfUtil 的 openStartWfDialog
353
- startWf() {
354
- let ws = this.wfStart();
355
- if (!ws || !ws.option || !ws.ctx) return;
356
- // startConfirmText 原样透传:它只承载模板脚本 wfStartConfirmText 的文案(见 indexMixin
357
- // 的 option 构造)。按钮名拼接与「后台 param19 自动确认」都由 wf.js 统一处理——
358
- // 在这里替它拼默认文案的话,wf.js 会把它当成脚本自定义的告警,自动确认就失效了。
359
- let innerOnStart = ws.option.onStart;
360
- let formRef = this.getFormRef && this.getFormRef();
361
- openStartWfDialog(ws.ctx, {
362
- ...ws.option, //浅拷贝:wfStartOption 是 formRef 上的共享对象,不可原地改
363
- // 提交前置处理:确认框在 wfStartDialog 内部,这里(打开弹框前)是唯一能拦的位置。
364
- // 既有的 onStart 是表单校验(仅 wfStartBindSave 开启时注入),把它的 done 换成
365
- // 「跑前置脚本」,即得到「先校验、后脚本、再确认框」;没开自动保存时直接跑脚本。
366
- onStart: (done) => {
367
- let runBefore = () => {
368
- if (!formRef || typeof formRef.runBeforeSubmitWf !== "function") {
369
- return done();
370
- }
371
- formRef.runBeforeSubmitWf("wfStart").then((pass) => {
372
- if (pass) done();
373
- });
374
- };
375
- if (typeof innerOnStart === "function") {
376
- innerOnStart(runBefore);
377
- } else {
378
- runBefore();
379
- }
380
- },
381
- });
382
- },
383
- // 触发宿主翻单:step = -1 上一单 / +1 下一单
384
- navBill(step) {
385
- let host = this.getFormRef && this.getFormRef();
386
- host = host && host.getListPageRef && host.getListPageRef();
387
- host && host.navigateActiveBill && host.navigateActiveBill(step);
388
- },
389
- reload() {
390
- this.getFormRef().$parent.$baseReload();
391
- },
392
- saveData() {
393
- if (!!this.previewState) return;
394
- this.getFormRef().saveForm();
395
- },
396
- createWf() {
397
- if (!!this.previewState || !this.widget.options.wfEnabled) return;
398
- let formTarget = this.getFormRef();
399
- let formData = formTarget.formData;
400
- if (formData && formData.object_foreign_id) {
401
- let reportTemplate = formTarget.reportTemplate;
402
- let formCode = reportTemplate.formCode;
403
- let option = {
404
- objId: formData.object_foreign_id, //单据ID
405
- wfCode: formCode, //流程编码
406
- showStartBtn: true, //需要启动按钮(语义不变)
407
- injectStartBtn: false, //不走 DOM 强制注入,改由本组件模板声明式渲染
408
- serviceId: "user",
409
- startConfirmText: () => formTarget.getWfStartConfirmText(),
410
- callback: ({ wfInfo, hasWf }) => {
411
- this.wfInfo = wfInfo;
412
- this.wfHasStarted = !!hasWf; //已启动则不显示启动按钮(与原注入条件 !hasWf 一致)
413
- this.wfReady = true; //状态就绪后才显示按钮,避免回调前闪现
414
- },
415
- };
416
- this.wfStartOption = option;
417
- this.wfStartCtx = formTarget.$parent; //与 initWf 执行上下文一致,供 openStartWfDialog 使用
418
- initWf.call(formTarget.$parent, option);
419
- }
420
- },
421
- },
422
- };
423
- </script>
424
-
425
- <style lang="scss" scoped>
426
- .grid-container {
427
- min-height: 50px;
428
- //line-height: 48px;
429
- //padding: 6px;
430
- outline: 1px dashed #336699;
431
-
432
- > div {
433
- height: 100%;
434
- }
435
-
436
- .form-widget-list {
437
- // min-height: 28px;
438
- height: 100%;
439
- }
440
- }
441
-
442
- .grid-container.selected,
443
- .grid-cell.selected {
444
- outline: 2px solid $--color-primary !important;
445
- }
446
- </style>
1
+ <!--
2
+ /**
3
+ * author: vformAdmin
4
+ * email: vdpadmin@163.com
5
+ * website: https://www.vform666.com
6
+ * date: 2021.08.18
7
+ * remark: 如果要分发VForm源码,需在本文件顶部保留此文件头信息!!
8
+ */
9
+ -->
10
+
11
+ <template>
12
+ <container-item-wrapper :widget="widget">
13
+ <div
14
+ class="detail-wrap grid-container"
15
+ :class="[customClass]"
16
+ :ref="widget.id"
17
+ >
18
+ <div class="d-header clearfix" v-if="showHeader">
19
+ <div class="fl">
20
+ <i class="el-icon-info" />
21
+ {{ getI18nLabel(widget.options.label) }}
22
+ </div>
23
+ <!-- <div class="fr">
24
+ <el-button type="primary" plain class="button-sty" icon="el-icon-refresh-right" @click="reload">重置
25
+ </el-button>
26
+ <el-button type="primary" class="button-sty" icon="el-icon-check" @click="saveData" v-if="!wfInfo.id">保存
27
+ </el-button>
28
+ </div>-->
29
+ <div class="fr">
30
+ <template v-if="billNav">
31
+ <el-button
32
+ class="button-sty"
33
+ size="mini"
34
+ icon="el-icon-arrow-left"
35
+ :disabled="!billNav.canPrev"
36
+ @click="navBill(-1)"
37
+ >上一单</el-button
38
+ >
39
+ <el-button
40
+ class="button-sty"
41
+ size="mini"
42
+ :disabled="!billNav.canNext"
43
+ @click="navBill(1)"
44
+ >下一单<i class="el-icon-arrow-right el-icon--right"></i
45
+ ></el-button>
46
+ </template>
47
+ <el-button
48
+ v-if="showSaveSubmitBtn()"
49
+ class="button-sty"
50
+ type="success"
51
+ icon="el-icon-video-play"
52
+ @click="saveAndSubmitWfHandle()"
53
+ >{{ saveSubmitButtonName }}</el-button
54
+ >
55
+ <el-button
56
+ v-if="showWfStartBtn()"
57
+ class="button-sty yyy"
58
+ type="success"
59
+ icon="el-icon-video-play"
60
+ @click="startWf"
61
+ >{{ wfStartButtonName }}</el-button
62
+ >
63
+ <template v-for="(subWidget, swIdx) in widget.widgetList">
64
+ <template v-if="'container' === subWidget.category">
65
+ <component
66
+ :is="subWidget.type + '-item'"
67
+ :widget="subWidget"
68
+ :key="swIdx"
69
+ :parent-list="widget.widgetList"
70
+ :index-of-parent-list="swIdx"
71
+ :parent-widget="widget"
72
+ >
73
+ <!-- 递归传递插槽!!! -->
74
+ <template
75
+ v-for="slot in Object.keys($scopedSlots)"
76
+ v-slot:[slot]="scope"
77
+ >
78
+ <slot :name="slot" v-bind="scope" />
79
+ </template>
80
+ </component>
81
+ </template>
82
+ <template v-else>
83
+ <component
84
+ :is="subWidget.type + '-widget'"
85
+ :field="subWidget"
86
+ :designer="null"
87
+ :key="swIdx"
88
+ :parent-list="widget.widgetList"
89
+ :index-of-parent-list="swIdx"
90
+ :parent-widget="widget"
91
+ >
92
+ <!-- 递归传递插槽!!! -->
93
+ <template
94
+ v-for="slot in Object.keys($scopedSlots)"
95
+ v-slot:[slot]="scope"
96
+ >
97
+ <slot :name="slot" v-bind="scope" />
98
+ </template>
99
+ </component>
100
+ </template>
101
+ </template>
102
+ </div>
103
+ </div>
104
+ <!-- 正文区:panes 是通用容器列表,既可直接挂 detail-pane(模块),也可挂 tab 等其它容器。
105
+ baseTabs 的锚点导航靠 getTabs() 探测 slot 子级:detail-pane 带 tabPaneGrade=2 会被收进
106
+ navList;tab 等容器两个条件都不满足,被静默跳过——即标签内的模块不进外层导航,由标签内
107
+ 自己的 detail-plain 负责(见 docs/详情模块标签承载方案.md)。 -->
108
+ <baseTabs :showNav="!widget.options.hideNav && !plainMode">
109
+ <template v-for="(paneWidget, index) in widget.panes">
110
+ <detail-pane-widget
111
+ v-if="paneWidget.type === 'detail-pane'"
112
+ :key="paneWidget.id || index"
113
+ :widget="paneWidget"
114
+ :parent-list="widget.panes"
115
+ :index-of-parent-list="index"
116
+ :parent-widget="widget"
117
+ :col-height="widget.options.colHeight"
118
+ tabRef="baseTabRef"
119
+ :tabPaneGrade="2"
120
+ ></detail-pane-widget>
121
+ <component
122
+ v-else-if="'container' === paneWidget.category"
123
+ :is="paneWidget.type + '-item'"
124
+ :key="paneWidget.id || index"
125
+ :widget="paneWidget"
126
+ :parent-list="widget.panes"
127
+ :index-of-parent-list="index"
128
+ :parent-widget="widget"
129
+ >
130
+ <!-- 递归传递插槽!!! -->
131
+ <template
132
+ v-for="slot in Object.keys($scopedSlots)"
133
+ v-slot:[slot]="scope"
134
+ >
135
+ <slot :name="slot" v-bind="scope" />
136
+ </template>
137
+ </component>
138
+ <component
139
+ v-else
140
+ :is="paneWidget.type + '-widget'"
141
+ :key="paneWidget.id || index"
142
+ :field="paneWidget"
143
+ :designer="null"
144
+ :parent-list="widget.panes"
145
+ :index-of-parent-list="index"
146
+ :parent-widget="widget"
147
+ >
148
+ <!-- 递归传递插槽!!! -->
149
+ <template
150
+ v-for="slot in Object.keys($scopedSlots)"
151
+ v-slot:[slot]="scope"
152
+ >
153
+ <slot :name="slot" v-bind="scope" />
154
+ </template>
155
+ </component>
156
+ </template>
157
+ </baseTabs>
158
+ </div>
159
+ </container-item-wrapper>
160
+ </template>
161
+
162
+ <script>
163
+ import emitter from "../../../../components/xform/utils/emitter";
164
+ import i18n from "../../../../components/xform/utils/i18n";
165
+ import refMixin from "../../../../components/xform/form-render/refMixin";
166
+ import ContainerItemWrapper from "./container-item-wrapper";
167
+ import containerItemMixin from "./containerItemMixin";
168
+ import detailPaneWidget from "../../../../components/xform/form-render/container-item/detail-pane-item";
169
+ import FieldComponents from "../../../../components/xform/form-designer/form-widget/field-widget/index";
170
+
171
+ import { initWf } from "../../../../components/wf/wfUtil";
172
+ import {
173
+ getWfButtonNames,
174
+ DEFAULT_SAVE_SUBMIT_BTN,
175
+ } from "../../../../components/wf/wfButtonName";
176
+ import wfStartMixin from "@base/components/xform/mixins/wfStart";
177
+
178
+ export default {
179
+ name: "detail-item",
180
+ componentName: "ContainerItem",
181
+ mixins: [emitter, i18n, refMixin, containerItemMixin, wfStartMixin],
182
+ components: {
183
+ ContainerItemWrapper,
184
+ detailPaneWidget,
185
+ ...FieldComponents,
186
+ },
187
+ props: {
188
+ widget: Object,
189
+ },
190
+ inject: ["refList", "sfRefList", "globalModel", "previewState"],
191
+ created() {
192
+ this.initRefList();
193
+ this.handleOnCreated();
194
+ this.initWfStartSettings();
195
+ },
196
+ mounted() {
197
+ this.handleOnMounted();
198
+ this.$nextTick(() => {
199
+ this.createWf();
200
+ });
201
+ },
202
+ beforeDestroy() {
203
+ this.unregisterFromRefList();
204
+ },
205
+ data() {
206
+ return {
207
+ wfInfo: {},
208
+ wfReady: false, // 流程状态是否已就绪(callback 返回后)
209
+ wfHasStarted: false, // 流程是否已启动(已启动则不显示启动按钮)
210
+ wfStartOption: null, // 启动流程所需 option(createWf 构造)
211
+ wfStartCtx: null, // openStartWfDialog 的执行上下文(与 initWf 一致)
212
+ };
213
+ },
214
+ computed: {
215
+ // 素身详情模式(宿主 VFormRender 传 plainDetailMode):去标题栏、去模块导航,只留模块
216
+ plainMode() {
217
+ let formRef = this.getFormRef && this.getFormRef();
218
+ return !!(formRef && formRef.plainDetailMode);
219
+ },
220
+ // 标题栏:模板配 hideHeader 或宿主素身模式,两者任一命中即不渲染
221
+ showHeader() {
222
+ return !this.plainMode && !this.widget.options.hideHeader;
223
+ },
224
+ saveSubmitButtonName() {
225
+ return (
226
+ getWfButtonNames().saveSubmitBtn || this.$t1(DEFAULT_SAVE_SUBMIT_BTN)
227
+ );
228
+ },
229
+ // 「上一单/下一单」翻单按钮状态:详情表单开启 billNavEnabled 且宿主列表有翻单快照时才显示。
230
+ // detail-item 在每次翻单时随详情表单重挂载,故此处按当前挂载时机取一次即可。
231
+ billNav() {
232
+ let formRef = this.getFormRef && this.getFormRef();
233
+ if (
234
+ !formRef ||
235
+ !formRef.formConfig ||
236
+ !formRef.formConfig.billNavEnabled
237
+ ) {
238
+ return null;
239
+ }
240
+ let host = formRef.getListPageRef && formRef.getListPageRef();
241
+ if (!host || typeof host.getActiveBillNavState !== "function")
242
+ return null;
243
+ let state = host.getActiveBillNavState();
244
+ if (!state) return null;
245
+ return {
246
+ canPrev: state.index > 0,
247
+ canNext: state.index < state.rows.length - 1,
248
+ };
249
+ },
250
+ },
251
+ methods: {
252
+ // 解析流程启动信息:优先取表单实例(indexMixin,xform 主链路)暴露的,回退到本组件 createWf(旧路径)。
253
+ // 用方法实时求值,避免 computed 缓存导致早于 wf 就绪时求值后不再更新。
254
+ wfStart() {
255
+ let formRef = this.getFormRef && this.getFormRef();
256
+ if (formRef && formRef.wfStartOption) {
257
+ return {
258
+ ctx: formRef.wfStartCtx || formRef,
259
+ option: formRef.wfStartOption,
260
+ hasWf: !!formRef.hasWf,
261
+ };
262
+ }
263
+ if (this.wfReady && this.wfStartOption && this.wfStartCtx) {
264
+ return {
265
+ ctx: this.wfStartCtx,
266
+ option: this.wfStartOption,
267
+ hasWf: this.wfHasStarted,
268
+ };
269
+ }
270
+ return null;
271
+ },
272
+ // 与 showWfStartBtn 一样用方法而非 computed:流程状态是异步就绪的,computed 缓存会漏更新。
273
+ showSaveSubmitBtn() {
274
+ if (this.previewState) return false;
275
+ let formRef = this.getFormRef && this.getFormRef();
276
+ let formConfig = formRef && formRef.formConfig;
277
+ if (formRef && formRef.businessLocked) return false;
278
+ if (!formConfig || !formConfig.wfEnabled || !formConfig.saveSubmitEnabled)
279
+ return false;
280
+ if (formRef.dataId) return false; //只有新增页(编辑态一律不显示)
281
+ let wfParam = formRef.wfParam || {};
282
+ //流程已启动不显示(与 save_submit_wf_button-widget 的 isWfStarted 同口径)
283
+ if (formRef.hasWf || wfParam.hasWf || wfParam.wfInfo?.procInstId)
284
+ return false;
285
+ return true;
286
+ },
287
+ // 触发宿主翻单:step = -1 上一单 / +1 下一单
288
+ navBill(step) {
289
+ let host = this.getFormRef && this.getFormRef();
290
+ host = host && host.getListPageRef && host.getListPageRef();
291
+ host && host.navigateActiveBill && host.navigateActiveBill(step);
292
+ },
293
+ reload() {
294
+ this.getFormRef().$parent.$baseReload();
295
+ },
296
+ saveData() {
297
+ if (!!this.previewState) return;
298
+ this.getFormRef().saveForm();
299
+ },
300
+ createWf() {
301
+ if (!!this.previewState || !this.widget.options.wfEnabled) return;
302
+ let formTarget = this.getFormRef();
303
+ let formData = formTarget.formData;
304
+ if (formData && formData.object_foreign_id) {
305
+ let reportTemplate = formTarget.reportTemplate;
306
+ let formCode = reportTemplate.formCode;
307
+ let option = {
308
+ objId: formData.object_foreign_id, //单据ID
309
+ wfCode: formCode, //流程编码
310
+ showStartBtn: true, //需要启动按钮(语义不变)
311
+ injectStartBtn: false, //不走 DOM 强制注入,改由本组件模板声明式渲染
312
+ serviceId: "user",
313
+ startConfirmText: () => formTarget.getWfStartConfirmText(),
314
+ callback: ({ wfInfo, hasWf }) => {
315
+ this.wfInfo = wfInfo;
316
+ this.wfHasStarted = !!hasWf; //已启动则不显示启动按钮(与原注入条件 !hasWf 一致)
317
+ this.wfReady = true; //状态就绪后才显示按钮,避免回调前闪现
318
+ },
319
+ };
320
+ this.wfStartOption = option;
321
+ this.wfStartCtx = formTarget.$parent; //与 initWf 执行上下文一致,供 openStartWfDialog 使用
322
+ initWf.call(formTarget.$parent, option);
323
+ }
324
+ },
325
+ },
326
+ };
327
+ </script>
328
+
329
+ <style lang="scss" scoped>
330
+ .grid-container {
331
+ min-height: 50px;
332
+ //line-height: 48px;
333
+ //padding: 6px;
334
+ outline: 1px dashed #336699;
335
+
336
+ > div {
337
+ height: 100%;
338
+ }
339
+
340
+ .form-widget-list {
341
+ // min-height: 28px;
342
+ height: 100%;
343
+ }
344
+ }
345
+
346
+ .grid-container.selected,
347
+ .grid-cell.selected {
348
+ outline: 2px solid $--color-primary !important;
349
+ }
350
+ </style>