cloud-web-corejs 1.1.0-dev.11 → 1.1.0-dev.13
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/VabUpload/view.vue +25 -25
- package/src/components/excelExport/exportColumnMemory.js +118 -0
- package/src/components/excelExport/exportFieldDialog.vue +50 -38
- package/src/components/excelExport/index.js +30 -13
- package/src/components/excelExport/mixins.js +390 -64
- package/src/components/excelImport/mixins.js +898 -850
- package/src/components/mobile/wf/addTaskUserdialog.vue +100 -100
- package/src/components/mobile/wf/content.vue +4 -4
- package/src/components/mobile/wf/deleteTaskUserDialog.vue +73 -73
- package/src/components/mobile/wf/setCandidateDialog.vue +69 -69
- package/src/components/mobile/wf/urgingDialog.vue +75 -75
- package/src/components/table/index.js +14 -1
- 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" +64 -57
- 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
- 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
- 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
- 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
- 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/form-designer/form-widget/container-widget/detail-h5-widget.vue +243 -243
- package/src/components/xform/form-designer/form-widget/container-widget/h5-card-pane-widget.vue +228 -228
- package/src/components/xform/form-designer/form-widget/container-widget/h5-card-widget.vue +165 -165
- package/src/components/xform/form-designer/form-widget/dialog/importDialogMixin.js +61 -2
- package/src/components/xform/form-designer/form-widget/field-widget/select-export-item-button-widget.vue +3 -0
- package/src/components/xform/form-designer/form-widget/field-widget/singleUpload-widget.vue +145 -145
- package/src/components/xform/form-render/container-item/data-table-item.vue +350 -347
- package/src/components/xform/form-render/container-item/data-table-mixin.js +240 -37
- package/src/components/xform/form-render/container-item/list-h5-item.vue +1 -1
- package/src/components/xform/utils/dynamicSchemaUtil.js +385 -361
- package/src/layout/components/TagsView/index.vue +325 -323
- package/src/store/modules/tagsView.js +241 -217
- package/src/utils/importLimit.js +72 -0
- package/src/utils/pdfUtil.js +6 -1
- package/src/utils/request.js +17 -0
- package/src/views/demo/bizTab/bill/billConfig.js +41 -0
- package/src/views/demo/bizTab/bill/detail.vue +436 -0
- package/src/views/demo/bizTab/bill/list.vue +288 -0
- package/src/views/demo/bizTab/bill/productDialog.vue +176 -0
- package/src/views/demo/bizTab/simple/billApi.js +123 -0
- package/src/views/demo/bizTab/simple/detail.vue +407 -0
- package/src/views/demo/bizTab/simple/list.vue +217 -0
- package/src/views/demo/bizTab/standalone/index.vue +121 -0
- package/src/views/demo/bizTab/wf/billConfig.js +44 -0
- package/src/views/demo/bizTab/wf/detail.vue +661 -0
- package/src/views/demo/bizTab/wf/list.vue +242 -0
- package/src/views/user/home/wjl/content.vue +1569 -0
- package/src/views/user/outLink/cc_form_view.vue +188 -0
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<!-- 外壳是 el-dialog + append-to-body:节点挂到 document.body,不再依赖
|
|
3
|
-
`position:fixed` 在宿主子树里侥幸生效(祖先一旦有 transform/display:none 就废),
|
|
4
|
-
层级也交给 element 的 PopupManager,不用再手写 z-index。
|
|
5
|
-
el-dialog 自带的表头关掉——标题和关闭按钮都在下面的 .header-box 里。
|
|
6
|
-
骨架样式见 xform/styles/h5.scss 的 .h5-pop-dialog(必须是顶层选择器)。 -->
|
|
7
|
-
<el-dialog
|
|
8
|
-
:visible.sync="dialogVisible"
|
|
9
|
-
:append-to-body="true"
|
|
10
|
-
:modal="false"
|
|
11
|
-
:show-close="false"
|
|
12
|
-
:close-on-click-modal="false"
|
|
13
|
-
:destroy-on-close="true"
|
|
14
|
-
custom-class="h5-pop-dialog"
|
|
15
|
-
width="100%"
|
|
16
|
-
top="0px"
|
|
17
|
-
@close="dialogClose"
|
|
18
|
-
>
|
|
19
|
-
<div>
|
|
20
|
-
<div class="header-box">
|
|
21
|
-
<i class="back el-icon-close" @click="dialogClose"></i>
|
|
22
|
-
{{ $t2('流程催办', 'components.wf.wfUrging') }}<span>—</span><b
|
|
23
|
-
class="f-red">{{ $t2('节点', 'components.wf.node') }}:{{
|
|
24
|
-
wfInfo.taskName
|
|
25
|
-
}}</b>
|
|
26
|
-
</div>
|
|
27
|
-
<div class="flex">
|
|
28
|
-
<div class="checkbox-all_list" @scroll="handleScroll">
|
|
29
|
-
<div v-for="(row,rowIndex) in page.records" :key="rowIndex" class="order-item"
|
|
30
|
-
:class="{on:checkList.includes(row.id)}" @click="changeCheck(row,rowIndex)">
|
|
31
|
-
<div class="is-checked" v-if="checkList.includes(row.id)"><i class="el-icon-check"></i>已选择</div>
|
|
32
|
-
<div class="t1">
|
|
33
|
-
<i class="icon"></i>
|
|
34
|
-
<span>{{ row.name }}</span>
|
|
35
|
-
</div>
|
|
36
|
-
<div class="t2">
|
|
37
|
-
<p>催办人ID:{{ row.id }}</p>
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
<div class="checkbox-all">
|
|
42
|
-
<div class='ca-t'>已选择用户:</div>
|
|
43
|
-
<div class="ca-c">
|
|
44
|
-
<span v-for="(checkRow, rowIndex) in checkRows" :key="rowIndex">{{ checkRow.name }}<i
|
|
45
|
-
class="el-icon-delete" @click="changeCheck(checkRow)"></i></span>
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
<div class="fixed-bottom-btns">
|
|
50
|
-
<el-button type="primary" plain class="btn" @click="dialogClose" icon="el-icon-close">取 消
|
|
51
|
-
</el-button>
|
|
52
|
-
<el-button type="primary" class="btn" @click="subUrgingForm" icon="el-icon-check">
|
|
53
|
-
{{ $t2('确认催办', 'components.wf.confirmUrging') }}
|
|
54
|
-
</el-button>
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
</el-dialog>
|
|
58
|
-
</template>
|
|
59
|
-
|
|
60
|
-
<script>
|
|
61
|
-
import selectDialogMixins from '../../../mixins/mobile/selectDialog/index.js';
|
|
62
|
-
import mixin from './mixins/urgingDialogMixin';
|
|
63
|
-
|
|
64
|
-
export default {
|
|
65
|
-
name: 'urgingDialog',
|
|
66
|
-
mixins: [selectDialogMixins, mixin],
|
|
67
|
-
data() {
|
|
68
|
-
// 父级用 v-if 控制存活,组件一挂载就是「已打开」。关闭统一走 dialogClose()
|
|
69
|
-
// 向父级 $emit('update:visiable', false),由父级的 v-if 销毁本组件。
|
|
70
|
-
return {
|
|
71
|
-
dialogVisible: true
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<!-- 外壳是 el-dialog + append-to-body:节点挂到 document.body,不再依赖
|
|
3
|
+
`position:fixed` 在宿主子树里侥幸生效(祖先一旦有 transform/display:none 就废),
|
|
4
|
+
层级也交给 element 的 PopupManager,不用再手写 z-index。
|
|
5
|
+
el-dialog 自带的表头关掉——标题和关闭按钮都在下面的 .header-box 里。
|
|
6
|
+
骨架样式见 xform/styles/h5.scss 的 .h5-pop-dialog(必须是顶层选择器)。 -->
|
|
7
|
+
<el-dialog
|
|
8
|
+
:visible.sync="dialogVisible"
|
|
9
|
+
:append-to-body="true"
|
|
10
|
+
:modal="false"
|
|
11
|
+
:show-close="false"
|
|
12
|
+
:close-on-click-modal="false"
|
|
13
|
+
:destroy-on-close="true"
|
|
14
|
+
custom-class="h5-pop-dialog"
|
|
15
|
+
width="100%"
|
|
16
|
+
top="0px"
|
|
17
|
+
@close="dialogClose"
|
|
18
|
+
>
|
|
19
|
+
<div>
|
|
20
|
+
<div class="header-box">
|
|
21
|
+
<i class="back el-icon-close" @click="dialogClose"></i>
|
|
22
|
+
{{ $t2('流程催办', 'components.wf.wfUrging') }}<span>—</span><b
|
|
23
|
+
class="f-red">{{ $t2('节点', 'components.wf.node') }}:{{
|
|
24
|
+
wfInfo.taskName
|
|
25
|
+
}}</b>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="flex">
|
|
28
|
+
<div class="checkbox-all_list" @scroll="handleScroll">
|
|
29
|
+
<div v-for="(row,rowIndex) in page.records" :key="rowIndex" class="order-item"
|
|
30
|
+
:class="{on:checkList.includes(row.id)}" @click="changeCheck(row,rowIndex)">
|
|
31
|
+
<div class="is-checked" v-if="checkList.includes(row.id)"><i class="el-icon-check"></i>已选择</div>
|
|
32
|
+
<div class="t1">
|
|
33
|
+
<i class="icon"></i>
|
|
34
|
+
<span>{{ row.name }}</span>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="t2">
|
|
37
|
+
<p>催办人ID:{{ row.id }}</p>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="checkbox-all">
|
|
42
|
+
<div class='ca-t'>已选择用户:</div>
|
|
43
|
+
<div class="ca-c">
|
|
44
|
+
<span v-for="(checkRow, rowIndex) in checkRows" :key="rowIndex">{{ checkRow.name }}<i
|
|
45
|
+
class="el-icon-delete" @click="changeCheck(checkRow)"></i></span>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="fixed-bottom-btns">
|
|
50
|
+
<el-button type="primary" plain class="btn" @click="dialogClose" icon="el-icon-close">取 消
|
|
51
|
+
</el-button>
|
|
52
|
+
<el-button type="primary" class="btn" @click="subUrgingForm" icon="el-icon-check">
|
|
53
|
+
{{ $t2('确认催办', 'components.wf.confirmUrging') }}
|
|
54
|
+
</el-button>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</el-dialog>
|
|
58
|
+
</template>
|
|
59
|
+
|
|
60
|
+
<script>
|
|
61
|
+
import selectDialogMixins from '../../../mixins/mobile/selectDialog/index.js';
|
|
62
|
+
import mixin from './mixins/urgingDialogMixin';
|
|
63
|
+
|
|
64
|
+
export default {
|
|
65
|
+
name: 'urgingDialog',
|
|
66
|
+
mixins: [selectDialogMixins, mixin],
|
|
67
|
+
data() {
|
|
68
|
+
// 父级用 v-if 控制存活,组件一挂载就是「已打开」。关闭统一走 dialogClose()
|
|
69
|
+
// 向父级 $emit('update:visiable', false),由父级的 v-if 销毁本组件。
|
|
70
|
+
return {
|
|
71
|
+
dialogVisible: true
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
</script>
|
|
@@ -86,6 +86,7 @@ async function initVxeTable(option) {
|
|
|
86
86
|
isQueryAllPage: true,
|
|
87
87
|
exportColumns: null,
|
|
88
88
|
exportItemColumns: null,
|
|
89
|
+
exportItemColumnsMap: null,
|
|
89
90
|
};
|
|
90
91
|
|
|
91
92
|
let isQueryAllPage = true;
|
|
@@ -168,7 +169,8 @@ async function initVxeTable(option) {
|
|
|
168
169
|
// nOption.getCellValue = getCellValue;
|
|
169
170
|
// nOption.createParams = createParams;
|
|
170
171
|
// nOption.searchColumns = searchColumns;
|
|
171
|
-
// 运行时可变状态(tableName / isQueryAllPage / exportColumns / exportItemColumns
|
|
172
|
+
// 运行时可变状态(tableName / isQueryAllPage / exportColumns / exportItemColumns /
|
|
173
|
+
// exportItemColumnsMap)
|
|
172
174
|
// 一并放进 params:params 随 vxeOption 由页面持有,grid 被 v-if 销毁重建后依然带着这些值
|
|
173
175
|
let params = {
|
|
174
176
|
originOption,
|
|
@@ -978,12 +980,18 @@ function addTableJson(that, $grid, tableName, columns, success) {
|
|
|
978
980
|
};
|
|
979
981
|
let exportColumns = getTableStateValue($grid, "exportColumns");
|
|
980
982
|
let exportItemColumns = getTableStateValue($grid, "exportItemColumns");
|
|
983
|
+
// 明细导出的勾选记忆按导出按钮分组(见 excelExport/exportColumnMemory.js);
|
|
984
|
+
// exportItemColumns 是分组之前的扁平表,旧记录里还有,只读不再写,故一并原样存回。
|
|
985
|
+
let exportItemColumnsMap = getTableStateValue($grid, "exportItemColumnsMap");
|
|
981
986
|
if (exportColumns && exportColumns.length) {
|
|
982
987
|
jsonData.exportColumns = exportColumns;
|
|
983
988
|
}
|
|
984
989
|
if (exportItemColumns && exportItemColumns.length) {
|
|
985
990
|
jsonData.exportItemColumns = exportItemColumns;
|
|
986
991
|
}
|
|
992
|
+
if (exportItemColumnsMap && Object.keys(exportItemColumnsMap).length) {
|
|
993
|
+
jsonData.exportItemColumnsMap = exportItemColumnsMap;
|
|
994
|
+
}
|
|
987
995
|
let json = encodeURIComponent(JSON.stringify(jsonData));
|
|
988
996
|
|
|
989
997
|
let url = USER_PREFIX + "/table_column/addTableJson";
|
|
@@ -1081,6 +1089,11 @@ function initExportColumns($grid, tableState, json) {
|
|
|
1081
1089
|
tableState.exportItemColumns = json.exportItemColumns;
|
|
1082
1090
|
setTableState($grid, "exportItemColumns", json.exportItemColumns);
|
|
1083
1091
|
}
|
|
1092
|
+
let itemMap = json?.exportItemColumnsMap;
|
|
1093
|
+
if (itemMap && Object.keys(itemMap).length) {
|
|
1094
|
+
tableState.exportItemColumnsMap = itemMap;
|
|
1095
|
+
setTableState($grid, "exportItemColumnsMap", itemMap);
|
|
1096
|
+
}
|
|
1084
1097
|
}
|
|
1085
1098
|
|
|
1086
1099
|
function initSearchColumns(oriColumns, syncColumns) {
|
|
@@ -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 {
|
|
@@ -1,57 +1,64 @@
|
|
|
1
|
-
# table / excelExport 排查修复与导出优化记录(2026-07)
|
|
2
|
-
|
|
3
|
-
> 排查范围:`src/components/table/**`、`src/components/excelExport/**`(xform 数据表格与列表导出的底层依赖)。
|
|
4
|
-
> 本轮实施了方案的第一期(稳定性)与第二期(性能),第三期(体验:分片上送、完成通知、可中断取消、blob 下载)未实施。
|
|
5
|
-
> 验证:`npm run build:prod` 编译通过;ESLint 改动文件除存量 `operator-linebreak` 外零错误。
|
|
6
|
-
|
|
7
|
-
## 一、table/ 修复清单
|
|
8
|
-
|
|
9
|
-
| # | 位置 | 问题 | 修复 |
|
|
10
|
-
|---|------|------|------|
|
|
11
|
-
| T1 | `index.js` handleToolbar | 引用未定义的 `that`,配置 `bindToolbar: true` 即 ReferenceError | 改用 `$grid.$nextTick` |
|
|
12
|
-
| T2 | `index.js` addTableData | `success` 回调被塞进请求体 data,永不执行 | 移到 `$http` 顶层参数 |
|
|
13
|
-
| T3 | `index.js` exportConfig | vxe 原生导出 mode=all 的 `queryAll` 用无鉴权 `fetch`,从未可用 | 删除 queryAll,`modes` 收敛为 `["current"]`(全量导出走 `$excelExport`) |
|
|
14
|
-
| T4 | `index.js` columnDrop.onEnd | 原地放下不触发 onMove 时 `tableColumn[-1]` 崩 | oldIndex 从 `dropParam.oldIndex` 兜底 + `!oldColumn \|\| !newColumn` 防护;顺带清理 6 个调试残留变量 |
|
|
15
|
-
| T5 | `vxeFilter/mixin.js` searchEvent | 列含空值/数值时 `value.indexOf` 崩(筛选面板输入关键字必现) | `value != null && String(value).indexOf(...)` |
|
|
16
|
-
| T6 | `index.js` ×3 | `console.log` 残留(列拖拽/自定义列保存高频打印) | 删除 |
|
|
17
|
-
| T7 | `index.js` | `$http({async:false})` 无效参数;`addTableExportJson(){}` 空函数 | 删除 |
|
|
18
|
-
| — | `util/index.js` groupDomRender | 存量空代码块(no-empty) | 合并条件 |
|
|
19
|
-
|
|
20
|
-
## 二、excelExport/ 修复清单
|
|
21
|
-
|
|
22
|
-
| # | 位置 | 问题 | 修复 |
|
|
23
|
-
|---|------|------|------|
|
|
24
|
-
| E1 | `mixins.js` | `exportTimer/countTimer` 是 IIFE 模块级共享变量,**并发导出互相清除对方定时器/泄漏** | 移入组件 `data()`,全部引用改 `this.*`;无用的 `exportMtTimer` 删除 |
|
|
25
|
-
| E2 | `index.js` | 单例 `instance` 被二次调用直接覆盖(旧实例与 DOM 泄漏);`destroyComponent(undefined)` 时 `loadingObj.close()` 崩 | initInstance 先销毁旧实例;loadingObj 判空(对齐 button.vue 版本) |
|
|
26
|
-
| E3 | `mixins.js` | `clearInterval(loopToDo)` 传的是函数(no-op),分页调度 interval 空转 | 改 `clearInterval(that.exportTimer)` 并置空 |
|
|
27
|
-
| E4 | `mixins.js` hadleMergeExcel | 合并阶段同步 `clearExportTimer` 把用时统计一起停掉;`startPage > endPage` 不 return 仍发非法请求 | 删同步清除;越界改 `abortExport() + return` |
|
|
28
|
-
| E5 | `mixins.js` startProcess/2 | `path.split`:path 为函数时崩、`'#'` 时得到 `/undefined` | 函数先求值 + `'#'` 排除;推不出 prefix 时明确提示并中止 |
|
|
29
|
-
| E6 | `mixins.js` exportAll | `input.tpye` 拼写错误 | `input.type` |
|
|
30
|
-
| E7 | `exportFieldDialog.vue` dialogConfirm + `mixins.js` getHeadTitleJson | rowSpan 只对顶层无 children 列设置,≥3 层分组表头导出错位;且依赖导出明细列上不存在的 `item.level` | 按勾选后的实际树深度计算:叶子 `rowSpan = maxRowspan - depth + 1`,分组行 `rowSpan = 1` |
|
|
31
|
-
| E8 | `exportFieldDialog.vue` generateId | 随机数范围 0~12.5 万,列多时 node-key 碰撞导致勾选错乱 | `"expcol_" + Date.now() + "_" + 自增序号` |
|
|
32
|
-
| E9 | `mixins.js` 全链路 | 任何一页失败只静默 clearTimer:无提示、进度条永卡 | 新增 `abortExport(msg?)`:停定时器 + 进度条置 exception(index.vue 两个进度条挂 `:status`)+ 按需弹错(服务端已 toast 的不重复弹);createExcelFile/addExcelData/mergeExcel 失败路径全部接入 |
|
|
33
|
-
| 重试 | `mixins.js` loopToDo | 网络抖动一页失败整个导出报废 | 单页拉取失败自动重试 1 次(500ms 后),仍失败中止并提示失败页码(**缺页合并会静默丢行,必须中止而非跳过**) |
|
|
34
|
-
|
|
35
|
-
## 三、导出性能优化(第二期)
|
|
36
|
-
|
|
37
|
-
1. **取值快路径**(`mixins.js` 新增 `getExportCellValue`):导出取值按 `column.params.exportVal` → `slots.filterVal` → `formatter`(纯函数)→ 原始字段 的优先级;配置了 `renderCell` / 函数型或字符串型 `slots.default` 的列才走 `getCellValue` 的 vNode 挂载渲染兜底。此前每个渲染型单元格都要挂载一次 Vue 组件再爬 DOM(千行×20列 = 2 万次挂载),是导出耗时大头。
|
|
38
|
-
- **行为变化注意**:formatter 列(如 d1/n1 日期数字格式化、UTC 转换列)此前导出的是**原始值**,现在导出**格式化后的值**(与界面显示一致)。属修正,但导出文件内容会与旧版不同。
|
|
39
|
-
2. **vNode 渲染实例批量复用**(`table/util/index.js` 新增 `beginCellValueBatch/endCellValueBatch`):导出期间兜底路径复用同一个临时实例,`getExcelData` 以 try/finally 包裹,结束统一销毁。筛选面板等其它 `getCellValue` 调用方行为不变。
|
|
40
|
-
|
|
41
|
-
## 三补、导出取值补充修复(超链接按钮列导出为空 / 值错位)
|
|
42
|
-
|
|
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
|
-
2. **vNode 挂载补唯一 key**(`table/util/index.js` ensureVNodeKey):复用临时实例连续挂载**无 key** 的同类型组件 vNode 时,Vue patch 会复用旧组件实例、props 更新异步生效,同步爬 DOM 拿到的是**上一个单元格的内容**(jsdom 实测:`["AAA","AAA","AAA"]`;带 key 则正确)。该问题在旧代码中已存在于"同一单元格多个组件 vNode"场景(多个按钮只有第一个的值),批量复用后扩大到跨单元格。现挂载前为无 key 的 vNode 补全局唯一 key,强制全新挂载、同步渲染正确内容。
|
|
45
|
-
|
|
46
|
-
##
|
|
47
|
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
1
|
+
# table / excelExport 排查修复与导出优化记录(2026-07)
|
|
2
|
+
|
|
3
|
+
> 排查范围:`src/components/table/**`、`src/components/excelExport/**`(xform 数据表格与列表导出的底层依赖)。
|
|
4
|
+
> 本轮实施了方案的第一期(稳定性)与第二期(性能),第三期(体验:分片上送、完成通知、可中断取消、blob 下载)未实施。
|
|
5
|
+
> 验证:`npm run build:prod` 编译通过;ESLint 改动文件除存量 `operator-linebreak` 外零错误。
|
|
6
|
+
|
|
7
|
+
## 一、table/ 修复清单
|
|
8
|
+
|
|
9
|
+
| # | 位置 | 问题 | 修复 |
|
|
10
|
+
|---|------|------|------|
|
|
11
|
+
| T1 | `index.js` handleToolbar | 引用未定义的 `that`,配置 `bindToolbar: true` 即 ReferenceError | 改用 `$grid.$nextTick` |
|
|
12
|
+
| T2 | `index.js` addTableData | `success` 回调被塞进请求体 data,永不执行 | 移到 `$http` 顶层参数 |
|
|
13
|
+
| T3 | `index.js` exportConfig | vxe 原生导出 mode=all 的 `queryAll` 用无鉴权 `fetch`,从未可用 | 删除 queryAll,`modes` 收敛为 `["current"]`(全量导出走 `$excelExport`) |
|
|
14
|
+
| T4 | `index.js` columnDrop.onEnd | 原地放下不触发 onMove 时 `tableColumn[-1]` 崩 | oldIndex 从 `dropParam.oldIndex` 兜底 + `!oldColumn \|\| !newColumn` 防护;顺带清理 6 个调试残留变量 |
|
|
15
|
+
| T5 | `vxeFilter/mixin.js` searchEvent | 列含空值/数值时 `value.indexOf` 崩(筛选面板输入关键字必现) | `value != null && String(value).indexOf(...)` |
|
|
16
|
+
| T6 | `index.js` ×3 | `console.log` 残留(列拖拽/自定义列保存高频打印) | 删除 |
|
|
17
|
+
| T7 | `index.js` | `$http({async:false})` 无效参数;`addTableExportJson(){}` 空函数 | 删除 |
|
|
18
|
+
| — | `util/index.js` groupDomRender | 存量空代码块(no-empty) | 合并条件 |
|
|
19
|
+
|
|
20
|
+
## 二、excelExport/ 修复清单
|
|
21
|
+
|
|
22
|
+
| # | 位置 | 问题 | 修复 |
|
|
23
|
+
|---|------|------|------|
|
|
24
|
+
| E1 | `mixins.js` | `exportTimer/countTimer` 是 IIFE 模块级共享变量,**并发导出互相清除对方定时器/泄漏** | 移入组件 `data()`,全部引用改 `this.*`;无用的 `exportMtTimer` 删除 |
|
|
25
|
+
| E2 | `index.js` | 单例 `instance` 被二次调用直接覆盖(旧实例与 DOM 泄漏);`destroyComponent(undefined)` 时 `loadingObj.close()` 崩 | initInstance 先销毁旧实例;loadingObj 判空(对齐 button.vue 版本) |
|
|
26
|
+
| E3 | `mixins.js` | `clearInterval(loopToDo)` 传的是函数(no-op),分页调度 interval 空转 | 改 `clearInterval(that.exportTimer)` 并置空 |
|
|
27
|
+
| E4 | `mixins.js` hadleMergeExcel | 合并阶段同步 `clearExportTimer` 把用时统计一起停掉;`startPage > endPage` 不 return 仍发非法请求 | 删同步清除;越界改 `abortExport() + return` |
|
|
28
|
+
| E5 | `mixins.js` startProcess/2 | `path.split`:path 为函数时崩、`'#'` 时得到 `/undefined` | 函数先求值 + `'#'` 排除;推不出 prefix 时明确提示并中止 |
|
|
29
|
+
| E6 | `mixins.js` exportAll | `input.tpye` 拼写错误 | `input.type` |
|
|
30
|
+
| E7 | `exportFieldDialog.vue` dialogConfirm + `mixins.js` getHeadTitleJson | rowSpan 只对顶层无 children 列设置,≥3 层分组表头导出错位;且依赖导出明细列上不存在的 `item.level` | 按勾选后的实际树深度计算:叶子 `rowSpan = maxRowspan - depth + 1`,分组行 `rowSpan = 1` |
|
|
31
|
+
| E8 | `exportFieldDialog.vue` generateId | 随机数范围 0~12.5 万,列多时 node-key 碰撞导致勾选错乱 | `"expcol_" + Date.now() + "_" + 自增序号` |
|
|
32
|
+
| E9 | `mixins.js` 全链路 | 任何一页失败只静默 clearTimer:无提示、进度条永卡 | 新增 `abortExport(msg?)`:停定时器 + 进度条置 exception(index.vue 两个进度条挂 `:status`)+ 按需弹错(服务端已 toast 的不重复弹);createExcelFile/addExcelData/mergeExcel 失败路径全部接入 |
|
|
33
|
+
| 重试 | `mixins.js` loopToDo | 网络抖动一页失败整个导出报废 | 单页拉取失败自动重试 1 次(500ms 后),仍失败中止并提示失败页码(**缺页合并会静默丢行,必须中止而非跳过**) |
|
|
34
|
+
|
|
35
|
+
## 三、导出性能优化(第二期)
|
|
36
|
+
|
|
37
|
+
1. **取值快路径**(`mixins.js` 新增 `getExportCellValue`):导出取值按 `column.params.exportVal` → `slots.filterVal` → `formatter`(纯函数)→ 原始字段 的优先级;配置了 `renderCell` / 函数型或字符串型 `slots.default` 的列才走 `getCellValue` 的 vNode 挂载渲染兜底。此前每个渲染型单元格都要挂载一次 Vue 组件再爬 DOM(千行×20列 = 2 万次挂载),是导出耗时大头。
|
|
38
|
+
- **行为变化注意**:formatter 列(如 d1/n1 日期数字格式化、UTC 转换列)此前导出的是**原始值**,现在导出**格式化后的值**(与界面显示一致)。属修正,但导出文件内容会与旧版不同。
|
|
39
|
+
2. **vNode 渲染实例批量复用**(`table/util/index.js` 新增 `beginCellValueBatch/endCellValueBatch`):导出期间兜底路径复用同一个临时实例,`getExcelData` 以 try/finally 包裹,结束统一销毁。筛选面板等其它 `getCellValue` 调用方行为不变。
|
|
40
|
+
|
|
41
|
+
## 三补、导出取值补充修复(超链接按钮列导出为空 / 值错位)
|
|
42
|
+
|
|
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
|
+
2. **vNode 挂载补唯一 key**(`table/util/index.js` ensureVNodeKey):复用临时实例连续挂载**无 key** 的同类型组件 vNode 时,Vue patch 会复用旧组件实例、props 更新异步生效,同步爬 DOM 拿到的是**上一个单元格的内容**(jsdom 实测:`["AAA","AAA","AAA"]`;带 key 则正确)。该问题在旧代码中已存在于"同一单元格多个组件 vNode"场景(多个按钮只有第一个的值),批量复用后扩大到跨单元格。现挂载前为无 key 的 vNode 补全局唯一 key,强制全新挂载、同步渲染正确内容。
|
|
45
|
+
|
|
46
|
+
## 四、第三期备忘(**2026-09 已结项,勿照此重做**)
|
|
47
|
+
|
|
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 作为自动回退——后端是否放开该接口的头鉴权前端无法验证,回退保证下载不会因此失效 |
|
|
58
|
+
|
|
59
|
+
## 五、对下游的兼容性提示
|
|
60
|
+
|
|
61
|
+
- vxe 原生导出(toolbar 自带导出按钮)不再提供 mode=all 选项(该选项从未真正可用);
|
|
62
|
+
- formatter 列导出值从原始值变为格式化值(见三.1);
|
|
63
|
+
- 超链接按钮/按钮类列现在会导出渲染文字(isFormLabel 按钮导出表单值,静态标签按钮导出按钮文字);此前这类列导出为空或使导出崩溃;
|
|
64
|
+
- 建议回归:条件导出(含多文件分卷 >10 万行场景)、勾选/当前页导出、分组表头(2 层与 3 层)导出、带图片列导出、超链接按钮列导出、同一单元格多组件列导出(值不应重复第一个)、导出中途断网(应弹错并置红进度条)、两个页签同时导出。
|