cloud-web-corejs 1.0.54-dev.724 → 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 (26) 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/form-widget/field-widget/button-widget.vue +76 -66
  9. package/src/components/xform/form-designer/form-widget/field-widget/download-button-widget.vue +0 -1
  10. package/src/components/xform/form-designer/form-widget/field-widget/import-button-widget.vue +1 -1
  11. package/src/components/xform/form-designer/form-widget/field-widget/import2-button-widget.vue +1 -1
  12. package/src/components/xform/form-designer/form-widget/field-widget/table-export-button-widget.vue +1 -1
  13. package/src/components/xform/form-designer/setting-panel/form-setting.vue +31 -0
  14. package/src/components/xform/form-render/container-item/data-table-mixin.js +18 -0
  15. package/src/components/xform/form-render/container-item/detail-item.vue +107 -3
  16. package/src/components/xform/form-render/indexMixin.js +95 -1
  17. package/src/components/xform/mixins/defaultHandle.js +16 -0
  18. package/src/components/xform/mixins/scriptHttp.js +3 -0
  19. package/src/components/xform/utils/util.js +4 -0
  20. package/src/layout/components/AppMain.vue +2 -1
  21. package/src/views/user/form/view/list.vue +164 -11
  22. package/src/views/user/position/list.vue +68 -108
  23. package/src/components/xform/XFormTabContainer/IMPLEMENTATION_PLAN.md +0 -1079
  24. 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
  25. 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
  26. package/src/components/xform/form-designer/form-widget/field-widget/tableexportbuttonwidget.vue +0 -99
@@ -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"];
@@ -155,12 +155,28 @@ modules = {
155
155
  isConfirm: option?.config?.isConfirm ?? true,
156
156
  confirmText: option?.config?.confirmText || confirmText,
157
157
  success: (res) => {
158
+ // ★ 保存成功后,只升级新增页签的「元数据」为编辑态(设 dataId/isAdd/formCode/标题),
159
+ // 不重渲染详情内容。这样即便随后提交失败/取消候选人弹框,页签也已是编辑态,
160
+ // 再次提交只会更新、不会重复入库;而详情内容的刷新交给最终的提交后行为(避免刷两次)。
161
+ let editFormCode = formConfig.editFormCode;
162
+ let listPageRef = formRef.getListPageRef && formRef.getListPageRef();
163
+ if (listPageRef && listPageRef.promoteActiveAddTab) {
164
+ listPageRef.promoteActiveAddTab(res.objx, editFormCode, formRef);
165
+ }
166
+
158
167
  openStartWfDialog(formRef, {
159
168
  objId: res.objx,
160
169
  wfCode: reportTemplate.objTypeCode,
161
170
  formCode: reportTemplate.formCode,
162
171
  serviceId: reportTemplate.serviceName,
163
172
  skipStartConfirm: true,
173
+ // 提交后行为复用 xform 侧统一方法:
174
+ // 成功 → onWfStartSuccess(关页签/刷新本页);失败/取消 → onWfStartAbort(刷新到编辑态)
175
+ onStartSuccess: () =>
176
+ formRef.onWfStartSuccess && formRef.onWfStartSuccess(),
177
+ onStartFail: () =>
178
+ formRef.onWfStartAbort && formRef.onWfStartAbort(),
179
+ onCancel: () => formRef.onWfStartAbort && formRef.onWfStartAbort(),
164
180
  });
165
181
  option?.config?.success && option.config.success(res);
166
182
  },
@@ -149,6 +149,9 @@ modules = {
149
149
  let target2 = target1?.$attrs['parent-target'];
150
150
  let target = !target2 ? this : target1;*/
151
151
  let target = formTarget;
152
+ // 暴露启动流程所需的 option 与上下文,供 detail-item 声明式渲染启动按钮(替代 DOM 强制注入)
153
+ target.wfStartOption = opt2;
154
+ target.wfStartCtx = target;
152
155
  initWf.call(target, opt2);
153
156
  }
154
157
  }
@@ -999,6 +999,10 @@ export function getDefaultFormConfig() {
999
999
  wfConfigData: [],
1000
1000
  multiTabEnabled: false,
1001
1001
  multiTabLabelField: null,
1002
+ addTabEnabled: false, // 新增打开独立页签(不走「常规」页签)
1003
+ addTabLabel: "新增", // 新增页签初始标题
1004
+ submitBehavior: "refreshCurrent", // 提交后行为: refreshCurrent(默认,留在详情页) | closeToList
1005
+ billNavEnabled: false, // 详情页签显示「上一单/下一单」翻单按钮
1002
1006
  addFormCode: null,
1003
1007
  addFormName: null,
1004
1008
  wfTheme: null,
@@ -90,13 +90,14 @@ export default {
90
90
  .app-main {
91
91
  /* 45= navbar 45 */
92
92
  min-height: calc(100vh - 45px);
93
+ //height:calc(100vh - 10px);
93
94
  width: 100%;
94
95
  position: relative;
95
96
  overflow: hidden;
96
97
  }
97
98
 
98
99
  .fixed-header + .app-main {
99
- padding-top: 33px;
100
+ padding-top: 32px;
100
101
  }
101
102
 
102
103
  .hasTagsView {
@@ -68,7 +68,7 @@
68
68
  ></vFormRender>
69
69
  </div>
70
70
  </el-tab-pane>
71
- <template #editTab="{ tab, index, reloadTabContent }">
71
+ <template #editTab="{ tab }">
72
72
  <el-tab-pane
73
73
  :key="tab.tabName"
74
74
  :label="tab.data[tab.tabLabelField]"
@@ -82,12 +82,7 @@
82
82
  :param="tab.param"
83
83
  v-if="tab.showContent"
84
84
  :parent-target="_self"
85
- @reload="
86
- (...args) => {
87
- formData = {};
88
- reloadTabContent(...args);
89
- }
90
- "
85
+ @reload="(...args) => onEditTabReload(tab, ...args)"
91
86
  @openCopyEditTab="openCopyEditTab"
92
87
  ></vFormRender>
93
88
  </el-tab-pane>
@@ -146,6 +141,9 @@ export default {
146
141
  return {
147
142
  activeName: "second",
148
143
  showEdit: false,
144
+ // 「上一单/下一单」翻单:非多标签(常规页签)模式的导航快照
145
+ firstNavRows: null,
146
+ firstNavIndex: -1,
149
147
  formJson: { formConfig: {} },
150
148
  formConfig: {},
151
149
  showRender: false,
@@ -297,18 +295,36 @@ export default {
297
295
 
298
296
  let dataId = row && row.id ? row.id : null;
299
297
  this.dataId = dataId;
298
+ let isAdd = !dataId;
300
299
  let formCode = null;
301
- if (!dataId) {
300
+ if (isAdd) {
302
301
  formCode = option?.formCode || this.addFormCode;
303
302
  } else {
304
303
  formCode = option?.formCode || this.formCode2;
305
304
  }
306
- if (multiTabEnabled && dataId) {
307
- this.$refs.xTabs.openEditTab(row, param, {
305
+ // 新增页签门控:需先启用「列表启用详情多标签」,再叠加 addTabEnabled、新增、配了标题字段
306
+ let addTab =
307
+ multiTabEnabled && formConfig.addTabEnabled && isAdd && tabLabelField;
308
+ if ((multiTabEnabled && dataId) || addTab) {
309
+ // 归一:新增/编辑都走 openEditTab
310
+ // 新增合成唯一 id,让去重(paneKeyName='id')/tabName/paneName 三者对齐
311
+ let key = isAdd
312
+ ? "add_" + Date.now() + "_" + Math.random().toString(36).slice(2, 6)
313
+ : dataId;
314
+ let openRow = isAdd
315
+ ? { ...row, id: key, [tabLabelField]: formConfig.addTabLabel || "新增" }
316
+ : row;
317
+ this.$refs.xTabs.openEditTab(openRow, param, {
308
318
  tabParam: {
309
319
  formCode,
310
320
  tabLabelField,
311
- tabName: formCode + dataId,
321
+ tabName: String(key), // el-tabs 的 name 需为 String,dataId 可能是 Number
322
+ isAdd,
323
+ // 新增时 dataId 必须为 null,避免 vFormRender 把合成 id 当真实单号去 getOne
324
+ ...(isAdd ? { dataId: null } : {}),
325
+ // 翻单快照:新增也带(navIndex=-1),保存后把新单插入序列首位
326
+ navRows: option?.navRows || null,
327
+ navIndex: option?.navIndex ?? -1,
312
328
  },
313
329
  });
314
330
  } else {
@@ -318,6 +334,9 @@ export default {
318
334
  this.showEditDialog = true;
319
335
  this.param = param;
320
336
  this.formData = null;
337
+ // 常规页签(非多标签)模式记录翻单快照;新增也记录(navIndex=-1),保存后插入序列首位
338
+ this.firstNavRows = option?.navRows || null;
339
+ this.firstNavIndex = dataId ? option?.navIndex ?? -1 : -1;
321
340
  this.$openEditView("showEdit");
322
341
  }
323
342
  },
@@ -350,11 +369,145 @@ export default {
350
369
  reload(...args) {
351
370
  this.formData = {};
352
371
  this.$reloadHandle(...args);
372
+ // 常规模式新增保存:把新单插入翻单序列首位(navIndex=-1 表示当前为新增未定位)
373
+ let param = args[1];
374
+ let newId = param && param.updateParam ? param.updateParam.dataId : null;
375
+ if (newId && Array.isArray(this.firstNavRows) && this.firstNavIndex === -1) {
376
+ this.firstNavRows = [{ id: newId }, ...this.firstNavRows];
377
+ this.firstNavIndex = 0;
378
+ }
353
379
  },
354
380
  reload2() {
355
381
  this.formData = {};
356
382
  this.$reloadHandle(...arguments);
357
383
  },
384
+ // 逐级解析真实表单的 formDataModel(target 可能是包裹组件,真实表单在内层 vFormRef 上)
385
+ resolveFormModel(target) {
386
+ return (
387
+ (target && target.formDataModel) ||
388
+ (target && target.$refs && target.$refs.vFormRef
389
+ ? target.$refs.vFormRef.formDataModel
390
+ : null) ||
391
+ (target && target.getFormRef && target.getFormRef()
392
+ ? target.getFormRef().formDataModel
393
+ : null) ||
394
+ {}
395
+ );
396
+ },
397
+ // 应用保存结果到页签「元数据」:升级编辑态 + 同步标题。不含内容重渲染。
398
+ applyTabSaveResult(tab, dataId, formCode, formModel) {
399
+ // ① 首次新增保存:就地升级本页签为编辑态
400
+ if (tab.isAdd && dataId) {
401
+ tab.data.id = dataId; // 从列表再打开同单号可复用本页签
402
+ tab.dataId = dataId; // 表单不 watch dataId,此处不会触发内容重载
403
+ tab.isAdd = false;
404
+ if (formCode) tab.formCode = formCode;
405
+ // 新增保存:把新单插入翻单序列首位(新数组,不改动列表原 fullData 引用)
406
+ if (Array.isArray(tab.navRows)) {
407
+ tab.navRows = [tab.data, ...tab.navRows];
408
+ tab.navIndex = 0;
409
+ }
410
+ }
411
+ // ② 同步标题为「标签名称字段」最新值;无值则保持原标题,不回退用单号
412
+ let labelField = tab.tabLabelField;
413
+ let labelVal =
414
+ labelField != null &&
415
+ formModel &&
416
+ formModel[labelField] != null &&
417
+ formModel[labelField] !== ""
418
+ ? formModel[labelField]
419
+ : null;
420
+ if (labelVal != null) {
421
+ this.$set(tab.data, labelField, labelVal);
422
+ }
423
+ },
424
+ onEditTabReload(tab, target, param) {
425
+ // 动态页签(新增/编辑)表单保存后回调,tab 直接在作用域内
426
+ this.formData = {};
427
+ let dataId =
428
+ param != null && param.updateParam != null
429
+ ? param.updateParam.dataId
430
+ : null;
431
+ let formCode =
432
+ param != null && param.updateParam != null
433
+ ? param.updateParam.formCode
434
+ : null;
435
+ this.applyTabSaveResult(tab, dataId, formCode, this.resolveFormModel(target));
436
+ this.$refs.xTabs.reloadTabContent(target, param, tab); // 重载编辑态(内容重渲染)
437
+ },
438
+ // 供「保存并提交」路径调用:只升级当前活动新增页签的元数据,不重渲染详情内容。
439
+ // 目的:保存成功即升级为编辑态(防止提交失败/取消后重复入库),且不多刷一次详情。
440
+ promoteActiveAddTab(dataId, formCode, target) {
441
+ // 常规「first」页签新增(addTabEnabled 关时新增走此)保存后:升级 dataId 为编辑态,
442
+ // 并把新单插入翻单序列首位,使提交后「刷新当前页签」能显示详情。
443
+ if (this.activeName === "first") {
444
+ if (dataId) {
445
+ this.dataId = dataId;
446
+ if (formCode) this.formCode1 = formCode;
447
+ if (Array.isArray(this.firstNavRows) && this.firstNavIndex === -1) {
448
+ this.firstNavRows = [{ id: dataId }, ...this.firstNavRows];
449
+ this.firstNavIndex = 0;
450
+ }
451
+ }
452
+ return;
453
+ }
454
+ let xTabs = this.$refs.xTabs;
455
+ if (!xTabs) return;
456
+ let tab = xTabs.tabs.find((t) => t.tabName === xTabs.activeName);
457
+ if (!tab) return;
458
+ this.applyTabSaveResult(tab, dataId, formCode, this.resolveFormModel(target));
459
+ },
460
+ // ===== 上一单/下一单:宿主 API,供详情表单 detail-item 的按钮区调用 =====
461
+ // 返回当前活动详情的翻单快照 {rows, index} —— 兼容多标签页签与常规「first」页签;无快照返回 null。
462
+ getActiveBillNavState() {
463
+ let activeName = this.activeName;
464
+ // 常规「first」页签
465
+ if (activeName === "first") {
466
+ if (
467
+ this.dataId &&
468
+ Array.isArray(this.firstNavRows) &&
469
+ this.firstNavRows.length > 0
470
+ ) {
471
+ return { rows: this.firstNavRows, index: this.firstNavIndex };
472
+ }
473
+ return null;
474
+ }
475
+ // 多标签动态编辑页签
476
+ let xTabs = this.$refs.xTabs;
477
+ let tab = xTabs && xTabs.tabs.find((t) => t.tabName === activeName);
478
+ if (
479
+ tab &&
480
+ !tab.isAdd &&
481
+ Array.isArray(tab.navRows) &&
482
+ tab.navRows.length > 0
483
+ ) {
484
+ return { rows: tab.navRows, index: tab.navIndex ?? -1, tab };
485
+ }
486
+ return null;
487
+ },
488
+ // 翻单:step = -1 上一单 / +1 下一单,把当前活动详情刷新到相邻单据。
489
+ navigateActiveBill(step) {
490
+ let state = this.getActiveBillNavState();
491
+ if (!state) return;
492
+ let idx = state.index + step;
493
+ if (idx < 0 || idx >= state.rows.length) return; // 到边界不循环
494
+ let newRow = state.rows[idx];
495
+ if (!newRow || !newRow.id) return;
496
+ if (state.tab) {
497
+ // 多标签模式
498
+ let tab = state.tab;
499
+ tab.navIndex = idx;
500
+ tab.data = newRow; // 页签标题取 tab.data[tabLabelField],随之更新
501
+ tab.dataId = newRow.id; // 表单不 watch dataId,靠下方重挂载取数
502
+ this.$refs.xTabs.reloadTabContent(null, null, tab);
503
+ } else {
504
+ // 常规「first」页签模式
505
+ this.firstNavIndex = idx;
506
+ this.dataId = newRow.id;
507
+ this.formData = null;
508
+ this.$openEditView("showEdit"); // toggle 重挂载 → 按新 dataId getOne
509
+ }
510
+ },
358
511
  initOtherTabList() {
359
512
  if (!this.formJson.formConfig.otherTabEnabled) return;
360
513
  this.otherTabList = this.formJson.formConfig.otherTabList