mirror of
https://github.com/501351981/vue-office.git
synced 2025-07-25 07:41:42 +08:00
fix: excel自适应
This commit is contained in:
parent
af0851f6c4
commit
3d64ba310d
12
README.md
12
README.md
@ -10,16 +10,20 @@
|
|||||||
- 体验好:选择每个文档的最佳预览方案,保证用户体验和性能都达到最佳状态
|
- 体验好:选择每个文档的最佳预览方案,保证用户体验和性能都达到最佳状态
|
||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
```
|
```shell
|
||||||
//docx文档预览组件
|
#docx文档预览组件
|
||||||
npm install @vue-office/docx vue-demi
|
npm install @vue-office/docx vue-demi
|
||||||
|
|
||||||
//excel文档预览组件
|
#excel文档预览组件
|
||||||
npm install @vue-office/excel vue-demi
|
npm install @vue-office/excel vue-demi
|
||||||
|
|
||||||
//pdf文档预览组件
|
#pdf文档预览组件
|
||||||
npm install @vue-office/pdf vue-demi
|
npm install @vue-office/pdf vue-demi
|
||||||
```
|
```
|
||||||
|
如果是vue2.6版本或以下还需要额外安装 @vue/composition-api
|
||||||
|
```shell
|
||||||
|
npm install @vue/composition-api/
|
||||||
|
```
|
||||||
|
|
||||||
## 使用示例
|
## 使用示例
|
||||||
文档预览场景大致可以分为两种:
|
文档预览场景大致可以分为两种:
|
||||||
|
@ -10,7 +10,14 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ant-design-vue": "^3.2.15",
|
"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": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
|
1500
examples/pnpm-lock.yaml
generated
Normal file
1500
examples/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
9169
package-lock.json
generated
9169
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,7 @@
|
|||||||
|
import * as Excel from 'exceljs/dist/exceljs'
|
||||||
import {getUrl} from "../../../utils/url";
|
import {getUrl} from "../../../utils/url";
|
||||||
|
import tinycolor from "tinycolor2";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
export function getData(src, options={}) {
|
export function getData(src, options={}) {
|
||||||
return fetchExcel(getUrl(src), options)
|
return fetchExcel(getUrl(src), options)
|
||||||
@ -12,3 +15,157 @@ function fetchExcel(src, options) {
|
|||||||
return res.arrayBuffer()
|
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
|
||||||
|
}
|
@ -1,10 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { defineComponent,ref, onMounted, watch } from 'vue-demi'
|
import { defineComponent,ref, onMounted, watch } from 'vue-demi'
|
||||||
import Spreadsheet from "x-data-spreadsheet";
|
import Spreadsheet from "x-data-spreadsheet";
|
||||||
import _ from "lodash";
|
import {getData, readExcelData, transferExcelToSpreadSheet} from './excel'
|
||||||
import * as Excel from 'exceljs/dist/exceljs'
|
|
||||||
import tinycolor from "tinycolor2";
|
|
||||||
import {getData} from './excel'
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'VueOfficeExcel',
|
name: 'VueOfficeExcel',
|
||||||
@ -13,6 +10,12 @@ export default defineComponent({
|
|||||||
requestOptions: {
|
requestOptions: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
minColLength: 20
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits:['rendered', 'error'],
|
emits:['rendered', 'error'],
|
||||||
@ -21,162 +24,20 @@ export default defineComponent({
|
|||||||
const rootRef = ref(null)
|
const rootRef = ref(null)
|
||||||
let xs = null
|
let xs = null
|
||||||
function renderExcel(buffer){
|
function renderExcel(buffer){
|
||||||
try {
|
readExcelData(buffer).then(workbook =>{
|
||||||
const wb = new Excel.Workbook();
|
xs.loadData(transferExcelToSpreadSheet(workbook, props.options));
|
||||||
// 微软的 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) {
|
|
||||||
// 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)
|
|
||||||
})
|
|
||||||
xs.loadData(workbookData);
|
|
||||||
emit('rendered')
|
emit('rendered')
|
||||||
})
|
}).catch(e=>{
|
||||||
}catch (e){
|
|
||||||
xs.loadData({})
|
xs.loadData({})
|
||||||
emit('error', e)
|
emit('error', e)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
onMounted(()=>{
|
onMounted(()=>{
|
||||||
let height = wrapperRef.value.clientHeight || 300
|
window.xs = xs = new Spreadsheet(rootRef.value,{
|
||||||
xs = new Spreadsheet(rootRef.value,{
|
|
||||||
mode: 'read',
|
mode: 'read',
|
||||||
showToolbar: false,
|
showToolbar: false,
|
||||||
view: {
|
view: {
|
||||||
height: () => height,
|
height: () => wrapperRef.value.clientHeight || 300,
|
||||||
width: () => document.documentElement.clientWidth,
|
width: () => document.documentElement.clientWidth,
|
||||||
},
|
},
|
||||||
}).loadData({});
|
}).loadData({});
|
||||||
|
6255
pnpm-lock.yaml
generated
Normal file
6255
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user