|
|
|
|
@ -4,11 +4,58 @@
|
|
|
|
|
<VueOfficeDocx :src="url" style="height: 100vh;" />
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else-if="type === 'xlsx' || type === 'xls'">
|
|
|
|
|
<VueOfficeExcel :src="url" style="height: 100vh;" />
|
|
|
|
|
<div v-if="excelLoading" v-loading="true" class="excel-preview excel-preview--loading" />
|
|
|
|
|
<div v-else-if="excelError" class="excel-preview excel-preview--error">
|
|
|
|
|
<h4>Excel 预览失败,请下载查看</h4>
|
|
|
|
|
<p>{{ excelError }}</p>
|
|
|
|
|
<el-link target="_blank" type="primary" :href="url">点击下载</el-link>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else class="excel-preview">
|
|
|
|
|
<div v-if="excelSheets.length > 1" class="excel-preview__tabs">
|
|
|
|
|
<button
|
|
|
|
|
v-for="(sheet, index) in excelSheets"
|
|
|
|
|
:key="sheet.name + index"
|
|
|
|
|
type="button"
|
|
|
|
|
class="excel-preview__tab"
|
|
|
|
|
:class="{ 'is-active': activeSheetIndex === index }"
|
|
|
|
|
@click="activeSheetIndex = index"
|
|
|
|
|
>
|
|
|
|
|
{{ sheet.name }}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="excel-preview__body">
|
|
|
|
|
<table v-if="currentSheet" class="excel-preview__table" cellspacing="0" cellpadding="0">
|
|
|
|
|
<colgroup>
|
|
|
|
|
<col
|
|
|
|
|
v-for="(width, colIndex) in currentSheet.colWidths"
|
|
|
|
|
:key="'col-' + colIndex"
|
|
|
|
|
:style="{ width: width + 'px', minWidth: width + 'px' }"
|
|
|
|
|
>
|
|
|
|
|
</colgroup>
|
|
|
|
|
<tbody>
|
|
|
|
|
<tr
|
|
|
|
|
v-for="(row, rowIndex) in currentSheet.rows"
|
|
|
|
|
:key="'row-' + rowIndex"
|
|
|
|
|
:style="{ height: row.height + 'px' }"
|
|
|
|
|
>
|
|
|
|
|
<td
|
|
|
|
|
v-for="(cell, colIndex) in row.cells"
|
|
|
|
|
v-show="!cell.hidden"
|
|
|
|
|
:key="'cell-' + rowIndex + '-' + colIndex"
|
|
|
|
|
:rowspan="cell.rowspan"
|
|
|
|
|
:colspan="cell.colspan"
|
|
|
|
|
:style="cell.style"
|
|
|
|
|
>
|
|
|
|
|
{{ cell.text }}
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else-if="type === 'pdf'">
|
|
|
|
|
<iframe :src="url" frameborder="0" style="width: 100%;height: 100%;" />
|
|
|
|
|
<!-- <VueOfficePdf :src="url" style="height: 100vh;" />-->
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else-if="['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'].indexOf(type) !== -1">
|
|
|
|
|
<el-image ref="elImage" fit="contain" :preview-src-list="[url]" :src="url" style="width: 100vw;height: 100vh;" alt="" @load="loadImg" />
|
|
|
|
|
@ -23,28 +70,171 @@
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
//引入VueOfficeDocx组件
|
|
|
|
|
import VueOfficeDocx from '@vue-office/docx'
|
|
|
|
|
//引入相关样式
|
|
|
|
|
import '@vue-office/docx/lib/index.css'
|
|
|
|
|
//引入VueOfficeExcel组件
|
|
|
|
|
import VueOfficeExcel from '@vue-office/excel'
|
|
|
|
|
//引入相关样式
|
|
|
|
|
import '@vue-office/excel/lib/index.css'
|
|
|
|
|
//引入VueOfficePdf组件
|
|
|
|
|
import VueOfficePdf from '@vue-office/pdf'
|
|
|
|
|
import axios from 'axios'
|
|
|
|
|
//import { renderAsync } from 'docx-preview'
|
|
|
|
|
import ExcelJS from 'exceljs'
|
|
|
|
|
import * as XLSX from 'xlsx'
|
|
|
|
|
|
|
|
|
|
const THEME_COLORS = [
|
|
|
|
|
'#FFFFFF', '#000000', '#E7E6E6', '#44546A', '#4472C4',
|
|
|
|
|
'#ED7D31', '#A5A5A5', '#FFC000', '#5B9BD5', '#70AD47'
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
function resolveThemeColor(color) {
|
|
|
|
|
if (!color) return null
|
|
|
|
|
if (color.argb) {
|
|
|
|
|
return `#${String(color.argb).replace(/^FF/i, '')}`
|
|
|
|
|
}
|
|
|
|
|
if (Object.prototype.hasOwnProperty.call(color, 'theme')) {
|
|
|
|
|
return THEME_COLORS[color.theme] || '#000000'
|
|
|
|
|
}
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getCellText(cell) {
|
|
|
|
|
if (cell.text != null && cell.text !== '') {
|
|
|
|
|
return String(cell.text)
|
|
|
|
|
}
|
|
|
|
|
const value = cell.value
|
|
|
|
|
if (value == null) return ''
|
|
|
|
|
if (typeof value === 'object') {
|
|
|
|
|
if (value.richText) {
|
|
|
|
|
return value.richText.map(item => item.text || '').join('')
|
|
|
|
|
}
|
|
|
|
|
if (value.formula != null) {
|
|
|
|
|
return cell.text || (value.result != null ? String(value.result) : '')
|
|
|
|
|
}
|
|
|
|
|
if (value.text != null) {
|
|
|
|
|
return String(value.text)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return String(value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildCellStyle(cell) {
|
|
|
|
|
const style = cell.style || {}
|
|
|
|
|
const css = {
|
|
|
|
|
border: '1px solid #d0d7de',
|
|
|
|
|
padding: '4px 6px',
|
|
|
|
|
verticalAlign: 'middle',
|
|
|
|
|
whiteSpace: style.alignment && style.alignment.wrapText ? 'normal' : 'nowrap',
|
|
|
|
|
wordBreak: style.alignment && style.alignment.wrapText ? 'break-word' : 'normal'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (style.font) {
|
|
|
|
|
if (style.font.bold) css.fontWeight = 'bold'
|
|
|
|
|
if (style.font.italic) css.fontStyle = 'italic'
|
|
|
|
|
if (style.font.size) css.fontSize = `${Math.round(style.font.size * 0.75)}px`
|
|
|
|
|
const fontColor = resolveThemeColor(style.font.color)
|
|
|
|
|
if (fontColor) css.color = fontColor
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (style.fill && style.fill.fgColor) {
|
|
|
|
|
const bgColor = resolveThemeColor(style.fill.fgColor)
|
|
|
|
|
if (bgColor) css.backgroundColor = bgColor
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (style.alignment) {
|
|
|
|
|
if (style.alignment.horizontal) css.textAlign = style.alignment.horizontal
|
|
|
|
|
if (style.alignment.vertical) css.verticalAlign = style.alignment.vertical
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return css
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildSheetView(worksheet) {
|
|
|
|
|
const dimension = worksheet.dimensions
|
|
|
|
|
const rowCount = dimension && dimension.bottom ? dimension.bottom : worksheet.rowCount
|
|
|
|
|
const colCount = dimension && dimension.right ? dimension.right : worksheet.columnCount
|
|
|
|
|
const hiddenCells = new Set()
|
|
|
|
|
const mergeMap = {}
|
|
|
|
|
|
|
|
|
|
Object.keys(worksheet._merges || {}).forEach(key => {
|
|
|
|
|
const merge = worksheet._merges[key]
|
|
|
|
|
const top = merge.model.top
|
|
|
|
|
const left = merge.model.left
|
|
|
|
|
const bottom = merge.model.bottom
|
|
|
|
|
const right = merge.model.right
|
|
|
|
|
mergeMap[`${top}:${left}`] = {
|
|
|
|
|
rowspan: bottom - top + 1,
|
|
|
|
|
colspan: right - left + 1
|
|
|
|
|
}
|
|
|
|
|
for (let r = top; r <= bottom; r++) {
|
|
|
|
|
for (let c = left; c <= right; c++) {
|
|
|
|
|
if (r !== top || c !== left) {
|
|
|
|
|
hiddenCells.add(`${r}:${c}`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const colWidths = []
|
|
|
|
|
for (let col = 1; col <= colCount; col++) {
|
|
|
|
|
const column = worksheet.getColumn(col)
|
|
|
|
|
colWidths.push(Math.max(Math.round((column.width || 9) * 7), 40))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const rows = []
|
|
|
|
|
for (let rowIndex = 1; rowIndex <= rowCount; rowIndex++) {
|
|
|
|
|
const row = worksheet.getRow(rowIndex)
|
|
|
|
|
const cells = []
|
|
|
|
|
for (let colIndex = 1; colIndex <= colCount; colIndex++) {
|
|
|
|
|
const key = `${rowIndex}:${colIndex}`
|
|
|
|
|
if (hiddenCells.has(key)) {
|
|
|
|
|
cells.push({ hidden: true })
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
const cell = row.getCell(colIndex)
|
|
|
|
|
const merge = mergeMap[key]
|
|
|
|
|
cells.push({
|
|
|
|
|
hidden: false,
|
|
|
|
|
text: getCellText(cell),
|
|
|
|
|
style: buildCellStyle(cell),
|
|
|
|
|
rowspan: merge ? merge.rowspan : 1,
|
|
|
|
|
colspan: merge ? merge.colspan : 1
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
rows.push({
|
|
|
|
|
height: Math.max(row.height || 20, 20),
|
|
|
|
|
cells
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
name: worksheet.name,
|
|
|
|
|
colWidths,
|
|
|
|
|
rows
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadWorkbookFromBuffer(buffer, isXls) {
|
|
|
|
|
let source = buffer
|
|
|
|
|
if (isXls) {
|
|
|
|
|
const legacyWorkbook = XLSX.read(source, { type: 'array' })
|
|
|
|
|
source = XLSX.write(legacyWorkbook, { bookType: 'xlsx', type: 'array' })
|
|
|
|
|
}
|
|
|
|
|
const workbook = new ExcelJS.Workbook()
|
|
|
|
|
await workbook.xlsx.load(source)
|
|
|
|
|
return workbook
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
components: {
|
|
|
|
|
VueOfficeDocx,
|
|
|
|
|
VueOfficePdf,
|
|
|
|
|
VueOfficeExcel
|
|
|
|
|
VueOfficeDocx
|
|
|
|
|
},
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
url: "",
|
|
|
|
|
type: ""
|
|
|
|
|
url: '',
|
|
|
|
|
type: '',
|
|
|
|
|
excelLoading: false,
|
|
|
|
|
excelError: '',
|
|
|
|
|
excelSheets: [],
|
|
|
|
|
activeSheetIndex: 0
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
currentSheet() {
|
|
|
|
|
return this.excelSheets[this.activeSheetIndex] || null
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
@ -56,38 +246,64 @@ export default {
|
|
|
|
|
document.querySelector('.el-image-viewer__mask').style.pointerEvents = 'none'
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
async resolveUrl(url) {
|
|
|
|
|
if(url) {
|
|
|
|
|
let originalUrl
|
|
|
|
|
try {
|
|
|
|
|
originalUrl = window.atob(window.decodeURIComponent(this.$route.query.url??""))
|
|
|
|
|
} catch (err) {
|
|
|
|
|
originalUrl = window.decodeURIComponent(this.$route.query.url??"")
|
|
|
|
|
async loadExcelPreview() {
|
|
|
|
|
this.excelLoading = true
|
|
|
|
|
this.excelError = ''
|
|
|
|
|
this.excelSheets = []
|
|
|
|
|
this.activeSheetIndex = 0
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(this.url)
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error(`文件加载失败 (${response.status})`)
|
|
|
|
|
}
|
|
|
|
|
this.type = this.$route.query.type || originalUrl.split('.').at(-1)
|
|
|
|
|
// this.url = originalUrl
|
|
|
|
|
if (['jpeg','jpg','png','gif','webp','svg'].indexOf(this.type) !== -1) {
|
|
|
|
|
this.url = originalUrl
|
|
|
|
|
} else {
|
|
|
|
|
try {
|
|
|
|
|
this.url = process.env.VUE_APP_DOMIAN + (window.location.origin === window.top.location.origin ? '/oa' : '') + new URL(originalUrl).pathname
|
|
|
|
|
} catch (err) {
|
|
|
|
|
//this.url = this.$route.query.url
|
|
|
|
|
this.url = process.env.VUE_APP_DOMIAN + '/oa' + new URL(originalUrl).pathname
|
|
|
|
|
}
|
|
|
|
|
const buffer = await response.arrayBuffer()
|
|
|
|
|
const workbook = await loadWorkbookFromBuffer(buffer, this.type === 'xls')
|
|
|
|
|
const sheets = []
|
|
|
|
|
workbook.eachSheet(worksheet => {
|
|
|
|
|
sheets.push(buildSheetView(worksheet))
|
|
|
|
|
})
|
|
|
|
|
if (!sheets.length) {
|
|
|
|
|
throw new Error('未读取到工作表数据')
|
|
|
|
|
}
|
|
|
|
|
this.excelSheets = sheets
|
|
|
|
|
const activeTab = workbook.views && workbook.views[0] && workbook.views[0].activeTab
|
|
|
|
|
if (typeof activeTab === 'number' && activeTab >= 0 && activeTab < sheets.length) {
|
|
|
|
|
this.activeSheetIndex = activeTab
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
this.excelError = err && err.message ? err.message : 'Excel 解析失败'
|
|
|
|
|
} finally {
|
|
|
|
|
this.excelLoading = false
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
async resolveUrl(url) {
|
|
|
|
|
if (!url) return
|
|
|
|
|
|
|
|
|
|
let originalUrl
|
|
|
|
|
try {
|
|
|
|
|
originalUrl = window.atob(window.decodeURIComponent(this.$route.query.url ?? ''))
|
|
|
|
|
} catch (err) {
|
|
|
|
|
originalUrl = window.decodeURIComponent(this.$route.query.url ?? '')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.type = this.$route.query.type || originalUrl.split('.').at(-1)
|
|
|
|
|
|
|
|
|
|
if (['jpeg', 'jpg', 'png', 'gif', 'webp', 'svg'].indexOf(this.type) !== -1) {
|
|
|
|
|
this.url = originalUrl
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
this.url = process.env.VUE_APP_DOMIAN + (window.location.origin === window.top.location.origin ? '/oa' : '') + new URL(originalUrl).pathname
|
|
|
|
|
} catch (err) {
|
|
|
|
|
this.url = process.env.VUE_APP_DOMIAN + '/oa' + new URL(originalUrl).pathname
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.type === 'xlsx' || this.type === 'xls') {
|
|
|
|
|
await this.loadExcelPreview()
|
|
|
|
|
}
|
|
|
|
|
console.log(this.url)
|
|
|
|
|
// if(this.type === 'doc') {
|
|
|
|
|
// const res = await fetch(this.url)
|
|
|
|
|
// const blob = res.blob()
|
|
|
|
|
// this.$nextTick(() => {
|
|
|
|
|
// renderAsync(blob, this.$refs['docPreview'])
|
|
|
|
|
// })
|
|
|
|
|
// }
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
computed: {},
|
|
|
|
|
created() {
|
|
|
|
|
this.resolveUrl(this.$route.query.url)
|
|
|
|
|
}
|
|
|
|
|
@ -99,4 +315,52 @@ export default {
|
|
|
|
|
height: 100vh;
|
|
|
|
|
width: 100vw;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.excel-preview {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
height: 100vh;
|
|
|
|
|
background: #fff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.excel-preview--loading,
|
|
|
|
|
.excel-preview--error {
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
padding: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.excel-preview__tabs {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 4px;
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
border-bottom: 1px solid #dcdfe6;
|
|
|
|
|
background: #f5f7fa;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.excel-preview__tab {
|
|
|
|
|
border: 1px solid #dcdfe6;
|
|
|
|
|
background: #fff;
|
|
|
|
|
color: #606266;
|
|
|
|
|
padding: 6px 14px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.excel-preview__tab.is-active {
|
|
|
|
|
color: #409eff;
|
|
|
|
|
border-color: #409eff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.excel-preview__body {
|
|
|
|
|
flex: 1;
|
|
|
|
|
overflow: auto;
|
|
|
|
|
padding: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.excel-preview__table {
|
|
|
|
|
border-collapse: collapse;
|
|
|
|
|
table-layout: fixed;
|
|
|
|
|
min-width: 100%;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|