文件预览

master
lion 6 hours ago
parent c8a9de4131
commit 170fc1d07e

@ -25,6 +25,7 @@
"echarts": "^5.0.0",
"element-resize-detector": "^1.2.4",
"element-ui": "2.13.2",
"exceljs": "^4.4.0",
"html2canvas": "^1.4.1",
"js-cookie": "2.2.0",
"less": "^3.13.1",
@ -40,7 +41,8 @@
"vue-router": "3.0.6",
"vuex": "3.1.0",
"vuex-persistedstate": "^4.1.0",
"wangeditor": "^4.7.12"
"wangeditor": "^4.7.12",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.4",

@ -2219,7 +2219,7 @@ export default {
url += row.join_last_flow_id
break;
case "other":
url += row.other_flow_id
url += row.other_flow_id || row.purchase_last_flow_id || row.req_last_flow_id
break;
default:
url = `${process.env.VUE_APP_OUT_URL}/flow/list/todo`
@ -2898,9 +2898,10 @@ export default {
}
} else {
if (item.flow_mod_id) {
//
flowStatus["other"].setStatus(item.other_flow_status??1)
if (item.other_flow_status === 1 || !item.other_flow_status) {
//other_flow_status 退 req_status
const otherStatus = item.other_flow_status || item.req_status || 1
flowStatus["other"].setStatus(otherStatus)
if (otherStatus === 1) {
flowStatus["other"].setExecutable(true)
}
}
@ -2909,7 +2910,7 @@ export default {
flowStatus["zhifu"].setStatus(1)
//
if (
(item.is_purchase === 0 && item.other_flow_status === 3) ||
(item.is_purchase === 0 && (item.other_flow_status === 3 || item.req_status === 3)) ||
(!item.is_contract && !item.is_substitute && item.purchase_status === 3) ||
(item.purchase_way?.value === "网上商城" && item.purchase_status === 3) ||
item.status === 2 ||

@ -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>

Loading…
Cancel
Save