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.
- package/package.json +1 -1
- package/src/components/vb-tabs/x-tabs.vue +3 -2
- package/src/components/wf/content.vue +1 -1
- package/src/components/wf/wf.js +16 -1
- package/src/components/wf/wfUtil.js +1 -1
- 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
- package/src/components/xform/form-designer/designer.js +4 -0
- package/src/components/xform/form-designer/setting-panel/form-setting.vue +31 -0
- package/src/components/xform/form-render/container-item/data-table-mixin.js +18 -0
- package/src/components/xform/form-render/container-item/detail-item.vue +107 -3
- package/src/components/xform/form-render/indexMixin.js +95 -1
- package/src/components/xform/mixins/defaultHandle.js +16 -0
- package/src/components/xform/mixins/scriptHttp.js +3 -0
- package/src/components/xform/utils/util.js +4 -0
- package/src/layout/components/AppMain.vue +2 -1
- package/src/views/user/form/view/list.vue +164 -11
- package/src/views/user/position/list.vue +68 -108
- package/src/components/xform/XFormTabContainer/IMPLEMENTATION_PLAN.md +0 -1079
- 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
- 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
|
@@ -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
|
-
?
|
|
96
|
-
:
|
|
96
|
+
? prev.tabName
|
|
97
|
+
: prev.data[paneKeyName] + "";
|
|
97
98
|
} else {
|
|
98
99
|
this.activeName = "second";
|
|
99
100
|
}
|
package/src/components/wf/wf.js
CHANGED
|
@@ -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"];
|