cloud-web-corejs 1.0.281 → 1.1.0-dev.4

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 (158) hide show
  1. package/package.json +6 -2
  2. package/src/components/VabUpload/image-viewer.vue +442 -442
  3. package/src/components/VabUpload/index.vue +44 -2
  4. package/src/components/VabUpload/mixins.js +126 -19
  5. package/src/components/VabUpload/view.vue +247 -209
  6. package/src/components/baseTabs/mixins.js +181 -181
  7. package/src/components/bizTab/BizBillActions.vue +129 -0
  8. package/src/components/bizTab/BizTabListPage.vue +391 -0
  9. package/src/components/bizTab/README.md +378 -0
  10. package/src/components/bizTab/billTableUtil.js +48 -0
  11. package/src/components/bizTab/billValidators.js +136 -0
  12. package/src/components/bizTab/bizBillDetailMixin.js +705 -0
  13. package/src/components/bizTab/index.js +24 -0
  14. package/src/components/excelExport/exportFieldDialog.vue +23 -12
  15. package/src/components/excelExport/exportItemConfigUtil.js +30 -0
  16. package/src/components/excelExport/mixins.js +3 -3
  17. package/src/components/mobile/statusTag/mixins.js +1 -61
  18. package/src/components/mobile/wf/content.vue +1662 -1556
  19. package/src/components/mobile/wf/mixins/wfModifyDialogMixin.js +8 -6
  20. package/src/components/table/config.js +78 -78
  21. package/src/components/table/index.js +1276 -1276
  22. package/src/components/table/plugins/cell-area/index.js +1055 -0
  23. package/src/components/table/plugins/cell-area/index.scss +77 -0
  24. package/src/components/table/plugins/cell-area/util.js +224 -0
  25. package/src/components/table/tableForm.vue +330 -330
  26. package/src/components/table/tableFormMixin.js +318 -318
  27. package/src/components/table/vxeFilter/index.js +163 -163
  28. package/src/components/table/vxeFilter/mixin.js +316 -316
  29. package/src/components/wf/content.vue +1188 -1188
  30. package/src/components/wf/wf.js +40 -20
  31. package/src/components/wf/wfActionDropdown.vue +1 -1
  32. package/src/components/wf/wfActionItems.js +1 -1
  33. 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
  34. 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
  35. 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
  36. 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
  37. package/src/components/xform/form-designer/designer.js +15 -0
  38. package/src/components/xform/form-designer/form-widget/container-widget/data-table-widget.vue +269 -269
  39. package/src/components/xform/form-designer/form-widget/container-widget/detail-temp-widget.vue +132 -0
  40. package/src/components/xform/form-designer/form-widget/container-widget/detail-widget.vue +250 -250
  41. package/src/components/xform/form-designer/form-widget/dialog/fileReferenceDialog.vue +319 -320
  42. package/src/components/xform/form-designer/form-widget/dialog/formDialog.vue +365 -365
  43. package/src/components/xform/form-designer/form-widget/dialog/searchFormDialog.vue +554 -555
  44. package/src/components/xform/form-designer/form-widget/dialog/vabSearchDialog.vue +409 -409
  45. package/src/components/xform/form-designer/form-widget/field-widget/a-link-widget.vue +150 -132
  46. package/src/components/xform/form-designer/form-widget/field-widget/date-range-widget.vue +114 -114
  47. package/src/components/xform/form-designer/form-widget/field-widget/date-widget.vue +154 -154
  48. package/src/components/xform/form-designer/form-widget/field-widget/form-item-wrapper.vue +47 -1
  49. package/src/components/xform/form-designer/form-widget/field-widget/mixins/date-limit-mixin.js +178 -178
  50. package/src/components/xform/form-designer/form-widget/field-widget/mixins/time-limit-mixin.js +77 -77
  51. package/src/components/xform/form-designer/form-widget/field-widget/select-export-item-button-widget.vue +6 -0
  52. package/src/components/xform/form-designer/form-widget/field-widget/select-widget.vue +139 -139
  53. package/src/components/xform/form-designer/form-widget/field-widget/time-range-widget.vue +106 -106
  54. package/src/components/xform/form-designer/form-widget/field-widget/time-widget.vue +134 -134
  55. package/src/components/xform/form-designer/form-widget/field-widget/vabUpload-widget.vue +1 -0
  56. package/src/components/xform/form-designer/setting-panel/form-setting.vue +39 -39
  57. package/src/components/xform/form-designer/setting-panel/property-editor/container-data-table/data-table-editor.vue +10 -10
  58. package/src/components/xform/form-designer/setting-panel/property-editor/container-data-table/exportItemColumns-dialog.vue +438 -432
  59. package/src/components/xform/form-designer/setting-panel/property-editor/container-data-table/table-column-dialog.vue +2103 -2103
  60. package/src/components/xform/form-designer/setting-panel/property-editor/container-detail/detail-temp-edito.vue +90 -0
  61. package/src/components/xform/form-designer/setting-panel/property-editor/container-grid/gutter-editor.vue +82 -82
  62. package/src/components/xform/form-designer/setting-panel/property-editor/container-list-h5/list-h5-editor.vue +19 -5
  63. package/src/components/xform/form-designer/setting-panel/property-editor/container-table/table-dynamicField-editor.vue +8 -8
  64. package/src/components/xform/form-designer/setting-panel/property-editor/dropdownFlag-editor.vue +137 -137
  65. package/src/components/xform/form-designer/setting-panel/property-editor/field-button/search-dialog-event-editor.vue +237 -237
  66. package/src/components/xform/form-designer/setting-panel/property-editor/field-date/date-defaultValue-editor.vue +43 -43
  67. package/src/components/xform/form-designer/setting-panel/property-editor/field-date-range/date-range-defaultValue-editor.vue +43 -43
  68. package/src/components/xform/form-designer/setting-panel/property-editor/field-import-button/import2-button-editor.vue +96 -96
  69. package/src/components/xform/form-designer/setting-panel/property-editor/field-table-export-button/select-export-item-button-editor.vue +51 -0
  70. package/src/components/xform/form-designer/setting-panel/property-editor/field-time/time-defaultValue-editor.vue +42 -42
  71. package/src/components/xform/form-designer/setting-panel/property-editor/field-time-range/time-range-defaultValue-editor.vue +43 -43
  72. package/src/components/xform/form-designer/setting-panel/property-editor/formScriptEnabled-editor.vue +158 -158
  73. package/src/components/xform/form-designer/setting-panel/property-editor/h5Span-editor.vue +51 -0
  74. package/src/components/xform/form-designer/setting-panel/property-editor/itemStyle-editor.vue +39 -0
  75. package/src/components/xform/form-designer/setting-panel/property-editor/name-editor.vue +188 -188
  76. package/src/components/xform/form-designer/setting-panel/property-editor/styleTempClass-editor.vue +51 -0
  77. package/src/components/xform/form-designer/setting-panel/property-editor/textFlag-editor.vue +347 -347
  78. package/src/components/xform/form-designer/setting-panel/propertyRegister.js +4 -0
  79. package/src/components/xform/form-designer/widget-panel/widgetsConfig.js +59 -3
  80. package/src/components/xform/form-render/container-item/data-table-mixin.js +151 -93
  81. package/src/components/xform/form-render/container-item/detail-h5-item.vue +183 -32
  82. package/src/components/xform/form-render/container-item/detail-temp-item.vue +110 -0
  83. package/src/components/xform/form-render/container-item/h5-card-pane-item.vue +160 -140
  84. package/src/components/xform/form-render/container-item/list-h5-item.vue +1688 -1700
  85. package/src/components/xform/form-render/index.vue +153 -147
  86. package/src/components/xform/form-render/indexMixin.js +12 -0
  87. package/src/components/xform/lang/en-US.js +10 -0
  88. package/src/components/xform/lang/zh-CN.js +12 -0
  89. package/src/components/xform/mixins/defaultHandle.js +707 -707
  90. package/src/components/xform/styles/h5.scss +1992 -0
  91. package/src/components/xform/utils/textMaskUtil.js +182 -179
  92. package/src/components/xform/utils/treeTableUtil.js +1292 -1265
  93. package/src/components/xform/utils/util.js +56 -11
  94. package/src/index.js +9 -4
  95. package/src/layout/components/extractedCode/viewDialog.vue +7 -7
  96. package/src/router/index.js +21 -0
  97. package/src/router/modules/customer.js +71 -14
  98. package/src/store/modules/permission.js +17 -18
  99. package/src/utils/pdfUtil.js +429 -429
  100. package/src/utils/resolveViewComponent.js +142 -16
  101. package/src/views/bd/project/branch/bd_branch/edit.vue +145 -0
  102. package/src/views/bd/project/branch/bd_branch/list.vue +72 -0
  103. package/src/views/bd/project/branch/bd_branch/menuKindDialog.vue +106 -0
  104. package/src/views/bd/project/branch/bd_branch/mixins/edit.js +189 -0
  105. package/src/views/bd/project/branch/bd_branch/mixins/list.js +124 -0
  106. package/src/views/bd/project/branch/bd_branch/mixins/menuKindDialog.js +85 -0
  107. package/src/views/bd/project/branch/form_script/list.vue +31 -0
  108. package/src/views/bd/project/branch/form_template/list.vue +30 -0
  109. package/src/views/bd/project/branch/menu_kind/list.vue +31 -0
  110. package/src/views/bd/project/common/form_script/list.vue +16 -0
  111. package/src/views/bd/project/common/form_template/list.vue +15 -0
  112. package/src/views/bd/project/common/menu_kind/list.vue +15 -0
  113. package/src/views/bd/project/core/branchContext.js +100 -0
  114. package/src/views/bd/project/core/branchPage.js +78 -0
  115. package/src/views/bd/project/core/branchPageWrap.vue +225 -0
  116. package/src/views/bd/project/core/branchScope.js +44 -0
  117. package/src/views/bd/project/core/branchSearchPage.js +285 -0
  118. package/src/views/bd/project/core/branchSelect.vue +85 -0
  119. package/src/views/bd/project/core/branchTreeBar.vue +81 -0
  120. package/src/views/bd/project/core/httpBranchPatch.js +58 -0
  121. package/src/views/bd/project/datatable/menu_kind/list.vue +15 -0
  122. package/src/views/bd/project/datatable/table_model/list.vue +15 -0
  123. package/src/views/bd/setting/bd_company_env/dialog.vue +174 -174
  124. package/src/views/bd/setting/table_model/edit.vue +1066 -1066
  125. package/src/views/bd/setting/table_model/mixins/edit.js +1455 -1455
  126. package/src/views/bd/setting/table_model/mixins/zdDialog.js +148 -148
  127. package/src/views/demo/bizTab/bill/billConfig.js +41 -0
  128. package/src/views/demo/bizTab/bill/detail.vue +436 -0
  129. package/src/views/demo/bizTab/bill/list.vue +288 -0
  130. package/src/views/demo/bizTab/bill/productDialog.vue +176 -0
  131. package/src/views/demo/bizTab/simple/billApi.js +123 -0
  132. package/src/views/demo/bizTab/simple/detail.vue +407 -0
  133. package/src/views/demo/bizTab/simple/list.vue +217 -0
  134. package/src/views/demo/bizTab/standalone/index.vue +121 -0
  135. package/src/views/demo/bizTab/wf/billConfig.js +44 -0
  136. package/src/views/demo/bizTab/wf/detail.vue +661 -0
  137. package/src/views/demo/bizTab/wf/list.vue +242 -0
  138. package/src/views/support/form-dialog-demo/demoPickerDialog.vue +72 -0
  139. package/src/views/support/form-dialog-demo/index.vue +537 -0
  140. package/src/views/user/file_view_ins/list.vue +3 -3
  141. package/src/views/user/home/index.vue +8 -6
  142. package/src/views/user/home/wjl/content.vue +1569 -0
  143. package/src/views/user/notify_message/dialog.vue +7 -5
  144. package/src/views/user/outLink/cc_form_view.vue +188 -0
  145. package/src/views/user/outLink/form_view.vue +2 -2
  146. package/src/views/user/outLink/index.vue +4 -4
  147. package/src/views/user/outLink/view.vue +3 -3
  148. package/src/views/user/role/edit.vue +632 -632
  149. package/src/views/user/role/list.vue +260 -260
  150. package/src/views/user/wf/iframe/index.vue +3 -3
  151. package/src/views/user/wf/iframe/index2.vue +3 -3
  152. package/src/views/user/wf/wf_manage/list.vue +1112 -1116
  153. package/src/views/user/wf/wf_manage/list2.vue +9 -13
  154. package/src/views/user/wf/wf_manage/wfContentDialog.vue +8 -5
  155. package/src/components/xform/form-designer/form-widget/field-widget/dropdown-menu-widget.vue +0 -106
  156. package/src/components/xform/form-designer/form-widget/field-widget/tableexportbuttonwidget.vue +0 -99
  157. package/src/components/xform/form-render/container-item/list-h5-item2.vue +0 -1340
  158. package/src/utils/wf.js +0 -766
@@ -0,0 +1,391 @@
1
+ <template>
2
+ <div id="containt">
3
+ <x-tabs
4
+ ref="xTabs"
5
+ v-model="activeName"
6
+ class="tab-box"
7
+ :tabNameEnable="true"
8
+ :beforeRemove="handleBeforeRemove"
9
+ @removeTab="handleTabRemoved"
10
+ @tabClick="handleTabClick"
11
+ >
12
+ <!-- 列表页签的 name 必须是 "second":x-tabs.doRemoveTab 关掉首个页签时会硬回退到 "second" -->
13
+ <el-tab-pane :label="listLabel" :name="LIST_TAB_NAME">
14
+ <div class="grid-height">
15
+ <slot name="list"></slot>
16
+ </div>
17
+ </el-tab-pane>
18
+ <template #editTab="{ tab }">
19
+ <el-tab-pane
20
+ :key="tab.tabName"
21
+ :label="getTabLabel(tab)"
22
+ :name="tab.tabName"
23
+ :closable="true"
24
+ >
25
+ <!-- showContent 由宿主控制:置 false 再置 true = 详情整体重挂载(重新取数) -->
26
+ <div v-if="tab.showContent" class="biz-tab-detail">
27
+ <slot name="detail" :tab="tab"></slot>
28
+ </div>
29
+ </el-tab-pane>
30
+ </template>
31
+ </x-tabs>
32
+ <!-- 页签之外的内容(各类选择弹框等),与 std 手写列表页把 dialog 放在 el-tabs 外的习惯一致 -->
33
+ <slot></slot>
34
+ </div>
35
+ </template>
36
+
37
+ <script>
38
+ /**
39
+ * 单据「列表 + 多详情页签」宿主壳。
40
+ *
41
+ * 只管页签,不认识任何业务表单:详情内容整个由 #detail 插槽提供。
42
+ * 详情组件通过 inject("bizTabHost") 拿到本组件实例,配合 bizBillDetailMixin 完成
43
+ * 「新增页签 → 保存升编辑态 → 提交流程 → 关页签回列表刷新」这条链路。
44
+ *
45
+ * 用法见 src/components/bizTab/README.md 与 src/views/demo/bizTab/。
46
+ */
47
+ // 列表页签固定名:x-tabs.doRemoveTab 关掉第一个动态页签时会硬回退到 "second"
48
+ const LIST_TAB_NAME = "second";
49
+
50
+ export default {
51
+ name: "BizTabListPage",
52
+ props: {
53
+ // 当前页签名,可选 v-model;不绑也能用(内部自持)
54
+ value: String,
55
+ // 列表页签标题
56
+ listLabel: {
57
+ type: String,
58
+ default: () => "列表",
59
+ },
60
+ // 页签标题取详情数据的哪个字段(如单号 sn、名称 name)
61
+ titleField: {
62
+ type: String,
63
+ default: () => "name",
64
+ },
65
+ // 新增页签的初始标题;保存成功后自动换成 titleField 的值
66
+ addTitle: {
67
+ type: String,
68
+ default: () => "新增",
69
+ },
70
+ /**
71
+ * 流程动作完成后的页签行为。
72
+ * "refreshCurrent" 刷新当前页签(默认,能看到最新流程信息)
73
+ * "closeToList" 关掉当前页签 → 回列表 → 刷新列表
74
+ * 传字符串 = 所有动作统一;传对象 = 按动作分别配置,未列出的走 default:
75
+ * { submit: "closeToList", agree: "closeToList", default: "refreshCurrent" }
76
+ * 动作类型:submit 提交(启动流程) / agree 通过 / reject 驳回 /
77
+ * interrupt 中断撤回 / transfer 转办 / talk 沟通
78
+ */
79
+ behavior: {
80
+ type: [String, Object],
81
+ default: () => "refreshCurrent",
82
+ },
83
+ /**
84
+ * 关闭页签前置钩子 beforeTabClose(tab, detail) => Boolean | Promise,返回 false / reject 中止关闭。
85
+ * 不传则用内置判断:详情实现了 isBillDirty() 就问它,否则「未保存过的新增页签」才确认。
86
+ */
87
+ beforeTabClose: Function,
88
+ // 是否启用上面那套内置关闭确认(自己传了 beforeTabClose 时本项无效)
89
+ confirmOnClose: {
90
+ type: Boolean,
91
+ default: () => true,
92
+ },
93
+ // 激活列表页签时(含关掉详情页签自动回到列表)是否自动刷新列表
94
+ refreshOnActivateList: {
95
+ type: Boolean,
96
+ default: () => true,
97
+ },
98
+ // 页签数量上限。x-tabs 内部超过 30 会顶掉第一个,这里提前拦下来给提示
99
+ maxTabs: {
100
+ type: Number,
101
+ default: () => 10,
102
+ },
103
+ },
104
+ data() {
105
+ return {
106
+ LIST_TAB_NAME,
107
+ activeName: LIST_TAB_NAME,
108
+ };
109
+ },
110
+ provide() {
111
+ return {
112
+ bizTabHost: this,
113
+ };
114
+ },
115
+ created() {
116
+ // 详情组件实例登记表(key = tabName)。故意不放 data:这里只做查表,
117
+ // 进了响应式反而会让详情实例被递归 observe。
118
+ this.detailRefs = {};
119
+ if (this.value) this.activeName = this.value;
120
+ },
121
+ watch: {
122
+ value(val) {
123
+ if (val && val !== this.activeName) this.activeName = val;
124
+ },
125
+ activeName(val) {
126
+ this.$emit("input", val);
127
+ this.$emit("tab-change", val);
128
+ if (val !== LIST_TAB_NAME) return;
129
+ // 回到列表就刷新:手动关页签、程序化关页签、手动点列表页签三条路径都在这里收口,
130
+ // 免得列表停留在陈旧数据上(低代码 list.vue 的 watch activeName 是同一意图)。
131
+ if (this._skipListRefresh) {
132
+ this._skipListRefresh = false;
133
+ return;
134
+ }
135
+ if (this.refreshOnActivateList) this.refreshList();
136
+ },
137
+ },
138
+ methods: {
139
+ // ===== 对外:打开页签 =====
140
+ /**
141
+ * 打开「新增」页签。
142
+ * 关键点:合成一个唯一 id 当页签标识,同时把 dataId 显式置 null——
143
+ * 否则 x-tabs 默认拿 row.id 当 dataId,详情会拿着这个假 id 去 getOne。
144
+ */
145
+ openAdd(param, option) {
146
+ if (!this.checkTabLimit()) return null;
147
+ let key
148
+ = "add_" + Date.now() + "_" + Math.random().toString(36).slice(2, 6);
149
+ let row = { id: key };
150
+ row[this.titleField] = this.buildAddTitle();
151
+ this.$refs.xTabs.openEditTab(row, param || null, {
152
+ tabParam: {
153
+ tabName: key,
154
+ tabLabelField: this.titleField,
155
+ isAdd: true,
156
+ dataId: null,
157
+ // 翻单快照:新增也带上(navIndex=-1),保存后把新单插到序列首位
158
+ navRows: (option && option.navRows) || null,
159
+ navIndex: -1,
160
+ ...((option && option.tabParam) || {}),
161
+ },
162
+ });
163
+ return key;
164
+ },
165
+ /**
166
+ * 打开「详情/编辑」页签。同一单据已开则复用并激活,不重复开。
167
+ * option.navRows / option.navIndex 传了就能用「上一单/下一单」翻单,
168
+ * 一般直接把列表的 fullData 和当前行下标丢进来。
169
+ */
170
+ openDetail(row, param, option) {
171
+ let dataId = row ? row.id : null;
172
+ if (dataId == null || dataId === "") return this.openAdd(param, option);
173
+ if (!this.hasTab(dataId) && !this.checkTabLimit()) return null;
174
+ let key = String(dataId);
175
+ // 这单已经开着、而且里面有没保存的修改:只激活,不重新取数。
176
+ // openEditTab 的复用分支会 showContent 置 false 再置 true(等于重挂载),
177
+ // 从列表再点一次就把用户改了半天的内容无声冲掉了。
178
+ if (this.hasTab(key) && this.isDetailDirty(this.getTab(key))) {
179
+ this.activeName = key;
180
+ return key;
181
+ }
182
+ this.$refs.xTabs.openEditTab(row, param || null, {
183
+ tabParam: {
184
+ tabName: key,
185
+ tabLabelField: this.titleField,
186
+ isAdd: false,
187
+ dataId: dataId,
188
+ navRows: (option && option.navRows) || null,
189
+ navIndex: (option && option.navIndex != null) ? option.navIndex : -1,
190
+ ...((option && option.tabParam) || {}),
191
+ },
192
+ });
193
+ return key;
194
+ },
195
+
196
+ // ===== 对外:供详情组件(bizBillDetailMixin)回调 =====
197
+ /**
198
+ * 新增保存成功后「只升级页签元数据」:转编辑态 + 换标题,不重渲染详情内容。
199
+ *
200
+ * 元数据升级与内容刷新必须分开做:
201
+ * - 升级了 dataId,后续再保存走更新,不会重复入库(提交失败/取消后重点依赖这条);
202
+ * - 不在这里重渲染,把那唯一一次刷新留给流程结束后的分支,详情最多刷一次、不闪。
203
+ */
204
+ promoteTab(tab, info) {
205
+ if (!tab) return;
206
+ let dataId = info ? info.dataId : null;
207
+ if (dataId != null && dataId !== "") {
208
+ let isAdd = tab.isAdd;
209
+ tab.dataId = dataId;
210
+ tab.isAdd = false;
211
+ if (tab.data) this.$set(tab.data, "id", dataId);
212
+ // 新增保存:把新单插到翻单序列首位(新数组,不改列表的 fullData 引用)
213
+ if (isAdd && Array.isArray(tab.navRows)) {
214
+ tab.navRows = [tab.data, ...tab.navRows];
215
+ tab.navIndex = 0;
216
+ }
217
+ // 页签 key(tabName) 仍是当初的合成 id:换掉会连带 el-tab-pane 重建、详情被迫重挂载,
218
+ // 正好破坏「保存后不刷详情」的设计。重复打开的去重靠 tab.data.id,它已是真单号。
219
+ }
220
+ this.syncTabTitle(tab, info ? info.title : null);
221
+ },
222
+ // 同步页签标题;空值保持原标题,不回退成单号
223
+ syncTabTitle(tab, title) {
224
+ if (!tab || !tab.data) return;
225
+ if (title == null || title === "") return;
226
+ this.$set(tab.data, this.titleField, title);
227
+ },
228
+ // 重挂载详情内容(重新取数)。页签已被关掉时自动跳过。
229
+ refreshTab(tab) {
230
+ if (!tab || !this.isLiveTab(tab)) return;
231
+ tab.showContent = false;
232
+ setTimeout(() => {
233
+ if (this.isLiveTab(tab)) tab.showContent = true;
234
+ }, 200);
235
+ },
236
+ // 关页签 → 回列表 → 刷新列表。顺序不能反:doRemoveTab 会自己抢 activeName。
237
+ closeTabToList(tab) {
238
+ this.backToList();
239
+ if (tab) this.$refs.xTabs.doRemoveTab(tab.tabName);
240
+ },
241
+ // 程序化关页签,跳过关闭确认
242
+ closeTab(tab) {
243
+ if (tab) this.$refs.xTabs.doRemoveTab(tab.tabName);
244
+ },
245
+ // 切到列表页签;刷新由 activeName 的 watcher 统一收口,refresh === false 可跳过这次刷新
246
+ backToList(refresh) {
247
+ if (refresh === false) this._skipListRefresh = true;
248
+ if (this.activeName === LIST_TAB_NAME) {
249
+ this._skipListRefresh = false;
250
+ if (refresh !== false && this.refreshOnActivateList) this.refreshList();
251
+ return;
252
+ }
253
+ this.activeName = LIST_TAB_NAME;
254
+ },
255
+ refreshList() {
256
+ this.$emit("refresh-list");
257
+ },
258
+ /**
259
+ * 取某个动作完成后的页签行为。详情组件用它决定「刷新本页」还是「关页签回列表」。
260
+ */
261
+ getBehavior(kind) {
262
+ let behavior = this.behavior;
263
+ if (typeof behavior === "string") return behavior;
264
+ if (behavior && typeof behavior === "object") {
265
+ return behavior[kind] || behavior.default || "refreshCurrent";
266
+ }
267
+ return "refreshCurrent";
268
+ },
269
+
270
+ // ===== 对外:上一单 / 下一单 =====
271
+ /**
272
+ * 能不能翻到相邻单据(同步判断:不是新增页签、有快照、没到边界)。
273
+ * 返回目标行下标,不能翻返回 -1。
274
+ */
275
+ canNavigate(tab, step) {
276
+ if (!tab || tab.isAdd) return -1;
277
+ let rows = tab.navRows;
278
+ if (!Array.isArray(rows) || !rows.length) return -1;
279
+ let idx = (tab.navIndex == null ? -1 : tab.navIndex) + step;
280
+ if (idx < 0 || idx >= rows.length) return -1;
281
+ let row = rows[idx];
282
+ if (!row || row.id == null || row.id === "") return -1;
283
+ return idx;
284
+ },
285
+ /**
286
+ * 把某个页签翻到相邻单据。step = -1 上一单 / +1 下一单,到边界不循环。
287
+ * 页签标题取 tab.data[titleField],所以整行换掉即可;详情靠重挂载按新 dataId 取数。
288
+ * 翻单会丢掉未保存的修改,所以先过一遍脏数据确认(与关页签同一判据)。
289
+ * @returns {Promise<Boolean>} 是否真的翻了
290
+ */
291
+ navigateTab(tab, step) {
292
+ let idx = this.canNavigate(tab, step);
293
+ if (idx < 0) return Promise.resolve(false);
294
+ return this.confirmLeaveDetail(tab).then((pass) => {
295
+ if (!pass) return false;
296
+ // 确认期间可能已经被关掉或翻走了,重新校一次
297
+ let target = this.canNavigate(tab, step);
298
+ if (target < 0 || !this.isLiveTab(tab)) return false;
299
+ tab.navIndex = target;
300
+ tab.data = tab.navRows[target];
301
+ tab.dataId = tab.data.id; // 详情不 watch dataId,靠下面的重挂载按新单号取数
302
+ this.refreshTab(tab);
303
+ return true;
304
+ });
305
+ },
306
+ // 详情有没有未保存的修改(详情实现了 isBillDirty 才判得出来)
307
+ isDetailDirty(tab) {
308
+ let detail = this.getDetail(tab);
309
+ return !!(
310
+ detail
311
+ && typeof detail.isBillDirty === "function"
312
+ && detail.isBillDirty()
313
+ );
314
+ },
315
+ // 要离开/重载当前详情(翻单等)前的确认;不脏直接放行
316
+ confirmLeaveDetail(tab) {
317
+ if (!this.isDetailDirty(tab)) return Promise.resolve(true);
318
+ return this.$baseConfirm(
319
+ this.$t1("单据已修改尚未保存,确定放弃修改吗?")
320
+ ).then(
321
+ () => true,
322
+ () => false
323
+ );
324
+ },
325
+
326
+ // ===== 内部 =====
327
+ // 详情实例登记:refreshTab 会销毁重建详情,所以登记/注销都由详情自己在生命周期里做
328
+ registerDetail(tab, vm) {
329
+ if (tab && tab.tabName) this.detailRefs[tab.tabName] = vm;
330
+ },
331
+ unregisterDetail(tab, vm) {
332
+ if (!tab || !tab.tabName) return;
333
+ if (this.detailRefs[tab.tabName] === vm) delete this.detailRefs[tab.tabName];
334
+ },
335
+ getDetail(tab) {
336
+ return tab && tab.tabName ? this.detailRefs[tab.tabName] || null : null;
337
+ },
338
+ getTabs() {
339
+ return this.$refs.xTabs ? this.$refs.xTabs.tabs : [];
340
+ },
341
+ getTab(tabName) {
342
+ return this.getTabs().find((tab) => tab.tabName === tabName) || null;
343
+ },
344
+ isLiveTab(tab) {
345
+ return this.getTabs().indexOf(tab) >= 0;
346
+ },
347
+ hasTab(dataId) {
348
+ return this.getTabs().some((tab) => tab.tabName === String(dataId));
349
+ },
350
+ getTabLabel(tab) {
351
+ let label = tab.data ? tab.data[this.titleField] : null;
352
+ return label == null || label === "" ? this.addTitle : label;
353
+ },
354
+ // 多个新增页签标题一样会分不清,第二个起加序号:新增 / 新增2 / 新增3
355
+ buildAddTitle() {
356
+ let count = this.getTabs().filter((tab) => tab.isAdd).length;
357
+ return count > 0 ? this.addTitle + (count + 1) : this.addTitle;
358
+ },
359
+ checkTabLimit() {
360
+ if (this.getTabs().length < this.maxTabs) return true;
361
+ this.$message.warning(
362
+ this.$t1
363
+ ? this.$t1(`最多同时打开 ${this.maxTabs} 个页签,请先关闭部分页签`)
364
+ : `最多同时打开 ${this.maxTabs} 个页签,请先关闭部分页签`
365
+ );
366
+ return false;
367
+ },
368
+ handleBeforeRemove(targetName, tab) {
369
+ let detail = this.getDetail(tab);
370
+ if (this.beforeTabClose) return this.beforeTabClose(tab, detail);
371
+ if (!this.confirmOnClose) return true;
372
+ // 详情自己会判脏就听它的,否则退回「新增还没存过」这一条
373
+ if (detail && typeof detail.isBillDirty === "function") {
374
+ if (!detail.isBillDirty()) return true;
375
+ return this.$baseConfirm(this.$t1("单据已修改尚未保存,确定关闭吗?"));
376
+ }
377
+ if (!tab || !tab.isAdd) return true;
378
+ return this.$baseConfirm(this.$t1("新增内容尚未保存,确定关闭吗?"));
379
+ },
380
+ handleTabRemoved(targetName) {
381
+ let detail = this.detailRefs[targetName];
382
+ if (detail) delete this.detailRefs[targetName];
383
+ this.$emit("tab-removed", targetName);
384
+ },
385
+ // x-tabs 转发的是 el-tabs 的 tab-click 载荷(页签组件实例,不是名字),原样往上抛
386
+ handleTabClick(tab) {
387
+ this.$emit("tab-click", tab);
388
+ },
389
+ },
390
+ };
391
+ </script>