需求:前端使用了 LuckySheet 来实现表格,现在需要实现对这个表格带有样式的导入及导出。在导入时只导入绿色背景单元格中的数据,其他背景颜色单元格的数据将有 LuckySheet 公式自动计算得出。
安装的库
首先安装 luckysheet
然后是其他一些库
"dependencies": {
...
"luckyexcel": "1.0.1",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.18.9/xlsx-0.18.9.tgz",
"xlsx-style": "file:./src/libs/xlsx-style" // 在 xlsx 中有一些代码异常,需要
},
其中 xlsx-style 项目有一个 bug,所以我把项目放到本地进行了小改动。所以可以看到 xlsx-style 的路径是本地路径。
全局配置
// config.js
export default {
SHEET_COLUMN_KEYS: [ 'a', 'b', 'c' ],
VALUE_CELL_COLORS: ['#00B050'], // 可以编辑区域的背景色
DOWNLOAD_FILE_NAME: 'file',
}
导出 Excel
// exportExcel.js
import * as XLSX from 'xlsx'
import * as XLSXStyle from 'xlsx-style'
import config from './config'
export default function () {
// 1. 获取到要下载的数据
const allSheetData = window.luckysheet.getluckysheetfile()
const sheet1 = allSheetData[0]
const downOriginData = sheet1.data
let arr = [] // 所有的单元格数据组成的二维数组
const fConfig = {}
let bgConfig = {}
let percentageReg = /%$/
//列下标 数字转字母
function chatatABC (n) {
var orda = 'a'.charCodeAt(0)
var ordz = 'z'.charCodeAt(0)
var len = ordz - orda + 1
var s = ''
while (n >= 0) {
s = String.fromCharCode((n % len) + orda) + s
n = Math.floor(n / len) - 1
}
return s.toUpperCase()
}
// 获取单元格的背景色
function setBackground (row, col, bg) {
var colA = chatatABC(col)
var key = colA + (row + 1)
bgConfig[key] = bg.replace(/\#?/, '')
}
function setFormat (row, col, f) {
var colA = chatatABC(col)
var key = colA + (row + 1)
fConfig[key] = f
}
// 判断值类型是否为百分比 %
function isPercentage (value) {
return percentageReg.test(value.m) && value.ct && value.ct.t === 'n'
}
// 获取二维数组
for (let row = 0; row < downOriginData.length; row++) {
let arrRow = []
for (let col = 0; col < downOriginData[row].length; col++) {
const cellValue = downOriginData[row][col]
// if (row === 0) console.log('cellValue', cellValue)
if (cellValue) {
// 处理单元格的背景颜色
if (cellValue.bg) {
setBackground(row, col, cellValue.bg)
}
if (cellValue.f) {
setFormat(row, col, cellValue.f)
}
if (cellValue.ct != null && cellValue.ct.t == 'd') {
// d为时间格式 2019-01-01 或者2019-01-01 10:10:10
arrRow.push(new Date(cellValue.m.replace(/\-/g, '/'))) //兼容IE
} else if (cellValue.m && isPercentage(cellValue)) {
//百分比问题
arrRow.push(cellValue.m)
} else {
arrRow.push(cellValue.v)
}
} else {
arrRow.push(undefined)
}
}
arr.push(arrRow)
}
// 2. 通过SheetJs将数据转化为excel格式数据
let opts = {
dateNF: 'm/d/yy h:mm',
cellDates: true,
cellStyles: true
}
let ws = XLSX.utils.aoa_to_sheet(arr, opts)
// 3. 设置单元格的类型以及单元格样式
let reg = /[\u4e00-\u9fa5]/g
for (let key in ws) {
let item = ws[key]
if (item.t === 'd') {
if (item.w) {
//时间格式的设置
let arr = item.w.split(' ')
if (arr[1] && arr[1] == '0:00') {
ws[key].z = 'm/d/yy'
} else {
item.z = 'yyyy/m/d h:mm:ss'
}
}
} else if (item.t === 's') {
//百分比设置格式
if (item.v && !item.v.match(reg) && item.v.indexOf('%') > -1) {
item.t = 'n'
item.z = '0.00%'
item.v = Number.parseFloat(item.v) / 100
} else if (item.v && item.v.match(reg)) {
//含有中文的设置居中样式
item['s'] = {
alignment: { vertical: 'center', horizontal: 'center' }
}
}
}
// 设置单元格样式
if (bgConfig[key]) {
ws[key].s = {
alignment: { vertical: 'center', horizontal: 'center' },
fill: { bgColor: { indexed: 32 }, fgColor: { rgb: bgConfig[key] } },
border: {
top: { style: 'thin', color: { rgb: '999999' } },
bottom: { style: 'thin', color: { rgb: '999999' } },
left: { style: 'thin', color: { rgb: '999999' } },
right: { style: 'thin', color: { rgb: '999999' } }
}
}
}
if (fConfig[key]) {
ws[key].f = fConfig[key]
}
}
console.log('ws', ws)
// 4. 组装下载数据格式
let name = 'sheet1'
let tmpWB = {
SheetNames: [name], //保存的表标题
Sheets: {
[name]: Object.assign({}, ws) //内容
}
}
// 5. 合并单元格配置
let mergeConfig = sheet1.config.merge
let mergeArr = []
if (JSON.stringify(mergeConfig) !== '{}') {
mergeArr = handleMergeData(mergeConfig)
tmpWB.Sheets[name]['!merges'] = mergeArr
}
//处理合并单元格config数据
function handleMergeData (origin) {
let result = []
if (origin instanceof Object) {
var r = 'r',
c = 'c',
cs = 'cs',
rs = 'rs'
for (var key in origin) {
var startR = origin[key][r]
var endR = origin[key][r]
var startC = origin[key][c]
var endC = origin[key][c]
// 如果只占一行 为1 如果占两行 为2
if (origin[key][cs] > 0) {
endC = startC + (origin[key][cs] - 1)
}
if (origin[key][rs] > 0) {
endR = startR + (origin[key][rs] - 1)
}
// s为合并单元格的开始坐标 e为结束坐标
var obj = { s: { r: startR, c: startC }, e: { r: endR, c: endC } }
result.push(obj)
}
}
return result
}
// 6. 写入文件
let fileName = config.DOWNLOAD_FILE_NAME
// sheetjs js-xlsx 的方法 ,导出不带有样式的表格
// XLSX.writeFile(tmpWB, fileName + '.xlsx')
/**
* 通过 xlsx-style 来下载带有颜色的表格数据
* 目前存在一个小问题,需要去 node_modules 里面清理一行代码否则会报错。
* https://github.com/protobi/js-xlsx/issues/78#issuecomment-315063340
*/
function s2ab (s) {
if (typeof ArrayBuffer !== 'undefined') {
var buf = new ArrayBuffer(s.length)
var view = new Uint8Array(buf)
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff
return buf
} else {
var buf = new Array(s.length)
for (var i = 0; i != s.length; ++i) buf[i] = s.charCodeAt(i) & 0xff
return buf
}
}
function saveAs (obj, fileName) {
var tmpa = document.createElement('a')
tmpa.download = fileName || 'download'
tmpa.href = URL.createObjectURL(obj)
tmpa.click()
setTimeout(function () {
URL.revokeObjectURL(obj)
}, 100)
}
ws = new Blob([
s2ab(
XLSXStyle.write(tmpWB, {
bookType: 'xlsx',
bookSST: false,
type: 'binary'
}) //这里的数据是用来定义导出的格式类型
)
])
saveAs(ws, fileName + '.xlsx')
}
导入 excel
// importExcel.js
import LuckyExcel from 'luckyexcel'
import InitExcelJson from './data'
import config from './config'
import { Message } from 'element-ui'
export default function (files, cb) {
if (files == null || files.length == 0) {
Message.warning('取消导入文件!')
return
}
let name = files[0].name
let suffixArr = name.split('.'),
suffix = suffixArr[suffixArr.length - 1]
if (suffix != 'xlsx') {
Message.warning('当前只支持导入 xlsx 文件!')
return
}
// 得到xlsx文件后
LuckyExcel.transformExcelToLucky(
files[0],
function (exportJson, luckysheetfile) {
console.log('import', exportJson, luckysheetfile)
// 转换后获取工作表数据
if (exportJson.sheets == null || exportJson.sheets.length == 0) {
Message.warning('读取 xlsx 文件失败!')
return
}
updateSheet(exportJson)
if (cb) {
cb()
}
// const mixinJson = mixinStyle(exportJson) // 进行一次转化
// window.luckysheet.destroy()
// window.luckysheet.create({
// container: 'luckysheet', //luckysheet is the container id
// showinfobar: false,
// data: mixinJson.sheets,
// title: mixinJson.info.name,
// userInfo: mixinJson.info.name.creator
// })
},
function (error) {
console.log('error', error)
// 如果抛出任何错误,则处理错误
}
)
}
// 通过代码算法来保持原有 data.js 配置
function mixinStyle (json) {
const result = InitExcelJson
const sourceCellData = result.sheets[0].celldata
const targetCellData = json.sheets[0].celldata
const targetCellMap = {}
targetCellData.forEach(cell => {
targetCellMap[`${cell.r}-${cell.c}`] = cell
})
sourceCellData.forEach(cell => {
const findCell = targetCellMap[`${cell.r}-${cell.c}`]
if (findCell) {
if (findCell.v.v) cell.v.v = findCell.v.v
if (findCell.v.m) cell.v.m = findCell.v.m
}
})
return result
}
function updateSheet (json) {
const { VALUE_CELL_COLORS } = config // 可以编辑区域的背景色
const sourceCellData = InitExcelJson.sheets[0].celldata
const targetCellData = json.sheets[0].celldata
const targetCellMap = {}
targetCellData.forEach(cell => {
targetCellMap[`${cell.r}-${cell.c}`] = cell
})
sourceCellData.forEach(cell => {
if (VALUE_CELL_COLORS.includes(cell.v.bg)) {
const findCell = targetCellMap[`${cell.r}-${cell.c}`]
if (findCell) {
window.luckysheet.setCellValue(cell.r, cell.c, {
v: findCell.v.v
})
}
}
})
}
具体到 Vue 项目中的使用:
importExcel(evt) {
this.loading = true;
importExcel(evt.target.files);
setTimeout(() => {
this.loading = false;
this.$refs.importInput.value = ''
}, 1000);
},
最后
最后吐槽一句,xlsx-style 的坑挺多的,而且这玩意已经不维护了,请注意~
参考资料
- 关于 xlsx-style 导出 excel 文件无法应用公式解决方案
- 解决xlsx-style导出带样式和公式的excel表格遇到的坑
- ERROR CORRECT => module was not found: ./cptable in ./node_modules/[email protected]@xlsx-style/dist/cpexcel.js
- xlsx 文档
- LuckySheet 文档
- xlsx-style 文档 - npm
- LuckyExcel