aegon-gen 1.0.0
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 +12 -0
- package/src/App.vue +3 -0
- package/src/api/index.ts +19 -0
- package/src/api/modules/gen-ai/gen-entry/index.ts +30 -0
- package/src/api/modules/gen-ai/model-manager/index.ts +42 -0
- package/src/api/modules/gen-ai/model-manager/mockApi.ts +33 -0
- package/src/api/modules/index.ts +98 -0
- package/src/api/modules/user/index.ts +4 -0
- package/src/api/request.ts +102 -0
- package/src/assets/sample-access-icon.png +0 -0
- package/src/assets/sample-pie-chart.png +0 -0
- package/src/assets/vue.svg +1 -0
- package/src/components/CapsuleScrollbar.vue +93 -0
- package/src/components/Export/ExcelExport.vue +592 -0
- package/src/components/Export/ExcelExport2.vue +494 -0
- package/src/components/Export/ExcelExport3.vue +342 -0
- package/src/components/Export/ExcelExport4.vue +665 -0
- package/src/components/Export/excelExport.js +547 -0
- package/src/components/Export/excelExport.ts +551 -0
- package/src/components/GEN-AI/index.vue +142 -0
- package/src/components/GEN-AI/index1.vue +456 -0
- package/src/components/GEN-AI/index10.vue +5 -0
- package/src/components/GEN-AI/index2.vue +568 -0
- package/src/components/GEN-AI/index3.vue +623 -0
- package/src/components/GEN-AI/index4.vue +629 -0
- package/src/components/GEN-AI/index5.vue +578 -0
- package/src/components/GEN-AI/index6.vue +656 -0
- package/src/components/GEN-AI/index7.vue +717 -0
- package/src/components/GEN-AI/index8.vue +405 -0
- package/src/components/GEN-AI/index9.vue +1065 -0
- package/src/components/GEN-AI/types.ts +12 -0
- package/src/components/GEN-AI/utils.ts +42 -0
- package/src/components/HelloWorld.vue +41 -0
- package/src/components/PageCard.vue +7 -0
- package/src/components/PageHeader.vue +32 -0
- package/src/components/backup/index5 copy.vue +556 -0
- package/src/components/backup/index5.vue +620 -0
- package/src/components/backup/index9 copy.vue +1029 -0
- package/src/components/backup/index9-pro.vue +1065 -0
- package/src/components/backup/index9.vue +1057 -0
- package/src/components/el-date-picker.vue +64 -0
- package/src/directives/btnLoading.ts +427 -0
- package/src/directives/debounce copy.ts +670 -0
- package/src/directives/debounce.ts +98 -0
- package/src/directives/index.ts +25 -0
- package/src/layouts/MainLayout.vue +101 -0
- package/src/main.ts +85 -0
- package/src/router/index.ts +76 -0
- package/src/router/menus.ts +28 -0
- package/src/style.css +79 -0
- package/src/styles/_variables.scss +24 -0
- package/src/styles/app-button.css +26 -0
- package/src/styles/element-overrides.css +23 -0
- package/src/styles/global.css +44 -0
- package/src/styles/index.scss +1 -0
- package/src/styles/page-card.css +21 -0
- package/src/styles/variables.css +26 -0
- package/src/test/mock.ts +101 -0
- package/src/test/test1.vue +402 -0
- package/src/test/test2.vue +1689 -0
- package/src/types/gen-ai/gen-entry/index.ts +17 -0
- package/src/types/gen-ai/model-manager/index.ts +19 -0
- package/src/utils/docxExport.ts +1610 -0
- package/src/utils/gen-ai-navigation.ts +37 -0
- package/src/utils/gen-ai-scroll.ts +455 -0
- package/src/utils/openDataLoaderWordExport.ts +33 -0
- package/src/utils/pageScrollbar.ts +115 -0
- package/src/utils/randomTranscode.ts +87 -0
- package/src/utils/reportPdfExport.ts +44 -0
- package/src/views/AdminCenter/index.vue +817 -0
- package/src/views/Blank.vue +68 -0
- package/src/views/Home.vue +29 -0
- package/src/views/ReportCenter/index.vue +1380 -0
- package/src/views/TemplateCenter/Knowledge.ts +83 -0
- package/src/views/TemplateCenter/data.d.ts +10 -0
- package/src/views/TemplateCenter/index.vue +1205 -0
- package/src/views/TemplateCenter/service.ts +69 -0
- package/src/views/gen-ai/components/RecentReportsTable.vue +193 -0
- package/src/views/gen-ai/gen-entry/index.vue +309 -0
- package/src/views/gen-ai/gen-entry/mockData.ts +160 -0
- package/src/views/gen-ai/management-center/index.vue +53 -0
- package/src/views/gen-ai/model-manager/ChapterTitleScroll.vue +275 -0
- package/src/views/gen-ai/model-manager/index.vue +1205 -0
- package/src/views/gen-ai/model-manager/mockData.ts +122 -0
- package/src/views/gen-ai/report-center/index.vue +158 -0
- package/src/vite-env.d.ts +38 -0
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
// excelExport.js
|
|
2
|
+
import ExcelJS from "exceljs";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 工具函数:安全获取单元格值的长度
|
|
6
|
+
* @param {any} cellValue Excel单元格值
|
|
7
|
+
* @returns {number} 单元格值的字符长度(数字转字符串,空值返回0)
|
|
8
|
+
*/
|
|
9
|
+
const getCellValueLength = (cellValue) => {
|
|
10
|
+
if (cellValue === null || cellValue === undefined) return 0;
|
|
11
|
+
// 处理不同类型的CellValue,统一转为字符串后计算长度
|
|
12
|
+
let strValue = "";
|
|
13
|
+
if (typeof cellValue === "string") {
|
|
14
|
+
strValue = cellValue;
|
|
15
|
+
} else if (typeof cellValue === "number" || typeof cellValue === "boolean") {
|
|
16
|
+
strValue = cellValue.toString();
|
|
17
|
+
} else if (cellValue instanceof Date) {
|
|
18
|
+
strValue = cellValue.toISOString();
|
|
19
|
+
} else {
|
|
20
|
+
// 处理富文本、公式等特殊类型,转为字符串
|
|
21
|
+
strValue = String(cellValue);
|
|
22
|
+
}
|
|
23
|
+
return strValue.length;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 判断是否为数字的辅助函数
|
|
28
|
+
* @param {any} value 单元格值
|
|
29
|
+
* @returns {boolean} 是否为数字(包括带小数点的数字字符串)
|
|
30
|
+
*/
|
|
31
|
+
const isNumberValue = (value) => {
|
|
32
|
+
if (value === null || value === undefined) return false;
|
|
33
|
+
if (typeof value === "number") return true;
|
|
34
|
+
if (typeof value !== "string") return false;
|
|
35
|
+
|
|
36
|
+
// 移除千分位逗号和货币符号
|
|
37
|
+
const cleanedValue = value.replace(/[,$]/g, "").replace(/\s/g, "");
|
|
38
|
+
|
|
39
|
+
// 检查是否为数字(包括负数、小数、带单位的数字)
|
|
40
|
+
return /^-?\d+(\.\d+)?/.test(cleanedValue);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 按日期分组数据
|
|
45
|
+
* @param {Array<Array<string>>} data 原始数据(包含表头)
|
|
46
|
+
* @param {string} dateColumnName 日期列名,默认为"會計日"
|
|
47
|
+
* @returns {Object<string, Array<Array<string>>>} 按日期分组的对象,键为日期,值为该日期的数据数组
|
|
48
|
+
*/
|
|
49
|
+
const groupDataByDate = (data, dateColumnName = "會計日") => {
|
|
50
|
+
if (data.length <= 1) return {}; // 只有表头或空数据
|
|
51
|
+
|
|
52
|
+
const header = data[0] || [];
|
|
53
|
+
const dateGroups = {};
|
|
54
|
+
|
|
55
|
+
// 安全检查:确保header存在
|
|
56
|
+
if (!header || !Array.isArray(header)) {
|
|
57
|
+
dateGroups["全部数据"] = data;
|
|
58
|
+
return dateGroups;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const dateColumnIndex = header.indexOf(dateColumnName);
|
|
62
|
+
|
|
63
|
+
if (dateColumnIndex === -1) {
|
|
64
|
+
// 如果没有找到日期列,将所有数据放在一个分组中
|
|
65
|
+
dateGroups["全部数据"] = data;
|
|
66
|
+
return dateGroups;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 遍历数据行(跳过表头)
|
|
70
|
+
for (let i = 1; i < data.length; i++) {
|
|
71
|
+
const row = data[i] || [];
|
|
72
|
+
const date = row[dateColumnIndex];
|
|
73
|
+
|
|
74
|
+
if (!date) continue;
|
|
75
|
+
|
|
76
|
+
if (!dateGroups[date]) {
|
|
77
|
+
// 为新日期创建分组,包含表头
|
|
78
|
+
dateGroups[date] = [header];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 添加数据行到对应日期的分组
|
|
82
|
+
dateGroups[date].push(row);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return dateGroups;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 配置工作表的样式
|
|
90
|
+
* @param {Worksheet} worksheet Excel工作表对象
|
|
91
|
+
* @param {Array<string>} header 表头数组
|
|
92
|
+
* @param {Object} options 配置选项
|
|
93
|
+
*/
|
|
94
|
+
const configureWorksheetStyle = (worksheet, header, options = {}) => {
|
|
95
|
+
const {
|
|
96
|
+
rightAlignedColumns = [],
|
|
97
|
+
centerAlignedColumns = [],
|
|
98
|
+
headerStyle = {},
|
|
99
|
+
dataRowStyle = {},
|
|
100
|
+
columnWidthLimit = 50,
|
|
101
|
+
} = options;
|
|
102
|
+
|
|
103
|
+
if (!header || !Array.isArray(header)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 1. 配置表头样式
|
|
108
|
+
const headerRow = worksheet.getRow(1);
|
|
109
|
+
if (headerRow) {
|
|
110
|
+
headerRow.eachCell((cell) => {
|
|
111
|
+
// 默认表头样式
|
|
112
|
+
const defaultHeaderStyle = {
|
|
113
|
+
name: "Calibri",
|
|
114
|
+
size: 11,
|
|
115
|
+
bold: true,
|
|
116
|
+
color: { argb: "FF000000" }, // 黑色文本
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
cell.font = { ...defaultHeaderStyle, ...headerStyle.font };
|
|
120
|
+
cell.fill = {
|
|
121
|
+
type: "pattern",
|
|
122
|
+
pattern: "solid",
|
|
123
|
+
fgColor: { argb: "FFFFFFFF" }, // 白色背景
|
|
124
|
+
...headerStyle.fill,
|
|
125
|
+
};
|
|
126
|
+
cell.alignment = {
|
|
127
|
+
vertical: "middle",
|
|
128
|
+
horizontal: "left", // 左对齐
|
|
129
|
+
wrapText: true,
|
|
130
|
+
...headerStyle.alignment,
|
|
131
|
+
};
|
|
132
|
+
cell.border = {
|
|
133
|
+
top: { style: "thin", color: { argb: "FF000000" } },
|
|
134
|
+
left: { style: "thin", color: { argb: "FF000000" } },
|
|
135
|
+
bottom: { style: "thin", color: { argb: "FF000000" } },
|
|
136
|
+
right: { style: "thin", color: { argb: "FF000000" } },
|
|
137
|
+
...headerStyle.border,
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 2. 配置数据行样式
|
|
143
|
+
worksheet.eachRow((row, rowIndex) => {
|
|
144
|
+
// 跳过表头行
|
|
145
|
+
if (rowIndex === 1) return;
|
|
146
|
+
|
|
147
|
+
row.eachCell((cell, cellNumber) => {
|
|
148
|
+
// 基础字体设置
|
|
149
|
+
const defaultFontStyle = {
|
|
150
|
+
name: "Calibri",
|
|
151
|
+
size: 11,
|
|
152
|
+
color: { argb: "FF000000" }, // 黑色文本
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
cell.font = { ...defaultFontStyle, ...dataRowStyle.font };
|
|
156
|
+
|
|
157
|
+
// 获取列名
|
|
158
|
+
const columnIndex = cellNumber - 1;
|
|
159
|
+
const columnName = columnIndex < header.length ? header[columnIndex] : "";
|
|
160
|
+
|
|
161
|
+
// 特殊列处理:垂直水平居中
|
|
162
|
+
if (columnName && centerAlignedColumns.includes(columnName)) {
|
|
163
|
+
cell.alignment = {
|
|
164
|
+
vertical: "middle",
|
|
165
|
+
horizontal: "center", // 水平居中
|
|
166
|
+
wrapText: true,
|
|
167
|
+
...dataRowStyle.alignment,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
// 数字列处理:右对齐
|
|
171
|
+
else if (
|
|
172
|
+
(columnName && rightAlignedColumns.includes(columnName)) ||
|
|
173
|
+
isNumberValue(cell.value)
|
|
174
|
+
) {
|
|
175
|
+
cell.alignment = {
|
|
176
|
+
vertical: "middle",
|
|
177
|
+
horizontal: "right", // 右对齐
|
|
178
|
+
wrapText: true,
|
|
179
|
+
...dataRowStyle.alignment,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
// 其他列:左对齐
|
|
183
|
+
else {
|
|
184
|
+
cell.alignment = {
|
|
185
|
+
vertical: "middle",
|
|
186
|
+
horizontal: "left", // 左对齐
|
|
187
|
+
wrapText: true,
|
|
188
|
+
...dataRowStyle.alignment,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 数据行边框
|
|
193
|
+
cell.border = {
|
|
194
|
+
top: { style: "thin", color: { argb: "FF000000" } },
|
|
195
|
+
left: { style: "thin", color: { argb: "FF000000" } },
|
|
196
|
+
bottom: { style: "thin", color: { argb: "FF000000" } },
|
|
197
|
+
right: { style: "thin", color: { argb: "FF000000" } },
|
|
198
|
+
...dataRowStyle.border,
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// 3. 列宽自适应
|
|
204
|
+
worksheet.columns?.forEach((column) => {
|
|
205
|
+
// 初始化默认宽度
|
|
206
|
+
let maxWidth = 10;
|
|
207
|
+
|
|
208
|
+
// 安全处理column.values(空值判断)
|
|
209
|
+
if (column.values && column.values.length > 0) {
|
|
210
|
+
// 明确指定reduce的返回类型为number
|
|
211
|
+
maxWidth = column.values.reduce((w, cellValue) => {
|
|
212
|
+
// 安全获取单元格值长度(处理所有CellValue类型)
|
|
213
|
+
const cellLength = getCellValueLength(cellValue);
|
|
214
|
+
return Math.max(w, cellLength);
|
|
215
|
+
}, 10); // 初始值设为数字10
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// 确保maxWidth是数字后再计算
|
|
219
|
+
column.width = Math.min(maxWidth + 2, columnWidthLimit);
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 数据分组函数:根据档案编号分组数据
|
|
225
|
+
* @param {Array<Array<any>>} data 原始数据数组(不包含表头)
|
|
226
|
+
* @param {string} groupColumnName 分组列名,默认为第一列
|
|
227
|
+
* @returns {Array} 分组后的数据,包含每个组的起始行和结束行索引
|
|
228
|
+
*/
|
|
229
|
+
const groupDataByColumn = (data, groupColumnName = "") => {
|
|
230
|
+
const groups = [];
|
|
231
|
+
|
|
232
|
+
let currentGroup = null;
|
|
233
|
+
const groupColumnIndex = groupColumnName
|
|
234
|
+
? data[0]?.indexOf(groupColumnName)
|
|
235
|
+
: 0;
|
|
236
|
+
|
|
237
|
+
data.forEach((row, index) => {
|
|
238
|
+
const groupValue = row[groupColumnIndex || 0]; // 分组值
|
|
239
|
+
|
|
240
|
+
if (!currentGroup || currentGroup.groupValue !== groupValue) {
|
|
241
|
+
// 开始新的分组
|
|
242
|
+
if (currentGroup) {
|
|
243
|
+
currentGroup.endRow = index - 1;
|
|
244
|
+
groups.push(currentGroup);
|
|
245
|
+
}
|
|
246
|
+
currentGroup = {
|
|
247
|
+
groupValue,
|
|
248
|
+
startRow: index,
|
|
249
|
+
endRow: index,
|
|
250
|
+
rows: [row],
|
|
251
|
+
};
|
|
252
|
+
} else {
|
|
253
|
+
// 继续当前分组
|
|
254
|
+
currentGroup.endRow = index;
|
|
255
|
+
currentGroup.rows.push(row);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// 添加最后一个分组
|
|
260
|
+
if (currentGroup) {
|
|
261
|
+
groups.push(currentGroup);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return groups;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 合并相同分组的数据
|
|
269
|
+
* @param {Worksheet} worksheet Excel工作表对象
|
|
270
|
+
* @param {Array<Array<any>>} data 原始数据(不包含表头)
|
|
271
|
+
* @param {Array<string>} columnsToMerge 需要合并的列名
|
|
272
|
+
* @param {Array<string>} headers 表头数组
|
|
273
|
+
*/
|
|
274
|
+
const mergeCellsByGroup = (worksheet, data, columnsToMerge, headers) => {
|
|
275
|
+
if (
|
|
276
|
+
!data ||
|
|
277
|
+
data.length === 0 ||
|
|
278
|
+
!columnsToMerge ||
|
|
279
|
+
columnsToMerge.length === 0
|
|
280
|
+
) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const groups = groupDataByColumn(data);
|
|
285
|
+
const mergedRanges = new Set();
|
|
286
|
+
|
|
287
|
+
groups.forEach((group) => {
|
|
288
|
+
// 如果组内只有一行数据,不需要合并
|
|
289
|
+
if (group.startRow === group.endRow) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
columnsToMerge.forEach((columnName) => {
|
|
294
|
+
// 获取该列名的所有索引(处理重复列名)
|
|
295
|
+
const columnIndexes = [];
|
|
296
|
+
headers.forEach((header, index) => {
|
|
297
|
+
if (header === columnName) {
|
|
298
|
+
columnIndexes.push(index);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
columnIndexes.forEach((columnIndex) => {
|
|
303
|
+
if (columnIndex === -1) return;
|
|
304
|
+
|
|
305
|
+
// Excel行号从1开始,加上表头行
|
|
306
|
+
const startRow = group.startRow + 2; // +2是因为Excel行从1开始,表头占第1行
|
|
307
|
+
const endRow = group.endRow + 2;
|
|
308
|
+
|
|
309
|
+
// 创建唯一标识符来避免重复合并
|
|
310
|
+
const rangeKey = `${startRow}-${endRow}-${columnIndex + 1}`;
|
|
311
|
+
|
|
312
|
+
// 检查是否已经合并过这个范围
|
|
313
|
+
if (mergedRanges.has(rangeKey)) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
// 合并单元格
|
|
319
|
+
worksheet.mergeCells(
|
|
320
|
+
startRow,
|
|
321
|
+
columnIndex + 1,
|
|
322
|
+
endRow,
|
|
323
|
+
columnIndex + 1
|
|
324
|
+
);
|
|
325
|
+
mergedRanges.add(rangeKey);
|
|
326
|
+
|
|
327
|
+
// 获取合并后的单元格值(取第一个非空值)
|
|
328
|
+
let mergedValue = "";
|
|
329
|
+
|
|
330
|
+
// 修复:确保rows存在且非空
|
|
331
|
+
if (group.rows && group.rows.length > 0) {
|
|
332
|
+
for (let i = 0; i < group.rows.length; i++) {
|
|
333
|
+
const rowData = group.rows[i];
|
|
334
|
+
if (rowData && rowData[columnIndex] !== undefined) {
|
|
335
|
+
const value = rowData[columnIndex];
|
|
336
|
+
if (
|
|
337
|
+
value !== null &&
|
|
338
|
+
value !== undefined &&
|
|
339
|
+
String(value).trim() !== ""
|
|
340
|
+
) {
|
|
341
|
+
mergedValue = String(value);
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// 设置合并后单元格的值
|
|
349
|
+
const cell = worksheet.getCell(startRow, columnIndex + 1);
|
|
350
|
+
cell.value = mergedValue;
|
|
351
|
+
} catch (error) {
|
|
352
|
+
console.warn(`合并单元格失败: ${error.message}`, {
|
|
353
|
+
startRow,
|
|
354
|
+
columnIndex: columnIndex + 1,
|
|
355
|
+
endRow,
|
|
356
|
+
columnName,
|
|
357
|
+
groupValue: group.groupValue,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* 获取日期范围字符串
|
|
367
|
+
* @param {Array<Array<string>>} data 数据数组
|
|
368
|
+
* @param {string} dateColumnName 日期列名,默认为"會計日"
|
|
369
|
+
* @returns {string} 日期范围字符串,如 "2025-12-10~2025-12-12"
|
|
370
|
+
*/
|
|
371
|
+
const getDateRangeString = (data, dateColumnName = "會計日") => {
|
|
372
|
+
if (data.length <= 1) return "";
|
|
373
|
+
|
|
374
|
+
const header = data[0] || [];
|
|
375
|
+
const dateColumnIndex = header.indexOf(dateColumnName);
|
|
376
|
+
|
|
377
|
+
if (dateColumnIndex === -1) return "";
|
|
378
|
+
|
|
379
|
+
const dates = [];
|
|
380
|
+
|
|
381
|
+
// 收集所有日期
|
|
382
|
+
for (let i = 1; i < data.length; i++) {
|
|
383
|
+
const row = data[i] || [];
|
|
384
|
+
const date = row[dateColumnIndex];
|
|
385
|
+
if (date && !dates.includes(date)) {
|
|
386
|
+
dates.push(date);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (dates.length === 0) return "";
|
|
391
|
+
|
|
392
|
+
// 排序日期
|
|
393
|
+
dates.sort();
|
|
394
|
+
|
|
395
|
+
if (dates.length === 1) {
|
|
396
|
+
return dates[0];
|
|
397
|
+
} else {
|
|
398
|
+
return `${dates[0]}~${dates[dates.length - 1]}`;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* 统一的Excel导出函数
|
|
404
|
+
* @param {Array<Array<string>>} data 要导出的数据(二维数组,包含表头)
|
|
405
|
+
* @param {Object} options 配置选项
|
|
406
|
+
* @returns {Promise<void>}
|
|
407
|
+
*/
|
|
408
|
+
export const guiExportToExcel = async (data, options = {}) => {
|
|
409
|
+
const {
|
|
410
|
+
excelName = "导出数据",
|
|
411
|
+
sheetName = "数据",
|
|
412
|
+
dateColumnName = "會計日",
|
|
413
|
+
groupColumnName = "",
|
|
414
|
+
columnsToMerge = [],
|
|
415
|
+
rightAlignedColumns = [],
|
|
416
|
+
centerAlignedColumns = [],
|
|
417
|
+
exportType = "single", // 'single' 或 'multi-sheet'
|
|
418
|
+
headerStyle = {},
|
|
419
|
+
dataRowStyle = {},
|
|
420
|
+
columnWidthLimit = 50,
|
|
421
|
+
customFileName = "",
|
|
422
|
+
} = options;
|
|
423
|
+
|
|
424
|
+
const workbook = new ExcelJS.Workbook();
|
|
425
|
+
|
|
426
|
+
if (exportType === "multi-sheet" && dateColumnName) {
|
|
427
|
+
// 1. 按日期分组数据(多工作表模式)
|
|
428
|
+
const dateGroups = groupDataByDate(data, dateColumnName);
|
|
429
|
+
|
|
430
|
+
if (Object.keys(dateGroups).length === 0) {
|
|
431
|
+
// 如果没有数据,只创建一个空的工作表
|
|
432
|
+
const worksheet = workbook.addWorksheet(sheetName);
|
|
433
|
+
const header = data[0] || [];
|
|
434
|
+
worksheet.addRows(data.length > 0 ? data : [header]);
|
|
435
|
+
configureWorksheetStyle(worksheet, header, {
|
|
436
|
+
rightAlignedColumns,
|
|
437
|
+
centerAlignedColumns,
|
|
438
|
+
headerStyle,
|
|
439
|
+
dataRowStyle,
|
|
440
|
+
columnWidthLimit,
|
|
441
|
+
});
|
|
442
|
+
} else {
|
|
443
|
+
// 为每个日期创建独立的工作表
|
|
444
|
+
Object.keys(dateGroups).forEach((date) => {
|
|
445
|
+
const dateData = dateGroups[date] || [];
|
|
446
|
+
|
|
447
|
+
// 创建工作表,工作表名称为日期
|
|
448
|
+
let sheetName = date;
|
|
449
|
+
if (sheetName === "全部数据") {
|
|
450
|
+
sheetName = "汇总";
|
|
451
|
+
} else {
|
|
452
|
+
// 移除可能影响工作表名称的字符
|
|
453
|
+
sheetName = sheetName.replace(/[\/\\\*\?\[\]:]/g, "-");
|
|
454
|
+
// 限制工作表名称长度(Excel限制31个字符)
|
|
455
|
+
if (sheetName.length > 31) {
|
|
456
|
+
sheetName = sheetName.substring(0, 31);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const worksheet = workbook.addWorksheet(sheetName);
|
|
461
|
+
|
|
462
|
+
// 写入数据
|
|
463
|
+
if (dateData.length > 0) {
|
|
464
|
+
worksheet.addRows(dateData);
|
|
465
|
+
|
|
466
|
+
// 配置工作表样式
|
|
467
|
+
const header = dateData[0] || [];
|
|
468
|
+
configureWorksheetStyle(worksheet, header, {
|
|
469
|
+
rightAlignedColumns,
|
|
470
|
+
centerAlignedColumns,
|
|
471
|
+
headerStyle,
|
|
472
|
+
dataRowStyle,
|
|
473
|
+
columnWidthLimit,
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// 如果需要合并单元格
|
|
477
|
+
if (columnsToMerge.length > 0 && groupColumnName) {
|
|
478
|
+
const dataWithoutHeader = dateData.slice(1);
|
|
479
|
+
mergeCellsByGroup(
|
|
480
|
+
worksheet,
|
|
481
|
+
dataWithoutHeader,
|
|
482
|
+
columnsToMerge,
|
|
483
|
+
header
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
// 2. 单工作表模式
|
|
491
|
+
const worksheet = workbook.addWorksheet(sheetName);
|
|
492
|
+
const header = data[0] || [];
|
|
493
|
+
|
|
494
|
+
// 写入数据
|
|
495
|
+
worksheet.addRows(data);
|
|
496
|
+
|
|
497
|
+
// 配置工作表样式
|
|
498
|
+
configureWorksheetStyle(worksheet, header, {
|
|
499
|
+
rightAlignedColumns,
|
|
500
|
+
centerAlignedColumns,
|
|
501
|
+
headerStyle,
|
|
502
|
+
dataRowStyle,
|
|
503
|
+
columnWidthLimit,
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// 如果需要合并单元格
|
|
507
|
+
if (columnsToMerge.length > 0 && groupColumnName) {
|
|
508
|
+
const dataWithoutHeader = data.slice(1);
|
|
509
|
+
mergeCellsByGroup(worksheet, dataWithoutHeader, columnsToMerge, header);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// 3. 导出 Excel
|
|
514
|
+
try {
|
|
515
|
+
const buffer = await workbook.xlsx.writeBuffer();
|
|
516
|
+
const blob = new Blob([buffer], {
|
|
517
|
+
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
518
|
+
});
|
|
519
|
+
const url = URL.createObjectURL(blob);
|
|
520
|
+
const a = document.createElement("a");
|
|
521
|
+
|
|
522
|
+
// 构建文件名
|
|
523
|
+
let fileName = customFileName || excelName;
|
|
524
|
+
if (exportType === "multi-sheet" && dateColumnName) {
|
|
525
|
+
const dateRange = getDateRangeString(data, dateColumnName);
|
|
526
|
+
if (dateRange) {
|
|
527
|
+
fileName = `${excelName}_${dateRange}`;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
a.href = url;
|
|
532
|
+
a.download = `${fileName}.xlsx`;
|
|
533
|
+
document.body.appendChild(a);
|
|
534
|
+
a.click();
|
|
535
|
+
document.body.removeChild(a);
|
|
536
|
+
URL.revokeObjectURL(url);
|
|
537
|
+
return Promise.resolve();
|
|
538
|
+
} catch (error) {
|
|
539
|
+
return Promise.reject(error);
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
export default {
|
|
544
|
+
guiExportToExcel,
|
|
545
|
+
getDateRangeString,
|
|
546
|
+
groupDataByDate,
|
|
547
|
+
};
|