cloud-web-corejs 1.0.281 → 1.0.282
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/excelExport/exportColumnMemory.js +118 -0
- package/src/components/excelExport/exportFieldDialog.vue +63 -40
- package/src/components/excelExport/exportItemConfigUtil.js +30 -0
- package/src/components/excelExport/index.js +30 -13
- package/src/components/excelExport/mixins.js +406 -68
- package/src/components/table/index.js +14 -1
- package/src/components/xform/form-designer/form-widget/field-widget/select-export-item-button-widget.vue +9 -0
- package/src/components/xform/form-designer/setting-panel/property-editor/container-data-table/exportItemColumns-dialog.vue +7 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-table-export-button/select-export-item-button-editor.vue +51 -0
- package/src/components/xform/form-designer/widget-panel/widgetsConfig.js +4 -0
- package/src/components/xform/form-render/container-item/data-table-item.vue +7 -4
- package/src/components/xform/form-render/container-item/data-table-mixin.js +369 -118
- package/src/components/xform/utils/dynamicSchemaUtil.js +24 -0
- package/src/components/xform/utils/textMaskUtil.js +6 -3
- package/src/layout/components/TagsView/index.vue +4 -2
- package/src/store/modules/tagsView.js +27 -3
- package/src/utils/pdfUtil.js +6 -1
- package/src/utils/request.js +17 -0
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**version-1.0*/
|
|
2
2
|
let tmixins = {};
|
|
3
|
+
import axios from "axios";
|
|
3
4
|
import { getToken } from "../../utils/auth";
|
|
4
5
|
import indexUtil from "../../utils/index.js";
|
|
5
6
|
import exportFieldDialog from "./exportFieldDialog.vue";
|
|
6
7
|
import { getCellValue } from "@base/components/table/util/index";
|
|
7
8
|
import { isItemExport, isRowExport, requiresCheckedRows } from "./exportType";
|
|
9
|
+
import { getExportItemColumns } from "./exportItemConfigUtil";
|
|
10
|
+
import { viewKey } from "@base/utils/repeatOpen";
|
|
8
11
|
|
|
9
12
|
let configUtil = {
|
|
10
13
|
baseUrl: process.env.VUE_APP_BASE_API,
|
|
@@ -12,6 +15,45 @@ let configUtil = {
|
|
|
12
15
|
indexUtil,
|
|
13
16
|
};
|
|
14
17
|
|
|
18
|
+
/** 取值转换每处理多少行让出一次主线程,见 getExcelData。 */
|
|
19
|
+
const EXPORT_YIELD_ROWS = 200;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 让出主线程一个宏任务,给渲染与用户交互留出机会。
|
|
23
|
+
* 必须是宏任务:微任务(Promise.resolve())不会让浏览器插入渲染帧,进度条照样不动。
|
|
24
|
+
* @returns {Promise<void>}
|
|
25
|
+
*/
|
|
26
|
+
function yieldToBrowser() {
|
|
27
|
+
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 可以直接当文件存下来的响应体类型(前缀匹配)。
|
|
32
|
+
*/
|
|
33
|
+
const DOWNLOAD_BINARY_TYPES = [
|
|
34
|
+
"application/vnd.openxmlformats",
|
|
35
|
+
"application/vnd.ms-excel",
|
|
36
|
+
"application/octet-stream",
|
|
37
|
+
"application/zip",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 这份响应体是不是真的文件流。
|
|
42
|
+
* 用白名单而不是「排除 application/json」:网关拦截、登录态失效这类失败常见的是
|
|
43
|
+
* 200 + text/html,黑名单会把一张 HTML 错误页原样存成打不开的 .xlsx,用户看到的是
|
|
44
|
+
* 一个损坏文件而不是回退后的正常下载。
|
|
45
|
+
* 后端不给 Content-Type 时 blob.type 是空串,按二进制放行 —— 旧的表单 POST 本来也是
|
|
46
|
+
* 这么下的,不能因为这次改造反而下不动。
|
|
47
|
+
* @param {Blob} res 响应体。
|
|
48
|
+
* @returns {Boolean}
|
|
49
|
+
*/
|
|
50
|
+
function isDownloadableBlob(res) {
|
|
51
|
+
if (!res) return false;
|
|
52
|
+
let type = (res.type || "").toLowerCase();
|
|
53
|
+
if (!type) return true;
|
|
54
|
+
return DOWNLOAD_BINARY_TYPES.some((item) => type.indexOf(item) === 0);
|
|
55
|
+
}
|
|
56
|
+
|
|
15
57
|
function getGrid(that, tableRef) {
|
|
16
58
|
let $grid;
|
|
17
59
|
if (Array.isArray(that.$refs[tableRef])) {
|
|
@@ -40,6 +82,8 @@ function getGrid(that, tableRef) {
|
|
|
40
82
|
CURRENT_PREFIX: "",
|
|
41
83
|
option: {},
|
|
42
84
|
uuid: "",
|
|
85
|
+
// createExcelTemplate 返回的本次导出时间快照,随每页取数请求下发(见 loopToDo)
|
|
86
|
+
nowDate: null,
|
|
43
87
|
fileRows: [],
|
|
44
88
|
title: this.$t2("导出", "components.excelExport.title"),
|
|
45
89
|
tableTarget: null,
|
|
@@ -92,6 +136,17 @@ function getGrid(that, tableRef) {
|
|
|
92
136
|
exportTimer: null,
|
|
93
137
|
countTimer: null,
|
|
94
138
|
exportError: false,
|
|
139
|
+
// 用户点「取消」/组件被销毁的终止标记。此前取消只是停掉定时器 + 销毁组件,
|
|
140
|
+
// 已经发出去的那几页请求照样会回调下来继续上送、继续合并,甚至最后弹出一个
|
|
141
|
+
// 半截文件的下载。所有异步续点都要先问 isExportStopped()。
|
|
142
|
+
aborted: false,
|
|
143
|
+
// 本次导出全部请求共用的取消令牌源,取消时一次性掐断在途请求
|
|
144
|
+
cancelSource: null,
|
|
145
|
+
// lockMenu 实际锁上的那一个页签。必须记下来:解锁若再按「当时的
|
|
146
|
+
// $route.path」现查,用户在导出期间切了页签(弹框可最小化,切走完全合法)
|
|
147
|
+
// 解的就是另一把锁 —— 原页签会永久 affix,而 tagsView 的 delOthersViews /
|
|
148
|
+
// delAllViews 都保留 affix,「关闭其他」「全部关闭」也删不掉它,只能刷新页面。
|
|
149
|
+
lockedView: null,
|
|
95
150
|
};
|
|
96
151
|
},
|
|
97
152
|
created() {},
|
|
@@ -99,10 +154,46 @@ function getGrid(that, tableRef) {
|
|
|
99
154
|
// this.exc();
|
|
100
155
|
},
|
|
101
156
|
beforeDestroy() {
|
|
157
|
+
this.aborted = true;
|
|
102
158
|
this.clearExportTimer();
|
|
159
|
+
this.cancelPendingRequests();
|
|
103
160
|
this.unlockMenu();
|
|
104
161
|
},
|
|
105
162
|
methods: {
|
|
163
|
+
/**
|
|
164
|
+
* 导出是否已终止:用户取消、组件销毁、或失败中止。
|
|
165
|
+
* 每一个跨越 await / .then / setTimeout 的续点都必须先问它,否则在途请求的回调
|
|
166
|
+
* 会在导出早已"取消"之后继续推进流程。
|
|
167
|
+
* @returns {Boolean}
|
|
168
|
+
*/
|
|
169
|
+
isExportStopped() {
|
|
170
|
+
if (this.aborted === true) return true;
|
|
171
|
+
if (this.exportError === true) return true;
|
|
172
|
+
return this._isDestroyed === true;
|
|
173
|
+
},
|
|
174
|
+
/**
|
|
175
|
+
* 本次导出共用的取消令牌,首次取用时创建。
|
|
176
|
+
* 每次导出都是一个全新的组件实例(index.js 的 initInstance 会先销毁旧实例),
|
|
177
|
+
* 所以一个 source 覆盖一次导出的全部请求,不存在跨导出串味。
|
|
178
|
+
* @returns {Object} axios CancelToken
|
|
179
|
+
*/
|
|
180
|
+
getCancelToken() {
|
|
181
|
+
if (!this.cancelSource) {
|
|
182
|
+
this.cancelSource = axios.CancelToken.source();
|
|
183
|
+
}
|
|
184
|
+
return this.cancelSource.token;
|
|
185
|
+
},
|
|
186
|
+
/**
|
|
187
|
+
* 掐断本次导出全部在途请求。
|
|
188
|
+
* request.js 的错误拦截器已对 axios.isCancel 短路返回,不会弹错误框;
|
|
189
|
+
* 各调用点的 .catch 也都会先问 isExportStopped() 再决定要不要提示。
|
|
190
|
+
*/
|
|
191
|
+
cancelPendingRequests() {
|
|
192
|
+
if (this.cancelSource) {
|
|
193
|
+
this.cancelSource.cancel("export-cancelled");
|
|
194
|
+
this.cancelSource = null;
|
|
195
|
+
}
|
|
196
|
+
},
|
|
106
197
|
exc() {
|
|
107
198
|
this.option = this.param;
|
|
108
199
|
let $grid = getGrid(this.option.vue, this.option.targetRef);
|
|
@@ -110,10 +201,9 @@ function getGrid(that, tableRef) {
|
|
|
110
201
|
// 明细导出的列来自 exportItemConfig,未维护时字段树为空、表头 JSON 也无从生成,
|
|
111
202
|
// 必须在此拦掉:这里是所有导出的唯一入口,放行下去会在字段选择弹框里抛异常。
|
|
112
203
|
if (isItemExport(this.option.type)) {
|
|
113
|
-
|
|
114
|
-
if (!originOption.exportItemConfig?.columns?.length) {
|
|
204
|
+
if (!getExportItemColumns(this.option, $grid).length) {
|
|
115
205
|
this.$baseAlert(
|
|
116
|
-
this.$t1("
|
|
206
|
+
this.$t1("未维护明细导出列,请先在导出按钮或数据表格属性中配置")
|
|
117
207
|
);
|
|
118
208
|
this.param.destroyComponent();
|
|
119
209
|
return;
|
|
@@ -149,6 +239,9 @@ function getGrid(that, tableRef) {
|
|
|
149
239
|
},
|
|
150
240
|
dialogClose2() {
|
|
151
241
|
if (this.isMinimize) return;
|
|
242
|
+
// 最小化不是取消,只有真正关闭才置终止标记
|
|
243
|
+
this.aborted = true;
|
|
244
|
+
this.cancelPendingRequests();
|
|
152
245
|
let loadingObj = window.$vueRoot.$baseLoading({
|
|
153
246
|
target: document.body,
|
|
154
247
|
background: "unset",
|
|
@@ -202,10 +295,13 @@ function getGrid(that, tableRef) {
|
|
|
202
295
|
this.abortExport(
|
|
203
296
|
this.$t1("无法确定导出服务前缀,请在导出配置中指定 prefix")
|
|
204
297
|
);
|
|
205
|
-
this.unlockMenu();
|
|
206
298
|
return;
|
|
207
299
|
}
|
|
208
300
|
this.CURRENT_PREFIX = options.prefix;
|
|
301
|
+
// 取数走 originOption.exportAjax 时请求由 xform 侧构造,前端拿不到那个 config,
|
|
302
|
+
// 只能把令牌顺着导出参数带过去(data-table-mixin 的 exportAjax 会塞进
|
|
303
|
+
// customParam.config,最终由 formHttp 的 ...opts 透传给 axios)
|
|
304
|
+
options.cancelToken = this.getCancelToken();
|
|
209
305
|
|
|
210
306
|
this.title = title;
|
|
211
307
|
that.tTotalPage = 0;
|
|
@@ -213,6 +309,7 @@ function getGrid(that, tableRef) {
|
|
|
213
309
|
that
|
|
214
310
|
.createExcelFile()
|
|
215
311
|
.then((resultMsg) => {
|
|
312
|
+
if (that.isExportStopped()) return;
|
|
216
313
|
if (resultMsg.type === "success") {
|
|
217
314
|
let aObj = resultMsg.objx;
|
|
218
315
|
that.uuid = aObj.uuid;
|
|
@@ -221,6 +318,7 @@ function getGrid(that, tableRef) {
|
|
|
221
318
|
setTimeout(function () {
|
|
222
319
|
/*num = 1;
|
|
223
320
|
loopToDo(0, num);*/
|
|
321
|
+
if (that.isExportStopped()) return;
|
|
224
322
|
that.handleLoopToDo();
|
|
225
323
|
}, 300);
|
|
226
324
|
} else {
|
|
@@ -230,6 +328,7 @@ function getGrid(that, tableRef) {
|
|
|
230
328
|
}
|
|
231
329
|
})
|
|
232
330
|
.catch((error) => {
|
|
331
|
+
if (that.isExportStopped()) return;
|
|
233
332
|
console.error(error);
|
|
234
333
|
that.abortExport(that.$t1("创建导出文件失败,导出已中止"));
|
|
235
334
|
that.showImportDialog = false;
|
|
@@ -250,9 +349,18 @@ function getGrid(that, tableRef) {
|
|
|
250
349
|
if (hasExportImage || (hasExportImage2 && showImageAtTable)) {
|
|
251
350
|
pageSize = Math.min(pageSize, 150);
|
|
252
351
|
}
|
|
352
|
+
// 单页压力取决于「行数 × 列数」,此前只按有无图片列降档,宽表(100+ 列)一页
|
|
353
|
+
// 的请求体与同步转换耗时是窄表的十几倍,同样的 1000 行/页天差地别。
|
|
354
|
+
let columnNum = this.leafColumns.length;
|
|
355
|
+
if (columnNum > 100) {
|
|
356
|
+
pageSize = Math.min(pageSize, 200);
|
|
357
|
+
} else if (columnNum > 50) {
|
|
358
|
+
pageSize = Math.min(pageSize, 500);
|
|
359
|
+
}
|
|
253
360
|
return pageSize;
|
|
254
361
|
},
|
|
255
362
|
hadleMergeExcel(pPageSize) {
|
|
363
|
+
if (this.isExportStopped()) return;
|
|
256
364
|
let that = this;
|
|
257
365
|
let title = this.title;
|
|
258
366
|
let options = this.option;
|
|
@@ -290,6 +398,9 @@ function getGrid(that, tableRef) {
|
|
|
290
398
|
that
|
|
291
399
|
.mergeExcel(startPage, endPage)
|
|
292
400
|
.then(function (resultMsg3) {
|
|
401
|
+
// 取消后组件与进度弹框都已销毁,下面 $refs["exportTable"].insertAt
|
|
402
|
+
// 会直接抛错
|
|
403
|
+
if (that.isExportStopped()) return;
|
|
293
404
|
if (resultMsg3.type === "success") {
|
|
294
405
|
mtExcedSuccessNum++;
|
|
295
406
|
let fileInfo = resultMsg3.objx;
|
|
@@ -311,25 +422,17 @@ function getGrid(that, tableRef) {
|
|
|
311
422
|
// that.fileRows.push(dataMap);
|
|
312
423
|
if (mtExcedNum < fileNum) {
|
|
313
424
|
loopDo();
|
|
314
|
-
} else if (
|
|
315
|
-
mtExcedNum >= fileNum &&
|
|
316
|
-
mtExcedSuccessNum >= fileNum
|
|
317
|
-
) {
|
|
318
|
-
setTimeout(function () {
|
|
319
|
-
that.percentageNum = parseInt(100);
|
|
320
|
-
}, 100);
|
|
321
|
-
that.clearExportTimer();
|
|
322
425
|
} else if (mtExcedSuccessNum >= fileNum) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
that.clearExportTimer();
|
|
426
|
+
// 走到 else 分支时 mtExcedNum >= fileNum 必然成立,
|
|
427
|
+
// 原先拆成的两个 else-if 条件与分支体完全相同,合并为一处
|
|
428
|
+
that.completeExport();
|
|
327
429
|
}
|
|
328
430
|
} else {
|
|
329
431
|
that.abortExport();
|
|
330
432
|
}
|
|
331
433
|
})
|
|
332
434
|
.catch(function (e) {
|
|
435
|
+
if (that.isExportStopped()) return;
|
|
333
436
|
console.error(e);
|
|
334
437
|
that.abortExport(that.$t1("文件合并异常,导出已中止"));
|
|
335
438
|
});
|
|
@@ -355,14 +458,28 @@ function getGrid(that, tableRef) {
|
|
|
355
458
|
let excedNum = 0;
|
|
356
459
|
let cSize = this.doneSize;
|
|
357
460
|
|
|
358
|
-
let handleData = function (resultMsg) {
|
|
461
|
+
let handleData = function (resultMsg, flag) {
|
|
462
|
+
// 取消之后到达的在途响应直接丢弃,不再进入上送/合并流程
|
|
463
|
+
if (that.isExportStopped()) return;
|
|
359
464
|
let pageNumber = resultMsg.objx.pageNumber || 1;
|
|
465
|
+
// 并发窗口在"该页上送完成"后才释放,而不是"该页拉取完成"就释放。
|
|
466
|
+
// addExcelData 此前完全不受闸门约束:上送慢于拉取时 pending 的 POST 会无限
|
|
467
|
+
// 堆积(每个都攥着一份几 MB 的 JSON body),加上浏览器每域名 6 连接会让上送
|
|
468
|
+
// 排到拉取后面,这种反压对调度器完全不可见 —— 大数据量导出的内存曲线因此
|
|
469
|
+
// 始终只涨不落。
|
|
470
|
+
let released = flag !== 1; // 首页(flag=0)不占窗口,无需释放
|
|
471
|
+
let releaseWindow = function () {
|
|
472
|
+
if (released) return;
|
|
473
|
+
released = true;
|
|
474
|
+
if (waitReqNum > 0) waitReqNum--;
|
|
475
|
+
};
|
|
360
476
|
that
|
|
361
477
|
.addExcelData({
|
|
362
478
|
rows: resultMsg.objx.records,
|
|
363
479
|
pageNumber: pageNumber,
|
|
364
480
|
})
|
|
365
481
|
.then(function (resultMsg2) {
|
|
482
|
+
releaseWindow();
|
|
366
483
|
let data = resultMsg.objx;
|
|
367
484
|
let total = data.total;
|
|
368
485
|
|
|
@@ -481,6 +598,8 @@ function getGrid(that, tableRef) {
|
|
|
481
598
|
}
|
|
482
599
|
})
|
|
483
600
|
.catch(function (e) {
|
|
601
|
+
releaseWindow();
|
|
602
|
+
if (that.isExportStopped()) return;
|
|
484
603
|
console.error(e);
|
|
485
604
|
that.abortExport(that.$t1("导出数据写入失败,导出已中止"));
|
|
486
605
|
});
|
|
@@ -489,8 +608,20 @@ function getGrid(that, tableRef) {
|
|
|
489
608
|
let waitReqNum = 0;
|
|
490
609
|
let promise;
|
|
491
610
|
let loopToDo = function (flag, pageNumber, retryCount = 0) {
|
|
611
|
+
// 取消后不再发起新页,也不再重试(重试是从 .catch 里递归回来的)
|
|
612
|
+
if (that.isExportStopped()) return;
|
|
492
613
|
let maxWaitNum = limitThreadNum;
|
|
493
|
-
|
|
614
|
+
// 分页取数的时间快照:createExcelTemplate 返回后存在 this.nowDate(见
|
|
615
|
+
// startProcess),此前这里读的是 options.nowDate —— 那是调用方入参,全库
|
|
616
|
+
// 没有任何调用方维护它,于是每页请求的 nowDate 恒为 undefined、被
|
|
617
|
+
// JSON.stringify 丢掉,后端拿不到快照点。导出期间数据发生增删时,分页会
|
|
618
|
+
// 整体前后错位,合出来的文件既漏行又重行,且全程不报错。
|
|
619
|
+
// 仍保留 options.nowDate 兜底:服务端未返回快照时,调用方若显式指定了一个,
|
|
620
|
+
// 用它总好过不带。
|
|
621
|
+
// 两条取数通道都要带:通用表格随请求体平铺下发;xform 走 exportAjax,快照
|
|
622
|
+
// 顺 aParam 进去后由 data-table-mixin.loadDefaultQueryList 提到信封层(与
|
|
623
|
+
// formCode 同级),不能留在内层 data —— 那是业务查询条件的地盘。
|
|
624
|
+
let nowDate = that.nowDate ?? options.nowDate;
|
|
494
625
|
if (flag === 1) {
|
|
495
626
|
/*if (num > that.tTotalPage) {
|
|
496
627
|
let size = page.records.length;
|
|
@@ -508,7 +639,11 @@ function getGrid(that, tableRef) {
|
|
|
508
639
|
let url = originOption.exportPath || originOption.path;
|
|
509
640
|
|
|
510
641
|
// let params = originOption.param() || {};
|
|
511
|
-
|
|
642
|
+
// 每页必须独立参数对象。此前所有页共享 that.queryParam 这一个对象,下面的
|
|
643
|
+
// Object.assign 把 current 写进去后,靠"派发间隔 100ms、微任务已 flush"侥幸
|
|
644
|
+
// 不冲突;请求拦截器一旦变成异步(token 续期等),并发的两页会带上同一个
|
|
645
|
+
// current —— 合并出的文件重复页 + 缺页,且全程不报错。
|
|
646
|
+
let params = that.$baseLodash.cloneDeep(that.queryParam);
|
|
512
647
|
let aParam = {
|
|
513
648
|
current: pageNumber,
|
|
514
649
|
size: pPageSize,
|
|
@@ -540,6 +675,7 @@ function getGrid(that, tableRef) {
|
|
|
540
675
|
modal: false,
|
|
541
676
|
queryCreateInfo: originOption.queryCreateInfo,
|
|
542
677
|
addCreateInfo: originOption.addCreateInfo,
|
|
678
|
+
cancelToken: that.getCancelToken(),
|
|
543
679
|
});
|
|
544
680
|
} else {
|
|
545
681
|
promise = originOption.exportAjax(aParam, options);
|
|
@@ -592,9 +728,9 @@ function getGrid(that, tableRef) {
|
|
|
592
728
|
clearInterval(that.exportTimer);
|
|
593
729
|
that.exportTimer = null;
|
|
594
730
|
}
|
|
595
|
-
} else {
|
|
596
|
-
if (waitReqNum > 0) waitReqNum--;
|
|
597
731
|
}
|
|
732
|
+
// 注意:此处不再释放并发窗口。拉取完成只是这一页做完了一半,
|
|
733
|
+
// 窗口由 handleData 在上送(addExcelData)落地后释放,见那里的注释。
|
|
598
734
|
|
|
599
735
|
setTimeout(function () {
|
|
600
736
|
handleData(resultMsg, flag);
|
|
@@ -611,6 +747,8 @@ function getGrid(that, tableRef) {
|
|
|
611
747
|
}
|
|
612
748
|
})
|
|
613
749
|
.catch(function (e) {
|
|
750
|
+
// 取消导致的 reject 不是失败,也不该重试或弹提示
|
|
751
|
+
if (that.isExportStopped()) return;
|
|
614
752
|
console.error(e);
|
|
615
753
|
if (retryCount < 1) {
|
|
616
754
|
setTimeout(function () {
|
|
@@ -641,6 +779,7 @@ function getGrid(that, tableRef) {
|
|
|
641
779
|
data: datas,
|
|
642
780
|
modal: loading || false,
|
|
643
781
|
isLoading: loading,
|
|
782
|
+
cancelToken: this.getCancelToken(),
|
|
644
783
|
});
|
|
645
784
|
},
|
|
646
785
|
/*getExportTitleJson() {
|
|
@@ -742,11 +881,21 @@ function getGrid(that, tableRef) {
|
|
|
742
881
|
}
|
|
743
882
|
return titleArr;
|
|
744
883
|
},
|
|
745
|
-
addExcelData(opts, loading) {
|
|
884
|
+
async addExcelData(opts, loading) {
|
|
746
885
|
let target = opts.target;
|
|
747
886
|
let pageNumber = opts.pageNumber || 1;
|
|
748
887
|
let callback = opts.callback;
|
|
749
|
-
|
|
888
|
+
// 已取消就不要再算、更不要再发。这里是所有上送的唯一入口,卡住它等于卡住
|
|
889
|
+
// 取消之后的全部后续动作。
|
|
890
|
+
if (this.isExportStopped()) {
|
|
891
|
+
return { type: "abort" };
|
|
892
|
+
}
|
|
893
|
+
let datas = await this.getExcelData(opts.rows);
|
|
894
|
+
// 取值过程中导出已被中止(getExcelData 让出主线程时会检查),不再上送这一页。
|
|
895
|
+
// 返回非 success 让调用方走它自己的中止分支,abortExport 幂等不会重复弹窗。
|
|
896
|
+
if (datas === null) {
|
|
897
|
+
return { type: "abort" };
|
|
898
|
+
}
|
|
750
899
|
let cUUid = this.uuid;
|
|
751
900
|
let ippaaapp = this.ippaaapp;
|
|
752
901
|
let url1 =
|
|
@@ -766,6 +915,7 @@ function getGrid(that, tableRef) {
|
|
|
766
915
|
data: datas,
|
|
767
916
|
modal: loading || false,
|
|
768
917
|
isLoading: loading || false,
|
|
918
|
+
cancelToken: this.getCancelToken(),
|
|
769
919
|
});
|
|
770
920
|
},
|
|
771
921
|
mergeExcel(startPage, endPage, loading) {
|
|
@@ -780,6 +930,7 @@ function getGrid(that, tableRef) {
|
|
|
780
930
|
method: "post",
|
|
781
931
|
modal: loading || false,
|
|
782
932
|
isLoading: loading || false,
|
|
933
|
+
cancelToken: this.getCancelToken(),
|
|
783
934
|
});
|
|
784
935
|
},
|
|
785
936
|
createCountTimer() {
|
|
@@ -797,13 +948,32 @@ function getGrid(that, tableRef) {
|
|
|
797
948
|
this.exportTimer = null;
|
|
798
949
|
this.countTimer = null;
|
|
799
950
|
},
|
|
951
|
+
/**
|
|
952
|
+
* 导出全部文件合并成功:进度置满、停掉计时,并**立刻解开页签锁**。
|
|
953
|
+
* 锁只保护取数期间。文件已经生成之后弹框还开着(用户在看列表、点下载)是完全
|
|
954
|
+
* 合法的状态,那时页签必须能正常关闭 —— 解锁此前只挂在 beforeDestroy 上,
|
|
955
|
+
* 用户不点「取消/确定」就永远解不开,正是「导出完成后页签关不掉」的来源。
|
|
956
|
+
*/
|
|
957
|
+
completeExport() {
|
|
958
|
+
setTimeout(() => {
|
|
959
|
+
if (this.isExportStopped()) return;
|
|
960
|
+
this.percentageNum = parseInt(100);
|
|
961
|
+
}, 100);
|
|
962
|
+
this.clearExportTimer();
|
|
963
|
+
this.unlockMenu();
|
|
964
|
+
},
|
|
800
965
|
/**
|
|
801
966
|
* 中止导出:停止全部定时器并把进度条置为错误态。
|
|
802
967
|
* @param {String} [msg] 需要额外弹给用户的提示;服务端已 toast 过的失败不传,避免重复提示。
|
|
803
968
|
*/
|
|
804
969
|
abortExport(msg) {
|
|
805
970
|
this.clearExportTimer();
|
|
971
|
+
// 必须先置标志再取消:取消会让其余在途请求 reject,它们的 .catch 先问
|
|
972
|
+
// isExportStopped() 才不会重复弹提示
|
|
806
973
|
this.exportError = true;
|
|
974
|
+
this.cancelPendingRequests();
|
|
975
|
+
// 中止后弹框可能还挂在那儿(错误态进度条),页签没有理由继续锁着
|
|
976
|
+
this.unlockMenu();
|
|
807
977
|
if (msg) this.$baseAlert(msg);
|
|
808
978
|
},
|
|
809
979
|
isPicture: function (suffix) {
|
|
@@ -858,7 +1028,7 @@ function getGrid(that, tableRef) {
|
|
|
858
1028
|
}
|
|
859
1029
|
return getCellValue(params);
|
|
860
1030
|
},
|
|
861
|
-
getExcelData(rows) {
|
|
1031
|
+
async getExcelData(rows) {
|
|
862
1032
|
let that = this;
|
|
863
1033
|
|
|
864
1034
|
let showImageAtTable = this.option.showImageAtTable || false;
|
|
@@ -875,8 +1045,19 @@ function getGrid(that, tableRef) {
|
|
|
875
1045
|
let contents;
|
|
876
1046
|
let params;
|
|
877
1047
|
let resultStr;
|
|
878
|
-
|
|
879
|
-
|
|
1048
|
+
let index = -1;
|
|
1049
|
+
for (const row of rows) {
|
|
1050
|
+
index++;
|
|
1051
|
+
// 每 EXPORT_YIELD_ROWS 行让出一次主线程。取值本身是同步的双层遍历,一页
|
|
1052
|
+
// 默认 1000 行、渲染兜底列还要逐单元格挂载组件,整页跑完可达数秒;并发几页
|
|
1053
|
+
// 几乎同时到达时主线程被连续占死,进度条不动、「取消」点不动,浏览器还可能
|
|
1054
|
+
// 直接判定页面无响应而杀掉标签页。让出不减少总工作量,只保证 UI 活着。
|
|
1055
|
+
if (index > 0 && index % EXPORT_YIELD_ROWS === 0) {
|
|
1056
|
+
await yieldToBrowser();
|
|
1057
|
+
// 让出期间用户可能已取消/导出已中止,此时继续算完这一页没有意义,
|
|
1058
|
+
// 且合并根本不会发生,返回 null 让调用方跳过上送。
|
|
1059
|
+
if (that.isExportStopped()) return null;
|
|
1060
|
+
}
|
|
880
1061
|
contents = [];
|
|
881
1062
|
columns.forEach((column) => {
|
|
882
1063
|
if (column.title) {
|
|
@@ -897,6 +1078,14 @@ function getGrid(that, tableRef) {
|
|
|
897
1078
|
: null;
|
|
898
1079
|
if (exportVal) {
|
|
899
1080
|
resultStr = exportVal(params);
|
|
1081
|
+
} else if (column.params?.exportBlank && !exportType) {
|
|
1082
|
+
// 列保留、格留空:没配导出类型的附件列没有可写的内容,更不该为此
|
|
1083
|
+
// 逐单元格挂载附件组件去爬文件名。判定见 xform 的 isBlankExportColumn。
|
|
1084
|
+
// 放在 exportVal 之后:调用方显式写了取值函数就以它为准。
|
|
1085
|
+
// 必须再判一次 exportType:exportBlank 是建列时按 t.exportType 算死的,
|
|
1086
|
+
// 而 params.exportType 之后还会被 tableColumnConfig 脚本覆盖,只认
|
|
1087
|
+
// exportBlank 会把运行期才拿到导出类型的图片列错误地导成空。
|
|
1088
|
+
resultStr = null;
|
|
900
1089
|
} else if (exportType === "Number") {
|
|
901
1090
|
resultStr = that.getExportCellValue(params);
|
|
902
1091
|
if (
|
|
@@ -911,7 +1100,10 @@ function getGrid(that, tableRef) {
|
|
|
911
1100
|
exportType === "Image" ||
|
|
912
1101
|
(showImageAtTable && exportType === "Image2")
|
|
913
1102
|
) {
|
|
914
|
-
|
|
1103
|
+
// 取值刻意延到"确实用得上"的那条分支才做:附件的正常形态是数组,
|
|
1104
|
+
// 那条分支会用 row[column.field] 自己拼 [EXPIMG],把取值结果整个覆盖;
|
|
1105
|
+
// 空值分支同样直接给 null。而附件列没有 filterVal,取值会落到
|
|
1106
|
+
// getCellValue 的 vNode 挂载兜底——等于每个单元格白挂一次附件组件。
|
|
915
1107
|
let attachments = row[column.field];
|
|
916
1108
|
if (attachments) {
|
|
917
1109
|
if (Array.isArray(attachments)) {
|
|
@@ -931,6 +1123,8 @@ function getGrid(that, tableRef) {
|
|
|
931
1123
|
resultStr = null;
|
|
932
1124
|
}
|
|
933
1125
|
} else {
|
|
1126
|
+
// 单值形态(字段里直接存 URL)才需要取值来嗅后缀
|
|
1127
|
+
resultStr = that.getExportCellValue(params);
|
|
934
1128
|
if (
|
|
935
1129
|
that.isPicture(
|
|
936
1130
|
that.$commonFileUtil.getFileSuffix(resultStr)
|
|
@@ -959,10 +1153,35 @@ function getGrid(that, tableRef) {
|
|
|
959
1153
|
}
|
|
960
1154
|
});
|
|
961
1155
|
arr.push(contents);
|
|
962
|
-
}
|
|
1156
|
+
}
|
|
963
1157
|
return arr;
|
|
964
1158
|
},
|
|
1159
|
+
/**
|
|
1160
|
+
* 从响应头解析下载文件名,取不到时用导出标题兜底。
|
|
1161
|
+
* @param {Object} response axios 原始响应。
|
|
1162
|
+
* @param {String} fallbackName 兜底文件名(不含扩展名)。
|
|
1163
|
+
* @returns {String}
|
|
1164
|
+
*/
|
|
1165
|
+
resolveDownloadFileName(response, fallbackName) {
|
|
1166
|
+
let headers = response?.headers || {};
|
|
1167
|
+
let disposition = headers["content-disposition"] || headers["Content-Disposition"];
|
|
1168
|
+
if (disposition) {
|
|
1169
|
+
// filename*=UTF-8''xxx 优先,其次 filename="xxx"
|
|
1170
|
+
let starMatch = /filename\*=(?:UTF-8'')?([^;]+)/i.exec(disposition);
|
|
1171
|
+
let plainMatch = /filename="?([^";]+)"?/i.exec(disposition);
|
|
1172
|
+
let raw = (starMatch && starMatch[1]) || (plainMatch && plainMatch[1]);
|
|
1173
|
+
if (raw) {
|
|
1174
|
+
try {
|
|
1175
|
+
return decodeURIComponent(raw.trim());
|
|
1176
|
+
} catch (e) {
|
|
1177
|
+
return raw.trim();
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
return fallbackName + ".xlsx";
|
|
1182
|
+
},
|
|
965
1183
|
exportAll(row) {
|
|
1184
|
+
let that = this;
|
|
966
1185
|
let cUUid = encodeURIComponent(row.uuid);
|
|
967
1186
|
let ippaaapp = this.ippaaapp;
|
|
968
1187
|
let fileName = row.title;
|
|
@@ -1006,7 +1225,63 @@ function getGrid(that, tableRef) {
|
|
|
1006
1225
|
document.body.removeChild(form);
|
|
1007
1226
|
}
|
|
1008
1227
|
|
|
1009
|
-
|
|
1228
|
+
/**
|
|
1229
|
+
* 优先走 axios blob 下载:token 由 request.js 的请求拦截器注入
|
|
1230
|
+
* X-Token / Authorization 头,不必再把 access_token 塞进表单字段随请求体
|
|
1231
|
+
* 明文提交;失败时也能拿到响应体做判断,而表单 POST 打开新窗口后失败只会给
|
|
1232
|
+
* 用户看一个空白页或一坨错误 JSON。
|
|
1233
|
+
*
|
|
1234
|
+
* 后端若尚未放开该接口的头鉴权(此前只认表单里的 access_token),这里会拿到
|
|
1235
|
+
* JSON 错误体或直接报错 —— 一律回退到原来的表单 POST,保证下载不会因为这次
|
|
1236
|
+
* 改造而失效。同体系的 /export_ins/download 已经是 blob + 头鉴权的用法
|
|
1237
|
+
* (见 components/baseInputExport/mixins.js),大概率是通的,但**需联调确认**。
|
|
1238
|
+
*/
|
|
1239
|
+
let fallbackToFormPost = function (reason) {
|
|
1240
|
+
console.warn("[excelExport] blob 下载不可用,回退表单 POST:", reason);
|
|
1241
|
+
downLoadFile2();
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
let apiUrl = `${this.CURRENT_PREFIX}/excel/download?uuid=${cUUid}&ippaaapp=${ippaaapp}&n=${encodeURIComponent(fileName)}`;
|
|
1245
|
+
|
|
1246
|
+
this.$http({
|
|
1247
|
+
url: apiUrl,
|
|
1248
|
+
method: "post",
|
|
1249
|
+
data: {},
|
|
1250
|
+
responseType: "blob",
|
|
1251
|
+
resultType: "other",
|
|
1252
|
+
modal: false,
|
|
1253
|
+
// 失败要静默回退,不能先弹一个错误框
|
|
1254
|
+
errorMsg: false,
|
|
1255
|
+
// 刻意**不带**本次导出的取消令牌。下载不是导出流水线的一环,而是用户单独
|
|
1256
|
+
// 发起的动作:分卷文件大时点完「下载」要等几秒,此时关掉进度弹框(「取消」
|
|
1257
|
+
// 和「确定」都走 dialogClose2 → cancelPendingRequests)会掐断这个请求,而
|
|
1258
|
+
// 下面的 isCancel 分支又刻意不回退表单 POST —— 结果是文件静默地下不下来,
|
|
1259
|
+
// 且没有任何提示。旧的表单 POST 脱离组件生命周期,本就不受关弹框影响。
|
|
1260
|
+
callback: (res, response) => {
|
|
1261
|
+
// 后端把错误当 200 返回时,body 是 JSON / HTML 错误页而不是 xlsx 流
|
|
1262
|
+
if (!isDownloadableBlob(res)) {
|
|
1263
|
+
fallbackToFormPost("响应不是文件流:" + (res && res.type));
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
let blobUrl = window.URL.createObjectURL(res);
|
|
1267
|
+
let a = document.createElement("a");
|
|
1268
|
+
a.href = blobUrl;
|
|
1269
|
+
a.download = that.resolveDownloadFileName(response, fileName);
|
|
1270
|
+
document.body.appendChild(a);
|
|
1271
|
+
a.click();
|
|
1272
|
+
document.body.removeChild(a);
|
|
1273
|
+
// 不释放会一直占着这份文件的内存,大导出下很可观
|
|
1274
|
+
setTimeout(() => window.URL.revokeObjectURL(blobUrl), 0);
|
|
1275
|
+
},
|
|
1276
|
+
}).catch((e) => {
|
|
1277
|
+
// 只挡真正的取消:取消后回退表单 POST 会在取消之后又弹出一次下载。
|
|
1278
|
+
// 本方法已不带取消令牌,这里是防御性保留(组件销毁等外部因素仍可能取消)。
|
|
1279
|
+
// 这里**不能**用 isExportStopped()——它含 exportError,而 exportAll 同时是
|
|
1280
|
+
// 进度弹框里的手动下载按钮:导出中途失败后用户手动下载已生成的分卷,正是最
|
|
1281
|
+
// 需要回退兜底的场景,用它拦会变成点了毫无反应。
|
|
1282
|
+
if (axios.isCancel(e)) return;
|
|
1283
|
+
fallbackToFormPost(e && e.message);
|
|
1284
|
+
});
|
|
1010
1285
|
},
|
|
1011
1286
|
startProcess2(datas) {
|
|
1012
1287
|
this.tableTarget = getGrid(this.option.vue, this.option.targetRef);
|
|
@@ -1035,20 +1310,49 @@ function getGrid(that, tableRef) {
|
|
|
1035
1310
|
}
|
|
1036
1311
|
this.CURRENT_PREFIX = options.prefix;
|
|
1037
1312
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1313
|
+
// 勾选/当前页导出此前把全部行塞进**一个**请求上送。`getCheckboxRecords(true)`
|
|
1314
|
+
// 是跨页全量勾选,选得多时请求体轻易超限(后端/网关直接拒),而且整批取值转换
|
|
1315
|
+
// 与 JSON 序列化一次性压在主线程上。改为复用条件导出那套「分页 append + 一次
|
|
1316
|
+
// merge」:按 getExportPageSize() 切片顺序上送。
|
|
1317
|
+
// 注意页数 ≤ 1 时(勾选量不超过一页,即绝大多数场景)请求序列与旧实现完全一致,
|
|
1318
|
+
// 不引入任何行为变化;这里刻意保持**串行**,避免把条件导出那边"上送无并发闸门"
|
|
1319
|
+
// 的老问题原样复制过来。
|
|
1320
|
+
let pageSize = this.getExportPageSize();
|
|
1321
|
+
let totalPages = Math.max(Math.ceil(datas.length / pageSize), 1);
|
|
1322
|
+
|
|
1323
|
+
let appendPage = function (pageNumber) {
|
|
1324
|
+
let start = (pageNumber - 1) * pageSize;
|
|
1325
|
+
return that
|
|
1041
1326
|
.addExcelData(
|
|
1042
1327
|
{
|
|
1043
|
-
rows: datas,
|
|
1044
|
-
pageNumber:
|
|
1328
|
+
rows: datas.slice(start, start + pageSize),
|
|
1329
|
+
pageNumber: pageNumber,
|
|
1045
1330
|
},
|
|
1046
1331
|
true
|
|
1047
1332
|
)
|
|
1048
1333
|
.then((resultMsg2) => {
|
|
1049
|
-
if (resultMsg2.type
|
|
1050
|
-
|
|
1051
|
-
|
|
1334
|
+
if (resultMsg2.type !== "success") {
|
|
1335
|
+
// "abort" 是取值过程中组件已销毁/已中止,无需再提示;
|
|
1336
|
+
// 其余失败服务端已 toast 过,abortExport 不带 msg 避免重复弹
|
|
1337
|
+
if (resultMsg2.type !== "abort") that.abortExport();
|
|
1338
|
+
return false;
|
|
1339
|
+
}
|
|
1340
|
+
if (pageNumber >= totalPages) return true;
|
|
1341
|
+
return appendPage(pageNumber + 1);
|
|
1342
|
+
});
|
|
1343
|
+
};
|
|
1344
|
+
|
|
1345
|
+
let handleData = function () {
|
|
1346
|
+
appendPage(1)
|
|
1347
|
+
.then((completed) => {
|
|
1348
|
+
// 缺页合并会静默丢行,任一页失败都必须停在这里
|
|
1349
|
+
if (!completed) return;
|
|
1350
|
+
setTimeout(() => {
|
|
1351
|
+
that
|
|
1352
|
+
.mergeExcel(1, totalPages, true)
|
|
1353
|
+
.then((resultMsg3) => {
|
|
1354
|
+
// 取消后不能再自动弹下载:那会是一个半截文件
|
|
1355
|
+
if (that.isExportStopped()) return;
|
|
1052
1356
|
if (resultMsg3.type === "success") {
|
|
1053
1357
|
let fileInfo = resultMsg3.objx;
|
|
1054
1358
|
let fileName = fileInfo.fileName;
|
|
@@ -1056,28 +1360,49 @@ function getGrid(that, tableRef) {
|
|
|
1056
1360
|
|
|
1057
1361
|
let dataMap = {};
|
|
1058
1362
|
dataMap["startPage"] = 1;
|
|
1059
|
-
dataMap["endPage"] =
|
|
1363
|
+
dataMap["endPage"] = totalPages;
|
|
1060
1364
|
dataMap["title"] = title;
|
|
1061
1365
|
dataMap["uuid"] = fileName;
|
|
1062
1366
|
dataMap["fileSize"] = fileSize;
|
|
1063
1367
|
that.exportAll(dataMap);
|
|
1368
|
+
} else {
|
|
1369
|
+
that.abortExport();
|
|
1064
1370
|
}
|
|
1371
|
+
})
|
|
1372
|
+
.catch((e) => {
|
|
1373
|
+
if (that.isExportStopped()) return;
|
|
1374
|
+
console.error(e);
|
|
1375
|
+
that.abortExport(that.$t1("文件合并异常,导出已中止"));
|
|
1065
1376
|
});
|
|
1066
|
-
|
|
1067
|
-
|
|
1377
|
+
}, 0);
|
|
1378
|
+
})
|
|
1379
|
+
.catch((e) => {
|
|
1380
|
+
if (that.isExportStopped()) return;
|
|
1381
|
+
console.error(e);
|
|
1382
|
+
that.abortExport(that.$t1("导出数据写入失败,导出已中止"));
|
|
1068
1383
|
});
|
|
1069
1384
|
};
|
|
1070
|
-
that
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
that.
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1385
|
+
that
|
|
1386
|
+
.createExcelFile(true)
|
|
1387
|
+
.then((resultMsg) => {
|
|
1388
|
+
if (that.isExportStopped()) return;
|
|
1389
|
+
if (resultMsg.type === "success") {
|
|
1390
|
+
let aObj = resultMsg.objx;
|
|
1391
|
+
that.uuid = aObj.uuid;
|
|
1392
|
+
that.nowDate = aObj.nowDate;
|
|
1393
|
+
that.ippaaapp = aObj.ippaaapp;
|
|
1394
|
+
setTimeout(() => {
|
|
1395
|
+
handleData();
|
|
1396
|
+
}, 0);
|
|
1397
|
+
} else {
|
|
1398
|
+
that.abortExport();
|
|
1399
|
+
}
|
|
1400
|
+
})
|
|
1401
|
+
.catch((error) => {
|
|
1402
|
+
if (that.isExportStopped()) return;
|
|
1403
|
+
console.error(error);
|
|
1404
|
+
that.abortExport(that.$t1("创建导出文件失败,导出已中止"));
|
|
1405
|
+
});
|
|
1081
1406
|
},
|
|
1082
1407
|
closeExportFieldDialog() {
|
|
1083
1408
|
this.dialogClose2();
|
|
@@ -1122,29 +1447,42 @@ function getGrid(that, tableRef) {
|
|
|
1122
1447
|
}
|
|
1123
1448
|
});
|
|
1124
1449
|
},
|
|
1450
|
+
/**
|
|
1451
|
+
* 导出期间锁住当前页签(不让关),并记下锁的是哪一个,见 lockedView。
|
|
1452
|
+
* 本就是固定页签(affix)的不接管 —— 那不是我们加的锁,解的时候也不能动它。
|
|
1453
|
+
*
|
|
1454
|
+
* 只写 user_affix,**绝不能再写 meta.affix**:isRepeatable() 见到 affix 就返回
|
|
1455
|
+
* false,可重复打开的页签的 viewKey 会从 fullPath 塌成 path,于是 AppMain 的
|
|
1456
|
+
* router-view key 与 BaseKeepAlive 的缓存名同时错位 —— 切走再切回整页重建。
|
|
1457
|
+
* 「不让批量关闭」的语义由 tagsView 的 isPinned 认 user_affix 兜住。
|
|
1458
|
+
*
|
|
1459
|
+
* 定位页签用 viewKey 而不是 path:可重复打开时同一 path 会有多个页签,
|
|
1460
|
+
* 按 path 找到的可能是另一个实例。
|
|
1461
|
+
*/
|
|
1125
1462
|
lockMenu() {
|
|
1126
1463
|
let vueTarget = window.$vueRoot;
|
|
1127
|
-
let
|
|
1464
|
+
let key = viewKey(vueTarget.$route);
|
|
1128
1465
|
let visitedViews = vueTarget.$store.state.tagsView.visitedViews;
|
|
1129
|
-
if (visitedViews)
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
}
|
|
1466
|
+
if (!visitedViews) return;
|
|
1467
|
+
let currentItem = visitedViews.find((item) => viewKey(item) === key);
|
|
1468
|
+
if (currentItem && !currentItem.meta.affix) {
|
|
1469
|
+
this.$set(currentItem.meta, "user_affix", true);
|
|
1470
|
+
this.lockedView = currentItem;
|
|
1135
1471
|
}
|
|
1136
1472
|
},
|
|
1473
|
+
/**
|
|
1474
|
+
* 解开自己锁的那一个页签。
|
|
1475
|
+
* **不能**再按当前路由去查:解锁的时机是组件销毁(取消、或下一次导出把上一个
|
|
1476
|
+
* 实例顶掉),那时的当前路由未必还是上锁时那一个。典型漏锁路径:A 页开始条件
|
|
1477
|
+
* 导出 → 切到 B 页 → 在 B 页再点一次导出,initInstance 先销毁旧实例,旧实例按
|
|
1478
|
+
* $route.path 查到的是 B(B 还没锁,什么也没做),于是 A 永久锁死。
|
|
1479
|
+
* 页签可能已被别处删掉,此时改的是一个游离对象,无副作用。
|
|
1480
|
+
*/
|
|
1137
1481
|
unlockMenu() {
|
|
1138
|
-
let
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
let currentItem = visitedViews.find((item) => item.path === path);
|
|
1143
|
-
if (currentItem && currentItem.meta.user_affix) {
|
|
1144
|
-
currentItem.meta.affix = false;
|
|
1145
|
-
currentItem.meta.user_affix = false;
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1482
|
+
let view = this.lockedView;
|
|
1483
|
+
this.lockedView = null;
|
|
1484
|
+
if (!view || !view.meta.user_affix) return;
|
|
1485
|
+
view.meta.user_affix = false;
|
|
1148
1486
|
},
|
|
1149
1487
|
},
|
|
1150
1488
|
};
|