cloud-web-corejs 1.0.282 → 1.0.283
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/excelImport/index.vue +1 -1
- package/src/components/excelImport/mixins.js +56 -8
- package/src/components/table/vxeFilter/mixin.js +28 -18
- package/src/components/xform/docs/2026-07 table/344/270/216excelExport/344/277/256/345/244/215/345/217/212/345/257/274/345/207/272/344/274/230/345/214/226.md" +11 -4
- package/src/components/xform/docs/2026-09 /346/235/241/344/273/266/345/257/274/345/207/272/345/244/247/346/225/260/346/215/256/351/207/217/345/264/251/346/272/203/346/216/222/346/237/245.md" +389 -0
- package/src/components/xform/docs/2026-09 /347/274/226/350/276/221/350/241/250/350/241/214/345/206/205/344/277/235/345/255/230/346/240/241/351/252/214/350/214/203/345/233/264/344/270/216/345/210/227/345/277/205/345/241/253/350/247/243/346/236/220/344/277/256/345/244/215.md" +233 -0
- package/src/components/xform/form-designer/form-widget/dialog/importDialogMixin.js +61 -2
- package/src/components/xform/form-render/container-item/data-table-mixin.js +96 -10
- package/src/components/xform/utils/tableColumnHelper.js +46 -4
- package/src/utils/assetUrl.js +19 -0
- package/src/utils/importLimit.js +72 -0
- package/src/utils/vab.js +5 -1
package/package.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
</div>
|
|
27
27
|
</el-upload>
|
|
28
28
|
<div class="upload-finish" v-if="fileRaw">
|
|
29
|
-
<img :src="require('@/resources' + '/images/ico-excel.svg')" class="ico"/>
|
|
29
|
+
<img :src="$assetUrl(require('@/resources' + '/images/ico-excel.svg'))" class="ico"/>
|
|
30
30
|
<p class="name">{{ fileRaw.name }}</p>
|
|
31
31
|
<!-- <p>2022-1-17</p> -->
|
|
32
32
|
<el-tooltip :enterable="false" class="item" effect="dark"
|
|
@@ -6,14 +6,22 @@ var XLSX = window.XLSX || { read, utils };
|
|
|
6
6
|
import { getToken } from "../../utils/auth";
|
|
7
7
|
import XEUtils from "xe-utils";
|
|
8
8
|
import { getErrorMsg } from "@base/utils/index.js";
|
|
9
|
+
import {
|
|
10
|
+
getImportCellText,
|
|
11
|
+
getImportFileSizeLimit,
|
|
12
|
+
getImportRowLimit,
|
|
13
|
+
isImportFileOversize,
|
|
14
|
+
isImportRowsOverLimit,
|
|
15
|
+
toFileSizeMb,
|
|
16
|
+
} from "../../utils/importLimit";
|
|
9
17
|
|
|
10
18
|
let configUtil = {
|
|
11
19
|
baseUrl: process.env.VUE_APP_BASE_API,
|
|
12
20
|
XLSX,
|
|
13
21
|
getToken,
|
|
14
22
|
};
|
|
15
|
-
|
|
16
|
-
|
|
23
|
+
// 计时器与遮罩句柄必须挂实例:模块级单例在 tagsView 同时开两个导入页时会被后开的
|
|
24
|
+
// 那个覆盖,先开的 100ms interval 永远停不掉,还会把整个组件钉住不被回收。
|
|
17
25
|
tmixins = {
|
|
18
26
|
name: "excelImport",
|
|
19
27
|
props: ["param"],
|
|
@@ -40,6 +48,8 @@ tmixins = {
|
|
|
40
48
|
option: {},
|
|
41
49
|
showContent: false,
|
|
42
50
|
resOption: {},
|
|
51
|
+
importTimer: null,
|
|
52
|
+
loadingObj: null,
|
|
43
53
|
};
|
|
44
54
|
},
|
|
45
55
|
created() {},
|
|
@@ -225,6 +235,24 @@ tmixins = {
|
|
|
225
235
|
let that = this;
|
|
226
236
|
let option = this.option;
|
|
227
237
|
|
|
238
|
+
// 整文件读进内存前先卡一刀:下面的 readAsBinaryString + XLSX.read 是同步的,
|
|
239
|
+
// 占用约为文件体积的 5~20 倍,超限时崩在库内部,连提示都给不出来。
|
|
240
|
+
if (isImportFileOversize(file, option)) {
|
|
241
|
+
this.showImportDialog2 = false;
|
|
242
|
+
this.showContent = false;
|
|
243
|
+
this.clearImportTimer();
|
|
244
|
+
this.$baseAlert(
|
|
245
|
+
this.$t1(
|
|
246
|
+
"导入文件过大({size}MB),当前最多支持 {limit}MB,请拆分后分批导入",
|
|
247
|
+
{
|
|
248
|
+
size: toFileSizeMb(file.size),
|
|
249
|
+
limit: toFileSizeMb(getImportFileSizeLimit(option)),
|
|
250
|
+
}
|
|
251
|
+
)
|
|
252
|
+
);
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
|
|
228
256
|
const fileReader = new FileReader();
|
|
229
257
|
fileReader.onload = async (ev) => {
|
|
230
258
|
var resultData;
|
|
@@ -250,6 +278,23 @@ tmixins = {
|
|
|
250
278
|
var range = configUtil.XLSX.utils.decode_range(sheet["!ref"]);
|
|
251
279
|
var dataSize = range.e.r + 1; //行数
|
|
252
280
|
that.dataSize = dataSize - 3 >= 0 ? dataSize - 3 : 0;
|
|
281
|
+
// 行数闸门:下面逐格取值、建行对象、再整批发包全是同步的,行数失控时
|
|
282
|
+
// 主线程会被占死甚至 OOM。文件本身不大但行极多(窄表)时只有这一道能拦。
|
|
283
|
+
if (isImportRowsOverLimit(that.dataSize, option)) {
|
|
284
|
+
that.showImportDialog2 = false;
|
|
285
|
+
that.showContent = false;
|
|
286
|
+
that.clearImportTimer();
|
|
287
|
+
that.$baseAlert(
|
|
288
|
+
that.$t1(
|
|
289
|
+
"导入数据 {size} 行,超过单次上限 {limit} 行,请拆分后分批导入",
|
|
290
|
+
{
|
|
291
|
+
size: that.dataSize,
|
|
292
|
+
limit: getImportRowLimit(option),
|
|
293
|
+
}
|
|
294
|
+
)
|
|
295
|
+
);
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
253
298
|
var titles = [];
|
|
254
299
|
var fields = [];
|
|
255
300
|
|
|
@@ -275,7 +320,7 @@ tmixins = {
|
|
|
275
320
|
if (typeof nextCell === "undefined") {
|
|
276
321
|
row.push(void 0);
|
|
277
322
|
} else {
|
|
278
|
-
row.push(nextCell
|
|
323
|
+
row.push(getImportCellText(nextCell));
|
|
279
324
|
}
|
|
280
325
|
}
|
|
281
326
|
if (rowNum === 1) {
|
|
@@ -794,15 +839,17 @@ tmixins = {
|
|
|
794
839
|
createImportTimer() {
|
|
795
840
|
this.excTime = 0;
|
|
796
841
|
let beginTime = new Date().getTime();
|
|
797
|
-
importTimer = setInterval(() => {
|
|
842
|
+
this.importTimer = setInterval(() => {
|
|
798
843
|
let nowTime = new Date().getTime();
|
|
799
844
|
let diffTime = (nowTime - beginTime) / 1000;
|
|
800
845
|
this.$set(this, "excTime", this.$baseLodash.round(diffTime, 2));
|
|
801
846
|
}, 100);
|
|
802
847
|
},
|
|
803
848
|
clearImportTimer() {
|
|
804
|
-
clearInterval(importTimer);
|
|
805
|
-
|
|
849
|
+
if (this.importTimer) clearInterval(this.importTimer);
|
|
850
|
+
this.importTimer = null;
|
|
851
|
+
this.loadingObj && this.loadingObj.close();
|
|
852
|
+
this.loadingObj = null;
|
|
806
853
|
},
|
|
807
854
|
async dialogPrimary3() {
|
|
808
855
|
if (!this.fileRaw) {
|
|
@@ -825,7 +872,7 @@ tmixins = {
|
|
|
825
872
|
}
|
|
826
873
|
return;
|
|
827
874
|
}
|
|
828
|
-
loadingObj = this.$baseLoading({
|
|
875
|
+
this.loadingObj = this.$baseLoading({
|
|
829
876
|
target: document.body,
|
|
830
877
|
background: "unset",
|
|
831
878
|
});
|
|
@@ -841,7 +888,8 @@ tmixins = {
|
|
|
841
888
|
console.error(et);
|
|
842
889
|
}
|
|
843
890
|
setTimeout(function () {
|
|
844
|
-
loadingObj && loadingObj.close();
|
|
891
|
+
that.loadingObj && that.loadingObj.close();
|
|
892
|
+
that.loadingObj = null;
|
|
845
893
|
}, 200);
|
|
846
894
|
});
|
|
847
895
|
},
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// initVxeTable 只把 getCellValue/createParams 挂在初始化时的那个 xTable 实例上,
|
|
3
3
|
// 页面若用 v-if 销毁重建 vxe-grid(如 product_problem_record/list.vue),
|
|
4
4
|
// 新实例上没有这两个方法,筛选面板会报 $table.getCellValue is not a function
|
|
5
|
-
|
|
5
|
+
// (createParams 已不再直接使用:候选值取值统一走下面 load() 里的 readCellValue 缓存)
|
|
6
|
+
import { getCellValue } from "../util/index";
|
|
6
7
|
|
|
7
8
|
let modules = {};
|
|
8
9
|
|
|
@@ -54,15 +55,34 @@ modules = {
|
|
|
54
55
|
let valMap =
|
|
55
56
|
option.data && option.data.valMap ? option.data.valMap : null;
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
// 取值可能落到 getCellValue 的兜底渲染:非 filterVal 白名单的组件列每个单元格
|
|
59
|
+
// 都要 new Vue + $mount + 爬 DOM,还会跑一遍用户的表单脚本。而本方法要把全表
|
|
60
|
+
// 跑三遍(候选值 → 按其他筛选列过滤 → 过滤后计数),行多列宽时打开一次筛选面板
|
|
61
|
+
// 就是几千次同步挂载,主线程直接定住。同一 (列, 行) 只算一次。
|
|
62
|
+
// 注:第三遍原本按过滤后子集的下标取值,改用缓存后统一为整表下标——只有依赖
|
|
63
|
+
// 行下标的自定义渲染列会有差异,而这类列本就不该出现在值列表筛选里。
|
|
64
|
+
let cellValueCache = new Map();
|
|
65
|
+
const readCellValue = (row, col, $rowIndex) => {
|
|
66
|
+
let colCache = cellValueCache.get(col);
|
|
67
|
+
if (!colCache) {
|
|
68
|
+
colCache = new Map();
|
|
69
|
+
cellValueCache.set(col, colCache);
|
|
70
|
+
}
|
|
71
|
+
if (colCache.has(row)) return colCache.get(row);
|
|
72
|
+
let value = getCellValue({
|
|
61
73
|
row,
|
|
62
74
|
$rowIndex,
|
|
63
75
|
$table,
|
|
64
|
-
column,
|
|
76
|
+
column: col,
|
|
65
77
|
});
|
|
78
|
+
colCache.set(row, value);
|
|
79
|
+
return value;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
let lsm = {};
|
|
83
|
+
let colValList = [];
|
|
84
|
+
fullData.map((row, $rowIndex) => {
|
|
85
|
+
let cellValue = readCellValue(row, column, $rowIndex);
|
|
66
86
|
let sortValue = cellValue
|
|
67
87
|
let sortField = column.params?.sortField;
|
|
68
88
|
if(sortField){
|
|
@@ -106,24 +126,14 @@ modules = {
|
|
|
106
126
|
return false;
|
|
107
127
|
}
|
|
108
128
|
const vals = option1.data.vals || [];
|
|
109
|
-
let
|
|
110
|
-
$table: $table,
|
|
111
|
-
row,
|
|
112
|
-
column: filterColumn,
|
|
113
|
-
});
|
|
114
|
-
let cellValue = getCellValue(param);
|
|
129
|
+
let cellValue = readCellValue(row, filterColumn);
|
|
115
130
|
return !vals.includes(cellValue);
|
|
116
131
|
});
|
|
117
132
|
return item == null;
|
|
118
133
|
});
|
|
119
134
|
let lsm2 = {};
|
|
120
135
|
datas.map((row, $rowIndex) => {
|
|
121
|
-
let cellValue =
|
|
122
|
-
row,
|
|
123
|
-
$rowIndex,
|
|
124
|
-
$table,
|
|
125
|
-
column,
|
|
126
|
-
});
|
|
136
|
+
let cellValue = readCellValue(row, column, $rowIndex);
|
|
127
137
|
if (lsm2[cellValue]) {
|
|
128
138
|
lsm2[cellValue].count = lsm2[cellValue].count + 1;
|
|
129
139
|
} else {
|
|
@@ -43,11 +43,18 @@
|
|
|
43
43
|
1. **字符串具名插槽列支持导出**(`table/util/index.js` getCellValue):xform 的 aLink 超链接按钮、widget、widgetList 等列的 `slots.default` 是字符串插槽名(由 data-table-item.vue 的 `#widget` 等模板提供),此前导出侧无法执行——修复前直接当函数调用会崩,快路径版本则退化为取原始字段,而设计器对按钮类列会清空 `prop`(table-column-dialog.vue changeFormatS:`row.prop = null`),导致**超链接按钮列导出全为空**。现在 getCellValue 遇到字符串插槽时从 `$table.$xegrid.$scopedSlots[插槽名]` 解析出插槽函数再走 vNode 渲染(所见即所得:isFormLabel 的按钮导出表单值,静态标签导出按钮文字);解析不到仍回退原始字段。`mixins.js` getExportCellValue 同步把字符串插槽列纳入渲染兜底。
|
|
44
44
|
2. **vNode 挂载补唯一 key**(`table/util/index.js` ensureVNodeKey):复用临时实例连续挂载**无 key** 的同类型组件 vNode 时,Vue patch 会复用旧组件实例、props 更新异步生效,同步爬 DOM 拿到的是**上一个单元格的内容**(jsdom 实测:`["AAA","AAA","AAA"]`;带 key 则正确)。该问题在旧代码中已存在于"同一单元格多个组件 vNode"场景(多个按钮只有第一个的值),批量复用后扩大到跨单元格。现挂载前为无 key 的 vNode 补全局唯一 key,强制全新挂载、同步渲染正确内容。
|
|
45
45
|
|
|
46
|
-
##
|
|
46
|
+
## 四、第三期备忘(**2026-09 已结项,勿照此重做**)
|
|
47
47
|
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
|
|
48
|
+
本节原为未实施清单,2026-09 那一轮已逐条处理完,明细见
|
|
49
|
+
`2026-09 条件导出大数据量崩溃排查.md` §八第三、四轮。此处只留结论:
|
|
50
|
+
|
|
51
|
+
| 原备忘项 | 结论 |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `startProcess2`(当前页/勾选导出)一次性单包上送,请求体超限风险 → 建议分片复用分页 append | **已做**。按 `getExportPageSize()` 串行切片 append + `mergeExcel(1,N)`;页数 ≤1 时请求序列与旧实现完全一致 |
|
|
54
|
+
| 导出完成无右下角通知 | **明确不做**(2026-09 需求方决定) |
|
|
55
|
+
| 「取消」只销毁前端组件,进行中的请求不中止 | **已做**。axios `CancelToken` 挂到全部导出请求;配套在 `request.js` 错误拦截器最前面加了 `axios.isCancel` 短路(**该文件的唯一一处改动,已单独获批**),否则取消 N 个在途请求会连弹 N 个 ErroreBox 弹窗 |
|
|
56
|
+
| 服务端临时文件不清理(后端如有 `/excel/clear` 类接口可接入 beforeDestroy) | **不做**。全仓检索确认后端至今无此类端点;需求方决定由后端定时清理兜底,前端不再预留 |
|
|
57
|
+
| 下载仍为表单 POST 携带 `access_token`,可改 axios blob + header 鉴权(需后端支持) | **已做**,且保留表单 POST 作为自动回退——后端是否放开该接口的头鉴权前端无法验证,回退保证下载不会因此失效 |
|
|
51
58
|
|
|
52
59
|
## 五、对下游的兼容性提示
|
|
53
60
|
|