vue2server7 4.0.0 → 5.0.1

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.
Binary file
Binary file
@@ -0,0 +1,13 @@
1
+ <template>
2
+ <div>
3
+ <button @click="handleExport">导出Excel</button>
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { exportBankExcelWithDefaultLogo } from '../utils/exprotExcel'
9
+
10
+ const handleExport = async (): Promise<void> => {
11
+ await exportBankExcelWithDefaultLogo()
12
+ }
13
+ </script>
@@ -1,5 +1,6 @@
1
1
  import TablePage from '../pages/TablePage.vue'
2
2
  import CascaderPage from '../pages/CascaderPage.vue'
3
+ import ExportExcelPage from '../pages/ExportExcelPage.vue'
3
4
 
4
5
  export const routes = [
5
6
  {
@@ -23,5 +24,14 @@ export const routes = [
23
24
  title: '级联选择',
24
25
  showInMenu: true
25
26
  }
27
+ },
28
+ {
29
+ path: '/export-excel',
30
+ name: 'ExportExcel',
31
+ component: ExportExcelPage,
32
+ meta: {
33
+ title: '导出Excel',
34
+ showInMenu: true
35
+ }
26
36
  }
27
37
  ]
@@ -0,0 +1,760 @@
1
+
2
+ import ExcelJS from 'exceljs'
3
+
4
+ const saveBlobAsFile = (blob: Blob, fileName: string): void => {
5
+ const url = URL.createObjectURL(blob)
6
+ const a = document.createElement('a')
7
+ a.href = url
8
+ a.download = fileName
9
+ a.rel = 'noopener'
10
+ document.body.appendChild(a)
11
+ a.click()
12
+ a.remove()
13
+ URL.revokeObjectURL(url)
14
+ }
15
+
16
+ /**
17
+ * 导出 Excel 所需的业务数据结构。
18
+ * - 该结构尽量与表格展示字段保持 1:1,对应关系更直观
19
+ * - 数值字段建议统一使用“亿”为单位(与表格右侧单位一致)
20
+ */
21
+ export interface ExportFormData {
22
+ /**
23
+ * 导出文件与表格标题。
24
+ * - 同时用于生成文件名:`${title}.xlsx`
25
+ */
26
+ title: string
27
+
28
+ /**
29
+ * 表格日期显示文本(建议格式:YYYY-MM-DD)。
30
+ */
31
+ date: string
32
+
33
+ // 顶部当前可用余额
34
+ currentAvailableBalance: number
35
+
36
+ // 今日大额收付款报备情况
37
+ reportAmountIncome: number
38
+ reportAmountPay: number
39
+
40
+ unverifiedWriteOffIncome: number
41
+ unverifiedWriteOffPay: number
42
+
43
+ // 未核销业务流水
44
+ unliquidatedBusinessIncome: number
45
+ unliquidatedBusinessPay: number
46
+
47
+ // 日初可用资金(红字)
48
+ initialAvailableFunds: number
49
+
50
+ // 今日预计调拨金额
51
+ expectedTransferIncome: number
52
+ expectedTransferPay: number
53
+ expectedTransferDiff: number
54
+
55
+ // 跨境人民币头寸清算账户预留金额
56
+ reserveAmount: number
57
+ }
58
+
59
+ /**
60
+ * 示例数据:页面点击“导出Excel”时使用该对象生成报表。
61
+ * 实际接入时可替换为接口返回结果或表单输入值。
62
+ */
63
+ export const exportFormDataDemo: ExportFormData = {
64
+ title: '跨境人民币头寸压算表',
65
+ date: '2026-02-02',
66
+
67
+ currentAvailableBalance: 0,
68
+
69
+ reportAmountIncome: 0,
70
+ reportAmountPay: 0,
71
+
72
+ unverifiedWriteOffIncome: 0,
73
+ unverifiedWriteOffPay: 0,
74
+
75
+ unliquidatedBusinessIncome: 0,
76
+ unliquidatedBusinessPay: 0,
77
+
78
+ initialAvailableFunds: 0,
79
+
80
+ expectedTransferIncome: 2,
81
+ expectedTransferPay: 2,
82
+ expectedTransferDiff: 0,
83
+
84
+ reserveAmount: 0
85
+ }
86
+
87
+ /**
88
+ * Logo 插入说明(重点)
89
+ * 1) 只允许插入“静态图片”:PNG / JPG(JPEG),不插入 GIF 等动图
90
+ * 2) 图片来自 public 目录(打包时会被静态托管),通过 fetch 拉取后转 base64 传给 ExcelJS
91
+ * 3) 通过缓存避免每次点击“导出Excel”都重复请求与转换,提升稳定性
92
+ *
93
+ * public 目录下的静态资源会映射到站点根路径:
94
+ * - 文件:frontEnd/public/logo.jpg
95
+ * - URL: /logo.jpg
96
+ */
97
+ const LOGO_PUBLIC_PATH = '/logo.jpg'
98
+
99
+ /**
100
+ * 仅允许的静态图片扩展名。
101
+ * - ExcelJS 的 addImage 需要显式的 extension 参数
102
+ */
103
+ export type StaticLogoExtension = 'png' | 'jpeg'
104
+
105
+ /**
106
+ * 静态 Logo 的内存表示。
107
+ * - base64:不包含 data: 前缀,仅为纯 base64 内容
108
+ * - extension:与图片真实格式一致
109
+ */
110
+ export type StaticLogo = { base64: string; extension: StaticLogoExtension }
111
+
112
+ /**
113
+ * Logo 缓存:避免重复 fetch 与 base64 转换,提升导出稳定性与性能。
114
+ */
115
+ let cachedStaticLogo: StaticLogo | null = null
116
+
117
+ /**
118
+ * 将 Blob 转换为纯 base64 字符串(去掉 dataURL 头部)。
119
+ * @param blob 通过 fetch 获取到的图片二进制
120
+ * @returns 纯 base64 字符串;解析失败时返回空字符串
121
+ */
122
+ const blobToBase64 = (blob: Blob): Promise<string> => {
123
+ return new Promise((resolve, reject) => {
124
+ const reader = new FileReader()
125
+ reader.onload = () => {
126
+ const result = reader.result
127
+ if (typeof result !== 'string') {
128
+ resolve('')
129
+ return
130
+ }
131
+
132
+ const commaIndex = result.indexOf(',')
133
+ resolve(commaIndex >= 0 ? result.slice(commaIndex + 1) : result)
134
+ }
135
+ reader.onerror = () => reject(new Error('读取图片失败'))
136
+ reader.readAsDataURL(blob)
137
+ })
138
+ }
139
+
140
+ /**
141
+ * 根据 content-type 与 URL 后缀推断图片扩展名。
142
+ * - 优先使用 content-type(更可靠)
143
+ * - content-type 缺失时再回退到 URL 后缀
144
+ * @param contentType response header: content-type
145
+ * @param urlPath 图片 URL 路径(例如 /logo.jpg)
146
+ * @returns 可支持的 extension;不支持时返回 null
147
+ */
148
+ const guessLogoExtension = (contentType: string, urlPath: string): StaticLogoExtension | null => {
149
+ const ct = contentType.toLowerCase()
150
+ if (ct.includes('image/png')) return 'png'
151
+ if (ct.includes('image/jpeg') || ct.includes('image/jpg')) return 'jpeg'
152
+
153
+ const lowerPath = urlPath.toLowerCase()
154
+ if (lowerPath.endsWith('.png')) return 'png'
155
+ if (lowerPath.endsWith('.jpg') || lowerPath.endsWith('.jpeg')) return 'jpeg'
156
+ return null
157
+ }
158
+
159
+ /**
160
+ * 获取“可插入 Excel 的静态 Logo”。
161
+ * - 从 public 目录拉取图片(站点根路径映射)
162
+ * - 过滤 GIF 等动图格式,避免 ExcelJS 插入异常
163
+ * - 首次成功后写入内存缓存,后续直接复用
164
+ * @returns Logo 信息;不可用或失败时返回 null
165
+ */
166
+ export const getStaticLogo = async (): Promise<StaticLogo | null> => {
167
+ if (cachedStaticLogo) return cachedStaticLogo
168
+
169
+ try {
170
+ const res = await fetch(LOGO_PUBLIC_PATH)
171
+ if (!res.ok) return null
172
+
173
+ const contentType = res.headers.get('content-type') ?? ''
174
+ const extension = guessLogoExtension(contentType, LOGO_PUBLIC_PATH)
175
+ if (!extension) return null
176
+
177
+ if (contentType.toLowerCase().includes('image/gif')) return null
178
+
179
+ const blob = await res.blob()
180
+ if (blob.type.toLowerCase().includes('image/gif')) return null
181
+
182
+ const base64 = await blobToBase64(blob)
183
+ if (!base64) return null
184
+
185
+ cachedStaticLogo = { base64, extension }
186
+ return cachedStaticLogo
187
+ } catch {
188
+ return null
189
+ }
190
+ }
191
+
192
+
193
+
194
+ export interface ExportBankExcelOptions {
195
+ /**
196
+ * 需要插入到工作表中的静态 Logo(可选)。
197
+ * - 为 null/undefined 时回退到文字 Logo
198
+ */
199
+ logo?: StaticLogo | null
200
+ }
201
+
202
+ /**
203
+ * 生成并下载“跨境人民币头寸压算表”Excel。
204
+ * @param data 业务数据
205
+ * @param options 可选配置(例如 Logo)
206
+ */
207
+ export async function exportBankExcel(data: ExportFormData, options: ExportBankExcelOptions = {}): Promise<void> {
208
+ const workbook = new ExcelJS.Workbook()
209
+ const worksheet = workbook.addWorksheet('跨境人民币头寸压算表', {
210
+ views: [{ showGridLines: true }]
211
+ })
212
+
213
+ // 页面设置
214
+ worksheet.pageSetup = {
215
+ paperSize: 9,
216
+ orientation: 'landscape',
217
+ fitToPage: true,
218
+ fitToWidth: 1,
219
+ fitToHeight: 0,
220
+ margins: {
221
+ left: 0.3,
222
+ right: 0.3,
223
+ top: 0.3,
224
+ bottom: 0.3,
225
+ header: 0.1,
226
+ footer: 0.1
227
+ }
228
+ }
229
+
230
+ // 列宽说明(重点)
231
+ // 1) ExcelJS 的 column.width 单位是“字符宽度”(大致相当于默认字体下能容纳多少个字符),不是像素
232
+ // 2) 合并单元格(mergeCells)并不会单独有“合并后的宽度”,它的可视宽度 = 被合并列宽之和
233
+ // 3) 因此要调整某个合并区域(例如 A3:B3)的显示效果,应优先调整 A 列和 B 列的宽度
234
+ //
235
+ // 下面给一组更适配当前表格内容的默认列宽(可直接改这里达到你想要的效果)
236
+ const COLUMN_WIDTHS = {
237
+ A: 28,
238
+ B: 16,
239
+ C: 18,
240
+ D: 18,
241
+ E: 14
242
+ } as const
243
+
244
+ const applyColumnWidths = (): void => {
245
+ worksheet.columns = [
246
+ { width: COLUMN_WIDTHS.A },
247
+ { width: COLUMN_WIDTHS.B },
248
+ { width: COLUMN_WIDTHS.C },
249
+ { width: COLUMN_WIDTHS.D },
250
+ { width: COLUMN_WIDTHS.E }
251
+ ]
252
+ }
253
+
254
+ applyColumnWidths()
255
+
256
+ // 行高说明
257
+ // 1) row.height 单位是“点”(points),会直接影响打印/分页效果
258
+ // 2) 建议用配置表统一管理,避免散落在代码中导致后续维护不稳定
259
+ const ROW_HEIGHTS: Record<number, number> = {
260
+ 1: 34,
261
+ 2: 26,
262
+ 3: 24,
263
+ 4: 24,
264
+ 5: 24,
265
+ 6: 24,
266
+ 7: 24,
267
+ 8: 24,
268
+ 9: 24,
269
+ 10: 24,
270
+ 11: 20
271
+ }
272
+
273
+ for (const [rowIndex, height] of Object.entries(ROW_HEIGHTS)) {
274
+ worksheet.getRow(Number(rowIndex)).height = height
275
+ }
276
+
277
+ const BORDER_THIN: Partial<ExcelJS.Borders> = {
278
+ top: { style: 'thin', color: { argb: '000000' } },
279
+ left: { style: 'thin', color: { argb: '000000' } },
280
+ bottom: { style: 'thin', color: { argb: '000000' } },
281
+ right: { style: 'thin', color: { argb: '000000' } }
282
+ }
283
+
284
+ const BORDER_MEDIUM: Partial<ExcelJS.Borders> = {
285
+ top: { style: 'medium', color: { argb: '000000' } },
286
+ left: { style: 'medium', color: { argb: '000000' } },
287
+ bottom: { style: 'medium', color: { argb: '000000' } },
288
+ right: { style: 'medium', color: { argb: '000000' } }
289
+ }
290
+
291
+ const FONT_NORMAL: Partial<ExcelJS.Font> = {
292
+ name: 'Microsoft YaHei',
293
+ size: 12,
294
+ color: { argb: '333333' }
295
+ }
296
+
297
+ const FONT_TITLE: Partial<ExcelJS.Font> = {
298
+ name: 'Microsoft YaHei',
299
+ size: 22,
300
+ color: { argb: '333333' }
301
+ }
302
+
303
+ const FONT_DATE: Partial<ExcelJS.Font> = {
304
+ name: 'Microsoft YaHei',
305
+ size: 12,
306
+ color: { argb: '666666' }
307
+ }
308
+
309
+ const FONT_RED_BOLD: Partial<ExcelJS.Font> = {
310
+ name: 'Microsoft YaHei',
311
+ size: 14,
312
+ bold: true,
313
+ color: { argb: 'C00000' }
314
+ }
315
+
316
+ const FONT_RED_BANK: Partial<ExcelJS.Font> = {
317
+ name: 'Microsoft YaHei',
318
+ size: 18,
319
+ bold: true,
320
+ color: { argb: 'C00000' }
321
+ }
322
+
323
+ const ALIGN_CENTER: Partial<ExcelJS.Alignment> = {
324
+ vertical: 'middle',
325
+ horizontal: 'center',
326
+ wrapText: true
327
+ }
328
+
329
+ const ALIGN_RIGHT: Partial<ExcelJS.Alignment> = {
330
+ vertical: 'middle',
331
+ horizontal: 'right'
332
+ }
333
+
334
+ const PINK_FILL: ExcelJS.Fill = {
335
+ type: 'pattern',
336
+ pattern: 'solid',
337
+ fgColor: { argb: 'F3D4DC' }
338
+ }
339
+
340
+ const BLUE_FILL: ExcelJS.Fill = {
341
+ type: 'pattern',
342
+ pattern: 'solid',
343
+ fgColor: { argb: '18B6E6' }
344
+ }
345
+
346
+ const CYAN_FILL: ExcelJS.Fill = {
347
+ type: 'pattern',
348
+ pattern: 'solid',
349
+ fgColor: { argb: '00B7E8' }
350
+ }
351
+
352
+ const LIGHT_BLUE_FILL: ExcelJS.Fill = {
353
+ type: 'pattern',
354
+ pattern: 'solid',
355
+ fgColor: { argb: 'FFDCE6F1' }
356
+ }
357
+
358
+ const GREEN_FILL: ExcelJS.Fill = {
359
+ type: 'pattern',
360
+ pattern: 'solid',
361
+ fgColor: { argb: '8EBB43' }
362
+ }
363
+
364
+ /**
365
+ * 设置单个单元格的值与样式。
366
+ * @param address 单元格地址(例如 A1、C3)
367
+ * @param value 单元格值
368
+ * @param options 样式选项(字体/对齐/填充/边框/数字格式)
369
+ */
370
+ const setCell = (
371
+ address: string,
372
+ value: string | number,
373
+ options?: {
374
+ font?: Partial<ExcelJS.Font>
375
+ alignment?: Partial<ExcelJS.Alignment>
376
+ fill?: ExcelJS.Fill
377
+ border?: Partial<ExcelJS.Borders>
378
+ numFmt?: string
379
+ }
380
+ ): void => {
381
+ const cell = worksheet.getCell(address)
382
+ cell.value = value
383
+
384
+ if (options?.font) {
385
+ cell.font = options.font as ExcelJS.Font
386
+ }
387
+ if (options?.alignment) {
388
+ cell.alignment = options.alignment as ExcelJS.Alignment
389
+ }
390
+ if (options?.fill) {
391
+ cell.fill = options.fill
392
+ }
393
+ if (options?.border) {
394
+ cell.border = options.border as ExcelJS.Borders
395
+ }
396
+ if (options?.numFmt) {
397
+ cell.numFmt = options.numFmt
398
+ }
399
+ }
400
+
401
+ /**
402
+ * 给指定区域批量设置边框。
403
+ * @param startRow 起始行(从 1 开始)
404
+ * @param endRow 结束行(包含)
405
+ * @param startCol 起始列(A=1)
406
+ * @param endCol 结束列(包含)
407
+ * @param border 边框样式,默认细边框
408
+ */
409
+ const setRangeBorder = (
410
+ startRow: number,
411
+ endRow: number,
412
+ startCol: number,
413
+ endCol: number,
414
+ border: Partial<ExcelJS.Borders> = BORDER_THIN
415
+ ): void => {
416
+ for (let r = startRow; r <= endRow; r++) {
417
+ for (let c = startCol; c <= endCol; c++) {
418
+ worksheet.getCell(r, c).border = border as ExcelJS.Borders
419
+ }
420
+ }
421
+ }
422
+
423
+ /**
424
+ * 给指定区域设置“外边框加粗、内部细线”的效果。
425
+ * @param startRow 起始行(从 1 开始)
426
+ * @param endRow 结束行(包含)
427
+ * @param startCol 起始列(A=1)
428
+ * @param endCol 结束列(包含)
429
+ */
430
+ const setOuterMediumBorder = (
431
+ startRow: number,
432
+ endRow: number,
433
+ startCol: number,
434
+ endCol: number
435
+ ): void => {
436
+ for (let r = startRow; r <= endRow; r++) {
437
+ for (let c = startCol; c <= endCol; c++) {
438
+ const cell = worksheet.getCell(r, c)
439
+ const border: Partial<ExcelJS.Borders> = {
440
+ top: { style: 'thin', color: { argb: '000000' } },
441
+ left: { style: 'thin', color: { argb: '000000' } },
442
+ bottom: { style: 'thin', color: { argb: '000000' } },
443
+ right: { style: 'thin', color: { argb: '000000' } }
444
+ }
445
+
446
+ if (r === startRow) border.top = { style: 'medium', color: { argb: '000000' } }
447
+ if (r === endRow) border.bottom = { style: 'medium', color: { argb: '000000' } }
448
+ if (c === startCol) border.left = { style: 'medium', color: { argb: '000000' } }
449
+ if (c === endCol) border.right = { style: 'medium', color: { argb: '000000' } }
450
+
451
+ cell.border = border as ExcelJS.Borders
452
+ }
453
+ }
454
+ }
455
+
456
+ /**
457
+ * 统一数值兜底,避免 undefined/null 导致单元格为空或计算报错。
458
+ */
459
+ const formatAmount = (value: number | null | undefined): number => value ?? 0
460
+
461
+ // ========== 顶部区域 ==========
462
+ worksheet.mergeCells('A1:B2')
463
+ worksheet.mergeCells('C1:E1')
464
+
465
+ const logo = options.logo
466
+
467
+ if (logo?.base64) {
468
+ const imageId = workbook.addImage({
469
+ base64: logo.base64,
470
+ extension: logo.extension
471
+ })
472
+ worksheet.addImage(imageId, {
473
+ tl: { col: 0.15, row: 0.25 },
474
+ ext: { width: 240, height: 52 }
475
+ })
476
+ } else {
477
+ setCell('A1', '北京银行\nBANK OF BEIJING', {
478
+ font: FONT_RED_BANK,
479
+ alignment: {
480
+ vertical: 'middle',
481
+ horizontal: 'center',
482
+ wrapText: true
483
+ }
484
+ })
485
+ }
486
+
487
+ // 顶部粉色标题块
488
+ for (const addr of ['C1', 'D1', 'E1', 'C2', 'D2', 'E2']) {
489
+ worksheet.getCell(addr).fill = PINK_FILL
490
+ }
491
+
492
+ setCell('C1', data.title, {
493
+ font: FONT_TITLE,
494
+ alignment: ALIGN_CENTER,
495
+ fill: PINK_FILL
496
+ })
497
+
498
+ setCell('D2', data.date, {
499
+ font: FONT_DATE,
500
+ alignment: ALIGN_RIGHT,
501
+ fill: PINK_FILL
502
+ })
503
+ setCell('E2', '备注', {
504
+ font: FONT_NORMAL,
505
+ alignment: ALIGN_CENTER,
506
+ fill: PINK_FILL
507
+ })
508
+
509
+ // ========== 主体表格 ==========
510
+ // A3:B3
511
+ worksheet.mergeCells('A3:B3')
512
+ setCell('A3', '跨境人民币头寸账户当前可用余额', {
513
+ font: FONT_NORMAL,
514
+ alignment: ALIGN_CENTER,
515
+ border: BORDER_THIN
516
+ })
517
+
518
+ // C3:D3
519
+ worksheet.mergeCells('C3:D3')
520
+ setCell('C3', formatAmount(data.currentAvailableBalance), {
521
+ font: FONT_NORMAL,
522
+ alignment: ALIGN_CENTER,
523
+ border: BORDER_THIN,
524
+ numFmt: '0.00'
525
+ })
526
+
527
+ // E3
528
+ setCell('E3', '单位:亿元', {
529
+ font: FONT_NORMAL,
530
+ alignment: ALIGN_CENTER,
531
+ border: BORDER_THIN
532
+ })
533
+
534
+ worksheet.mergeCells('A4:B4')
535
+ setCell('A4', '', {
536
+ font: FONT_NORMAL,
537
+ alignment: ALIGN_CENTER,
538
+ border: BORDER_THIN
539
+ })
540
+
541
+ worksheet.mergeCells('A5:A6')
542
+ setCell('A5', '今日大额收付款报备情况', {
543
+ font: FONT_NORMAL,
544
+ alignment: ALIGN_CENTER,
545
+ border: BORDER_THIN
546
+ })
547
+ setCell('B5', '报备金额', {
548
+ font: FONT_NORMAL,
549
+ alignment: ALIGN_CENTER,
550
+ border: BORDER_THIN
551
+ })
552
+ setCell('B6', '未核销金额', {
553
+ font: FONT_NORMAL,
554
+ alignment: ALIGN_CENTER,
555
+ border: BORDER_THIN
556
+ })
557
+
558
+ // C4
559
+ setCell('C4', '收入', {
560
+ font: FONT_NORMAL,
561
+ alignment: ALIGN_CENTER,
562
+ border: BORDER_THIN
563
+ })
564
+
565
+ // D4
566
+ setCell('D4', '支付', {
567
+ font: FONT_NORMAL,
568
+ alignment: ALIGN_CENTER,
569
+ border: BORDER_THIN
570
+ })
571
+
572
+ // E4
573
+ setCell('E4', '轧差', {
574
+ font: FONT_NORMAL,
575
+ alignment: ALIGN_CENTER,
576
+ border: BORDER_THIN
577
+ })
578
+
579
+ // C5 / D5 / E5
580
+ setCell('C5', formatAmount(data.reportAmountIncome), {
581
+ font: FONT_NORMAL,
582
+ alignment: ALIGN_CENTER,
583
+ border: BORDER_THIN,
584
+ numFmt: '0.00'
585
+ })
586
+ setCell('D5', formatAmount(data.reportAmountPay), {
587
+ font: FONT_NORMAL,
588
+ alignment: ALIGN_CENTER,
589
+ border: BORDER_THIN,
590
+ numFmt: '0.00'
591
+ })
592
+ setCell('E5', formatAmount(data.reportAmountIncome - data.reportAmountPay), {
593
+ font: FONT_NORMAL,
594
+ alignment: ALIGN_CENTER,
595
+ border: BORDER_THIN,
596
+ fill: BLUE_FILL,
597
+ numFmt: '0.00'
598
+ })
599
+
600
+ // C6 / D6 / E6
601
+ setCell('C6', formatAmount(data.unverifiedWriteOffIncome), {
602
+ font: FONT_NORMAL,
603
+ alignment: ALIGN_CENTER,
604
+ border: BORDER_THIN,
605
+ numFmt: '0.00'
606
+ })
607
+ setCell('D6', formatAmount(data.unverifiedWriteOffPay), {
608
+ font: FONT_NORMAL,
609
+ alignment: ALIGN_CENTER,
610
+ border: BORDER_THIN,
611
+ numFmt: '0.00'
612
+ })
613
+ setCell('E6', formatAmount(data.unverifiedWriteOffIncome - data.unverifiedWriteOffPay), {
614
+ font: FONT_NORMAL,
615
+ alignment: ALIGN_CENTER,
616
+ border: BORDER_THIN,
617
+ fill: CYAN_FILL,
618
+ numFmt: '0.00'
619
+ })
620
+
621
+ // A7:B7
622
+ worksheet.mergeCells('A7:B7')
623
+ setCell('A7', '未核销业务流水', {
624
+ font: FONT_NORMAL,
625
+ alignment: ALIGN_CENTER,
626
+ border: BORDER_THIN
627
+ })
628
+
629
+ setCell('C7', formatAmount(data.unliquidatedBusinessIncome), {
630
+ font: FONT_NORMAL,
631
+ alignment: ALIGN_CENTER,
632
+ border: BORDER_THIN,
633
+ numFmt: '0.00'
634
+ })
635
+ setCell('D7', formatAmount(data.unliquidatedBusinessPay), {
636
+ font: FONT_NORMAL,
637
+ alignment: ALIGN_CENTER,
638
+ border: BORDER_THIN,
639
+ numFmt: '0.00'
640
+ })
641
+ setCell('E7', formatAmount(data.unliquidatedBusinessIncome - data.unliquidatedBusinessPay), {
642
+ font: FONT_NORMAL,
643
+ alignment: ALIGN_CENTER,
644
+ border: BORDER_THIN,
645
+ fill: GREEN_FILL,
646
+ numFmt: '0.00'
647
+ })
648
+
649
+ // A8:B8
650
+ worksheet.mergeCells('A8:B8')
651
+ setCell('A8', '日初可用资金', {
652
+ font: FONT_NORMAL,
653
+ alignment: ALIGN_CENTER,
654
+ border: BORDER_THIN
655
+ })
656
+
657
+ // C8:E8
658
+ worksheet.mergeCells('C8:E8')
659
+ setCell('C8', formatAmount(data.initialAvailableFunds), {
660
+ font: FONT_RED_BOLD,
661
+ alignment: ALIGN_CENTER,
662
+ border: BORDER_THIN,
663
+ numFmt: '0.00'
664
+ })
665
+
666
+ worksheet.mergeCells('A9:E9')
667
+ setCell('A9', '', {
668
+ alignment: ALIGN_CENTER,
669
+ border: BORDER_THIN
670
+ })
671
+
672
+ // A10:B10
673
+ worksheet.mergeCells('A10:B10')
674
+ setCell('A10', '今日预计调拨金额', {
675
+ font: FONT_NORMAL,
676
+ alignment: ALIGN_CENTER,
677
+ border: BORDER_THIN
678
+ })
679
+
680
+ setCell('C10', formatAmount(data.expectedTransferIncome), {
681
+ font: FONT_NORMAL,
682
+ alignment: ALIGN_CENTER,
683
+ border: BORDER_THIN,
684
+ fill: LIGHT_BLUE_FILL,
685
+ numFmt: data.expectedTransferIncome % 1 === 0 ? '0' : '0.00'
686
+ })
687
+
688
+ setCell('D10', formatAmount(data.expectedTransferPay), {
689
+ font: FONT_NORMAL,
690
+ alignment: ALIGN_CENTER,
691
+ border: BORDER_THIN,
692
+ fill: LIGHT_BLUE_FILL,
693
+ numFmt: data.expectedTransferPay % 1 === 0 ? '0' : '0.00'
694
+ })
695
+
696
+ setCell('E10', formatAmount(data.expectedTransferDiff), {
697
+ font: FONT_NORMAL,
698
+ alignment: ALIGN_CENTER,
699
+ border: BORDER_THIN,
700
+ fill: BLUE_FILL,
701
+ numFmt: '0.00'
702
+ })
703
+
704
+ // A11:B11
705
+ worksheet.mergeCells('A11:B11')
706
+ setCell('A11', '跨境人民币头寸清算账户预留金额', {
707
+ font: FONT_NORMAL,
708
+ alignment: ALIGN_CENTER,
709
+ border: BORDER_THIN
710
+ })
711
+
712
+ // C11:E11
713
+ worksheet.mergeCells('C11:E11')
714
+ setCell('C11', formatAmount(data.reserveAmount), {
715
+ font: {
716
+ name: 'Microsoft YaHei',
717
+ size: 14,
718
+ bold: true,
719
+ color: { argb: '000000' }
720
+ },
721
+ alignment: ALIGN_CENTER,
722
+ border: BORDER_THIN,
723
+ numFmt: '0.00'
724
+ })
725
+
726
+ // 主体所有区域边框
727
+ setRangeBorder(3, 11, 1, 5, BORDER_THIN)
728
+
729
+ // 外边框加粗,更接近图里效果
730
+ setOuterMediumBorder(3, 11, 1, 5)
731
+
732
+ // 分隔线:行 8 红字区域略强调
733
+ for (let c = 1; c <= 5; c++) {
734
+ worksheet.getCell(8, c).border = {
735
+ top: { style: 'medium', color: { argb: '000000' } },
736
+ left: c === 1 ? { style: 'medium', color: { argb: '000000' } } : { style: 'thin', color: { argb: '000000' } },
737
+ bottom: { style: 'medium', color: { argb: '000000' } },
738
+ right: c === 5 ? { style: 'medium', color: { argb: '000000' } } : { style: 'thin', color: { argb: '000000' } }
739
+ } as ExcelJS.Borders
740
+ }
741
+
742
+ // 如果想让顶部标题区也更像图片,可给 A1:B2 加一点边距感
743
+ worksheet.getCell('A1').alignment = {
744
+ vertical: 'middle',
745
+ horizontal: 'center',
746
+ wrapText: true
747
+ }
748
+
749
+ // 导出
750
+ const buffer = await workbook.xlsx.writeBuffer()
751
+ const blob = new Blob([buffer], {
752
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
753
+ })
754
+ saveBlobAsFile(blob, `${data.title}.xlsx`)
755
+ }
756
+
757
+ export async function exportBankExcelWithDefaultLogo(data: ExportFormData = exportFormDataDemo): Promise<void> {
758
+ const logo = await getStaticLogo()
759
+ await exportBankExcel(data, { logo })
760
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue2server7",
3
- "version": "4.0.0",
3
+ "version": "5.0.1",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "dev": "nodemon --watch src --ext ts --exec \"ts-node src/app.ts\"",