22//
33// SPDX-License-Identifier: MIT
44
5- import xlsx from 'xlsx' ;
5+ import type { CellValue , ColumnDef , WriteOptions } from 'hucre' ;
6+ import { toMarkdown , writeCsv , writeOds , writeXlsx } from 'hucre' ;
67
78import type { CellRenderFunc , CellType , Column } from './column' ;
89import { presetCellRenderFunc } from './column' ;
10+ import { createDownloadLink } from './download' ;
911import type { Page , Query } from './query' ;
1012
1113/**
@@ -26,11 +28,25 @@ export class Exporter<T extends object, Q extends Query> {
2628 /**
2729 * 支持可导出的文件扩展名
2830 */
29- static exts = [ '.csv' , '.xlsx' , '.numbers ' , '.ods' ] ;
31+ static exts = [ '.csv' , '.xlsx' , '.md ' , '.ods' ] as const ;
3032
31- readonly #sheet: xlsx . WorkSheet ;
33+ /**
34+ * 表格的列定义
35+ *
36+ * 这里的 content 主要是改为必选项
37+ */
3238 readonly #columns: Array < Omit < Column < T > , 'content' > & { content : CellRenderFunc < T > } > ;
3339
40+ /**
41+ * 表格的表头
42+ */
43+ readonly #header: Array < string > ;
44+
45+ /**
46+ * 表格的数据
47+ */
48+ readonly #rows: Array < Array < CellValue > > = [ ] ;
49+
3450 /**
3551 * 构造函数
3652 *
@@ -44,30 +60,30 @@ export class Exporter<T extends object, Q extends Query> {
4460 } ;
4561 } ) ;
4662
47- const row : Array < string > = [ ] ;
63+ const header : Array < string > = [ ] ;
4864 for ( const c of cols ) {
4965 if ( ! c . isUnexported ) {
50- row . push ( c . label ?? c . id . toString ( ) ) ;
66+ header . push ( c . label ?? c . id . toString ( ) ) ;
5167 }
5268 }
53- this . #sheet = xlsx . utils . aoa_to_sheet ( [ row ] ) ;
69+ this . #header = header ;
5470 }
5571
5672 /**
5773 * 添加一至多行数据
5874 */
5975 #append( ...rows : Array < T > ) : void {
6076 for ( const row of rows ) {
61- const data : Array < CellType > = [ ] ;
77+ const line : Array < CellType > = [ ] ;
6278 for ( const c of this . #columns) {
6379 if ( c . isUnexported ) {
6480 continue ;
6581 }
6682 const val = ( row [ c . id as keyof T ] ?? undefined ) as Parameters < CellRenderFunc < T > > [ 1 ] ;
67- data . push ( c . content ( c . id , val , row ) ) ;
83+ line . push ( c . content ( c . id , val , row ) ) ;
6884 }
6985
70- xlsx . utils . sheet_add_aoa ( this . #sheet , [ data ] , { origin : - 1 } ) ;
86+ this . #rows . push ( line . map ( v => ( v === undefined ? null : v ) ) ) ;
7187 }
7288 }
7389
@@ -86,35 +102,72 @@ export class Exporter<T extends object, Q extends Query> {
86102 }
87103 }
88104
105+ #buildHeader( ) : Array < ColumnDef > {
106+ return this . #header. map ( h => ( { header : h } ) ) ;
107+ }
108+
89109 /**
90110 * 导出数据
91111 *
92112 * 将 {@link Exporter#fetch} 下载的数据导出给用户。
93113 *
94- * @param filename - 文件名,如果是 excel,也作为工作表的名称;
95- * @param lang - 语言;
96- * @param ext - 后缀名,根据此值生成不同类型的文件;
97- * @param appName - 部分格式的元数据中会标注的应用名称;
98- * @param appVersion - 部分格式的元数据中会标注的应用版本;
114+ * @param filename - 文件名,如果是 excel 和 ods,文件名部分也作为工作表的名称;
99115 *
100116 * NOTE: 这将通过浏览器创建一个自动下载的功能。
101117 */
102- export (
103- filename : string ,
104- ext : ( typeof Exporter . exts ) [ number ] ,
105- lang ?: string ,
106- appName ?: string ,
107- appVersion ?: string ,
108- ) : void {
109- const book = xlsx . utils . book_new ( this . #sheet, filename ) ;
110- const d = new Date ( ) ;
111- book . Props = {
112- ModifiedDate : d ,
113- CreatedDate : d ,
114- Language : lang ,
115- Application : appName ,
116- AppVersion : appVersion ,
118+ async export ( filename : `${string } ${( typeof Exporter . exts ) [ number ] } `) : Promise < void > {
119+ const index = filename . lastIndexOf ( '.' ) ;
120+ const ext = filename . slice ( index ) ;
121+ const basename = filename . slice ( 0 , index ) ;
122+
123+ switch ( ext ) {
124+ case '.csv' : {
125+ const content = writeCsv ( this . #rows, { headers : this . #header } ) ;
126+ createDownloadLink ( content , filename , 'text/csv' ) ;
127+ break ;
128+ }
129+ case '.xlsx' : {
130+ const content = await writeXlsx ( this . buildWriteOptions ( basename ) ) ;
131+ createDownloadLink (
132+ content . buffer as ArrayBuffer ,
133+ filename ,
134+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ,
135+ ) ;
136+ break ;
137+ }
138+ case '.md' : {
139+ const sheet = {
140+ name : basename ,
141+ columns : this . #buildHeader( ) ,
142+ rows : this . #rows,
143+ } ;
144+ const content = toMarkdown ( sheet ) ;
145+ createDownloadLink ( content , filename , 'text/markdown' ) ;
146+ break ;
147+ }
148+ case '.ods' : {
149+ const content = await writeOds ( this . buildWriteOptions ( basename ) ) ;
150+ createDownloadLink ( content . buffer as ArrayBuffer , filename , 'application/vnd.oasis.opendocument.spreadsheet' ) ;
151+ break ;
152+ }
153+ }
154+ }
155+
156+ buildWriteOptions ( basename : string ) : WriteOptions {
157+ const now = new Date ( ) ;
158+ return {
159+ sheets : [
160+ {
161+ name : basename ,
162+ columns : this . #buildHeader( ) ,
163+ rows : this . #rows,
164+ } ,
165+ ] ,
166+ properties : {
167+ title : basename ,
168+ created : now ,
169+ modified : now ,
170+ } ,
117171 } ;
118- xlsx . writeFile ( book , filename + ext ) ;
119172 }
120173}
0 commit comments