JSON 导出 EXCEL
ts
// 安装依赖:pnpm add xlsx file-saver @types/file-saver -D
import FileSaver from 'file-saver'
import * as XLSX from 'xlsx'
export interface StringKeyValue {
[key: string]: string
}
export type TableHeadProps = StringKeyValue
interface TreeDate {
[key: string]: string | TreeDate[]
}
export interface TreeProps {
id: string
parentId: string
children: string
}
class ExcelTransform {
private readonly headProps: TableHeadProps = {}
private readonly treeProps: TreeProps = {
id: 'id',
parentId: 'parentId',
children: 'children',
}
private readonly indent?: boolean = true
constructor(headProps: TableHeadProps, treeProps?: TreeProps, indent?: boolean) {
this.headProps = headProps
this.treeProps = {
...this.treeProps,
...treeProps,
}
this.indent = indent
}
get headPropsKeys(): string[] {
return Object.keys(this.headProps)
}
get headPropsValues(): string[] {
return Object.values(this.headProps)
}
jsonToExcel(data: TreeDate[], filename?: string): void {
// 获取需要导出的数据,假设已经存储在 data 中
// 定义表头
const headers: string[] = [ ...this.headPropsValues ]
// 定义表格数据
const rows: string[][] = this.flattenData(data)
// 将表头和表格数据组合成工作表
const worksheet = XLSX.utils.aoa_to_sheet([ headers, ...rows ])
// 将工作表添加到工作簿
const workbook = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1')
// 将工作簿转换为二进制数据流
const excelData: any = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' })
// 使用 file-saver 库保存文件
const blob: Blob = new Blob([ excelData ], { type: 'application/octet-stream' })
FileSaver.saveAs(blob, `${ filename ?? Date.now() }.xlsx`)
}
async excelToJson(file: Blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = (e) => {
const data = e.target?.result
if (!data) reject(false)
const workbook = XLSX.read(data, { type: 'binary' })
// 读取第一个工作表
const worksheet = workbook.Sheets[workbook.SheetNames[0]]
// 将工作表转换为 JSON 对象
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: this.headPropsKeys })
// 处理 JSON 数据
resolve(this.restoreTree(jsonData.slice(1) as TreeDate[]))
}
reader.readAsBinaryString(file)
})
}
private flattenData(data: TreeDate[], level: number = 1): string[][] {
const rows: string[][] = []
const fn: (data: TreeDate[], level: number) => void = (data, level: number): void => {
data.forEach(item => {
// 将每个节点展平成一行数据
const row: string[] = Object
.keys(this.headProps)
.map((v: string, i: number): string => {
let value = item[v]
if (typeof value !== 'string') {
value = value.toString()
}
const indent: string = ' '.repeat(level) // 使用全角空格缩进节点名
return (i === 0 && this.indent) ? indent + value : value
})
rows.push([ ...row ])
// 递归处理子节点
const children = item[this.treeProps.children]
if (children && typeof children !== 'string') {
fn(children, level + 1)
}
})
}
fn(data, level)
return rows
}
private restoreTree(list: TreeDate[]) {
const map = new Map()
list.forEach(node => {
node[this.treeProps['children']] = []
map.set(node[this.treeProps['id']], node)
})
list.forEach(node => {
const parent = map.get(node[this.treeProps['parentId']])
if (parent) {
parent.children.push(node)
}
})
return list.filter(node => !node[this.treeProps['parentId']])
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136