fix: excel自适应

This commit is contained in:
liyulin 2023-02-23 20:55:19 +08:00
parent af0851f6c4
commit 3d64ba310d
7 changed files with 7942 additions and 9327 deletions

View File

@ -10,16 +10,20 @@
- 体验好:选择每个文档的最佳预览方案,保证用户体验和性能都达到最佳状态
## 安装
```
//docx文档预览组件
```shell
#docx文档预览组件
npm install @vue-office/docx vue-demi
//excel文档预览组件
#excel文档预览组件
npm install @vue-office/excel vue-demi
//pdf文档预览组件
#pdf文档预览组件
npm install @vue-office/pdf vue-demi
```
如果是vue2.6版本或以下还需要额外安装 @vue/composition-api
```shell
npm install @vue/composition-api/
```
## 使用示例
文档预览场景大致可以分为两种:

View File

@ -10,7 +10,14 @@
},
"dependencies": {
"ant-design-vue": "^3.2.15",
"vue": "^3.2.45"
"vue": "^3.2.45",
"docx-preview": "^0.1.14",
"exceljs": "^4.3.0",
"lodash": "^4.17.21",
"rimraf": "^4.1.2",
"tinycolor2": "^1.6.0",
"vue-demi": "^0.13.11",
"x-data-spreadsheet": "^1.1.9"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.0.0",

1500
examples/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

9169
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,7 @@
import * as Excel from 'exceljs/dist/exceljs'
import {getUrl} from "../../../utils/url";
import tinycolor from "tinycolor2";
import _ from "lodash";
export function getData(src, options={}) {
return fetchExcel(getUrl(src), options)
@ -12,3 +15,157 @@ function fetchExcel(src, options) {
return res.arrayBuffer()
})
}
export function readExcelData(buffer){
try {
const wb = new Excel.Workbook();
return wb.xlsx.load(buffer)
}catch (e){
return Promise.reject(e)
}
}
export function transferExcelToSpreadSheet(workbook, options){
let workbookData = []
workbook.eachSheet((sheet) => {
// 构造x-data-spreadsheet 的 sheet 数据源结构
let sheetData = { name: sheet.name,styles : [], rows: {}, merges:[] }
// 收集合并单元格信息
let mergeAddressData = []
for(let mergeRange in sheet._merges) {
sheetData.merges.push(sheet._merges[mergeRange].shortRange)
let mergeAddress = {}
// 合并单元格起始地址
mergeAddress.startAddress = sheet._merges[mergeRange].tl
// 合并单元格终止地址
mergeAddress.endAddress = sheet._merges[mergeRange].br
// Y轴方向跨度
mergeAddress.YRange = sheet._merges[mergeRange].model.bottom - sheet._merges[mergeRange].model.top
// X轴方向跨度
mergeAddress.XRange = sheet._merges[mergeRange].model.right - sheet._merges[mergeRange].model.left
mergeAddressData.push(mergeAddress)
}
sheetData.cols = {}
for(let i = 0;i < (sheet.columns || []).length; i++)
{
sheetData.cols[i.toString()] = {}
if(sheet.columns[i].width) {
// 不知道为什么从 exceljs 读取的宽度显示到 x-data-spreadsheet 特别小, 这里乘以8
sheetData.cols[i.toString()].width = sheet.columns[i].width * 8
} else {
// 默认列宽
sheetData.cols[i.toString()].width = 100
}
}
sheetData.cols.len = Math.max(Object.keys(sheetData.cols).length, options.minColLength || 0)
// 遍历行
sheet.eachRow((row, rowIndex) => {
sheetData.rows[(rowIndex - 1).toString()] = { cells: {} }
//includeEmpty = false 不包含空白单元格
row.eachCell({ includeEmpty: true }, function(cell, colNumber) {
let cellText = ''
if(cell.value && cell.value.result) {
// Excel 单元格有公式
cellText = cell.value.result
} else if(cell.value && cell.value.richText) {
// Excel 单元格是多行文本
for(let text in cell.value.richText) {
// 多行文本做累加
cellText += cell.value.richText[text].text
}
}
else {
// Excel 单元格无公式
cellText = cell.value
}
//解析单元格,包含样式
//*********************单元格存在背景色******************************
// 单元格存在背景色
let backGroundColor = null
if(cell.style.fill && cell.style.fill.fgColor && cell.style.fill.fgColor.argb) {
// 8位字符颜色先转rgb再转16进制颜色
backGroundColor = ((val) => {
val = val.trim().toLowerCase(); //去掉前后空格
let color = {};
try {
let argb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(val);
color.r = parseInt(argb[2], 16);
color.g = parseInt(argb[3], 16);
color.b = parseInt(argb[4], 16);
color.a = parseInt(argb[1], 16) / 255;
return tinycolor(`rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`).toHexString()
} catch (e) {
//
}
})(cell.style.fill.fgColor.argb)
}
if(backGroundColor) {
cell.style.bgcolor = backGroundColor
}
//*************************************************************************** */
//*********************字体存在背景色******************************
// 字体颜色
let fontColor = null
if(cell.style.font && cell.style.font.color && cell.style.font.color.argb) {
// 8位字符颜色先转rgb再转16进制颜色
fontColor = ((val) => {
val = val.trim().toLowerCase(); //去掉前后空格
let color = {};
try {
let argb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(val)
color.r = parseInt(argb[2], 16);
color.g = parseInt(argb[3], 16);
color.b = parseInt(argb[4], 16);
color.a = parseInt(argb[1], 16) / 255;
return tinycolor(`rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`).toHexString()
} catch (e) {
//
}
})(cell.style.font.color.argb)
}
if(fontColor) {
cell.style.color = fontColor
}
// exceljs 对齐的格式转成 x-date-spreedsheet 能识别的对齐格式
if(cell.style.alignment && cell.style.alignment.horizontal) {
cell.style.align = cell.style.alignment.horizontal
cell.style.valign = cell.style.alignment.vertical
}
//处理合并单元格
let mergeAddress = _.find(mergeAddressData, function(o) { return o.startAddress == cell._address })
if(mergeAddress)
{
// 遍历的单元格属于合并单元格
if(cell.master.address != mergeAddress.startAddress){
// 不是合并单元格中的第一个单元格不需要计入数据源
return
}
// 说明是合并单元格区域的起始单元格
sheetData.rows[(rowIndex - 1).toString()].cells[(colNumber - 1).toString()] = { text: cellText, style: 0, merge: [mergeAddress.YRange, mergeAddress.XRange] }
sheetData.styles.push(cell.style)
//对应的style存放序号
sheetData.rows[(rowIndex - 1).toString()].cells[(colNumber - 1).toString()].style = sheetData.styles.length - 1
}
else {
// 非合并单元格
sheetData.rows[(rowIndex - 1).toString()].cells[(colNumber - 1).toString()] = { text: cellText, style: 0 }
//解析单元格,包含样式
sheetData.styles.push(cell.style)
//对应的style存放序号
sheetData.rows[(rowIndex - 1).toString()].cells[(colNumber - 1).toString()].style = sheetData.styles.length - 1
}
});
})
workbookData.push(sheetData)
})
return workbookData
}

View File

@ -1,10 +1,7 @@
<script>
import { defineComponent,ref, onMounted, watch } from 'vue-demi'
import Spreadsheet from "x-data-spreadsheet";
import _ from "lodash";
import * as Excel from 'exceljs/dist/exceljs'
import tinycolor from "tinycolor2";
import {getData} from './excel'
import {getData, readExcelData, transferExcelToSpreadSheet} from './excel'
export default defineComponent({
name: 'VueOfficeExcel',
@ -13,6 +10,12 @@ export default defineComponent({
requestOptions: {
type: Object,
default: () => ({})
},
options: {
type: Object,
default: () => ({
minColLength: 20
})
}
},
emits:['rendered', 'error'],
@ -21,162 +24,20 @@ export default defineComponent({
const rootRef = ref(null)
let xs = null
function renderExcel(buffer){
try {
const wb = new Excel.Workbook();
// Excel ColorIndex
wb.xlsx.load(buffer).then(workbook => {
let workbookData = []
workbook.eachSheet((sheet) => {
// x-data-spreadsheet sheet
let sheetData = { name: sheet.name,styles : [], rows: {}, merges:[] }
//
let mergeAddressData = []
for(let mergeRange in sheet._merges) {
sheetData.merges.push(sheet._merges[mergeRange].shortRange)
let mergeAddress = {}
//
mergeAddress.startAddress = sheet._merges[mergeRange].tl
//
mergeAddress.endAddress = sheet._merges[mergeRange].br
// Y
mergeAddress.YRange = sheet._merges[mergeRange].model.bottom - sheet._merges[mergeRange].model.top
// X
mergeAddress.XRange = sheet._merges[mergeRange].model.right - sheet._merges[mergeRange].model.left
mergeAddressData.push(mergeAddress)
}
sheetData.cols = {}
for(let i = 0;i < (sheet.columns || []).length; i++)
{
sheetData.cols[i.toString()] = {}
if(sheet.columns[i].width) {
// exceljs x-data-spreadsheet , 8
sheetData.cols[i.toString()].width = sheet.columns[i].width * 8
} else {
//
sheetData.cols[i.toString()].width = 100
}
}
//
sheet.eachRow((row, rowIndex) => {
sheetData.rows[(rowIndex - 1).toString()] = { cells: {} }
//includeEmpty = false
row.eachCell({ includeEmpty: true }, function(cell, colNumber) {
let cellText = ''
if(cell.value && cell.value.result) {
// Excel
cellText = cell.value.result
} else if(cell.value && cell.value.richText) {
// Excel
for(let text in cell.value.richText) {
//
cellText += cell.value.richText[text].text
}
}
else {
// Excel
cellText = cell.value
}
//,
//***************************************************
//
let backGroundColor = null
if(cell.style.fill && cell.style.fill.fgColor && cell.style.fill.fgColor.argb) {
// 8rgb16
backGroundColor = ((val) => {
val = val.trim().toLowerCase(); //
let color = {};
try {
let argb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(val);
color.r = parseInt(argb[2], 16);
color.g = parseInt(argb[3], 16);
color.b = parseInt(argb[4], 16);
color.a = parseInt(argb[1], 16) / 255;
return tinycolor(`rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`).toHexString()
} catch (e) {
//
}
})(cell.style.fill.fgColor.argb)
}
if(backGroundColor) {
cell.style.bgcolor = backGroundColor
}
//*************************************************************************** */
//***************************************************
//
let fontColor = null
if(cell.style.font && cell.style.font.color && cell.style.font.color.argb) {
// 8rgb16
fontColor = ((val) => {
val = val.trim().toLowerCase(); //
let color = {};
try {
let argb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(val)
color.r = parseInt(argb[2], 16);
color.g = parseInt(argb[3], 16);
color.b = parseInt(argb[4], 16);
color.a = parseInt(argb[1], 16) / 255;
return tinycolor(`rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`).toHexString()
} catch (e) {
//
}
})(cell.style.font.color.argb)
}
if(fontColor) {
cell.style.color = fontColor
}
// exceljs x-date-spreedsheet
if(cell.style.alignment && cell.style.alignment.horizontal) {
cell.style.align = cell.style.alignment.horizontal
cell.style.valign = cell.style.alignment.vertical
}
//
let mergeAddress = _.find(mergeAddressData, function(o) { return o.startAddress == cell._address })
if(mergeAddress)
{
//
if(cell.master.address != mergeAddress.startAddress){
//
return
}
//
sheetData.rows[(rowIndex - 1).toString()].cells[(colNumber - 1).toString()] = { text: cellText, style: 0, merge: [mergeAddress.YRange, mergeAddress.XRange] }
sheetData.styles.push(cell.style)
//style
sheetData.rows[(rowIndex - 1).toString()].cells[(colNumber - 1).toString()].style = sheetData.styles.length - 1
}
else {
//
sheetData.rows[(rowIndex - 1).toString()].cells[(colNumber - 1).toString()] = { text: cellText, style: 0 }
//,
sheetData.styles.push(cell.style)
//style
sheetData.rows[(rowIndex - 1).toString()].cells[(colNumber - 1).toString()].style = sheetData.styles.length - 1
}
});
})
workbookData.push(sheetData)
})
xs.loadData(workbookData);
emit('rendered')
})
}catch (e){
readExcelData(buffer).then(workbook =>{
xs.loadData(transferExcelToSpreadSheet(workbook, props.options));
emit('rendered')
}).catch(e=>{
xs.loadData({})
emit('error', e)
}
})
}
onMounted(()=>{
let height = wrapperRef.value.clientHeight || 300
xs = new Spreadsheet(rootRef.value,{
window.xs = xs = new Spreadsheet(rootRef.value,{
mode: 'read',
showToolbar: false,
view: {
height: () => height,
height: () => wrapperRef.value.clientHeight || 300,
width: () => document.documentElement.clientWidth,
},
}).loadData({});

6255
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff