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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cloud-web-corejs",
3
3
  "private": false,
4
- "version": "1.0.282",
4
+ "version": "1.0.283",
5
5
  "scripts": {
6
6
  "dev": "vue-cli-service serve",
7
7
  "lint": "eslint --ext .js,.vue src",
@@ -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
- let importTimer = null;
16
- let loadingObj = null;
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.w.trim());
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
- loadingObj && loadingObj.close();
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
- import { getCellValue, createParams } from "../util/index";
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
- let lsm = {};
58
- let colValList = [];
59
- fullData.map((row, $rowIndex) => {
60
- let cellValue = getCellValue({
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 param = createParams({
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 = getCellValue({
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
- - `startProcess2`(当前页/勾选导出)仍是一次性单包上送,大数据量有请求体超限风险 → 建议分片复用分页 append;
49
- - 导出完成无右下角通知;「取消」只销毁前端组件,进行中的请求与服务端临时文件不中止/清理(后端如有 `/excel/clear` 类接口可接入 beforeDestroy);
50
- - 下载仍为表单 POST 携带 `access_token`,可改 axios blob + header 鉴权(需后端支持)。
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