cloud-web-corejs 1.0.283 → 1.0.285

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 (69) hide show
  1. package/package.json +1 -1
  2. package/src/components/VabUpload/mixins.js +126 -19
  3. package/src/components/VabUpload/view.vue +248 -209
  4. package/src/components/excelExport/conditionExportConfig.js +112 -0
  5. package/src/components/excelExport/exportType.js +8 -0
  6. package/src/components/excelExport/index.vue +7 -1
  7. package/src/components/excelExport/mixins.js +410 -259
  8. package/src/components/mobile/wf/addTaskUserdialog.vue +100 -80
  9. package/src/components/mobile/wf/content.vue +1705 -1556
  10. package/src/components/mobile/wf/deleteTaskUserDialog.vue +73 -54
  11. package/src/components/mobile/wf/mixins/wfModifyDialogMixin.js +8 -6
  12. package/src/components/mobile/wf/selectUserDialog.vue +118 -0
  13. package/src/components/mobile/wf/setCandidateDialog.vue +69 -48
  14. package/src/components/mobile/wf/urgingDialog.vue +75 -52
  15. package/src/components/mobile/wf/wfModifyDialog.vue +4 -0
  16. package/src/components/xform/docs/2026-08 xform H5 P0 /345/256/236/347/216/260/350/247/204/346/240/274.md" +528 -0
  17. package/src/components/xform/docs/2026-08 xform H5 P1 /345/256/236/347/216/260/350/247/204/346/240/274.md" +412 -0
  18. package/src/components/xform/docs/2026-08 xform H5 P2 /345/256/236/347/216/260/350/247/204/346/240/274.md" +287 -0
  19. package/src/components/xform/docs/2026-08 xform /347/247/273/345/212/250/347/253/257/345/270/203/345/261/200/345/257/271/351/275/220 bill_setting /346/226/271/346/241/210.md" +400 -0
  20. package/src/components/xform/form-designer/designer.js +5 -0
  21. package/src/components/xform/form-designer/form-widget/container-widget/detail-h5-widget.vue +243 -227
  22. package/src/components/xform/form-designer/form-widget/container-widget/h5-card-pane-widget.vue +228 -207
  23. package/src/components/xform/form-designer/form-widget/container-widget/h5-card-widget.vue +165 -136
  24. package/src/components/xform/form-designer/form-widget/dialog/fileReferenceDialog.vue +2 -3
  25. package/src/components/xform/form-designer/form-widget/dialog/formDialog.vue +3 -3
  26. package/src/components/xform/form-designer/form-widget/dialog/importDialog.vue +190 -185
  27. package/src/components/xform/form-designer/form-widget/dialog/importDialogMixin.js +2641 -1833
  28. package/src/components/xform/form-designer/form-widget/dialog/searchFormDialog.vue +2 -3
  29. package/src/components/xform/form-designer/form-widget/field-widget/form-item-wrapper.vue +39 -1
  30. package/src/components/xform/form-designer/form-widget/field-widget/import-button-widget.vue +18 -5
  31. package/src/components/xform/form-designer/form-widget/field-widget/select-export-item-button-widget.vue +2 -1
  32. package/src/components/xform/form-designer/form-widget/field-widget/singleUpload-widget.vue +1 -1
  33. package/src/components/xform/form-designer/form-widget/field-widget/table-export-button-widget.vue +2 -1
  34. package/src/components/xform/form-designer/form-widget/field-widget/vabUpload-widget.vue +3 -2
  35. package/src/components/xform/form-designer/indexMixin.js +35 -1
  36. package/src/components/xform/form-designer/setting-panel/property-editor/container-list-h5/list-h5-editor.vue +19 -5
  37. package/src/components/xform/form-designer/setting-panel/property-editor/field-import-button/import-button-editor.vue +62 -1
  38. package/src/components/xform/form-designer/setting-panel/property-editor/field-import-button/import2-button-editor.vue +236 -96
  39. package/src/components/xform/form-designer/setting-panel/property-editor/field-table-export-button/empty-number-input.vue +51 -0
  40. package/src/components/xform/form-designer/setting-panel/property-editor/field-table-export-button/select-export-item-button-editor.vue +27 -2
  41. package/src/components/xform/form-designer/setting-panel/property-editor/field-table-export-button/table-export-button-editor.vue +33 -3
  42. package/src/components/xform/form-designer/setting-panel/property-editor/h5Span-editor.vue +51 -0
  43. package/src/components/xform/form-designer/setting-panel/property-editor/itemStyle-editor.vue +39 -0
  44. package/src/components/xform/form-designer/setting-panel/propertyRegister.js +10 -0
  45. package/src/components/xform/form-designer/toolbar-panel/index.vue +6 -1
  46. package/src/components/xform/form-designer/toolbar-panel/indexMixin.js +13 -0
  47. package/src/components/xform/form-designer/widget-panel/indexMixin.js +22 -6
  48. package/src/components/xform/form-designer/widget-panel/widgetsConfig.js +88 -3
  49. package/src/components/xform/form-render/container-item/data-table-mixin.js +3 -1
  50. package/src/components/xform/form-render/container-item/detail-h5-item.vue +204 -34
  51. package/src/components/xform/form-render/container-item/h5-card-pane-item.vue +160 -140
  52. package/src/components/xform/form-render/container-item/list-h5-item.vue +1917 -1700
  53. package/src/components/xform/form-render/index.vue +7 -1
  54. package/src/components/xform/form-render/indexMixin.js +42 -4
  55. package/src/components/xform/lang/en-US.js +10 -0
  56. package/src/components/xform/lang/zh-CN.js +10 -0
  57. package/src/components/xform/styles/h5.scss +2305 -0
  58. package/src/directive/append-to-body/index.js +48 -0
  59. package/src/index.js +4 -0
  60. package/src/views/bd/setting/form_template/edit.vue +25 -3
  61. package/src/views/bd/setting/form_template/formDesignerDialog.vue +6 -0
  62. package/src/views/bd/setting/form_template/formTemplateType.js +43 -0
  63. package/src/views/bd/setting/form_template/list.vue +1 -0
  64. package/src/views/bd/setting/form_template/mixins/edit.js +64 -0
  65. package/src/views/bd/setting/form_template/mixins/list.js +27 -0
  66. package/src/views/user/form/vform/designer.vue +3 -1
  67. package/src/components/xform/docs/2026-09 /346/235/241/344/273/266/345/257/274/345/207/272/345/244/247/346/225/260/346/215/256/351/207/217/345/264/251/346/272/203/346/216/222/346/237/245.md" +0 -389
  68. package/src/components/xform/docs/2026-09 /347/274/226/350/276/221/350/241/250/350/241/214/345/206/205/344/277/235/345/255/230/346/240/241/351/252/214/350/214/203/345/233/264/344/270/216/345/210/227/345/277/205/345/241/253/350/247/243/346/236/220/344/277/256/345/244/215.md" +0 -233
  69. package/src/components/xform/form-render/container-item/list-h5-item2.vue +0 -1340
@@ -39,6 +39,7 @@ modules = {
39
39
  "saveReportTemplate",
40
40
  "getReportTemplate",
41
41
  "getFormTemplateTableDTOs",
42
+ "getLayoutTypeLocked",
42
43
  "readonly",
43
44
  ],
44
45
  data() {
@@ -105,6 +106,15 @@ modules = {
105
106
  layoutType() {
106
107
  return this.designer.getLayoutType();
107
108
  },
109
+
110
+ /* 布局模式由入口(表单模板类型)锁定时,PC/移动端按钮只回显不可切换 */
111
+ layoutTypeLocked() {
112
+ return !!this.getLayoutTypeLocked();
113
+ },
114
+
115
+ layoutTypeLockedTip() {
116
+ return this.$t1("模板类型由表单模板维护,设计器内不可切换");
117
+ },
108
118
  },
109
119
  watch: {
110
120
  "designer.widgetList": {
@@ -281,6 +291,9 @@ modules = {
281
291
  },
282
292
 
283
293
  changeLayoutType(newType) {
294
+ if (this.layoutTypeLocked) {
295
+ return;
296
+ }
284
297
  this.designer.changeLayoutType(newType);
285
298
  this.$emit("loadWidgets");
286
299
  },
@@ -6,7 +6,8 @@ import {
6
6
  advancedFields,
7
7
  customFields,
8
8
  businessFields,
9
- widgetPanelOrder
9
+ widgetPanelOrder,
10
+ h5PanelAllowTypes
10
11
  } from "./widgetsConfig"
11
12
  import {
12
13
  formTemplates
@@ -115,6 +116,16 @@ modules = {
115
116
  ? !1
116
117
  : !!this.designerConfig.metadataLib;
117
118
  },
119
+ currentLayoutType() {
120
+ return (this.designer && this.designer.formConfig && this.designer.formConfig.layoutType) || "PC";
121
+ },
122
+ },
123
+ watch: {
124
+ /* 打开 H5 模板是 setFormJson 后才写入 layoutType;面板 mounted 时仍是默认 PC。
125
+ 版式变化后必须按新 layoutType 重筛,否则左侧一直停在 PC 组件列表。 */
126
+ currentLayoutType() {
127
+ this.loadWidgets();
128
+ },
118
129
  },
119
130
  mounted() {
120
131
  this.designerConfig.chartLib && (this.firstTab = "chartLib"),
@@ -181,7 +192,8 @@ modules = {
181
192
 
182
193
  this.commonContainers = this.containers.filter(con => !!con.commonFlag && this.getIsShow(con));
183
194
 
184
- this.basicFields = this.basicFields.map(fld => {
195
+ /* 每次从 widgetsConfig 原表筛,不能拿 this.xxx(已被上次 layoutType 滤过)再滤一遍 */
196
+ this.basicFields = basicFields.map(fld => {
185
197
  return {
186
198
  ...fld,
187
199
  displayName: this.i18n2t(`designer.widgetLabel.${fld.type}`, `extension.widgetLabel.${fld.type}`)
@@ -190,13 +202,13 @@ modules = {
190
202
  return !this.isBanned(fld.type) && this.getIsShow(fld);
191
203
  });
192
204
 
193
- this.advancedFields = this.advancedFields.map(fld => {
205
+ this.advancedFields = advancedFields.map(fld => {
194
206
  return {
195
207
  ...fld,
196
208
  displayName: this.i18n2t(`designer.widgetLabel.${fld.type}`, `extension.widgetLabel.${fld.type}`)
197
209
  };
198
210
  }).filter(fld => {
199
- return !this.isBanned(fld.type) && this.getIsShow(fld);
211
+ return !fld.internal && !this.isBanned(fld.type) && this.getIsShow(fld);
200
212
  });
201
213
 
202
214
  /* 面板分组归一:表单数据字段(搜索框/自动完成/级联/凭证/状态等)展示在基础字段分组 */
@@ -209,7 +221,7 @@ modules = {
209
221
  );
210
222
  this.advancedFields = this.sortByPanelOrder(this.advancedFields, widgetPanelOrder.advancedFields);
211
223
 
212
- this.businessFields = this.businessFields.map(fld => {
224
+ this.businessFields = businessFields.map(fld => {
213
225
  return {
214
226
  ...fld,
215
227
  displayName: this.i18n2t(`designer.widgetLabel.${fld.type}`, `extension.widgetLabel.${fld.type}`)
@@ -229,7 +241,7 @@ modules = {
229
241
  && moveTypes.indexOf(fld.type) === -1;
230
242
  }), widgetPanelOrder.advancedFields);
231
243
 
232
- this.customFields = this.customFields.map(fld => {
244
+ this.customFields = customFields.map(fld => {
233
245
  return {
234
246
  ...fld,
235
247
  displayName: this.i18n2t(`designer.widgetLabel.${fld.type}`, `extension.widgetLabel.${fld.type}`)
@@ -343,6 +355,10 @@ modules = {
343
355
  let flag = !ftd.layoutType || ftd.layoutType === layoutType;
344
356
  let flag2 = this.checkColumnFlag(ftd)
345
357
  let flag3 = !!ftd.commonFlag
358
+ /* H5 面板按白名单收口,PC 不受影响 */
359
+ if (layoutType === "H5") {
360
+ flag = flag && h5PanelAllowTypes.indexOf(ftd.type) > -1;
361
+ }
346
362
  return flag && flag2 && flag3;
347
363
  }
348
364
  }
@@ -548,6 +548,7 @@ export const containers = [
548
548
  detailH5Container: true,
549
549
  // colHeight: null,
550
550
  customClass: "",
551
+ // 底栏平铺个数不做成选项,写死在 detail-h5-item 的 H5_BOTTOM_MAX_BUTTONS(=3)
551
552
  wfEnabled: false,
552
553
  onCreated: "",
553
554
  onMounted: "",
@@ -573,6 +574,8 @@ export const containers = [
573
574
  listH5: true,
574
575
  // colHeight: null,
575
576
  customClass: "",
577
+ // 明细行样式:'plain'=原有朴素列表(默认,保持存量表现);'card'=卡片皮肤
578
+ itemStyle: "plain",
576
579
  isQueryTable: false,
577
580
 
578
581
  accessUrl: null,
@@ -693,6 +696,7 @@ export const containers = [
693
696
  category: "container",
694
697
  icon: "tree-pane",
695
698
  commonFlag: !0,
699
+ layoutType: "PC",
696
700
  leftWidgetList: [],
697
701
  widgetList: [],
698
702
  options: {
@@ -3254,6 +3258,9 @@ export const advancedFields = [
3254
3258
  tableRef: "",
3255
3259
  tableExportParam: "",
3256
3260
  showImageAtTable: false,
3261
+ exportPageSize: null,
3262
+ maxQueryTotal: null,
3263
+ limitFileSize: null,
3257
3264
  tableExportFlag: 1,
3258
3265
 
3259
3266
  onCreated: "",
@@ -3316,6 +3323,9 @@ export const advancedFields = [
3316
3323
  tableRef: "",
3317
3324
  tableExportParam: "",
3318
3325
  showImageAtTable: false,
3326
+ exportPageSize: null,
3327
+ maxQueryTotal: null,
3328
+ limitFileSize: null,
3319
3329
  selectExportItemFlag: 1,
3320
3330
  exportItemConditionEnabled: !1,
3321
3331
  // 按钮级明细导出配置:三项各自独立,留空的那项沿用数据表格属性上维护的
@@ -3403,12 +3413,19 @@ export const advancedFields = [
3403
3413
  ...defaultWidgetShowRuleConfig,
3404
3414
  importMultiple: false,
3405
3415
  importMultiSize: 1,
3416
+ // 批量导入时给每行补一层实体信封以承载附件列;默认关=仍发裸行数组
3417
+ importMultiAttachEnvelope: false,
3406
3418
  importFileLimitSize: 200,
3407
3419
  importEntity: "",
3408
3420
  importAttachCode: "",
3409
3421
  importAttachName: "",
3410
3422
  importScriptCode: "",
3411
3423
  onBeforeImport: "",
3424
+ // 逐行补默认值脚本:返回对象,Object.assign 到该行后再发包
3425
+ importDefaultValue: "",
3426
+ onValidateRow: "",
3427
+ // 必填校验方式:"" 默认(逐行标记失败)/ "strict" 解析期整批硬拦 / "off" 不校验
3428
+ requiredCheckMode: "",
3412
3429
  enabledImportPreHandle: false,
3413
3430
  tableRef: "",
3414
3431
  onSuccessImport: "",
@@ -3458,6 +3475,24 @@ export const advancedFields = [
3458
3475
  importAttachCode: "",
3459
3476
  importAttachName: "",
3460
3477
  tableRef: "",
3478
+ // 进度框形态:逐行校验 + 成功/失败计数 + 结果表。
3479
+ // 默认关闭 —— 关闭时是"全屏 loading + 整批入表"的老链路,存量表单无感知。
3480
+ showProgressDialog: false,
3481
+ // 进度框形态下每行怎么处理:"table" 塞入关联表格;"script" 交给 onImportRow 逐行脚本
3482
+ importMode: "table",
3483
+ // 前台校验通过后,整批一次送给后台校验并补字段;默认关=不发请求
3484
+ backendValidateEnabled: false,
3485
+ importScriptCode: "",
3486
+ onImportParams: "",
3487
+ // 内嵌图片三段式:把 Excel 里贴的图取回来填进 attachments_ 列。
3488
+ // 依赖进度框,仅在 showProgressDialog 打开时生效
3489
+ enabledImportPreHandle: false,
3490
+ onImportRow: "",
3491
+ onValidateRow: "",
3492
+ // 入表前按该字段去重,空则不去重
3493
+ dedupField: "",
3494
+ // 必填校验方式:"" 默认(有逐行通道就逐行标记,否则整批拦)/ "strict" 整批硬拦 / "off" 不校验
3495
+ requiredCheckMode: "",
3461
3496
  onConfirmImportEnabled: false,
3462
3497
  onConfirmImport: "",
3463
3498
  onSuccessImport: "",
@@ -4629,18 +4664,68 @@ export const widgetPanelOrder = {
4629
4664
  ],
4630
4665
  };
4631
4666
 
4667
+ /**
4668
+ * H5 版式左侧组件面板白名单(仅影响面板展示,不影响已有模板渲染与按 type 查找)。
4669
+ * 容器:H5详情 / H5列表 / H5卡片 / h5表格
4670
+ * 基础字段:文本、状态、凭证、下拉选项
4671
+ * (status / vabUpload 定义在 advancedFields,面板会归入基础字段)
4672
+ * 业务组件:创建人、更新人、创建时间、更新时间
4673
+ */
4674
+ export const h5PanelAllowTypes = [
4675
+ "detail-h5",
4676
+ "list-h5",
4677
+ "h5-card",
4678
+ "h5-table",
4679
+ "text",
4680
+ "status",
4681
+ "vabUpload",
4682
+ "select",
4683
+ "create_by-text",
4684
+ "modify_by-text",
4685
+ "create_date-text",
4686
+ "modify_date-text",
4687
+ ];
4688
+
4689
+ /**
4690
+ * H5 版式:所有表单字段统一具备 h5Span(整宽/半宽)。
4691
+ *
4692
+ * 为什么用后处理而不是在 42 个字段定义里逐个展开 ...defaultH5Config:
4693
+ * 1) 一处逻辑覆盖全部字段,不会漏;
4694
+ * 2) 顺带覆盖 extension 通过 add*Schema 动态注册的组件;
4695
+ * 3) 用函数调用而非展开运算符,不踩本文件头部注释里那个 Babel TDZ 的坑。
4696
+ *
4697
+ * 属性面板显不显示「H5 占宽」由 designer.hasConfig 决定,它查的是**原型 widget
4698
+ * 的 options**(不是实例),所以必须在这里写进原型。
4699
+ * 详见 docs/2026-08 xform H5 P0 实现规格.md §3.1。
4700
+ */
4701
+ export const defaultH5Config = {
4702
+ h5Span: "full", // 'full' | 'half'
4703
+ };
4704
+
4705
+ function applyH5FieldDefaults(schema) {
4706
+ if (!schema || !schema.formItemFlag || !schema.options) return schema;
4707
+ if (!Object.prototype.hasOwnProperty.call(schema.options, "h5Span")) {
4708
+ Object.assign(schema.options, defaultH5Config);
4709
+ }
4710
+ return schema;
4711
+ }
4712
+
4713
+ [basicFields, advancedFields, businessFields, customFields].forEach((list) =>
4714
+ list.forEach(applyH5FieldDefaults)
4715
+ );
4716
+
4632
4717
  export function addContainerWidgetSchema(containerSchema) {
4633
4718
  containers.push(containerSchema);
4634
4719
  }
4635
4720
 
4636
4721
  export function addBasicFieldSchema(fieldSchema) {
4637
- basicFields.push(fieldSchema);
4722
+ basicFields.push(applyH5FieldDefaults(fieldSchema));
4638
4723
  }
4639
4724
 
4640
4725
  export function addAdvancedFieldSchema(fieldSchema) {
4641
- advancedFields.push(fieldSchema);
4726
+ advancedFields.push(applyH5FieldDefaults(fieldSchema));
4642
4727
  }
4643
4728
 
4644
4729
  export function addCustomWidgetSchema(widgetSchema) {
4645
- customFields.push(widgetSchema);
4730
+ customFields.push(applyH5FieldDefaults(widgetSchema));
4646
4731
  }
@@ -2782,7 +2782,9 @@ modules = {
2782
2782
  if (
2783
2783
  customParam?.exportParam?.type !== EXPORT_TYPE.ITEM_SELECTED
2784
2784
  ) {
2785
- formData = tableOption.param ? tableOption.param() || {} : {};
2785
+ formData = customParam?.exportParam?.exportQuerySnapshot !== undefined
2786
+ ? this.$baseLodash.cloneDeep(customParam.exportParam.exportQuerySnapshot)
2787
+ : tableOption.param ? tableOption.param() || {} : {};
2786
2788
  }
2787
2789
  const queryParams = Object.assign({}, formData);
2788
2790
 
@@ -10,9 +10,16 @@
10
10
 
11
11
  <template>
12
12
  <container-item-wrapper :widget="widget">
13
- <div class="h5-detail-wrap grid-container" :class="[customClass]" :ref="widget.id">
13
+ <!-- h5-no-bottom-btns:底栏一个按钮都没有时打上,让内容区吃掉底栏那一格高度。
14
+ 光靠底栏自己 v-if 不够 —— .h5-detail-body 的高度是
15
+ calc(100% - 底栏 - 标题栏) 硬减出来的(styles/h5.scss 块 G),
16
+ 不同步改的话底部会空出一条 56px 的白带。见块 G2。 -->
17
+ <div class="h5-detail-wrap grid-container"
18
+ :class="[customClass, { 'h5-no-bottom-btns': !hasBottomButtons() }]" :ref="widget.id">
14
19
  <div class="h5-header">{{ widget.options.label }}</div>
15
- <div>
20
+ <!-- 唯一滚动区:标题栏与底部动作栏 flex:none 固定,内容在这里滚。
21
+ 样式见 components/xform/styles/h5.scss 块 G(.h5-detail-body 必须有 min-height:0) -->
22
+ <div class="h5-detail-body">
16
23
  <transition-group name="fade" tag="div" class="form-widget-list">
17
24
  <template v-if="!!widget.panes && (widget.panes.length > 0)">
18
25
  <template v-for="(subWidget, swIdx) in widget.panes">
@@ -39,12 +46,26 @@
39
46
  </template>
40
47
  </template>
41
48
  </transition-group>
49
+ <!-- 移动版流程面板:自给自足(mounted 里自己取数),只需给 wfOption。
50
+ 全部用方法求值而非 computed —— 流程状态挂在表单实例上且非响应式,
51
+ computed 会缓存住早于流程就绪时的空值(见 wfStart 的注释)。
52
+ 样式见 styles/h5.scss 块 L。 -->
53
+ <mobileWfContent v-if="showWfPanel()" ref="wfContent" :wfOption="getWfOption()"
54
+ :defaultShowWfContent="getDefaultShowWfContent()"
55
+ :hideActions="true"/>
42
56
  </div>
43
- <div class="h5-fixed-bottom-btns ">
57
+ <!-- 一个按钮都没有时整条不渲染:底栏是 flex:none 的定高块,
58
+ 留着就是一条 56px 的空白胶囊条,还带阴影。 -->
59
+ <div class="h5-fixed-bottom-btns " v-if="hasBottomButtons()">
44
60
  <transition-group name="fade" tag="div" class="form-widget-list">
45
- <template v-if="!!widget.widgetList && (widget.widgetList.length > 0)">
46
- <template v-for="(subWidget, swIdx) in widget.widgetList">
47
- <template v-if="'container' === subWidget.category">
61
+ <template v-if="visibleButtons().length > 0">
62
+ <template v-for="(subWidget, swIdx) in visibleButtons()">
63
+ <!-- 审批动作按钮,不是 widget,单独渲染。只出文字不带图标 -->
64
+ <div class="field-wrapper" v-if="subWidget.__builtin" :key="'b-' + subWidget.__builtin">
65
+ <el-button class="button-sty" :type="subWidget.type"
66
+ @click="subWidget.onClick">{{ subWidget.label }}</el-button>
67
+ </div>
68
+ <template v-else-if="'container' === subWidget.category">
48
69
  <component :is="subWidget.type + '-item'" :widget="subWidget" :key="swIdx"
49
70
  :parent-list="widget.widgetList"
50
71
  :index-of-parent-list="swIdx" :parent-widget="widget">
@@ -67,6 +88,25 @@
67
88
  </template>
68
89
  </template>
69
90
  </transition-group>
91
+ <!-- 超出的按钮收进纯图标下拉。它按内容宽(不参与等分),排在最右。
92
+ popper-class 而非嵌套作用域:el-dropdown-menu 默认 append-to-body,
93
+ 菜单 DOM 会被挪到 body 下、脱离 .xform-h5,故样式写成顶层
94
+ .xform-h5-dropdown 选择器(见 styles/h5.scss)。 -->
95
+ <el-dropdown v-if="overflowButtons().length > 0" class="h5-more-dropdown"
96
+ trigger="click" popper-class="xform-h5-dropdown">
97
+ <!-- 裸图标触发器,不套 el-button:底栏在页面底部、菜单往上弹,故用向上箭头 -->
98
+ <i class="el-icon-arrow-up h5-more-icon"></i>
99
+ <el-dropdown-menu slot="dropdown">
100
+ <el-dropdown-item v-for="(subWidget, swIdx) in overflowButtons()" :key="swIdx">
101
+ <el-button v-if="subWidget.__builtin" class="button-sty h5-dropdown-button" :type="subWidget.type"
102
+ @click="subWidget.onClick">{{ subWidget.label }}</el-button>
103
+ <component v-else :is="subWidget.type + '-widget'" :field="subWidget" :designer="null"
104
+ :parent-list="widget.widgetList"
105
+ :index-of-parent-list="swIdx + visibleButtons().length"
106
+ :parent-widget="widget"/>
107
+ </el-dropdown-item>
108
+ </el-dropdown-menu>
109
+ </el-dropdown>
70
110
  </div>
71
111
  </div>
72
112
 
@@ -83,7 +123,18 @@ import containerItemMixin from "./containerItemMixin"
83
123
  import FieldComponents from '../../../../components/xform/form-designer/form-widget/field-widget/index'
84
124
 
85
125
 
86
- import {initWf} from '../../../../components/wf/wfUtil'
126
+ import {getVisibleWfActionItems} from '../../../../components/wf/wfActionItems'
127
+
128
+ /* H5 底栏只放这四个审批动作,其余(催办/加签/减签/沟通/修改候选人/撤回…)不进底栏。
129
+ ★ 这里的书写序就是底栏的显示序:通过 → 驳回 → 中断 → 转办。 */
130
+ const H5_WF_ACTION_KEYS = ['agree', 'reject', 'manual', 'transfer'];
131
+
132
+ /* 底栏平铺的按钮个数上限,超出的收进最右侧的「更多」图标下拉 ——
133
+ 即一行最多「3 个操作按钮 + 1 个更多图标」。
134
+ 写死而不做成表单选项:这个数由底栏宽度决定,配大了会挤成一团,
135
+ 而且此前做成 options.maxVisibleButtons 后,已保存的表单会锁住旧值,
136
+ 改了原型默认值也不生效,反成绊脚石。 */
137
+ const H5_BOTTOM_MAX_BUTTONS = 3;
87
138
 
88
139
  export default {
89
140
  name: "detail-h5-item",
@@ -92,6 +143,11 @@ export default {
92
143
  components: {
93
144
  ContainerItemWrapper,
94
145
  ...FieldComponents,
146
+ /* 懒加载:PC 版式的表单不该把移动 wf 那坨打进主包。
147
+ 注:content.vue 的根节点是 <h5-card-pane>,Vue 2 的组件解析只查自身
148
+ components 与全局注册、不查父组件的局部注册,所以那个依赖必须由
149
+ content.vue 自己声明(已在该文件补上),在这里注册是没用的。 */
150
+ mobileWfContent: () => import('../../../../components/mobile/wf/content.vue'),
95
151
  },
96
152
  props: {
97
153
  widget: Object,
@@ -103,19 +159,154 @@ export default {
103
159
  },
104
160
  mounted() {
105
161
  this.handleOnMounted();
106
- this.$nextTick(() => {
107
- this.createWf();
108
- })
109
162
  },
110
163
  beforeDestroy() {
111
164
  this.unregisterFromRefList()
112
165
  },
113
166
  data() {
114
- return {
115
- wfInfo: {}
116
- }
167
+ return {}
117
168
  },
118
169
  methods: {
170
+ /**
171
+ * 流程状态实时求值 —— 与 PC 的 detail-item.wfStart() 同口径。
172
+ *
173
+ * 取数不归本组件管:form-render 的 loadDefaultEntityData 在
174
+ * formConfig.wfEnabled 为真时就已经初始化好流程,把结果挂在表单实例上
175
+ * (wfStartOption / wfStartCtx / wfParam / hasWf),入参也在那边算好
176
+ * (objId = dataId、wfCode = reportTemplate.objTypeCode,见 indexMixin:1394)。
177
+ *
178
+ * 必须用方法而不是 computed:wfStartOption / wfStartCtx / wfParam **都不在
179
+ * form-render 的 data 里**(只有 hasWf 在),不是响应式的,computed 会把
180
+ * 早于流程就绪时求得的空值缓存住。监听 hasWf 也不行 —— 单据没启动流程时
181
+ * 它是 false 赋 false,watcher 根本不触发。
182
+ *
183
+ * 注意:容器 options 上那个 wfEnabled 是旧路径的开关,与表单设置里的
184
+ * formConfig.wfEnabled 不是一回事,这里一律以表单实例的状态为准。
185
+ */
186
+ wfStart() {
187
+ let f = this.getFormRef && this.getFormRef();
188
+ if (!f || !f.wfStartOption) return null
189
+ let param = f.wfParam || {};
190
+ return {
191
+ ctx: f.wfStartCtx || f.$parent,
192
+ option: f.wfStartOption,
193
+ param: param,
194
+ hasWf: !!(f.hasWf || param.hasWf),
195
+ wfInfo: param.wfInfo || {},
196
+ taskParam: param.taskParam || {},
197
+ };
198
+ },
199
+ /**
200
+ * 映射成移动版 content.vue 要的 wfOption。
201
+ * 结果缓存在非响应式实例属性上:模板里调用的方法每次渲染都会执行,
202
+ * 每次返回新对象会让子组件 props 无谓变化;而在渲染期写 data 会触发重渲染循环。
203
+ */
204
+ getWfOption() {
205
+ if (this._wfOptionCache) return this._wfOptionCache
206
+ let ws = this.wfStart();
207
+ if (!ws) return null
208
+ let opt = ws.option;
209
+ let serviceId = opt.serviceId || "user";
210
+ this._wfOptionCache = {
211
+ objId: opt.objId,
212
+ wfCode: opt.wfCode,
213
+ serviceId: serviceId,
214
+ wf_prefix: "/" + serviceId,
215
+ startParam: opt.startParam,
216
+ startConfirmText: opt.startConfirmText,
217
+ parentVue: ws.ctx,
218
+ };
219
+ return this._wfOptionCache
220
+ },
221
+ /* 流程面板:已启动流程且非预览态才渲染 */
222
+ showWfPanel() {
223
+ let ws = this.wfStart();
224
+ return !this.previewState && !!ws && ws.hasWf;
225
+ },
226
+ /* 宿主通过 $attrs 传「进来就展开流程区」,与 PC 版 wfUtil 同口径 */
227
+ getDefaultShowWfContent() {
228
+ let ws = this.wfStart();
229
+ let target = ws && ws.ctx;
230
+ return !!(target && target.$attrs && target.$attrs.defaultShowWfContent);
231
+ },
232
+ /* 注:H5 底栏不放「流程启动」和「修改单据」两个内建按钮
233
+ (PC 的 wfUtil 与移动版 mixin 里都有,这里按需求去掉),
234
+ 底栏只有审批动作 + 业务按钮。 */
235
+ /**
236
+ * 审批动作按钮(通过/驳回/中断/转办)。
237
+ *
238
+ * 复用 PC 的动作注册表 wfActionItems —— 它的 run 是 (wfVm) => wfVm.openAgreeDialog()
239
+ * 这种形式,而这些方法名在移动版 content.vue 里完全一致,所以同一份注册表两边通用。
240
+ *
241
+ * show 用表单实例上的 wfInfo 求值(读 f.hasWf 会建立响应式依赖,流程就绪时能重渲染);
242
+ * run 则要真实的面板实例,点击时才从 $refs 取 —— 那时面板一定已经渲染好了。
243
+ * 面板内那一排同名按钮由 :hideActions="true" 藏掉,不会出现两份。
244
+ */
245
+ wfActionButtons() {
246
+ let ws = this.wfStart();
247
+ if (!ws || !ws.hasWf || this.previewState) return []
248
+ // 只用 wfInfo 算显隐,不依赖面板实例(它可能还没渲染)
249
+ let items = getVisibleWfActionItems({ wfInfo: ws.wfInfo });
250
+ let byKey = {};
251
+ items.forEach((item) => {
252
+ byKey[item.key] = item;
253
+ });
254
+ // 按 H5_WF_ACTION_KEYS 取,顺序由它决定 —— 不能用 items.filter,
255
+ // 那样保留的是注册表的定义序(通过/中断/驳回/转办),与要求的顺序不一致
256
+ return H5_WF_ACTION_KEYS.map((key) => byKey[key])
257
+ .filter((item) => !!item)
258
+ .map((item) => ({
259
+ __builtin: "wfAction-" + item.key,
260
+ label: item.i18nPath
261
+ ? this.$t2(item.defaultLabel, item.i18nPath)
262
+ : item.defaultLabel,
263
+ // 注册表里的 iconClass/elIcon 不取:H5 底栏按钮只出文字
264
+ type: item.buttonType || "primary",
265
+ onClick: () => {
266
+ let wfVm = this.$refs.wfContent;
267
+ if (!wfVm) return
268
+ item.run(wfVm);
269
+ },
270
+ }));
271
+ },
272
+ /**
273
+ * 底栏按钮:审批动作在前(审批中的单据以审批为主),然后是流程启动/修改单据,
274
+ * 最后是业务按钮。hidden 的业务按钮先剔除,否则「更多」里会留空条目
275
+ * (static-content-wrapper 内部只是 v-show 隐藏,el-dropdown-item 仍占一行)。
276
+ */
277
+ bottomButtons() {
278
+ let widgetButtons = (this.widget.widgetList || []).filter(
279
+ (w) => !w.options || !w.options.hidden
280
+ );
281
+ return this.wfActionButtons().concat(widgetButtons);
282
+ },
283
+ /**
284
+ * 底栏要不要出。与 visibleButtons/overflowButtons 同源(都走 bottomButtons),
285
+ * 不会出现「有按钮却不渲染」的错位。
286
+ *
287
+ * 用方法而非 computed,同本文件其余流程相关方法:wfActionButtons 依赖的流程状态
288
+ * 挂在表单实例上、多数不是响应式的。重渲染的触发点是 f.hasWf(它在 form-render
289
+ * 的 data 里)与 widget.widgetList —— 流程就绪、按钮显隐变化时都会重算,
290
+ * 底栏跟着出现或消失。
291
+ */
292
+ hasBottomButtons() {
293
+ return this.bottomButtons().length > 0;
294
+ },
295
+ /**
296
+ * 平铺的按钮。宽度由 CSS 的 flex:1 等分决定 ——
297
+ * 1 个铺满、2 个各一半、3 个各三分之一,不需要在这里算。
298
+ */
299
+ visibleButtons() {
300
+ let list = this.bottomButtons();
301
+ if (list.length <= H5_BOTTOM_MAX_BUTTONS) return list;
302
+ return list.slice(0, H5_BOTTOM_MAX_BUTTONS);
303
+ },
304
+ /** 超出上限的按钮,收进底栏最右侧那个纯图标下拉 */
305
+ overflowButtons() {
306
+ let list = this.bottomButtons();
307
+ if (list.length <= H5_BOTTOM_MAX_BUTTONS) return [];
308
+ return list.slice(H5_BOTTOM_MAX_BUTTONS);
309
+ },
119
310
  reload() {
120
311
  this.getFormRef().$parent.$baseReload()
121
312
  },
@@ -123,27 +314,6 @@ export default {
123
314
  if (!!this.previewState) return
124
315
  this.getFormRef().saveForm();
125
316
  },
126
- createWf() {
127
- if (!!this.previewState || !this.widget.options.wfEnabled) return
128
- let formTarget = this.getFormRef();
129
- let formData = formTarget.formData;
130
- if (formData && formData.object_foreign_id) {
131
- let reportTemplate = formTarget.reportTemplate;
132
- let formCode = reportTemplate.formCode;
133
- let option = {
134
- objId: formData.object_foreign_id, //单据ID
135
- wfCode: formCode, //流程编码
136
- showStartBtn: true, //是否放出启动按钮,流程已启动时此参数失效
137
- serviceId: 'user',
138
- startConfirmText: () => formTarget.getWfStartConfirmText(),
139
- callback: ({wfInfo}) => {
140
- this.wfInfo = wfInfo;
141
- }
142
- }
143
- initWf.call(formTarget.$parent, option);
144
- }
145
-
146
- }
147
317
  },
148
318
  }
149
319
  </script>