Browse Source

预览优化

master
xuhuajiao 1 month ago
parent
commit
93e8d77069
  1. 74
      src/vendors/pdf/PdfView.vue
  2. 95
      src/views/preview/index.vue
  3. 167
      src/views/system/fileLibraryManage/processManage/index-old.vue

74
src/vendors/pdf/PdfView.vue

@ -53,7 +53,9 @@ export default {
initColor: null, initColor: null,
fileParentInfo: null, fileParentInfo: null,
selectedCategory:null, selectedCategory:null,
formatData:[]
formatData:[],
// 带水印的 PDF 字节,供外部打印使用
pdfBytes: null
} }
}, },
mounted() { mounted() {
@ -78,7 +80,8 @@ export default {
return return
} }
this.pdf_scale = this.pdf_scale + 0.1 this.pdf_scale = this.pdf_scale + 0.1
this.loadFile()
// 只重新渲染 canvas,不重新加载文档,避免大文件每次缩放都卡顿
this.renderPagePdf()
}, },
scaleX() { scaleX() {
// 缩小 // 缩小
@ -87,7 +90,7 @@ export default {
return return
} }
this.pdf_scale = this.pdf_scale - 0.1 this.pdf_scale = this.pdf_scale - 0.1
this.loadFile()
this.renderPagePdf()
}, },
formateTime() { formateTime() {
const time = new Date() const time = new Date()
@ -114,35 +117,8 @@ export default {
await getInitWatermark().then((res) => { await getInitWatermark().then((res) => {
this.watermarkInfo = res this.watermarkInfo = res
if (res.watermarkType === '1') { if (res.watermarkType === '1') {
// 图片水印
let dom = document.getElementsByClassName('page')
let canvas = document.createElement('canvas')
let cxt = canvas.getContext('2d')
let divImg = document.createElement('div')
let imgScale = 0.1
//创建新的图片对象
let img = new Image();
//指定图片的URL
img.src = this.watermarkInfo.context;
let _this = this;
//浏览器加载图片完毕后再绘制图片
img.onload = function() {
//cxt.drawImage(img,0,0);
cxt.rotate(-20 * Math.PI / 180)
let whScale = img.width / img.height
cxt.clearRect(0, 0, canvas.width, canvas.height);
cxt.drawImage(img, //规定要使用的图像、画布或视频。
0, 0, //开始剪切的 x 坐标位置。
img.width, img.height, //被剪切图像的高度。
0, 50,//在画布上放置图像的 x 、y坐标位置。
img.width * imgScale, img.height * imgScale //要使用的图像的宽度、高度
)
divImg.style.opacity = parseFloat(_this.watermarkInfo.transparency) / 100
divImg.style.background = 'url(' + canvas.toDataURL('image/png') + ') left top repeat'
_this.drawImage2(divImg,dom)
}
// 图片水印画到 DOM 层(.page div),延迟到页面渲染后再画,避免此时 .page div 还未渲染导致 drawImage2 取不到节点
this._pendingImageWatermark = true
} else if (res.watermarkType === '0') { } else if (res.watermarkType === '0') {
const rgbaValue = this.watermarkInfo.transparency; const rgbaValue = this.watermarkInfo.transparency;
const startIndex = rgbaValue.indexOf('('); // 找到第一个括号的位置 const startIndex = rgbaValue.indexOf('('); // 找到第一个括号的位置
@ -195,9 +171,14 @@ export default {
}) })
const pdfBytes = await pdfDoc.save() const pdfBytes = await pdfDoc.save()
this.pdfBytes = pdfBytes
this.pdfDoc = await PDFJS.getDocument(pdfBytes).promise this.pdfDoc = await PDFJS.getDocument(pdfBytes).promise
this.pdf_pages = this.pdfDoc.numPages this.pdf_pages = this.pdfDoc.numPages
this.$nextTick(() => this.renderPagePdf())
this.$nextTick(() => {
this.renderPagePdf()
// 归档章只在文档加载后调一次,不在每页渲染时重复请求
this.getFilingsealFormat()
})
}, },
drawImage2 (div,dom) { drawImage2 (div,dom) {
div.style.pointerEvents = 'none' div.style.pointerEvents = 'none'
@ -274,8 +255,31 @@ export default {
}) })
} }
// 归档章数据获取操作
this.getFilingsealFormat()
// 图片水印延迟到第一页渲染后画,此时 .page div 已存在
if (num === 1 && this._pendingImageWatermark) {
this._pendingImageWatermark = false
this.drawImageWatermarkToDom()
}
},
// 图片水印画到 DOM 层(.page div),从 loadFile 延迟到此调用,解决 .page div 未渲染时画不上的竞态
drawImageWatermarkToDom() {
if (!this.watermarkInfo || this.watermarkInfo.watermarkType !== '1') return
const dom = document.getElementsByClassName('page')
if (!dom || dom.length === 0) return
const canvas = document.createElement('canvas')
const cxt = canvas.getContext('2d')
const divImg = document.createElement('div')
const imgScale = 0.1
const img = new Image()
img.src = this.watermarkInfo.context
img.onload = () => {
cxt.rotate(-20 * Math.PI / 180)
cxt.clearRect(0, 0, canvas.width, canvas.height)
cxt.drawImage(img, 0, 0, img.width, img.height, 0, 50, img.width * imgScale, img.height * imgScale)
divImg.style.opacity = parseFloat(this.watermarkInfo.transparency) / 100
divImg.style.background = 'url(' + canvas.toDataURL('image/png') + ') left top repeat'
this.drawImage2(divImg, dom)
}
}, },
getFilingsealFormat() { getFilingsealFormat() {
FetchGetFilingsealFormat().then((res) => { FetchGetFilingsealFormat().then((res) => {

95
src/views/preview/index.vue

@ -43,7 +43,7 @@
</ul> </ul>
<div class="preview-btn"> <div class="preview-btn">
<!-- <el-button :loading="downloading" class="iconfont icon-xiazai" @click="downloadFile(fileCurrent)">下载</el-button> --> <!-- <el-button :loading="downloading" class="iconfont icon-xiazai" @click="downloadFile(fileCurrent)">下载</el-button> -->
<!-- <el-button v-print="printObj" class="iconfont icon-dayin">打印</el-button> -->
<!-- <el-button class="iconfont icon-dayin" @click="handlePrint">打印</el-button> -->
</div> </div>
</div> </div>
<div id="printArea" ref="output" class="well-box" :style="{overflowY : !loading ? 'auto' : 'hidden'}" /> <div id="printArea" ref="output" class="well-box" :style="{overflowY : !loading ? 'auto' : 'hidden'}" />
@ -221,6 +221,49 @@ export default {
this.downloading = false this.downloading = false
}) })
}, },
// 打印入口:按文件类型分流,保证水印带入打印
handlePrint() {
if (!this.fileCurrent || !this.fileCurrent.file_name) {
this.$message.warning('请先选择文件')
return
}
const extend = getExtend(this.fileCurrent.file_name)
if (extend.includes('pdf')) {
// PDF:水印已用 pdf-lib 打进内容,直接 iframe 打印带水印字节
this.printPdf()
} else {
// 非 PDF:水印是 DOM 覆盖层,打印前克隆注入 printArea,打印完清理
this.printWithWatermark()
}
},
printPdf() {
// 用原始文件 Blob(不含 pdf-lib 打的水印)打印
if (!this.toFile) {
this.$message.warning('PDF 尚未加载完成,请稍候')
return
}
const iframe = document.createElement('iframe')
iframe.style.cssText = 'position:absolute;width:0;height:0;border:0;'
document.body.appendChild(iframe)
const url = URL.createObjectURL(this.toFile)
iframe.src = url
iframe.onload = () => {
iframe.contentWindow.focus()
iframe.contentWindow.print()
}
const cleanup = () => {
URL.revokeObjectURL(url)
if (iframe.parentNode) iframe.parentNode.removeChild(iframe)
window.removeEventListener('afterprint', cleanup)
}
window.addEventListener('afterprint', cleanup)
},
printWithWatermark() {
// 非 PDF:直接打印 printArea 内容,不注入水印,预览水印不受影响
const printArea = document.getElementById('printArea')
if (!printArea) return
printJS({ printable: 'printArea', type: 'html', scanStyles: true })
},
selectFile(item) { selectFile(item) {
this.fileCurrent = item this.fileCurrent = item
this.getBase64ByFileId() this.getBase64ByFileId()
@ -258,18 +301,13 @@ export default {
} }
}, },
base64ToFile(base64String, fileName, mimeType) { base64ToFile(base64String, fileName, mimeType) {
var byteCharacters = atob(base64String)
var byteArrays = []
for (var offset = 0; offset < byteCharacters.length; offset += 512) {
var slice = byteCharacters.slice(offset, offset + 512)
var byteNumbers = new Array(slice.length)
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i)
}
var byteArray = new Uint8Array(byteNumbers)
byteArrays.push(byteArray)
const binaryString = atob(base64String)
// 一次性创建 Uint8Array,避免 512 字节切片循环带来的多次内存分配
const bytes = new Uint8Array(binaryString.length)
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i)
} }
var file = new Blob(byteArrays, { type: mimeType })
const file = new Blob([bytes], { type: mimeType })
file.name = fileName file.name = fileName
return file return file
}, },
@ -436,41 +474,32 @@ export default {
async handleChange(e) { async handleChange(e) {
try { try {
clearTimeout(this.timer) clearTimeout(this.timer)
// const [file] = e.target.files
// console.log('file', file)
const fileReader = new FileReader()
fileReader.readAsArrayBuffer(this.toFile)
fileReader.onload = () => {
this.src = fileReader.result
}
// 直接用 readBuffer 读取 Blob,不再用 FileReader 重复读取同一文件
const arrayBuffer = await readBuffer(this.toFile) const arrayBuffer = await readBuffer(this.toFile)
this.last = await this.displayResult(arrayBuffer, this.toFile) this.last = await this.displayResult(arrayBuffer, this.toFile)
this.$nextTick(() => { this.$nextTick(() => {
// 取得扩展名
const extend = getExtend(this.toFile.name) const extend = getExtend(this.toFile.name)
// 加水印
const maskDiv = document.getElementsByClassName('mask_div')
const imgMaskDiv = document.getElementsByClassName('weterbox')
if (imgMaskDiv.length === 1) {
document.getElementsByClassName('water-mask')[0].removeChild(imgMaskDiv[0])
}
// docx-wrapper docx
for (var j = maskDiv.length - 1; j >= 0; j--) {
document.getElementsByClassName('water-mask')[0].removeChild(maskDiv[j])
// 切换文件时清除所有外部水印层(DOM + canvas),避免 PDF 与非 PDF 切换时水印残留
const container = document.getElementsByClassName('water-mask')[0]
if (container) {
container.querySelectorAll('.mask_div, .weterbox, canvas.wm-canvas').forEach(el => el.remove())
} }
if (!extend.includes('pdf')) { if (!extend.includes('pdf')) {
// 非 PDF:重新渲染外部水印并启动防篡改守卫
this.getWatermark() this.getWatermark()
} else {
// PDF 自带水印,停掉外部水印守卫,避免守卫把已清除的水印又恢复
this.stopWatermarkGuard()
} }
}) })
} catch (e) { } catch (e) {
console.error(e) console.error(e)
this.$message.error('文件预览失败')
} finally { } finally {
this.timer = setTimeout(() => {
this.loading = false
}, 1000)
// 渲染完成即关闭 loading,不再固定等 1 秒
this.loading = false
} }
}, },
displayResult(buffer, file) { displayResult(buffer, file) {

167
src/views/system/fileLibraryManage/processManage/index-old.vue

@ -0,0 +1,167 @@
<template>
<div class="process-container">
<div v-for="item in filteredFlowList" :key="item.id" class="process-item">
<span>{{ item.name }}</span>
<p>{{ item.flowName }}<i class="iconfont icon-shezhi" @click="toProcessSelected(item)" /></p>
<el-switch v-model="item.status" class="isEnable-chapter" @change="changeCategoryState(item)" />
</div>
<eForm ref="cuform" :selected-category="selectedCategory" :edit-info-flow="editInfoFlow" @refresh="getInitCategoryFlowBase" />
</div>
</template>
<script>
import { FetchInitCategoryFlowBase, FetchReDocumentInitFlowByCategoryId, FetchChangeCategoryState, FetchgetCategoryFlow } from '@/api/system/category/process'
import eForm from './module/form'
export default {
name: 'ProcessManage',
components: { eForm },
props: {
selectedCategory: {
type: Object,
default: function() {
return {}
}
}
},
data() {
return {
flowBaseList: [],
flowSetting: [],
editInfoFlow: null
}
},
computed: {
filteredFlowList() {
if (this.selectedCategory.pid === '0' && this.selectedCategory.isType === 1) {
return this.flowBaseList.filter(item => item.id === '5')
} else {
return this.flowBaseList.filter(item => item.id !== '5')
}
}
},
watch: {
},
mounted() {
this.getInitCategoryFlowBase()
},
methods: {
getInitCategoryFlowBase() {
FetchInitCategoryFlowBase().then((res) => {
this.flowBaseList = res.map(item => {
return {
...item,
status: false
}
})
this.getReDocumentInitFlowByCategoryId()
}).catch(err => {
console.log(err)
})
},
getReDocumentInitFlowByCategoryId() {
console.log(this.selectedCategory)
FetchReDocumentInitFlowByCategoryId({ 'documentId': this.selectedCategory.pid !== '0' ? this.selectedCategory.id : null }).then((res) => {
this.flowSetting = res
this.flowBaseList.forEach(item => {
this.flowSetting.forEach(v => {
if (item.id === v.flowManageId) {
item.status = !!v.status
item.flowName = v.flowName
}
})
})
}).catch(err => {
console.log(err)
})
},
toProcessSelected(item) {
const params = {
'categoryId': this.selectedCategory.pid !== '0' ? this.selectedCategory.id : null,
'manageId': item.id
}
FetchgetCategoryFlow(params).then((res) => {
if (res) {
// 判断所有值是否都为 null
if (this.allValuesNull(res)) {
// console.log('所有值都为 null') 新增
this.editInfoFlow = null
} else {
// console.log('不是所有值都为 null') 编辑
this.editInfoFlow = res
}
} else {
this.editInfoFlow = null
this.$message({ message: '数据请求失败', type: 'error', offset: 8 })
}
console.log('this.editInfoFlow', this.editInfoFlow)
this.$nextTick(() => {
this.$refs.cuform.cuDialogVisible = true
this.$refs.cuform.setPocessCurrent = item
this.$refs.cuform.getEditInfoFlow = this.editInfoFlow ? this.editInfoFlow : {}
this.$refs.cuform.handleEditInfoFlow(this.editInfoFlow)
})
}).catch(err => {
console.log(err)
})
},
changeCategoryState(item) {
const filterItem = this.flowSetting.find(v => item.id === v.flowManageId)
const params = {
'categoryId': this.selectedCategory.id,
'id': filterItem.id,
'status': item.status ? 1 : 0
}
FetchChangeCategoryState(params).then((res) => {
if (res === 'SUCCESS') {
this.$message({ message: '操作成功', type: 'success', offset: 8 })
} else {
this.$message({ message: '操作失败', type: 'error', offset: 8 })
}
}).catch(err => {
console.log(err)
})
},
allValuesNull(jsonObject) {
for (const key in jsonObject) {
if (jsonObject[key] !== null) {
return false
}
}
return true
}
}
}
</script>
<style lang='scss' scoped>
.process-item{
display: flex;
justify-content: flex-start;
padding: 36px 40px 0 40px;
font-size: 14px;
height: 32px;
line-height: 32px;
margin-bottom: 20px;
span{
display: block;
margin-right: 12px;
}
p{
position: relative;
width: 324px;
height: 32px;
line-height: 32px;
padding: 0 13px;
margin-right: 16px;
color: #0C0E1E;
background-color: #F5F7FA;
i{
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
color: #0348F3;
}
}
}
</style>
Loading…
Cancel
Save