├── .eslintignore ├── app ├── public │ └── 绩效考核统计表 (1).xlsx ├── controller │ ├── home.js │ ├── simple.js │ ├── complex.js │ └── import.js ├── router.js ├── view │ └── home.tpl └── extend │ └── helper.js ├── .eslintrc ├── config ├── plugin.js └── config.default.js ├── .gitignore ├── .travis.yml ├── appveyor.yml ├── .autod.conf.js ├── test └── app │ └── controller │ └── home.test.js ├── README.md ├── README.zh-CN.md └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /app/public/绩效考核统计表 (1).xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wuwei9536/egg-exceljs/HEAD/app/public/绩效考核统计表 (1).xlsx -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-egg", 3 | "rules": { 4 | "linebreak-style": [0 ,"error", "windows"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /config/plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // had enabled by egg 4 | // exports.static = true; 5 | exports.nunjucks = { 6 | enable: true, 7 | package: 'egg-view-nunjucks', 8 | }; 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | npm-debug.log 3 | yarn-error.log 4 | node_modules/ 5 | package-lock.json 6 | yarn.lock 7 | coverage/ 8 | .idea/ 9 | run/ 10 | .DS_Store 11 | *.sw* 12 | *.un~ 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '8' 5 | install: 6 | - npm i npminstall && npminstall 7 | script: 8 | - npm run ci 9 | after_script: 10 | - npminstall codecov && codecov 11 | -------------------------------------------------------------------------------- /app/controller/home.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Controller = require('egg').Controller; 4 | 5 | class HomeController extends Controller { 6 | async index() { 7 | // this.ctx.body = 'hi, egg'; 8 | await this.ctx.render('home.tpl', {}); 9 | } 10 | } 11 | 12 | module.exports = HomeController; 13 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '8' 4 | 5 | install: 6 | - ps: Install-Product node $env:nodejs_version 7 | - npm i npminstall && node_modules\.bin\npminstall 8 | 9 | test_script: 10 | - node --version 11 | - npm --version 12 | - npm run test 13 | 14 | build: off 15 | -------------------------------------------------------------------------------- /app/router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {Egg.Application} app - egg application 5 | */ 6 | module.exports = app => { 7 | const { router, controller } = app; 8 | router.get('/', controller.home.index); 9 | router.get('/simple', controller.simple.excel); 10 | router.get('/complex', controller.complex.excel); 11 | router.post('/import', controller.import.index); 12 | }; 13 | -------------------------------------------------------------------------------- /.autod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | write: true, 5 | prefix: '^', 6 | plugin: 'autod-egg', 7 | test: [ 8 | 'test', 9 | 'benchmark', 10 | ], 11 | dep: [ 12 | 'egg', 13 | 'egg-scripts', 14 | ], 15 | devdep: [ 16 | 'egg-ci', 17 | 'egg-bin', 18 | 'egg-mock', 19 | 'autod', 20 | 'autod-egg', 21 | 'eslint', 22 | 'eslint-config-egg', 23 | 'webstorm-disable-index', 24 | ], 25 | exclude: [ 26 | './test/fixtures', 27 | './dist', 28 | ], 29 | }; 30 | 31 | -------------------------------------------------------------------------------- /test/app/controller/home.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { app, assert } = require('egg-mock/bootstrap'); 4 | 5 | describe('test/app/controller/home.test.js', () => { 6 | 7 | it('should assert', function* () { 8 | const pkg = require('../../../package.json'); 9 | assert(app.config.keys.startsWith(pkg.name)); 10 | 11 | // const ctx = app.mockContext({}); 12 | // yield ctx.service.xx(); 13 | }); 14 | 15 | it('should GET /', () => { 16 | return app.httpRequest() 17 | .get('/') 18 | .expect('hi, egg') 19 | .expect(200); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eggjs-exceljs 2 | 3 | 4 | 5 | ## QuickStart 6 | 7 | 8 | 9 | see [egg docs][egg] for more detail. 10 | 11 | ### Development 12 | 13 | ```bash 14 | $ npm i 15 | $ npm run dev 16 | $ open http://localhost:7001/ 17 | ``` 18 | 19 | ### Deploy 20 | 21 | ```bash 22 | $ npm start 23 | $ npm stop 24 | ``` 25 | 26 | ### npm scripts 27 | 28 | - Use `npm run lint` to check code style. 29 | - Use `npm test` to run unit test. 30 | - Use `npm run autod` to auto detect dependencies upgrade, see [autod](https://www.npmjs.com/package/autod) for more detail. 31 | 32 | 33 | [egg]: https://eggjs.org -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # eggjs-exceljs 2 | 3 | 4 | 5 | ## 快速入门 6 | 7 | 8 | 9 | 如需进一步了解,参见 [egg 文档][egg]。 10 | 11 | ### 本地开发 12 | 13 | ```bash 14 | $ npm i 15 | $ npm run dev 16 | $ open http://localhost:7001/ 17 | ``` 18 | 19 | ### 部署 20 | 21 | ```bash 22 | $ npm start 23 | $ npm stop 24 | ``` 25 | 26 | ### 单元测试 27 | 28 | - [egg-bin] 内置了 [mocha], [thunk-mocha], [power-assert], [istanbul] 等框架,让你可以专注于写单元测试,无需理会配套工具。 29 | - 断言库非常推荐使用 [power-assert]。 30 | - 具体参见 [egg 文档 - 单元测试](https://eggjs.org/zh-cn/core/unittest)。 31 | 32 | ### 内置指令 33 | 34 | - 使用 `npm run lint` 来做代码风格检查。 35 | - 使用 `npm test` 来执行单元测试。 36 | - 使用 `npm run autod` 来自动检测依赖更新,详细参见 [autod](https://www.npmjs.com/package/autod) 。 37 | 38 | 39 | [egg]: https://eggjs.org 40 | -------------------------------------------------------------------------------- /config/config.default.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // const path = require('path'); 3 | module.exports = appInfo => { 4 | const config = exports = {}; 5 | 6 | config.security = { 7 | csrf: { enable: false }, 8 | }; 9 | config.multipart = { 10 | mode: 'file', 11 | fileExtensions: [ 12 | '.xlsx', 13 | ], 14 | }; 15 | 16 | config.view = { 17 | // root: [ 18 | // path.join(appInfo.baseDir, 'app/view'), 19 | // ].join(','), 20 | defaultViewEngine: 'nunjucks', 21 | }; 22 | 23 | // use for cookie sign key, should change to your own and keep security 24 | config.keys = appInfo.name + '_1548729941824_4568'; 25 | 26 | // add your config here 27 | config.middleware = []; 28 | 29 | return config; 30 | }; 31 | -------------------------------------------------------------------------------- /app/controller/simple.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable*/ 3 | //一级表头适用 4 | exports.excel = async ctx => { 5 | // 请求数据地址 6 | let url = 'https://www.easy-mock.com/mock/5c46cad124390d27ad616890/api-node/selectRateList'; 7 | // 请求数据参数 8 | let param = { 9 | dataType: 'json', 10 | // data: { 11 | // } 12 | }; 13 | 14 | // t:title k:key w:width ==>表头 15 | let headers = [[ 16 | { t: '姓名', k: 'userName', w: 20 }, 17 | { t: '所属部门', k: 'deptName' }, 18 | { t: '自评分', k: 'selfRate' }, 19 | { t: 'learder评分', k: 'leaderRate' }, 20 | { t: '绩效结果', k: 'rateResult' }, 21 | ]]; 22 | 23 | await ctx.helper.excelNew(url, param, headers, '绩效考核统计表', function (res) { 24 | //数据二次处理函数 25 | }); 26 | } 27 | 28 | -------------------------------------------------------------------------------- /app/view/home.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 基于eggjs导入导出excel表格 4 | 5 | 6 |

基于eggjs导入导出excel表格

7 |
8 | {# 导出 #} 9 | 10 | 11 | {# 导入 #} 12 |
13 |
14 |
15 | {# fetch 方式 #} 16 | 17 | 18 | {# #} 19 |
20 |
21 |
22 | {# form表单提交方式 #} 23 |
24 | title: 25 | file: 26 | 27 |
28 | {# #} 29 |
30 | 31 | 48 | 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eggjs-exceljs", 3 | "version": "1.0.0", 4 | "description": "", 5 | "private": true, 6 | "dependencies": { 7 | "egg": "^2.2.1", 8 | "egg-scripts": "^2.5.0", 9 | "egg-view-nunjucks": "^2.2.0", 10 | "exceljs": "^1.7.0" 11 | }, 12 | "devDependencies": { 13 | "autod": "^3.0.1", 14 | "autod-egg": "^1.0.0", 15 | "egg-bin": "^4.3.5", 16 | "egg-ci": "^1.8.0", 17 | "egg-mock": "^3.14.0", 18 | "eslint": "^4.11.0", 19 | "eslint-config-egg": "^6.0.0", 20 | "webstorm-disable-index": "^1.2.0" 21 | }, 22 | "engines": { 23 | "node": ">=8.9.0" 24 | }, 25 | "scripts": { 26 | "start": "egg-scripts start --daemon --title=egg-server-eggjs-exceljs", 27 | "stop": "egg-scripts stop --title=egg-server-eggjs-exceljs", 28 | "dev": "egg-bin dev", 29 | "debug": "egg-bin debug", 30 | "test": "npm run lint -- --fix && npm run test-local", 31 | "test-local": "egg-bin test", 32 | "cov": "egg-bin cov", 33 | "lint": "eslint .", 34 | "ci": "npm run lint && npm run cov", 35 | "autod": "autod" 36 | }, 37 | "ci": { 38 | "version": "8" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "" 43 | }, 44 | "author": "wien", 45 | "license": "MIT" 46 | } 47 | -------------------------------------------------------------------------------- /app/controller/complex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable*/ 3 | const Controller = require('egg').Controller; 4 | 5 | class ComplexController extends Controller { 6 | async excel(ctx) { 7 | // let req = ctx.helper.data(['strBeginTime', 'strEndTime', 'deptId']); 8 | // req.deptId = req.deptId || ctx.session.user.deptId; 9 | 10 | let headers = [[ 11 | { t: '单位名称', f: 'deptName', w: 20, m1: 'A1', m2: 'A3', totalRowText: '合计' }, 12 | { t: '办理身份证证件', m1: 'B1', m2: 'M1' }, 13 | { t: '临时身份证证件', m1: 'N1', m2: 'O1' }, 14 | { t: '总计', m1: 'P1', m2: 'R2' } 15 | ], [ 16 | { t: '申领', m1: 'B2', m2: 'D2' }, 17 | { t: '换领', m1: 'E2', m2: 'G2' }, 18 | { t: '补领', m1: 'H2', m2: 'J2' }, 19 | { t: '小计', m1: 'K2', m2: 'M2' }, 20 | { t: '临时身份证', m1: 'N2', m2: 'O2' } 21 | ], [ 22 | { t: '本地人数', f: 'slbdCount', totalRow: true }, 23 | { t: '异地人数', f: 'slydCount', totalRow: true }, 24 | { t: '金额', f: 'slJe', totalRow: true }, 25 | { t: '本地人数', f: 'hlbdCount', totalRow: true }, 26 | { t: '异地人数', f: 'hlydCount', totalRow: true }, 27 | { t: '金额', f: 'hlJe', totalRow: true }, 28 | { t: '本地人数', f: 'blbdCount', totalRow: true }, 29 | { t: '异地人数', f: 'blydCount', totalRow: true }, 30 | { t: '金额', f: 'blJe', totalRow: true }, 31 | { t: '本地人数', f: 'xj_bdrs', totalRow: true }, 32 | { t: '异地人数', f: 'xj_ydrs', totalRow: true }, 33 | { t: '金额', f: 'xj_je', totalRow: true }, 34 | { t: '人数', f: 'lsCount', totalRow: true }, 35 | { t: '金额', f: 'lsJe', totalRow: true }, 36 | { t: '本地人数', f: 'hj_bdrs', totalRow: true }, 37 | { t: '异地人数', f: 'hj_ydrs', totalRow: true }, 38 | { t: '金额', f: 'hj_je', totalRow: true } 39 | ]]; 40 | await ctx.helper.excelNewComplex('https://www.baidu.com', 'req', headers, '身份证受理统计', function (res) { 41 | for (let i = 0, len = res.data.length; i < len; i++) { 42 | let r = res.data[i]; 43 | r.xj_bdrs = r.slbdCount + r.hlbdCount + r.blbdCount; 44 | r.xj_ydrs = r.slydCount + r.hlydCount + r.blydCount; 45 | r.xj_je = r.slJe + r.hlJe + r.blJe; 46 | r.hj_bdrs = r.slbdCount + r.hlbdCount + r.blbdCount + r.lsCount; 47 | r.hj_ydrs = r.slydCount + r.hlydCount + r.blydCount; 48 | r.hj_je = r.slJe + r.hlJe + r.blJe + r.lsJe; 49 | } 50 | return res; 51 | }); 52 | } 53 | } 54 | 55 | module.exports = ComplexController; -------------------------------------------------------------------------------- /app/controller/import.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable*/ 3 | const Controller = require('egg').Controller; 4 | const Excel = require('exceljs'); 5 | // const fs = require('fs'); 6 | // const path = require('path'); 7 | 8 | class submitController extends Controller { 9 | //适用于简单表格的导入,所以一般导入要先下载固定格式的excel表格才能正确解析 10 | async index() { 11 | const { ctx } = this; 12 | const file = ctx.request.files[0]; 13 | 14 | // fs.exists(path.resolve(__dirname, '../public/绩效考核统计表 (1).xlsx') //绝对路径 15 | // , (exists) => { 16 | // console.log(exists ? 'it\'s there' : 'no passwd!'); 17 | // }); 18 | 19 | let dataArray = [];//最后得到的Json对象数组 20 | let workbook = new Excel.Workbook(); 21 | await workbook.xlsx.readFile(file.filepath) //绝对路径 22 | .then(function () { 23 | let worksheet = workbook.getWorksheet(1); 24 | dataArray = changeRowsToDict(worksheet); 25 | console.log(JSON.stringify(dataArray)); 26 | ctx.body = dataArray; 27 | // console.log('异步'); 28 | }); 29 | 30 | // console.log('同步'); 31 | 32 | /* 将所有的行数据转换为json */ 33 | function changeRowsToDict(worksheet) { 34 | let dataArray = []; 35 | let keys = []; 36 | worksheet.eachRow(function (row, rowNumber) { 37 | if (rowNumber == 1) { 38 | keys = row.values; 39 | } 40 | else { 41 | // method1 =============== 42 | // let rowDict = cellValueToDict(keys, row.values); 43 | // dataArray.push(rowDict); 44 | // method2 =============== 45 | let rowDict = cellValueToDict2(keys, row); 46 | dataArray.push(rowDict); 47 | } 48 | }); 49 | return dataArray; 50 | } 51 | 52 | /* keys: {id,name,phone}, rowValue:每一行的值数组, 执行次数3次 */ 53 | function cellValueToDict(keys, rowValue) { 54 | let rowDict = {}; 55 | keys.forEach((value, index) => { 56 | rowDict[value] = rowValue[index]; 57 | }); 58 | return rowDict; 59 | } 60 | 61 | /* keys: {id,name,phone}, rowValue:每一行的值数组, 执行次数3次 */ 62 | function cellValueToDict2(keys, row) { 63 | let data = {}; 64 | row.eachCell({ includeEmpty: true }, function (cell, colNumber) { 65 | let value = cell.value; 66 | // if (typeof value == "object") value = value.text; 67 | data[keys[colNumber]] = value; 68 | }); 69 | return data; 70 | } 71 | } 72 | } 73 | 74 | module.exports = submitController; 75 | -------------------------------------------------------------------------------- /app/extend/helper.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | //simple 3 | /** 4 | * 请求接口得到数据并生成excel 5 | * 支持数据自定义 func 6 | * 支持数据字典 7 | * 支持日期 8 | * @param {string} url 接口地址:相对地址或者http开头完整地址 9 | * @param {object} param 请求数据参数 10 | * @param {Array} headers excel标题栏 11 | * @param {string} name 文件名称 12 | * @param {function} func 数据自定义函数 13 | */ 14 | 15 | //complex 16 | /** 17 | * 请求接口得到数据并生成excel 18 | * 支持复杂表头(m1:合并单元格左上坐标;m2:合并单元格右下坐标) 19 | * 支持合计行 totalRowText totalRow 20 | * 支持数据自定义 func 21 | * 支持数据字典 22 | * 支持日期 23 | */ 24 | 25 | const Excel = require('exceljs'); 26 | module.exports = { 27 | //simple 28 | async excelNew(url, param, headers, name, func) { 29 | let columns = [];//exceljs要求的columns 30 | let titleRows = headers.length;//标题栏行数 31 | 32 | //处理表头 33 | for (let i = 0; i < titleRows; i++) { 34 | let row = headers[i]; 35 | for (let j = 0, rlen = row.length; j < rlen; j++) { 36 | let col = row[j]; 37 | let { k, t, w = 15 } = col; 38 | if (!k) continue;//不存在k则跳过 39 | col.style = { alignment: { vertical: 'middle', horizontal: 'center' } }; 40 | col.header = t; 41 | col.key = k; 42 | col.width = w; 43 | columns.push(col); 44 | } 45 | } 46 | 47 | //请求并处理数据 48 | let result = await this.ctx.curl(url, param); 49 | // if(func) result = func(result); 50 | 51 | //生成excel 这一部门语法需看下exceljs 52 | let workbook = new Excel.Workbook(); 53 | let sheet = workbook.addWorksheet('绩效考核统计表', { views: [{ xSplit: 1, ySplit: 1 }] }); 54 | sheet.columns = columns; 55 | sheet.addRows(result.data.data) 56 | 57 | //处理样式、日期、字典项 58 | let that = this; 59 | sheet.eachRow(function (row, rowNumber) { 60 | //设置行高 61 | row.height = 25; 62 | 63 | row.eachCell({ includeEmpty: true }, function (cell, colNumber) { 64 | //设置边框 黑色 细实线 65 | let top = left = bottom = right = { style: 'thin', color: { argb: '000000' } }; 66 | cell.border = { top, left, bottom, right }; 67 | 68 | //设置标题部分为粗体 69 | if (rowNumber <= titleRows) { cell.font = { bold: true }; return; } 70 | 71 | //处理数据项里面的日期和字典 72 | let { type, dict } = columns[colNumber - 1]; 73 | if (type && (cell.value || cell.value == 0)) return;//非日期、字典或值为空的直接返回 74 | switch (type) { 75 | case 'date': cell.value = that.parseDate(cell.value); break; 76 | case 'dict': cell.value = that.parseDict(cell.value.toString(), dict); break; 77 | } 78 | 79 | }); 80 | }); 81 | 82 | this.ctx.set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); 83 | this.ctx.set('Content-Disposition', "attachment;filename*=UTF-8' '" + encodeURIComponent(name) + '.xlsx'); 84 | this.ctx.body = await workbook.xlsx.writeBuffer(); 85 | }, 86 | 87 | // ------------------------------------------------------------------------------------------------------------------------ 88 | 89 | //complex 90 | async excelNewComplex(url, req, headers, name, func) { 91 | let columns = [];//exceljs要求的columns 92 | let hjRow = {};//合计行 93 | let titleRows = headers.length;//标题栏行数 94 | 95 | //处理表头 96 | for (let i = 0; i < titleRows; i++) { 97 | let row = headers[i]; 98 | for (let j = 0, rlen = row.length; j < rlen; j++) { 99 | let col = row[j]; 100 | let { f, t, w = 15 } = col; 101 | if (!f) continue;//不存在f则跳过 102 | 103 | if (col.totalRow) hjRow[f] = true; 104 | if (col.totalRowText) hjRow[f] = col.totalRowText; 105 | col.style = { alignment: { vertical: 'middle', horizontal: 'center' } }; 106 | col.header = t; 107 | col.key = f; 108 | col.width = w; 109 | columns.push(col); 110 | } 111 | } 112 | 113 | // const result = await this.post(url, req);//请求数据 114 | // let result = await this.ctx.curl(url); 115 | // let data = result.data; 116 | // if (func) data = func(data); 117 | let data = { "data": [] } //需要在这边自己适配数据,这边为空 118 | 119 | //处理合计行 120 | if (JSON.stringify(hjRow) != "{}") { 121 | let tr = {}; 122 | for (let i = 0, len = data.data.length; i < len; i++) { 123 | let item = data.data[i]; 124 | for (let key in item) { 125 | if (hjRow[key] === true) { 126 | tr[key] = (tr[key] || 0) + item[key]; 127 | continue; 128 | } 129 | tr[key] = hjRow[key] || ''; 130 | } 131 | } 132 | data.data.push(tr); 133 | } 134 | 135 | let workbook = new Excel.Workbook(); 136 | let sheet = workbook.addWorksheet('My Sheet', { views: [{ xSplit: 1, ySplit: 1 }] }); 137 | sheet.columns = columns; 138 | sheet.addRows(data.data); 139 | 140 | //处理复杂表头 141 | if (titleRows > 1) { 142 | for (let i = 1; i < titleRows; i++) sheet.spliceRows(1, 0, []);//头部插入空行 143 | 144 | for (let i = 0; i < titleRows; i++) { 145 | let row = headers[i]; 146 | for (let j = 0, rlen = row.length; j < rlen; j++) { 147 | let col = row[j]; 148 | if (!col.m1) continue; 149 | 150 | sheet.getCell(col.m1).value = col.t; 151 | sheet.mergeCells(col.m1 + ":" + col.m2); 152 | } 153 | } 154 | } 155 | 156 | //处理样式、日期、字典项 157 | let that = this; 158 | sheet.eachRow(function (row, rowNumber) { 159 | //设置行高 160 | row.height = 25; 161 | 162 | row.eachCell({ includeEmpty: true }, function (cell, colNumber) { 163 | //设置边框 黑色 细实线 164 | let top = left = bottom = right = { style: 'thin', color: { argb: '000000' } }; 165 | cell.border = { top, left, bottom, right }; 166 | 167 | //设置标题部分为粗体 168 | if (rowNumber <= titleRows) { cell.font = { bold: true }; return; } 169 | 170 | //处理数据项里面的日期和字典 171 | let { type, dict } = columns[colNumber - 1]; 172 | if (type && (cell.value || cell.value == 0)) return;//非日期、字典或值为空的直接返回 173 | switch (type) { 174 | case 'date': cell.value = that.parseDate(cell.value); break; 175 | case 'dict': cell.value = that.parseDict(cell.value.toString(), dict); break; 176 | } 177 | 178 | }); 179 | }); 180 | 181 | this.ctx.set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); 182 | this.ctx.set('Content-Disposition', "attachment;filename*=UTF-8" + encodeURIComponent(name) + '.xlsx'); 183 | this.ctx.body = await workbook.xlsx.writeBuffer(); 184 | }, 185 | }; 186 | 187 | --------------------------------------------------------------------------------