Skip to content

Commit e9d7da4

Browse files
author
Kelvin Poon
committed
feat:支持将多个docx文件打包成一个zip文件再下载,突破浏览器单次最多只可下载10个附件的限制
1 parent d559275 commit e9d7da4

File tree

8 files changed

+156
-57
lines changed

8 files changed

+156
-57
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@
6565

6666
<br/>
6767

68+
**单/多选字段如何取值?**
69+
70+
单选字段直接用括号即可取值,例如:```{类目}```
71+
多选字段的取值方式,跟成员字段略有不同,```{#字段名字}{.}{/字段的名字}```
72+
73+
6874
## 🙋‍♂️ 常见问题
6975

7076
**word模板修改完毕后需要重新上传,是每一行都要上传一次吗?**
@@ -94,6 +100,21 @@ safari的浏览器拦截了,暂不支持进行批量下载,只能一个一
94100
<br/>
95101

96102
## 🎯 更新日志
103+
v0.1.20 - 2022年6月30日
104+
- 【新增】批量导出功能升级,支持将多个docx文件打包成一个zip文件再下载,突破浏览器单次最多只可下载10个附件的限制
105+
106+
v0.1.19 - 2022年5月25日
107+
- 【新增】支持维格表深色主题
108+
109+
v0.1.16 - 2022年4月29日
110+
- 【优化】增加空文件的判断提醒。
111+
112+
v0.1.14 - 2022年4月28日
113+
- 【调整】简化单选字段的取值方式,改为跟普通文本字段一样的语法```{字段名称}```
114+
115+
v0.1.13 - 2022年4月27日
116+
- 【修复】无法读取多选字段内容的BUG
117+
97118
v0.1.10 - 2022年3月22日
98119
- 【优化】增加“教程”超链接
99120
- 【优化】调整部分交互逻辑

cover.jpg

21.5 KB
Loading

cover.png

-5.67 KB
Binary file not shown.

package.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
2-
"name": "word-document-generator",
3-
"version": "0.1.10",
2+
"version": "0.1.19",
43
"description": "a vika widget",
54
"engines": {
65
"node": ">=8.x"
@@ -28,9 +27,9 @@
2827
"dependencies": {
2928
"@types/react": "^16.9.43",
3029
"@types/react-dom": "^16.9.8",
31-
"@vikadata/components": "^0.0.3",
32-
"@vikadata/icons": "^0.0.1",
33-
"@vikadata/widget-sdk": "0.0.3",
30+
"@vikadata/components": "0.0.4",
31+
"@vikadata/icons": "0.0.1",
32+
"@vikadata/widget-sdk": "0.0.7",
3433
"docxtemplater": "^3.23.2",
3534
"file-saver": "^2.0.5",
3635
"pizzip": "^3.1.1",

src/developer_template.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const WidgetDeveloperTemplate: React.FC = () => {
1111
const permission = datasheet?.checkPermissionsForAddRecord()
1212

1313
return (
14-
<div style={{ display: 'flex', height: '100%', backgroundColor: '#fff', borderTop: '1px solid gainsboro' }}>
14+
<div style={{ display: 'flex', height: '100%', background: 'var(--defaultBg)', borderTop: '1px solid var(--lineColor)' }}>
1515
<div style={{ flexGrow: 1, overflow: 'auto', padding: '0 8px'}}>
1616
<DocxGenerator />
1717
</div>

src/docx_generator.tsx

Lines changed: 112 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useRecords, useFields, useActiveViewId, useSelection, useCloudStorage, useSettingsButton, useViewport, useField, Field, Record, IAttachmentValue, usePrimaryField, FieldType, useDatasheet } from '@vikadata/widget-sdk';
2-
import { Button } from '@vikadata/components';
2+
import { Button, IButtonProps } from '@vikadata/components';
33
import { InformationSmallOutlined } from '@vikadata/icons';
44
import React, { useEffect, useState } from 'react';
55
import Docxtemplater from 'docxtemplater';
@@ -75,8 +75,12 @@ function throwError(error: any) {
7575
/**
7676
* 遍历已选择的多条 record ,从中获取数据并生成 word 文档
7777
*/
78-
function generateDocuments(selectedRecords: Record[], fields: Field[], selectedAttachmentField: Field, primaryField: Field) {
78+
async function generateDocuments(selectedRecords: Record[], fields: Field[], selectedAttachmentField: Field, primaryField: Field) {
79+
const outputZip = new PizZip();
7980

81+
// 鼠标只选择了一行
82+
const single = selectedRecords.length>1 ? false : true;
83+
var outputs = []
8084

8185
for (let index = 0; index < selectedRecords.length; index++) {
8286
const record = selectedRecords[index]
@@ -92,11 +96,12 @@ function generateDocuments(selectedRecords: Record[], fields: Field[], selectedA
9296
row[field.name] = row[field.name].map(item => {
9397
return item.name
9498
})
99+
} else if(field.type == FieldType.SingleSelect){
100+
const selectOption = record.getCellValue(field.id)
101+
row[field.name] = selectOption ? selectOption.name : ""
95102
}
96103
})
97104

98-
99-
100105
const attachements = record.getCellValue(selectedAttachmentField.id)
101106
if (!attachements) {
102107
alert(`在指定的附件字段中找不到word模板,请上传。record:[${filename}]`)
@@ -114,11 +119,47 @@ function generateDocuments(selectedRecords: Record[], fields: Field[], selectedA
114119

115120
console.log({ row, attachements, prefix, suffix })
116121

117-
attachements && generateDocument(row, attachements[0], prefix + "-" + filename)
122+
if(attachements) {
123+
await generateDocument(row, attachements[0], prefix + "-" + filename, outputs)
124+
}
125+
}
126+
127+
console.log("outputs", outputs)
128+
if(outputs.length>0){
129+
let existedFilenames:Array<string> = []
130+
const outputFileName = !single ? `documents.zip` : (outputs[0] as any).filename + ".docx"
131+
132+
if(!single){
133+
for (let index = 0; index < outputs.length; index++) {
134+
const docxItem:any = outputs[index];
135+
const uniqueFilename = getUniqueFilename(existedFilenames, docxItem.filename)
136+
existedFilenames.push(uniqueFilename)
137+
outputZip.file(uniqueFilename + ".docx", docxItem.content)
138+
}
139+
const content = outputZip.generate({ type: "blob" })
140+
saveAs(content, outputFileName)
141+
} else {
142+
const outputZip = new PizZip((outputs[0] as any).content)
143+
saveAs(outputZip.generate({ type: "blob" }), outputFileName)
144+
}
145+
118146
}
119147

120148
}
121149

150+
const getUniqueFilename = (existedFilenames, newFilename) => {
151+
let targetFileName = newFilename
152+
if (existedFilenames.indexOf(newFilename)>-1) {
153+
for(var i=1; i<9999; i++){
154+
targetFileName = newFilename + "_" + i
155+
if (existedFilenames.indexOf(targetFileName) == -1) {
156+
break;
157+
}
158+
}
159+
}
160+
return targetFileName
161+
}
162+
122163
/**
123164
* Docxtemplater 自定义标签解析器
124165
* @param tag 标签名称,eg: {产品名称}
@@ -141,10 +182,13 @@ function generateDocuments(selectedRecords: Record[], fields: Field[], selectedA
141182
get(scope, context: DXT.ParserContext) {
142183
console.log({ tag, scope, context, TernaryResult })
143184

185+
if (tag === ".") {
186+
return (typeof scope == "string") ? scope : JSON.stringify(scope)
187+
}
188+
144189
if (["$index", "$序号"].includes(tag)) {
145190
const indexes = context.scopePathItem
146191
return indexes[indexes.length - 1] + 1
147-
148192
} else if(tag === "$isLast"){
149193
const totalLength = context.scopePathLength[context.scopePathLength.length - 1]
150194
const index = context.scopePathItem[context.scopePathItem.length - 1]
@@ -188,52 +232,53 @@ function generateDocuments(selectedRecords: Record[], fields: Field[], selectedA
188232
/**
189233
* 调用第三方库,生成word文档并调起浏览器附件下载事件
190234
*/
191-
function generateDocument(row: any, selectedAttachment: IAttachmentValue, filename: string) {
192-
193-
loadFile(selectedAttachment.url, function (error, content) {
194-
if (error) {
195-
throw error
196-
}
197-
198-
const zip = new PizZip(content)
199-
200-
try {
235+
async function generateDocument(row: any, selectedAttachment: IAttachmentValue, filename: string, outputs: any) {
236+
return new Promise<void>((resolve, reject) => {
237+
loadFile(selectedAttachment.url, function (error, content: ArrayBuffer) {
238+
if (error) {
239+
throw error
240+
}
201241

202-
const doc = new Docxtemplater(zip, {
203-
paragraphLoop: true,
204-
linebreaks: true,
205-
parser: customParser,
206-
})
242+
if (0 == content.byteLength) {
243+
return alert("Word模板文件的内容为空,请按照教程语法提前填写。")
244+
}
207245

208-
doc.setData({...row, userGreeting: (scope) => {
209-
return "How is it going, " + scope.user + " ? ";
210-
}})
246+
const zip = new PizZip(content)
211247

212248
try {
213-
doc.render();
214-
} catch (error: any) {
215-
throwError(error)
216-
}
217-
218-
const out = doc.getZip().generate({
219-
type: 'blob',
220-
mimeType:
221-
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
222-
});
223249

224-
saveAs(out, filename + ".docx")
225-
} catch (error) {
226-
console.log("错误信息", error)
227-
alert(`文件 ${selectedAttachment.name} 的模板语法不正确,请检查`)
228-
}
250+
const doc = new Docxtemplater(zip, {
251+
paragraphLoop: true,
252+
linebreaks: true,
253+
parser: customParser,
254+
})
229255

256+
try {
257+
doc.setData({...row}).render();
258+
} catch (error: any) {
259+
throwError(error)
260+
}
230261

231-
// await uploadAttachment(activeDatasheetId, out).then(res => {
232-
// console.log(res)
233-
// })
262+
const out = doc.getZip().generate({
263+
type: 'arraybuffer',
264+
mimeType:
265+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
266+
});
234267

268+
outputs.push({
269+
"content": out,
270+
"filename": filename
271+
})
235272

236-
});
273+
//saveAs(out, filename + ".docx")
274+
resolve()
275+
} catch (error) {
276+
console.log("错误信息", error)
277+
alert(`文件 ${selectedAttachment.name} 的模板语法不正确,请检查`)
278+
reject()
279+
}
280+
})
281+
})
237282
}
238283

239284
/**
@@ -285,6 +330,7 @@ export const DocxGenerator: React.FC = () => {
285330
const selectedAttachmentField = useField(fieldId)
286331

287332
const [selectedRecords, setSelectedRecords] = useState<Record[]>([])
333+
const [processing, setProcessing] = useState<Boolean>(false)
288334

289335
const recordIds = selectionRecords.map((record: Record)=>{
290336
return record.recordId
@@ -327,6 +373,14 @@ export const DocxGenerator: React.FC = () => {
327373
</a>
328374
)
329375

376+
let btnProps:IButtonProps = {
377+
variant: "fill",
378+
color: "primary",
379+
size:"small"
380+
}
381+
382+
if(processing) btnProps.disabled = true;
383+
330384
return (
331385
<div style={style1}>
332386
{helpLink}
@@ -348,7 +402,20 @@ export const DocxGenerator: React.FC = () => {
348402
</div>
349403

350404
<div style={{ marginTop: '16px', textAlign: 'center' }}>
351-
{(selectedRecords.length > 0) ? <Button onClick={(e)=> generateDocuments(selectedRecords, fields, selectedAttachmentField, primaryField)} variant="fill" color="primary" size="small" >导出 Word 文档</Button> : "请点击表格任意单元格"}
405+
{
406+
(selectedRecords.length > 0) ?
407+
<Button
408+
{...btnProps}
409+
onClick={async(e)=> {
410+
setProcessing(true)
411+
await generateDocuments(selectedRecords, fields, selectedAttachmentField, primaryField)
412+
setProcessing(false)
413+
}}
414+
>
415+
{!processing ? "导出 Word 文档" : "生成中,请稍候"}
416+
</Button> :
417+
"请点击表格任意单元格"
418+
}
352419
</div>
353420
</div>
354421
}
@@ -362,7 +429,7 @@ export const DocxGenerator: React.FC = () => {
362429
alignItems: 'center',
363430
width: '100%'
364431
}}>
365-
<img src='https://s1.vika.cn/space/2021/12/29/5a4c225aed81490583cedbecf4bc3419' style={{ width: '30%' }} />
432+
<img src='https://s1.vika.cn/space/2021/12/29/5a4c225aed81490583cedbecf4bc3419' style={{ width: '48px' }} />
366433
</div>
367434
<div>请设置一个存储word模板的附件字段</div>
368435

src/setting.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { useSettingsButton, useCloudStorage, FieldPicker, useActiveViewId, useFields, useViewIds } from '@vikadata/widget-sdk';
2+
import { useSettingsButton, useCloudStorage, FieldPicker, useActiveViewId, useFields, useViewIds, FieldType } from '@vikadata/widget-sdk';
33
import { InformationSmallOutlined } from '@vikadata/icons';
44

55
export const Setting: React.FC = () => {
@@ -23,11 +23,23 @@ export const Setting: React.FC = () => {
2323
}
2424

2525
return isShowingSettings ? (
26-
<div style={{ flexShrink: 0, width: '300px', borderLeft: 'solid 1px gainsboro', paddingLeft: '16px', paddingTop: '40px', paddingRight: '16px', backgroundColor: '#fff' }}>
27-
<h3>配置 <a style={{verticalAlign: "middle"}} title="查看教程" target="_blank" href="https://bbs.vika.cn/article/111" ><InformationSmallOutlined size={16} /></a> </h3>
26+
<div style={{ flexShrink: 0, width: '300px', borderLeft: 'solid 1px var(--lineColor)', paddingLeft: '16px', paddingTop: '40px', paddingRight: '16px', background: 'var(--defaultBg)' }}>
27+
<h3 style={{color: 'var(--firstLevelText)'}}>
28+
配置
29+
<a style={{verticalAlign: "middle", color: 'var(--thirdLevelText)', marginLeft: "4px"}} title="查看教程" target="_blank" href="https://bbs.vika.cn/article/111" >
30+
<InformationSmallOutlined size={16} />
31+
</a>
32+
</h3>
2833
<div style={{ marginTop: '16px' }}>
29-
<label style={{ fontSize: '12px', color: '#999' }}>请选择 word 模板所在的附件列名</label>
30-
<FieldPicker viewId={activeViewId?activeViewId:viewIds[0]} fieldId={fieldId} onChange={option => checkAndUpdateSelectedAttachmentField(option.value)} />
34+
<label style={{ fontSize: '12px', color: 'var(--thirdLevelText)' }}>请选择 word 模板所在的附件列名</label>
35+
<div style={{background: 'var(--fill0)'}}>
36+
<FieldPicker
37+
viewId={activeViewId?activeViewId:viewIds[0]}
38+
fieldId={fieldId}
39+
onChange={option => checkAndUpdateSelectedAttachmentField(option.value)}
40+
allowedTypes={[FieldType.Attachment]}
41+
/>
42+
</div>
3143
</div>
3244
</div>
3345
) : null;

widget.config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@
1616
"zh-CN": "根据docx模板,一键批量填充字段并合成新的word文档",
1717
"en-US": "Create docx file with specified fields"
1818
},
19-
"globalPackageId": "wpkIXaCPTCI8A"
19+
"website": "https://bbs.vika.cn/article/111"
2020
}

0 commit comments

Comments
 (0)