cloud-web-corejs 1.0.54-dev.725 → 1.0.54-dev.727

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 (20) hide show
  1. package/package.json +1 -1
  2. package/src/components/vb-tabs/x-tabs.vue +3 -2
  3. package/src/components/wf/content.vue +1 -1
  4. package/src/components/wf/wf.js +16 -1
  5. package/src/components/wf/wfUtil.js +1 -1
  6. package/src/components/xform/XFormTabContainer//346/226/260/345/242/236/351/241/265/347/255/276/346/250/241/345/274/217-/346/234/200/347/273/210/345/256/236/347/216/260.md +195 -0
  7. package/src/components/xform/form-designer/designer.js +4 -0
  8. package/src/components/xform/form-designer/setting-panel/form-setting.vue +31 -0
  9. package/src/components/xform/form-render/container-item/data-table-mixin.js +18 -0
  10. package/src/components/xform/form-render/container-item/detail-item.vue +107 -3
  11. package/src/components/xform/form-render/indexMixin.js +95 -1
  12. package/src/components/xform/mixins/defaultHandle.js +16 -0
  13. package/src/components/xform/mixins/scriptHttp.js +3 -0
  14. package/src/components/xform/utils/util.js +4 -0
  15. package/src/layout/components/AppMain.vue +2 -1
  16. package/src/views/user/form/view/list.vue +164 -11
  17. package/src/views/user/position/list.vue +68 -108
  18. package/src/components/xform/XFormTabContainer/IMPLEMENTATION_PLAN.md +0 -1079
  19. package/src/components/xform/XFormTabContainer//346/226/260/345/242/236/351/241/265/347/255/276/346/250/241/345/274/217-/350/220/275/345/234/260/345/256/236/346/226/275/346/226/207/346/241/243.md +0 -232
  20. package/src/components/xform/XFormTabContainer//346/226/260/345/242/236/351/241/265/347/255/276/346/250/241/345/274/217/345/256/236/347/216/260/346/226/271/346/241/210.md +0 -511
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cloud-web-corejs",
3
3
  "private": false,
4
- "version": "1.0.54-dev.725",
4
+ "version": "1.0.54-dev.727",
5
5
  "scripts": {
6
6
  "dev": "vue-cli-service serve",
7
7
  "lint": "eslint --ext .js,.vue src",
@@ -91,9 +91,10 @@ export default {
91
91
  });
92
92
  if (this.activeName === targetName) {
93
93
  if (tabIndex > 0) {
94
+ let prev = this.tabs[tabIndex - 1];
94
95
  this.activeName = tabNameEnable
95
- ? tab.tabName
96
- : this.tabs[tabIndex - 1].data[paneKeyName] + "";
96
+ ? prev.tabName
97
+ : prev.data[paneKeyName] + "";
97
98
  } else {
98
99
  this.activeName = "second";
99
100
  }
@@ -7,7 +7,7 @@
7
7
  >
8
8
  <template #default>
9
9
  <div
10
- style="margin: -49px 0 16px 124px; position: relative; z-index: 2; height: 28px"
10
+ style="margin: -36px 0 8px 124px; position: relative; z-index: 2; height: 28px"
11
11
  v-if="!wfConfig.hiddenAllButton"
12
12
  >
13
13
  <el-button
@@ -1659,7 +1659,8 @@ wfStartMixin = {
1659
1659
  startFormData:{},
1660
1660
  wfUserRangeEnabled: false,
1661
1661
  preStartResult:[],
1662
- showSetCandidateDialog2:false
1662
+ showSetCandidateDialog2:false,
1663
+ wfSubmitted: false
1663
1664
  };
1664
1665
  },
1665
1666
  computed: {
@@ -1815,6 +1816,7 @@ wfStartMixin = {
1815
1816
 
1816
1817
  },
1817
1818
  startSubmitHanlde(formData){
1819
+ this.wfSubmitted = true; // 标记已进入提交,避免随后关闭弹框误判为「取消」
1818
1820
  this.showDialog = false;
1819
1821
  let that = getTarget(this);
1820
1822
  let option = this.option;
@@ -1850,10 +1852,23 @@ wfStartMixin = {
1850
1852
  }
1851
1853
  });
1852
1854
  },
1855
+ handleWfCancel() {
1856
+ // 未进入提交就关闭 = 取消启动,回调通用 onCancel(一次性,避免多个弹框关闭重复触发)
1857
+ if (this.wfSubmitted) return;
1858
+ this.wfSubmitted = true;
1859
+ this.option && this.option.onCancel && this.option.onCancel();
1860
+ },
1853
1861
  close() {
1862
+ this.handleWfCancel();
1854
1863
  this.$emit("update:visible", false);
1855
1864
  },
1856
1865
  },
1866
+ watch: {
1867
+ showSetCandidateDialog2(val, oldVal) {
1868
+ // 候选人弹框未提交就关闭 = 取消启动
1869
+ if (oldVal && !val) this.handleWfCancel();
1870
+ },
1871
+ },
1857
1872
  };
1858
1873
 
1859
1874
  wfModifyMixin = {
@@ -141,7 +141,7 @@ f = async function (option) {
141
141
  appendPrevChildDom(wfBtnRegion, dom);
142
142
  }
143
143
 
144
- if (wfBtnRegion && showStartBtn && !hasWf && !option.hiddenAllButton) {
144
+ if (wfBtnRegion && showStartBtn && option.injectStartBtn !== false && !hasWf && !option.hiddenAllButton) {
145
145
  let wfStartButtonName = configUtil.settingConfig.wfStartButtonName || this.$t2('流程启动', 'components.wf.wfStart');
146
146
  let dom = new configUtil.Vue({
147
147
  render: function (h) {
@@ -0,0 +1,195 @@
1
+ # xform 表单「新增页签模式」——最终实现(已落地)
2
+
3
+ > 本文记录**实际已落地**的实现,替代前几版方案文档(`新增页签模式实现方案.md` / `新增页签模式-落地实施文档.md` / `新增页签模式-归一落地方案.md` / 弃用的 `IMPLEMENTATION_PLAN.md`)。
4
+ > 需求:去掉固定「常规」页签承载新增 → 新增打开独立可关闭页签(标题"新增")→ 保存后本页变编辑态(标题变名称字段)→ 提交(启动流程)后可配置「关闭页签回列表刷新」或「刷新本页」,并正确处理保存成功/提交失败/取消等边界。
5
+
6
+ ---
7
+
8
+ ## 一、最终交互与配置
9
+
10
+ ### 配置项(列表表单 formConfig,设计器「列表标签页信息」分组)
11
+
12
+ | 字段 | 类型 | 默认 | 含义 |
13
+ |------|------|------|------|
14
+ | `addTabEnabled` | Boolean | `false` | 新增打开独立页签总开关。开启后新增走动态可关闭页签,不走「常规」 |
15
+ | `addTabLabel` | String | `"新增"` | 新增页签初始标题 |
16
+ | `submitBehavior` | String | `"closeToList"` | 提交后行为:`closeToList`(关页签回列表刷新)/ `refreshCurrent`(刷新本页出流程信息) |
17
+
18
+ 复用既有字段(未新造):`hideNormalTab`(隐藏常规页签)、`multiTabLabelField`(页签标题字段,新增/编辑共用)、`multiTabEnabled`(编辑页签多开,语义不变)。
19
+
20
+ > **配置位置统一在「列表表单」**,与 `multiTabEnabled` 同处。`submitBehavior` 运行时优先读列表表单配置。
21
+
22
+ ### 端到端行为(详情内容恰好刷新一次)
23
+
24
+ | 分支 | 行为 | 详情刷新 |
25
+ |------|------|:---:|
26
+ | 新增保存(纯保存) | 页签升级编辑态、标题变名称字段、`dataId`=单号 | 1(编辑态重载) |
27
+ | 保存并提交 → 提交成功 + `refreshCurrent` | 刷新本页,出流程信息 | 1 |
28
+ | 保存并提交 → 提交成功 + `closeToList` | 关当前页签 → 切列表 → 刷新列表 | 0(详情) |
29
+ | 保存并提交 → 提交失败 / 取消候选人弹框 | 刷新当前详情到编辑态(不重复入库) | 1 |
30
+ | 直接启动流程(`wfStartBindSave` 开)→ 失败/取消 | 刷新到编辑态 | 1 |
31
+ | **直接启动流程(`wfStartBindSave` 关)→ 失败/取消** | **不刷新表单** | 0 |
32
+ | 直接启动流程 → 成功 | 按 `submitBehavior` 关页签/刷新 | 0 或 1 |
33
+
34
+ ---
35
+
36
+ ## 二、设计要点
37
+
38
+ 1. **归一**:新增 = 一个「还没有单号的编辑页签」,复用现有 `x-tabs` + `editTab` 插槽机制,不另写容器/分支体系。
39
+ 2. **合成唯一 id**:新增时给虚拟 row 合成 `id = tabName = "add_<时间戳>_<随机>"`,使 `x-tabs` 去重(按 `data.id`)/`tabName`/`paneName` 三者对齐;`dataId` 显式置 `null`,避免被当真实单号去 `getOne`。
40
+ 3. **元数据升级与内容重载解耦**:表单不 `watch` `dataId`,故改 `tab.dataId` 不触发重载。保存成功先只升级页签元数据(`dataId`/`isAdd`/`formCode`/标题),内容的那一次刷新交给流程结束后的分支,保证详情不刷两次。
41
+ 4. **wf 组件零业务耦合**:`wf.js` 只认通用钩子 `option.onStartSuccess` / `onStartFail` / `onCancel`,页签相关逻辑全部收在 xform 侧(`indexMixin`)。
42
+ 5. **两个入口统一**:「保存并提交」(`defaultHandle`)与「直接启动流程」(`indexMixin` 构造的 wf option)都通过同一批 xform 方法处理。
43
+
44
+ ---
45
+
46
+ ## 三、改动清单(实际代码)
47
+
48
+ ### 1. `components/xform/utils/util.js` — `getDefaultFormConfig()`
49
+ 新增 3 个默认字段(**真正生效的默认值源**,非 designer.js 内联对象):
50
+ ```js
51
+ addTabEnabled: false,
52
+ addTabLabel: "新增",
53
+ submitBehavior: "closeToList",
54
+ ```
55
+
56
+ ### 2. `components/xform/form-designer/setting-panel/form-setting.vue`
57
+ 「列表标签页信息」分组新增:`addTabEnabled` 开关、`addTabLabel` 输入、`submitBehavior` 单选。
58
+
59
+ ### 3. `components/vb-tabs/x-tabs.vue` — `removeTab` 既有 bug 修复
60
+ `tabNameEnable=true` 且关闭非首个页签时引用了未定义的 `tab`,修为 `this.tabs[tabIndex-1]`:
61
+ ```js
62
+ let prev = this.tabs[tabIndex - 1];
63
+ this.activeName = tabNameEnable ? prev.tabName : prev.data[paneKeyName] + "";
64
+ ```
65
+
66
+ ### 4. `views/user/form/view/list.vue`
67
+
68
+ **`openEditDialog`** — 归一分支,新增合成唯一 id、`dataId:null`、`isAdd`:
69
+ ```js
70
+ let isAdd = !dataId;
71
+ let addTab = formConfig.addTabEnabled && isAdd && tabLabelField;
72
+ if ((multiTabEnabled && dataId) || addTab) {
73
+ let key = isAdd ? "add_" + Date.now() + "_" + Math.random().toString(36).slice(2,6) : dataId;
74
+ let openRow = isAdd ? { ...row, id: key, [tabLabelField]: formConfig.addTabLabel || "新增" } : row;
75
+ this.$refs.xTabs.openEditTab(openRow, param, {
76
+ tabParam: { formCode, tabLabelField, tabName: key, isAdd, ...(isAdd ? { dataId: null } : {}) },
77
+ });
78
+ } else { /* 原「常规」分支不变 */ }
79
+ ```
80
+
81
+ **新增方法**(`editTab` 插槽 `@reload` 改为 `(...args) => onEditTabReload(tab, ...args)`):
82
+ - `resolveFormModel(target)`:逐级解析真实表单 `formDataModel`(`target` → `target.$refs.vFormRef` → `target.getFormRef()`)。
83
+ - `applyTabSaveResult(tab, dataId, formCode, formModel)`:升级编辑态 + 同步标题(标题取名称字段值,无值不回退单号)。**不含内容重渲染**。
84
+ - `onEditTabReload(tab, target, param)`:纯保存路径 → `applyTabSaveResult` + `reloadTabContent`(重渲染)。
85
+ - `promoteActiveAddTab(dataId, formCode, target)`:保存并提交路径 → 只对当前活动页签 `applyTabSaveResult`,**不重渲染**。
86
+
87
+ ### 5. `components/xform/mixins/defaultHandle.js` — `saveAndSubmitWfHandle`
88
+ 保存成功回调里:**先只升级页签元数据**(不刷详情),再开流程弹框,注入统一钩子:
89
+ ```js
90
+ success: (res) => {
91
+ let editFormCode = formConfig.editFormCode;
92
+ let listPageRef = formRef.getListPageRef && formRef.getListPageRef();
93
+ if (listPageRef && listPageRef.promoteActiveAddTab) {
94
+ listPageRef.promoteActiveAddTab(res.objx, editFormCode, formRef); // 元数据升级,防重复入库,不刷详情
95
+ }
96
+ openStartWfDialog(formRef, {
97
+ objId: res.objx, wfCode: ..., formCode: ..., serviceId: ..., skipStartConfirm: true,
98
+ onStartSuccess: () => formRef.onWfStartSuccess && formRef.onWfStartSuccess(),
99
+ onStartFail: () => formRef.onWfStartAbort && formRef.onWfStartAbort(), // 有保存 → 失败刷编辑态
100
+ onCancel: () => formRef.onWfStartAbort && formRef.onWfStartAbort(), // 有保存 → 取消刷编辑态
101
+ });
102
+ option?.config?.success && option.config.success(res);
103
+ },
104
+ ```
105
+
106
+ ### 6. `components/xform/form-render/indexMixin.js`
107
+
108
+ **wf option 构造处**(直接启动按钮入口,约 L1379):
109
+ ```js
110
+ onStartSuccess: () => this.onWfStartSuccess(), // 成功始终处理
111
+ // ...
112
+ if (formConfig.wfStartBindSave) { // 仅当伴随自动保存时,失败/取消才刷新
113
+ option.onStartFail = () => this.onWfStartAbort();
114
+ option.onCancel = () => this.onWfStartAbort();
115
+ option.onStart = ...; option.onBeforeStartSubmit = ...; // 既有自动保存逻辑
116
+ }
117
+ ```
118
+
119
+ **新增方法**(`switchToListTab` 附近):
120
+ ```js
121
+ getAddTabContext() {
122
+ // 返回 {xTabs, currentName, submitBehavior},仅当处于开启 addTabEnabled 的动态页签内;否则 null
123
+ }
124
+ onWfStartSuccess() {
125
+ let ctx = this.getAddTabContext();
126
+ if (ctx) {
127
+ if (ctx.submitBehavior === "refreshCurrent") this.$baseReload();
128
+ else { this.switchToListTab({ handleType: "reload" }); ctx.xTabs.removeTab(ctx.currentName); }
129
+ return;
130
+ }
131
+ this.$baseReload(); // 非本模式:原默认行为
132
+ }
133
+ onWfStartAbort() {
134
+ // 失败/取消:本模式下刷新当前详情到编辑态(dataId 已升级为单号);非本模式不处理
135
+ if (this.getAddTabContext()) this.$baseReload();
136
+ }
137
+ ```
138
+ > `getAddTabContext` 判定:当前页签名非 `first`/`second`/`otherTab*`,且列表表单 `addTabEnabled` 为真。
139
+
140
+ ### 7. `components/wf/wf.js` — `wfStartMixin`(仅通用钩子,无业务逻辑)
141
+ - `data` 增 `wfSubmitted: false`。
142
+ - `startSubmitHanlde` 首行 `this.wfSubmitted = true;`(进入提交后关闭弹框不算取消)。
143
+ - 新增 `handleWfCancel()`:未提交则一次性回调 `option.onCancel`(防多弹框重复触发)。
144
+ - `close()` 调 `handleWfCancel()`;新增 `watch.showSetCandidateDialog2`:候选人弹框未提交就关闭 → `handleWfCancel()`。
145
+ - 失败分支沿用既有 `option.onStartFail && option.onStartFail()`。
146
+
147
+ ---
148
+
149
+ ## 四、关键边界与结论
150
+
151
+ - **多次新增不覆盖**:合成唯一 id 保证每次新增各开一页签。
152
+ - **新增不误触发 getOne**:`tabParam.dataId = null` 覆盖 x-tabs 默认取 `row.id`。
153
+ - **标题**:取名称字段(`multiTabLabelField`)真实值,编辑态改名再保存会同步;无值保持原标题不回退单号。
154
+ - **防重复入库**:保存成功即升级 `tab.dataId`=单号,提交失败/取消后再保存走更新。
155
+ - **详情最多刷一次**:元数据升级不重渲染,内容刷新集中在流程结束分支。
156
+ - **wf 解耦**:`wf.js` 仅新增通用 `onCancel` 钩子;页签逻辑全在 xform。
157
+ - **`wfStartBindSave` 关 + 直接启动 + 失败/取消**:无保存,不注入 abort 钩子 → 不刷新表单。
158
+ - **向后兼容**:`addTabEnabled` 默认 false,存量表单(含 `multiTabEnabled=true`)行为不变;非 xform 的 wf 表单不受影响。
159
+
160
+ ---
161
+
162
+ ## 五、测试清单
163
+
164
+ | # | 场景 | 预期 |
165
+ |---|------|------|
166
+ | 1 | 旧表单未开 `addTabEnabled` 点新增 | 走「常规」,不变 |
167
+ | 2 | 存量 `multiTabEnabled=true` 未开 `addTabEnabled` | 编辑开页签,新增仍走常规 |
168
+ | 3 | 连续点新增 | 各开一页签,不覆盖 |
169
+ | 4 | 新增点开 | 空白新增表单,不调 getOne |
170
+ | 5 | 新增保存 | 标题变名称字段、转编辑态、`dataId` 正确、不新开页签、详情刷一次 |
171
+ | 6 | 编辑态改名再保存 | 页签标题同步为新名称 |
172
+ | 7 | 保存并提交成功 + `refreshCurrent` | 详情刷一次,出流程信息 |
173
+ | 8 | 保存并提交成功 + `closeToList` | 关页签、切列表、列表刷新 |
174
+ | 9 | 保存并提交 → 提交失败 | 详情刷到编辑态,不重复入库 |
175
+ | 10 | 保存并提交 → 关候选人弹框取消 | 详情刷到编辑态,不重复入库 |
176
+ | 11 | 保存并提交后重复提交 | 走更新,不产生重复单据 |
177
+ | 12 | `wfStartBindSave` 关 + 直接启动 + 失败/取消 | 表单不刷新 |
178
+ | 13 | `wfStartBindSave` 开 + 直接启动 + 失败/取消 | 刷到编辑态 |
179
+ | 14 | 关闭非首个动态页签 | 不报错(x-tabs 修复) |
180
+ | 15 | 非 xform 表单用流程 | 失败/取消不刷新,成功行为不变 |
181
+
182
+ ---
183
+
184
+ ## 六、涉及文件
185
+
186
+ | 文件 | 改动 |
187
+ |------|------|
188
+ | `components/xform/utils/util.js` | `getDefaultFormConfig` 加 3 字段 |
189
+ | `components/xform/form-designer/setting-panel/form-setting.vue` | 配置 UI |
190
+ | `components/vb-tabs/x-tabs.vue` | `removeTab` bug 修复 |
191
+ | `views/user/form/view/list.vue` | `openEditDialog` 归一分支 + `resolveFormModel`/`applyTabSaveResult`/`onEditTabReload`/`promoteActiveAddTab` |
192
+ | `components/xform/mixins/defaultHandle.js` | `saveAndSubmitWfHandle` 元数据升级 + 注入钩子 |
193
+ | `components/xform/form-render/indexMixin.js` | wf option 注入 + `getAddTabContext`/`onWfStartSuccess`/`onWfStartAbort` |
194
+ | `components/wf/wf.js` | `wfStartMixin` 通用 `onCancel` 钩子 + `wfSubmitted` 标志 |
195
+ </content>
@@ -91,6 +91,10 @@ let chartContainers = [],
91
91
  wfConfigData: [],
92
92
  multiTabEnabled: false,
93
93
  multiTabLabelField: null,
94
+ addTabEnabled: false, // 新增打开独立页签(不走「常规」页签)
95
+ addTabLabel: "新增", // 新增页签初始标题
96
+ submitBehavior: "refreshCurrent", // 提交后行为: refreshCurrent(默认,留在详情页) | closeToList
97
+ billNavEnabled: false, // 详情页签显示「上一单/下一单」翻单按钮
94
98
  addFormCode: null,
95
99
  addFormName: null,
96
100
  wfTheme: null,
@@ -123,6 +123,12 @@
123
123
  <el-form-item :label="i18nt('新增页面表单名称')">
124
124
  {{ formConfig.addFormName }}
125
125
  </el-form-item>
126
+ <el-form-item :label="i18nt('详情翻单按钮')">
127
+ <el-switch v-model="formConfig.billNavEnabled"></el-switch>
128
+ <span style="margin-left: 8px; color: #999; font-size: 12px">
129
+ 编辑态页签显示「上一单/下一单」
130
+ </span>
131
+ </el-form-item>
126
132
  </el-collapse-item>
127
133
  <el-collapse-item name="3" title="流程信息">
128
134
  <el-form-item :label="i18nt('启用流程')">
@@ -215,6 +221,31 @@
215
221
  <el-form-item :label="i18nt('标签名称字段')">
216
222
  <el-input type="text" v-model="formConfig.multiTabLabelField"></el-input>
217
223
  </el-form-item>
224
+ <el-form-item
225
+ v-if="formConfig.multiTabEnabled"
226
+ :label="i18nt('新增打开页签')"
227
+ >
228
+ <el-switch v-model="formConfig.addTabEnabled"></el-switch>
229
+ <span style="margin-left: 8px; color: #999; font-size: 12px">
230
+ 开启后新增打开独立可关闭页签,需配置「标签名称字段」
231
+ </span>
232
+ </el-form-item>
233
+ <el-form-item
234
+ v-if="formConfig.multiTabEnabled && formConfig.addTabEnabled"
235
+ :label="i18nt('新增页签标题')"
236
+ >
237
+ <el-input
238
+ type="text"
239
+ v-model="formConfig.addTabLabel"
240
+ placeholder="默认`新增`"
241
+ ></el-input>
242
+ </el-form-item>
243
+ <el-form-item :label="i18nt('提交后行为')">
244
+ <el-radio-group v-model="formConfig.submitBehavior">
245
+ <el-radio label="closeToList">关闭页签并回列表刷新</el-radio>
246
+ <el-radio label="refreshCurrent">刷新当前页签</el-radio>
247
+ </el-radio-group>
248
+ </el-form-item>
218
249
  </el-collapse-item>
219
250
  <el-collapse-item name="5" title="其他标签页信息">
220
251
  <el-form-item :label="i18nt('自定义列表标签名称')">
@@ -1526,11 +1526,29 @@ modules = {
1526
1526
  } else {
1527
1527
  formCode = formConfig.addFormCode || formConfig.editFormCode;
1528
1528
  }
1529
+ // 上一单/下一单:打开详情时,按当前列表(排序/筛选后)有序行做快照,供详情页翻单使用
1530
+ let navRows = null;
1531
+ let navIndex = -1;
1532
+ // 快照:当前页有序行引用 + 当前行下标(仅存引用,开销小)。
1533
+ // 新增无当前行 → navIndex 置 -1,保存后由宿主把新单插入序列首位。
1534
+ try {
1535
+ let $grid = this.getGridTable && this.getGridTable();
1536
+ let fullData = $grid && $grid.getTableData()?.fullData;
1537
+ if (Array.isArray(fullData) && fullData.length) {
1538
+ navRows = fullData;
1539
+ navIndex = row?.id ? fullData.findIndex((r) => r.id === row.id) : -1;
1540
+ }
1541
+ } catch (e) {
1542
+ navRows = null;
1543
+ navIndex = -1;
1544
+ }
1529
1545
  let parentTarget = formRef.$attrs["parent-target"];
1530
1546
  parentTarget &&
1531
1547
  parentTarget.$attrs.openEditDialog &&
1532
1548
  parentTarget.$attrs.openEditDialog(row, param, {
1533
1549
  formCode,
1550
+ navRows,
1551
+ navIndex,
1534
1552
  ...option,
1535
1553
  });
1536
1554
  },
@@ -27,6 +27,31 @@
27
27
  </el-button>
28
28
  </div>-->
29
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="showWfStartBtn()"
49
+ class="button-sty yyy"
50
+ type="success"
51
+ icon="el-icon-video-play"
52
+ @click="startWf"
53
+ >{{ wfStartButtonName }}</el-button
54
+ >
30
55
  <template v-for="(subWidget, swIdx) in widget.widgetList">
31
56
  <template v-if="'container' === subWidget.category">
32
57
  <component
@@ -94,7 +119,8 @@ import containerItemMixin from "./containerItemMixin";
94
119
  import detailPaneWidget from "../../../../components/xform/form-render/container-item/detail-pane-item";
95
120
  import FieldComponents from "../../../../components/xform/form-designer/form-widget/field-widget/index";
96
121
 
97
- import { initWf } from "../../../../components/wf/wfUtil";
122
+ import { initWf, openStartWfDialog } from "../../../../components/wf/wfUtil";
123
+ import settingConfig from "@/settings.js";
98
124
 
99
125
  export default {
100
126
  name: "detail-item",
@@ -125,9 +151,82 @@ export default {
125
151
  data() {
126
152
  return {
127
153
  wfInfo: {},
154
+ wfReady: false, // 流程状态是否已就绪(callback 返回后)
155
+ wfHasStarted: false, // 流程是否已启动(已启动则不显示启动按钮)
156
+ wfStartOption: null, // 启动流程所需 option(createWf 构造)
157
+ wfStartCtx: null, // openStartWfDialog 的执行上下文(与 initWf 一致)
128
158
  };
129
159
  },
160
+ computed: {
161
+ wfStartButtonName() {
162
+ return (
163
+ settingConfig.wfStartButtonName ||
164
+ this.$t2("流程启动", "components.wf.wfStart")
165
+ );
166
+ },
167
+ // 「上一单/下一单」翻单按钮状态:详情表单开启 billNavEnabled 且宿主列表有翻单快照时才显示。
168
+ // detail-item 在每次翻单时随详情表单重挂载,故此处按当前挂载时机取一次即可。
169
+ billNav() {
170
+ let formRef = this.getFormRef && this.getFormRef();
171
+ if (
172
+ !formRef ||
173
+ !formRef.formConfig ||
174
+ !formRef.formConfig.billNavEnabled
175
+ ) {
176
+ return null;
177
+ }
178
+ let host = formRef.getListPageRef && formRef.getListPageRef();
179
+ if (!host || typeof host.getActiveBillNavState !== "function")
180
+ return null;
181
+ let state = host.getActiveBillNavState();
182
+ if (!state) return null;
183
+ return {
184
+ canPrev: state.index > 0,
185
+ canNext: state.index < state.rows.length - 1,
186
+ };
187
+ },
188
+ },
130
189
  methods: {
190
+ // 解析流程启动信息:优先取表单实例(indexMixin,xform 主链路)暴露的,回退到本组件 createWf(旧路径)。
191
+ // 用方法实时求值,避免 computed 缓存导致早于 wf 就绪时求值后不再更新。
192
+ wfStart() {
193
+ let formRef = this.getFormRef && this.getFormRef();
194
+ if (formRef && formRef.wfStartOption) {
195
+ return {
196
+ ctx: formRef.wfStartCtx || formRef,
197
+ option: formRef.wfStartOption,
198
+ hasWf: !!formRef.hasWf,
199
+ };
200
+ }
201
+ if (this.wfReady && this.wfStartOption && this.wfStartCtx) {
202
+ return {
203
+ ctx: this.wfStartCtx,
204
+ option: this.wfStartOption,
205
+ hasWf: this.wfHasStarted,
206
+ };
207
+ }
208
+ return null;
209
+ },
210
+ // 流程启动按钮:有启动信息、需要按钮(showStartBtn 未显式关闭)、未启动、非预览态才显示。
211
+ // 声明式渲染,替代 wfUtil 的 DOM 强制注入。
212
+ showWfStartBtn() {
213
+ let ws = this.wfStart();
214
+ if (!ws || this.previewState) return false;
215
+ let opt = ws.option || {};
216
+ return opt.showStartBtn !== false && !ws.hasWf;
217
+ },
218
+ // 声明式触发流程启动(替代原注入按钮的点击),复用 wfUtil 的 openStartWfDialog
219
+ startWf() {
220
+ let ws = this.wfStart();
221
+ if (!ws || !ws.option || !ws.ctx) return;
222
+ openStartWfDialog(ws.ctx, ws.option);
223
+ },
224
+ // 触发宿主翻单:step = -1 上一单 / +1 下一单
225
+ navBill(step) {
226
+ let host = this.getFormRef && this.getFormRef();
227
+ host = host && host.getListPageRef && host.getListPageRef();
228
+ host && host.navigateActiveBill && host.navigateActiveBill(step);
229
+ },
131
230
  reload() {
132
231
  this.getFormRef().$parent.$baseReload();
133
232
  },
@@ -145,12 +244,17 @@ export default {
145
244
  let option = {
146
245
  objId: formData.object_foreign_id, //单据ID
147
246
  wfCode: formCode, //流程编码
148
- showStartBtn: true, //是否放出启动按钮,流程已启动时此参数失效
247
+ showStartBtn: true, //需要启动按钮(语义不变)
248
+ injectStartBtn: false, //不走 DOM 强制注入,改由本组件模板声明式渲染
149
249
  serviceId: "user",
150
- callback: ({ wfInfo }) => {
250
+ callback: ({ wfInfo, hasWf }) => {
151
251
  this.wfInfo = wfInfo;
252
+ this.wfHasStarted = !!hasWf; //已启动则不显示启动按钮(与原注入条件 !hasWf 一致)
253
+ this.wfReady = true; //状态就绪后才显示按钮,避免回调前闪现
152
254
  },
153
255
  };
256
+ this.wfStartOption = option;
257
+ this.wfStartCtx = formTarget.$parent; //与 initWf 执行上下文一致,供 openStartWfDialog 使用
154
258
  initWf.call(formTarget.$parent, option);
155
259
  }
156
260
  },
@@ -1342,7 +1342,8 @@ modules = {
1342
1342
  let option = {
1343
1343
  objId: dataId, //单据ID
1344
1344
  wfCode: wfCode, //流程编码
1345
- showStartBtn: true, //是否放出启动按钮,流程已启动时此参数失效
1345
+ showStartBtn: true, //需要启动按钮(语义不变)
1346
+ injectStartBtn: false, //但不走 wfUtil 的 DOM 强制注入,改由 detail-item 模板声明式渲染
1346
1347
  showWfFold: true,
1347
1348
  formCode: formCode,
1348
1349
  callback: (wfParam) => {
@@ -1376,9 +1377,16 @@ modules = {
1376
1377
  wfParam.done();
1377
1378
  }); */
1378
1379
  },
1380
+ // 流程启动成功后的行为(新增页签模式):复用 wf 已有的 onStartSuccess 契约,
1381
+ // 覆盖「直接启动流程」按钮入口。逻辑收在 xform 侧。
1382
+ onStartSuccess: () => this.onWfStartSuccess(),
1379
1383
  };
1380
1384
  if (formConfig.wfStartBindSave) {
1381
1385
  //启用流程启动自动保存
1386
+ // 本次启动伴随了保存,失败/取消需把详情刷新到编辑态;
1387
+ // 未启用自动保存(直接启动,无保存)时不注入,失败/取消不刷新表单。
1388
+ option.onStartFail = () => this.onWfStartAbort();
1389
+ option.onCancel = () => this.onWfStartAbort();
1382
1390
  option.onStart = (done) => {
1383
1391
  let formRef = this.getFormRef ? this.getFormRef() : this;
1384
1392
  formRef.validate((valid) => {
@@ -3576,6 +3584,16 @@ modules = {
3576
3584
  if (handleType) {
3577
3585
  let listTabFormRef = this.getListTabFormRef();
3578
3586
  if (handleType === "reload") {
3587
+ // 若列表配置了「切换tab自动查询」,切到 second 时 watcher 已会触发查询,
3588
+ // 此处再 reloadForm 会导致查询两次,故跳过,交给 tab 切换的自动查询。
3589
+ let grid =
3590
+ listTabFormRef && listTabFormRef.getGridWidgetRef
3591
+ ? listTabFormRef.getGridWidgetRef()
3592
+ : null;
3593
+ let opts = grid && grid.widget ? grid.widget.options : null;
3594
+ if (opts && opts.searchOnSwitchTab && opts.isQueryTable) {
3595
+ return;
3596
+ }
3579
3597
  listTabFormRef.reloadForm();
3580
3598
  } else if (handleType === "searchEvent") {
3581
3599
  listTabFormRef.getGridWidgetRef().searchEvent();
@@ -3584,6 +3602,82 @@ modules = {
3584
3602
  }
3585
3603
  }
3586
3604
  },
3605
+ // 判断当前表单是否处于「详情多标签」的动态页签内;是则返回 {xTabs, currentName, submitBehavior}
3606
+ // 门控为 multiTabEnabled(与「提交后行为」配置的显示条件一致):动态编辑页签、新增页签均适用。
3607
+ getAddTabContext() {
3608
+ let listPageRef = this.getListPageRef && this.getListPageRef();
3609
+ let listFormConfig =
3610
+ listPageRef && listPageRef.formJson
3611
+ ? listPageRef.formJson.formConfig
3612
+ : null;
3613
+ let xTabs =
3614
+ listPageRef && listPageRef.$refs ? listPageRef.$refs.xTabs : null;
3615
+ let currentName = xTabs ? xTabs.activeName : null;
3616
+ let isDynamicTab =
3617
+ currentName &&
3618
+ currentName !== "first" &&
3619
+ currentName !== "second" &&
3620
+ !/^otherTab/.test(currentName);
3621
+ if (listFormConfig && listFormConfig.multiTabEnabled && xTabs && isDynamicTab) {
3622
+ return {
3623
+ xTabs,
3624
+ currentName,
3625
+ submitBehavior: listFormConfig.submitBehavior || "refreshCurrent",
3626
+ };
3627
+ }
3628
+ return null;
3629
+ },
3630
+ // 常规「first」页签的提交后上下文;否则 null。「提交后行为」不受 multiTabEnabled 限制。
3631
+ getFirstPaneSubmitContext() {
3632
+ let listPageRef = this.getListPageRef && this.getListPageRef();
3633
+ let lfc =
3634
+ listPageRef && listPageRef.formJson
3635
+ ? listPageRef.formJson.formConfig
3636
+ : null;
3637
+ if (lfc && listPageRef.activeName === "first") {
3638
+ return {
3639
+ listPageRef,
3640
+ submitBehavior: lfc.submitBehavior || "refreshCurrent",
3641
+ };
3642
+ }
3643
+ return null;
3644
+ },
3645
+
3646
+ onWfStartSuccess() {
3647
+ // 流程启动成功后的通用钩子(由 wf 组件回调)。
3648
+ // 「详情多标签」:按列表表单配置的 submitBehavior 处理;否则等价于默认刷新,不影响其它表单。
3649
+ let ctx = this.getAddTabContext();
3650
+ if (ctx) {
3651
+ if (ctx.submitBehavior === "refreshCurrent") {
3652
+ this.$baseReload(); // 刷新当前页签(出流程信息),保持编辑态
3653
+ } else {
3654
+ // 关闭页签并回列表刷新:先切列表(会把 activeName 置 second)再关当前页签
3655
+ this.switchToListTab({ handleType: "reload" });
3656
+ ctx.xTabs.removeTab(ctx.currentName);
3657
+ }
3658
+ return;
3659
+ }
3660
+ // 常规「first」页签(addTabEnabled 关时新增走此)同样按 submitBehavior 处理
3661
+ let firstCtx = this.getFirstPaneSubmitContext();
3662
+ if (firstCtx) {
3663
+ if (firstCtx.submitBehavior === "refreshCurrent") {
3664
+ this.$baseReload(); // 刷新当前 first 页签,显示已提交单据详情(dataId 已升级)
3665
+ } else {
3666
+ this.switchToListTab({ handleType: "reload" }); // 切列表并刷新
3667
+ firstCtx.listPageRef.showEdit = false; // 关闭编辑态
3668
+ }
3669
+ return;
3670
+ }
3671
+ this.$baseReload();
3672
+ },
3673
+ onWfStartAbort() {
3674
+ // 流程启动失败 / 取消(关闭启动或候选人弹框)的通用钩子。
3675
+ // 「详情多标签」:把当前详情刷新到编辑态(此时元数据已升级,dataId 为单号);
3676
+ // 非本模式不处理,保持其它表单原有行为(失败/取消不刷新)。
3677
+ if (this.getAddTabContext() || this.getFirstPaneSubmitContext()) {
3678
+ this.$baseReload();
3679
+ }
3680
+ },
3587
3681
  reloadForm(option) {
3588
3682
  let target1 = this.$attrs["parent-target"];
3589
3683
  let target2 = target1?.$attrs["parent-target"];