14 changed files with 3179 additions and 114 deletions
-
2package.json
-
21src/api/system/borrower.js
-
48src/api/system/documentArchives.js
-
925src/views/archivesMIOD/miodLibrary/index copy.vue
-
507src/views/archivesMIOD/miodLibrary/index.vue
-
57src/views/archivesMIOD/miodLibrary/module/detail.vue
-
290src/views/archivesMIOD/miodLibrary/module/pdfViewer.vue
-
610src/views/archivesMIOD/miodLibrary/module/pdfViewer2.vue
-
293src/views/archivesMIOD/miodLibrary/module/pdfViewer3.vue
-
146src/views/archivesMIOD/miodLibrary/module/pdfViewer4.vue
-
10src/views/archivesMIOD/miodRecord/index.vue
-
338src/views/components/category/PreviewForm.vue
-
4src/views/components/category/SettingForm.vue
-
4src/views/system/borrowerManage/index.vue
@ -0,0 +1,925 @@ |
|||||
|
<template> |
||||
|
<div class="app-container"> |
||||
|
<!-- 门类列表 --> |
||||
|
<div class="container-main"> |
||||
|
<div class="elect-cont-left"> |
||||
|
<TreeList ref="treeList" @nodeClick="handleNodeClick" /> |
||||
|
</div> |
||||
|
<div v-if="selectedDocument.isType!==1" class="elect-cont-right"> |
||||
|
<!--工具栏--> |
||||
|
<div class="head-container" :style="isRecycle?'display:flex;justify-content: space-between; align-items: center;':'' "> |
||||
|
<div class="head-search" :style="isRecycle?'margin: 0;':''"> |
||||
|
<!-- 搜索 --> |
||||
|
<el-input v-model="query.search" clearable size="small" placeholder="输入题名搜索" prefix-icon="el-icon-search" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" /> |
||||
|
<rrOperation /> |
||||
|
<el-button class="filter-item filter-refresh" size="mini" type="warning" icon="el-icon-refresh-left" @click="resetQuery">重置</el-button> |
||||
|
</div> |
||||
|
<crudOperation v-if="!isRecycle" :permission="permission"> |
||||
|
<template v-slot:left> |
||||
|
<!-- 新增 --> |
||||
|
<el-button size="mini" @click="handleForm('add')"><i class="iconfont icon-xinzeng" />新增</el-button> |
||||
|
<!-- 修改 --> |
||||
|
<el-button size="mini" :disabled="crud.selections.length !== 1" @click="handleForm('edit')"><i class="iconfont icon-bianji" />编辑</el-button> |
||||
|
<!-- 删除btn 多选 --> |
||||
|
<el-button size="mini" :loading="crud.delAllLoading" :disabled="crud.selections.length === 0" @click="toDelete(crud.selections)"><i class="iconfont icon-shanchu" />删除</el-button> |
||||
|
<el-button :loading="crud.downloadLoading" size="mini" :disabled="crud.selections.length === 0" @click="doExport(crud.selections)"> |
||||
|
<i class="iconfont icon-daochu" /> |
||||
|
导出 |
||||
|
</el-button> |
||||
|
</template> |
||||
|
<template v-slot:rightButtonGroup> |
||||
|
<div> |
||||
|
<el-button size="mini" :disabled="crud.selections.length === 0" @click="doPrint(crud.selections)"><i class="iconfont icon-dayin" />打印处理单</el-button> |
||||
|
</div> |
||||
|
</template> |
||||
|
</crudOperation> |
||||
|
<div v-if="isRecycle"> |
||||
|
<el-button size="mini" type="success" @click="toRecover(crud.selections)"><i class="iconfont icon-huifu" />恢复</el-button> |
||||
|
<el-button size="mini" type="success" @click="toCompletelyDelete(crud.selections)"><i class="iconfont icon-shanchu" />彻底删除</el-button> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!--表格渲染--> |
||||
|
<div class="container-right"> |
||||
|
<span class="right-top-line" /> |
||||
|
<span class="left-bottom-line" /> |
||||
|
<el-table |
||||
|
ref="table" |
||||
|
v-loading="crud.loading" |
||||
|
class="archives-table" |
||||
|
:data="crud.data" |
||||
|
style="width: 100%;" |
||||
|
@row-click="clickRowHandler" |
||||
|
@select="crud.selectChange" |
||||
|
@select-all="crud.selectAllChange" |
||||
|
@selection-change="crud.selectionChangeHandler" |
||||
|
@cell-dblclick="tableDoubleClick" |
||||
|
> |
||||
|
<el-table-column type="selection" align="center" width="55" /> |
||||
|
<el-table-column v-for="field in tableDisplayFields" :key="field.id" :label="field.fieldCnName" :align="field.displayformatType" :width="field.displayLength" show-overflow-tooltip> |
||||
|
<template slot="header"> |
||||
|
<el-tooltip |
||||
|
class="item" |
||||
|
effect="dark" |
||||
|
:content="field.fieldCnName" |
||||
|
placement="top-start" |
||||
|
> |
||||
|
<span>{{ field.fieldCnName }}</span> |
||||
|
</el-tooltip> |
||||
|
</template> |
||||
|
<template slot-scope="scope"> |
||||
|
<!-- 仅针对read_type字段添加特殊处理 --> |
||||
|
<span |
||||
|
v-if="field.fieldName === 'read_type'" |
||||
|
:class="{ |
||||
|
'row-state row-packing': scope.row.read_type === '未传阅', |
||||
|
'row-state row-warehousing state-active': scope.row.read_type === '传阅中', |
||||
|
'row-state row-binding state-active': scope.row.read_type === '已完成', |
||||
|
}" |
||||
|
> |
||||
|
{{ scope.row[field.fieldName] }} |
||||
|
</span> |
||||
|
<span v-else>{{ scope.row[field.fieldName] }}</span> |
||||
|
</template> |
||||
|
</el-table-column> |
||||
|
</el-table> |
||||
|
<!--分页组件--> |
||||
|
<pagination v-if="crud.data.length !== 0" /> |
||||
|
</div> |
||||
|
<detail ref="archivesInfo" :selected-document="selectedDocument" /> |
||||
|
|
||||
|
<!--新增 / 编辑 表单组件--> |
||||
|
<el-dialog class="preview-dialog" :modal-append-to-body="false" :close-on-click-modal="false" append-to-body :before-close="closeDialog" :visible="formVisible" :title="formTitle"> |
||||
|
<span class="dialog-right-top" /> |
||||
|
<span class="dialog-left-bottom" /> |
||||
|
<div class="setting-dialog"> |
||||
|
<PreviewForm |
||||
|
ref="previewForm" |
||||
|
:form-preview-data.sync="formPreviewData" |
||||
|
:selected-category="selectedCategory" |
||||
|
:parents-id="parentsId" |
||||
|
:arc-id="arcId" |
||||
|
:is-des-form-type="isDesFormType" |
||||
|
:is-disabled="isDisabled" |
||||
|
:selected-document="selectedDocument" |
||||
|
:is-has-code="isHasCode" |
||||
|
@close-dialog="closeDialog" |
||||
|
@formLoadingShow="formLoadingShow" |
||||
|
@refreshTree="refreshTreeList" |
||||
|
/> |
||||
|
<div slot="footer" class="dialog-footer" style="margin-top: 20px !important;"> |
||||
|
<el-button type="text" @click="closeDialog">取消</el-button> |
||||
|
<el-button :loading="archivesBtnLoading" type="primary" @click="handlerArchivesSubmit">保存</el-button> |
||||
|
<!-- @click="handlerArchivesSubmit" --> |
||||
|
<el-button :loading="bindSaveLoading" type="primary">保存并绑定标签</el-button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</el-dialog> |
||||
|
<!--表单组件--> |
||||
|
<el-dialog class="tip-dialog" :close-on-click-modal="false" :modal-append-to-body="false" append-to-body title="提示" :visible.sync="printVisible"> |
||||
|
<div class="setting-dialog"> |
||||
|
<div class="tip-content"> |
||||
|
<p class="tipMsg">此操作将打印所选公文库数据</p> |
||||
|
</div> |
||||
|
<el-radio-group v-model="printType" style="padding-left: 36px;"> |
||||
|
<el-radio :label="0">套打</el-radio> |
||||
|
<el-radio :label="1">彩打</el-radio> |
||||
|
</el-radio-group> |
||||
|
<div slot="footer" class="dialog-footer"> |
||||
|
<el-button @click.native="printVisible = false">取消</el-button> |
||||
|
<el-button type="primary" @click.native="handlePrint">确定</el-button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</el-dialog> |
||||
|
<!-- v-show="isHidden" --> |
||||
|
<div id="print" ref="printId" class="print-container"> |
||||
|
<!-- 收文处理单表格 --> |
||||
|
<table class="official-table" width="100%" cellpadding="1" cellspacing="0"> |
||||
|
<!-- 列宽定义 --> |
||||
|
<col width="32*"> |
||||
|
<col width="70*"> |
||||
|
<col width="42*"> |
||||
|
<col width="56*"> |
||||
|
<col width="27*"> |
||||
|
<col width="29*"> |
||||
|
|
||||
|
<!-- 表头 --> |
||||
|
<tbody> |
||||
|
<tr> |
||||
|
<td colspan="6" class="header-cell"> |
||||
|
<p class="header-text" style="font-size: 30px;">武汉市交通运输局收文处理单</p> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
|
||||
|
<!-- 收文信息行 --> |
||||
|
<tbody> |
||||
|
<tr> |
||||
|
<td class="label-cell">收文号</td> |
||||
|
<td class="content-cell">{{ formData.receiveNo }}</td> |
||||
|
<td class="label-cell">收文日期</td> |
||||
|
<td class="content-cell">{{ formData.receiveDate }}</td> |
||||
|
<td class="label-cell">缓急</td> |
||||
|
<td class="content-cell">{{ formData.urgency }}</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
|
||||
|
<!-- 来文信息行 --> |
||||
|
<tbody> |
||||
|
<tr> |
||||
|
<td class="label-cell">来文单位</td> |
||||
|
<td class="content-cell">{{ formData.senderUnit }}</td> |
||||
|
<td class="label-cell">来文字号</td> |
||||
|
<td class="content-cell">{{ formData.senderNo }}</td> |
||||
|
<td class="label-cell">密级</td> |
||||
|
<td class="content-cell">{{ formData.secrecyLevel }}</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
|
||||
|
<!-- 提示行 --> |
||||
|
<tbody> |
||||
|
<tr> |
||||
|
<td class="label-cell">提示</td> |
||||
|
<td colspan="5" class="content-cell content-left">{{ formData.tip }}</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
|
||||
|
<!-- 文件标题行 --> |
||||
|
<tbody> |
||||
|
<tr> |
||||
|
<td class="label-cell">文件标题</td> |
||||
|
<td colspan="5" class="content-cell content-left">{{ formData.fileTitle }}</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
|
||||
|
<!-- 拟办意见行 --> |
||||
|
<tbody> |
||||
|
<tr> |
||||
|
<td class="label-cell">拟办意见</td> |
||||
|
<td colspan="5" class="content-cell content-left">{{ formData.recommendation }}</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
|
||||
|
<!-- 领导批示行 --> |
||||
|
<tbody> |
||||
|
<tr> |
||||
|
<td class="label-cell">领导批示</td> |
||||
|
<td colspan="5" class="content-cell content-left">{{ formData.leaderApproval }}</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
|
||||
|
<!-- 部门阅办行 --> |
||||
|
<tbody> |
||||
|
<tr> |
||||
|
<td class="label-cell">部门阅办</td> |
||||
|
<td colspan="5" class="content-cell content-center">{{ formData.departmentOpinion }}</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import CRUD, { presenter, header } from '@crud/crud' |
||||
|
import { miodLibraryCrud } from './mixins/index' |
||||
|
import crudDocumentArchives, { FetchDelArchives, FetchCompleteDelArchives, FetchRestoreArchives } from '@/api/system/documentArchives' |
||||
|
import rrOperation from '@crud/RR.operation' |
||||
|
import crudOperation from '@crud/CRUD.operation' |
||||
|
import pagination from '@crud/Pagination' |
||||
|
import TreeList from './treeList' |
||||
|
import PreviewForm from '@/views/components/category/PreviewForm' |
||||
|
import detail from './module/detail' |
||||
|
import { exportFile } from '@/utils/index' |
||||
|
import qs from 'qs' |
||||
|
import { mapGetters } from 'vuex' |
||||
|
|
||||
|
import html2canvas from 'html2canvas' |
||||
|
import printJS from 'print-js' |
||||
|
|
||||
|
export default { |
||||
|
name: 'MiodLibrary', |
||||
|
components: { TreeList, PreviewForm, detail, rrOperation, crudOperation, pagination }, |
||||
|
cruds() { |
||||
|
return [ |
||||
|
CRUD({ |
||||
|
title: '收发文', url: 'api/documentArchives/initPreDocument', |
||||
|
crudMethod: { ...crudDocumentArchives }, |
||||
|
optShow: { |
||||
|
add: false, |
||||
|
edit: false, |
||||
|
del: false, |
||||
|
download: false, |
||||
|
group: false, |
||||
|
reset: false |
||||
|
}, |
||||
|
queryOnPresenterCreated: false |
||||
|
}) |
||||
|
] |
||||
|
}, |
||||
|
provide() { |
||||
|
return { |
||||
|
parentsData: this |
||||
|
} |
||||
|
}, |
||||
|
mixins: [presenter(), header(), miodLibraryCrud], |
||||
|
props: { |
||||
|
isRecycle: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
}, |
||||
|
isdel: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
archivesBtnLoading: false, |
||||
|
bindSaveLoading: false, |
||||
|
permission: { |
||||
|
add: ['admin', 'prearchiveLibrary:add'], |
||||
|
edit: ['admin', 'prearchiveLibrary:edit'], |
||||
|
del: ['admin', 'prearchiveLibrary:del'], |
||||
|
sort: ['admin', 'prearchiveLibrary:sort'] |
||||
|
}, |
||||
|
tableDisplayFields: [], // table-list-title字段 |
||||
|
arrySort: [], |
||||
|
selectedDocument: {}, |
||||
|
form: {}, |
||||
|
formVisible: false, |
||||
|
formTitle: '新增文件', |
||||
|
formPreviewData: [], |
||||
|
selectedCategory: null, |
||||
|
parentsId: null, |
||||
|
arcId: null, |
||||
|
isDesFormType: 'miodLibrary', |
||||
|
isDisabled: false, |
||||
|
isHasCode: false, |
||||
|
printVisible: false, |
||||
|
printType: 0, |
||||
|
isHidden: false, |
||||
|
formData: { |
||||
|
receiveNo: '武交收〔2025〕12号', |
||||
|
receiveDate: '2025年05月20日', |
||||
|
urgency: '急件', |
||||
|
senderUnit: '湖北省交通运输厅', |
||||
|
senderNo: '鄂交发〔2025〕35号', |
||||
|
secrecyLevel: '普通', |
||||
|
tip: '请办公室尽快协调相关部门阅办', |
||||
|
fileTitle: '关于加强交通运输安全生产管理的通知', |
||||
|
recommendation: '建议转交运输科牵头办理,5个工作日内反馈意见', |
||||
|
leaderApproval: '同意拟办意见,张XX 2025.05.20', |
||||
|
departmentOpinion: '已阅,将于2025.05.25前反馈' |
||||
|
}, |
||||
|
printTitle: '' |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
...mapGetters([ |
||||
|
'baseApi' |
||||
|
]) |
||||
|
}, |
||||
|
watch: { |
||||
|
isdel: function(newValue, oldValue) { |
||||
|
}, |
||||
|
isRecycle: function(newValue, oldValue) { |
||||
|
} |
||||
|
}, |
||||
|
created() { |
||||
|
}, |
||||
|
methods: { |
||||
|
refreshTreeList() { |
||||
|
this.$refs.treeList.refreshData() |
||||
|
}, |
||||
|
resetQuery() { |
||||
|
if (this.selectedDocument.isType === 3) { |
||||
|
this.crud.query.docDepartment = this.selectedDocument.label |
||||
|
this.crud.query.archiveYear = null |
||||
|
this.crud.query.search = null |
||||
|
} else if (this.selectedDocument.isType === 4) { |
||||
|
this.crud.query.docDepartment = null |
||||
|
this.crud.query.archiveYear = this.selectedDocument.label |
||||
|
this.crud.query.search = null |
||||
|
} else { |
||||
|
this.crud.query.search = null |
||||
|
this.crud.query.docDepartment = null |
||||
|
this.crud.query.archiveYear = null |
||||
|
} |
||||
|
this.crud.toQuery() |
||||
|
}, |
||||
|
[CRUD.HOOK.beforeRefresh]() { |
||||
|
if (this.selectedDocument.isType === 2) { |
||||
|
this.crud.query.documentId = this.selectedDocument.id |
||||
|
} else { |
||||
|
this.crud.query.documentId = this.selectedDocument.documentId |
||||
|
} |
||||
|
this.crud.query.isdel = this.isdel |
||||
|
// this.crud.query.ignore = false |
||||
|
this.crud.query.fondsAffiliation = this.selectedDocument.fondsId |
||||
|
this.crud.query.sort = this.arrySort |
||||
|
}, |
||||
|
formLoadingShow(loadingType) { |
||||
|
this.archivesBtnLoading = loadingType |
||||
|
}, |
||||
|
handleNodeClick(data) { |
||||
|
if (data.isType !== 1) { |
||||
|
this.selectedDocument = data |
||||
|
let documentId = null |
||||
|
if (data.isType === 2) { |
||||
|
documentId = data.id |
||||
|
} else { |
||||
|
documentId = data.documentId |
||||
|
} |
||||
|
this.getInitDocumentsViewTable(documentId) |
||||
|
} |
||||
|
}, |
||||
|
// table字段项 |
||||
|
getInitDocumentsViewTable(documentId) { |
||||
|
crudDocumentArchives.FetchInitDocumentsViewTable({ documentId: documentId }).then(data => { |
||||
|
if (data) { |
||||
|
this.arrySort = [] |
||||
|
this.tableDisplayFields = data |
||||
|
const orderSortArry = this.tableDisplayFields.filter(item => item.queue).sort((a, b) => a.queue - b.queue) |
||||
|
orderSortArry.forEach(item => { |
||||
|
if (item.displayOrderBy) { |
||||
|
this.arrySort.push(item.fieldName + ',' + item.displayOrderBy) |
||||
|
} |
||||
|
}) |
||||
|
this.$nextTick(() => { |
||||
|
if (this.selectedDocument.isType === 3) { |
||||
|
this.crud.query.docDepartment = this.selectedDocument.label |
||||
|
this.crud.query.archiveYear = null |
||||
|
} else if (this.selectedDocument.isType === 4) { |
||||
|
this.crud.query.docDepartment = null |
||||
|
this.crud.query.archiveYear = this.selectedDocument.label |
||||
|
} else { |
||||
|
this.crud.query.search = null |
||||
|
this.crud.query.docDepartment = null |
||||
|
this.crud.query.archiveYear = null |
||||
|
} |
||||
|
this.crud.toQuery() |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
handleForm(type) { |
||||
|
const { selectedDocument, crud } = this |
||||
|
|
||||
|
if (!selectedDocument) { |
||||
|
console.warn('selectedDocument 未定义,无法继续操作') |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
this.selectedCategory = selectedDocument |
||||
|
this.isDesFormType = 'miodLibrary' |
||||
|
|
||||
|
let documentId |
||||
|
if (selectedDocument.isType === 2) { |
||||
|
documentId = selectedDocument.id |
||||
|
} else { |
||||
|
documentId = selectedDocument.documentId |
||||
|
} |
||||
|
|
||||
|
const params = { documentId } |
||||
|
|
||||
|
if (type === 'add') { |
||||
|
this.formTitle = '新增文件' |
||||
|
params.archivesId = null |
||||
|
} else if (type === 'edit') { |
||||
|
this.formTitle = '编辑文件' |
||||
|
const { id: archivesId } = crud.selections[0] |
||||
|
this.arcId = archivesId |
||||
|
params.archivesId = archivesId |
||||
|
} |
||||
|
|
||||
|
this.getFormInfo(params, type) |
||||
|
}, |
||||
|
|
||||
|
getFormInfo(params, type) { |
||||
|
crudDocumentArchives.FetchDoeditDocument(params).then(data => { |
||||
|
console.log('data', data) |
||||
|
const showFiledAll = data.showFiled.filter(item => item.isSequence).sort((a, b) => a.isSequence - b.isSequence) |
||||
|
this.$nextTick(() => { |
||||
|
this.formPreviewData = showFiledAll |
||||
|
this.formVisible = true |
||||
|
this.$nextTick(() => { |
||||
|
this.$refs.previewForm.fileOriginal = null |
||||
|
this.$refs.previewForm.fileJsonString = null |
||||
|
if (type === 'edit') { |
||||
|
this.$refs.previewForm.archivesType = 'edit' |
||||
|
this.$refs.previewForm.addOrUpdateForm = data.echo |
||||
|
if (data.fileecho) { |
||||
|
const fileecho = [] |
||||
|
fileecho.push(data.fileecho) |
||||
|
this.$refs.previewForm.fileOriginal = fileecho[0].file_name |
||||
|
this.$refs.previewForm.fileJsonString = JSON.stringify(fileecho) |
||||
|
} else { |
||||
|
this.$refs.previewForm.fileOriginal = '' |
||||
|
this.$refs.previewForm.fileJsonString = '' |
||||
|
} |
||||
|
} else { |
||||
|
this.$refs.previewForm.archivesType = 'add' |
||||
|
} |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
|
}, |
||||
|
handlerArchivesSubmit() { |
||||
|
let documentId |
||||
|
if (this.selectedDocument.isType === 2) { |
||||
|
documentId = this.selectedDocument.id |
||||
|
} else { |
||||
|
documentId = this.selectedDocument.documentId |
||||
|
} |
||||
|
this.$refs.previewForm.submitForm('addOrUpdateForm', documentId) |
||||
|
}, |
||||
|
clickRowHandler(row) { |
||||
|
this.$refs.table.clearSelection() |
||||
|
this.$refs.table.toggleRowSelection(row) |
||||
|
}, |
||||
|
// 双击查看详情 |
||||
|
tableDoubleClick(row) { |
||||
|
console.log('tableDoubleClick', row) |
||||
|
this.$refs.archivesInfo.archivesInfoVisible = true |
||||
|
this.$refs.archivesInfo.archivesTabIndex = 0 |
||||
|
this.$refs.archivesInfo.parentInfo = row |
||||
|
this.$refs.archivesInfo.getDetial(row.id) |
||||
|
}, |
||||
|
// 删除 |
||||
|
toDelete(datas) { |
||||
|
this.$confirm('此操作将删除当前所选公文库数据' + '<span>你是否还要继续?</span>', '提示', { |
||||
|
confirmButtonText: '继续', |
||||
|
cancelButtonText: '取消', |
||||
|
type: 'warning', |
||||
|
dangerouslyUseHTMLString: true |
||||
|
}).then(() => { |
||||
|
this.crud.delAllLoading = true |
||||
|
const ids = [] |
||||
|
datas.forEach(val => { |
||||
|
ids.push(val.id) |
||||
|
}) |
||||
|
let documentId |
||||
|
if (this.selectedDocument.isType === 2) { |
||||
|
documentId = this.selectedDocument.id |
||||
|
} else { |
||||
|
documentId = this.selectedDocument.documentId |
||||
|
} |
||||
|
|
||||
|
const params = { |
||||
|
'documentId': documentId, |
||||
|
'archivesIds': ids |
||||
|
} |
||||
|
|
||||
|
FetchDelArchives(params).then((res) => { |
||||
|
console.log('res', res) |
||||
|
if (res.code !== 500) { |
||||
|
this.crud.notify('删除成功', CRUD.NOTIFICATION_TYPE.SUCCESS) |
||||
|
this.crud.refresh() |
||||
|
} else { |
||||
|
this.crud.notify('删除失败', CRUD.NOTIFICATION_TYPE.ERROR) |
||||
|
} |
||||
|
this.crud.delAllLoading = false |
||||
|
}).catch(err => { |
||||
|
this.crud.delAllLoading = false |
||||
|
console.log(err) |
||||
|
}) |
||||
|
}).catch(() => { |
||||
|
}) |
||||
|
}, |
||||
|
// 导出 |
||||
|
doExport(datas) { |
||||
|
this.crud.downloadLoading = true |
||||
|
this.$confirm('此操作将导出所选数据' + '<span>你是否还要继续?</span>', '提示', { |
||||
|
confirmButtonText: '继续', |
||||
|
cancelButtonText: '取消', |
||||
|
type: 'warning', |
||||
|
dangerouslyUseHTMLString: true |
||||
|
}).then(() => { |
||||
|
const ids = [] |
||||
|
datas.forEach(val => { |
||||
|
ids.push(val.id) |
||||
|
}) |
||||
|
let documentId |
||||
|
if (this.selectedDocument.isType === 2) { |
||||
|
documentId = this.selectedDocument.id |
||||
|
} else { |
||||
|
documentId = this.selectedDocument.documentId |
||||
|
} |
||||
|
const params = { |
||||
|
'documentId': documentId, |
||||
|
'ids': ids |
||||
|
} |
||||
|
exportFile(this.baseApi + '/api/documentArchives/downloadDocumentArchives?' + qs.stringify(params, { indices: false })) |
||||
|
this.crud.downloadLoading = false |
||||
|
}).catch(() => { |
||||
|
this.crud.downloadLoading = false |
||||
|
}) |
||||
|
}, |
||||
|
doPrint(datas) { |
||||
|
this.printVisible = true |
||||
|
}, |
||||
|
handlePrint() { |
||||
|
this.printVisible = false |
||||
|
this.isHidden = true |
||||
|
if (this.isHidden) { |
||||
|
const timer = setTimeout(() => { |
||||
|
this.printFn() |
||||
|
this.isHidden = false |
||||
|
clearTimeout(timer) |
||||
|
}, 100) |
||||
|
} |
||||
|
}, |
||||
|
printFn() { |
||||
|
// printJS({ |
||||
|
// printable: 'print', |
||||
|
// type: 'html', |
||||
|
// style: `page { |
||||
|
// size: A4; |
||||
|
// margin: 2cm; |
||||
|
// } |
||||
|
// /* 基础样式 */ |
||||
|
// .print-container { |
||||
|
// font-family: "宋体", sans-serif; |
||||
|
// margin: 20px; |
||||
|
// } |
||||
|
|
||||
|
// /* 标题样式 */ |
||||
|
// .print-title { |
||||
|
// font-size: 22pt; |
||||
|
// font-family: "华文中宋"; |
||||
|
// color: #ff0000; |
||||
|
// margin: 20px 0; |
||||
|
// } |
||||
|
|
||||
|
// /* 表格样式 */ |
||||
|
// .official-table { |
||||
|
// border-collapse: collapse; |
||||
|
// width: 100%; |
||||
|
// } |
||||
|
|
||||
|
// .label-cell, .content-cell { |
||||
|
// border: 1px solid #ff0000; |
||||
|
// padding: 8px; |
||||
|
// } |
||||
|
|
||||
|
// .label-cell { |
||||
|
// background: #ffffff; |
||||
|
// text-align: center; |
||||
|
// font-size: 12pt; |
||||
|
// color: #ff0000; |
||||
|
// vertical-align: middle; |
||||
|
// } |
||||
|
|
||||
|
// .content-cell { |
||||
|
// background: #ffffff; |
||||
|
// font-size: 12pt; |
||||
|
// vertical-align: top; |
||||
|
// } |
||||
|
|
||||
|
// .content-left { |
||||
|
// text-align: left; |
||||
|
// } |
||||
|
|
||||
|
// .content-center { |
||||
|
// text-align: center; |
||||
|
// } |
||||
|
|
||||
|
// .header-cell { |
||||
|
// border: none; |
||||
|
// padding: 20px 0; |
||||
|
// text-align: center; |
||||
|
// } |
||||
|
|
||||
|
// .header-text { |
||||
|
// font-family: "华文中宋"; |
||||
|
// font-size: 10pt; |
||||
|
// color: #ff0000; |
||||
|
// margin: 0; |
||||
|
// } |
||||
|
// ` // 去除页眉页脚 |
||||
|
// }) |
||||
|
const printContent = this.$refs.printId |
||||
|
// 获取dom 宽度 高度 |
||||
|
const width = printContent.clientWidth |
||||
|
const height = printContent.clientHeight |
||||
|
console.log('height', height) |
||||
|
// 创建一个canvas节点 |
||||
|
const canvas = document.createElement('canvas') |
||||
|
|
||||
|
const scale = 1 // 定义任意放大倍数,支持小数;越大,图片清晰度越高,生成图片越慢。 |
||||
|
canvas.width = width * scale // 定义canvas 宽度 * 缩放 |
||||
|
canvas.height = height * scale // 定义canvas高度 *缩放 |
||||
|
canvas.style.width = width * scale + 'px' |
||||
|
canvas.style.height = height * scale + 'px' |
||||
|
canvas.getContext('2d').scale(scale, scale) // 获取context,设置scale |
||||
|
|
||||
|
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop // 获取滚动轴滚动的长度 |
||||
|
const scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft // 获取水平滚动轴的长度 |
||||
|
|
||||
|
html2canvas(printContent, { |
||||
|
canvas, |
||||
|
backgroundColor: null, |
||||
|
useCORS: true, |
||||
|
windowHeight: document.body.scrollHeight, |
||||
|
scrollX: -scrollLeft, // 解决水平偏移问题,防止打印的内容不全 |
||||
|
scrollY: -scrollTop |
||||
|
}).then((canvas) => { |
||||
|
const url = canvas.toDataURL('image/png') |
||||
|
printJS({ |
||||
|
printable: url, |
||||
|
type: 'image', |
||||
|
documentTitle: '', // 标题 |
||||
|
style: '@page{size:auto;margin: 0cm 1cm 0cm 1cm;}' // 去除页眉页脚 |
||||
|
}) |
||||
|
}).catch(err => { |
||||
|
console.error(err) |
||||
|
}) |
||||
|
}, |
||||
|
// 回收站 - 恢复 |
||||
|
toRecover(datas) { |
||||
|
if (datas.length === 0) { |
||||
|
this.$message({ message: '您还未勾选需要操作的条目,请先确认!', offset: 8 }) |
||||
|
return false |
||||
|
} |
||||
|
this.$confirm('此恢复将会把所选条目及其子集一并恢复' + '<span>你是否还要继续?</span>', '提示', { |
||||
|
confirmButtonText: '继续', |
||||
|
cancelButtonText: '取消', |
||||
|
type: 'warning', |
||||
|
dangerouslyUseHTMLString: true |
||||
|
}).then(() => { |
||||
|
const archivesIds = [] |
||||
|
datas.forEach(val => { |
||||
|
archivesIds.push(val.id) |
||||
|
}) |
||||
|
|
||||
|
let documentId |
||||
|
if (this.selectedDocument.isType === 2) { |
||||
|
documentId = this.selectedDocument.id |
||||
|
} else { |
||||
|
documentId = this.selectedDocument.documentId |
||||
|
} |
||||
|
const params = { |
||||
|
'documentId': documentId, |
||||
|
'archivesIds': archivesIds |
||||
|
} |
||||
|
console.log('params', params) |
||||
|
FetchRestoreArchives(params).then((res) => { |
||||
|
console.log('res', res) |
||||
|
if (res.code !== 500) { |
||||
|
if (res.includes('成功')) { |
||||
|
this.$message({ message: res, type: 'success', offset: 8 }) |
||||
|
this.crud.refresh() |
||||
|
this.refreshTreeList() |
||||
|
} else { |
||||
|
this.$message({ message: res, type: 'error', offset: 8 }) |
||||
|
} |
||||
|
} else { |
||||
|
this.$message({ message: '恢复所选档案失败', type: 'error', offset: 8 }) |
||||
|
} |
||||
|
}).catch(err => { |
||||
|
console.log(err) |
||||
|
}) |
||||
|
}).catch(() => { |
||||
|
}) |
||||
|
}, |
||||
|
// 回收站 - 彻底删除 |
||||
|
toCompletelyDelete(datas) { |
||||
|
if (datas.length === 0) { |
||||
|
this.$message({ message: '您还未勾选需要操作的条目,请先确认!', offset: 8 }) |
||||
|
return false |
||||
|
} |
||||
|
this.$confirm('此删除将把会所选条目与其子集彻底删除' + '<span>你是否还要继续?</span>', '提示', { |
||||
|
confirmButtonText: '继续', |
||||
|
cancelButtonText: '取消', |
||||
|
type: 'warning', |
||||
|
dangerouslyUseHTMLString: true |
||||
|
}).then(() => { |
||||
|
const archivesIds = [] |
||||
|
datas.forEach(val => { |
||||
|
archivesIds.push(val.id) |
||||
|
}) |
||||
|
let documentId |
||||
|
if (this.selectedDocument.isType === 2) { |
||||
|
documentId = this.selectedDocument.id |
||||
|
} else { |
||||
|
documentId = this.selectedDocument.documentId |
||||
|
} |
||||
|
const params = { |
||||
|
'documentId': documentId, |
||||
|
'archivesIds': archivesIds |
||||
|
} |
||||
|
console.log('params', params) |
||||
|
FetchCompleteDelArchives(params).then((res) => { |
||||
|
if (res.code !== 500) { |
||||
|
this.$message({ message: res, type: 'success', offset: 8 }) |
||||
|
this.crud.refresh() |
||||
|
this.refreshTreeList() |
||||
|
} else { |
||||
|
this.$message({ message: '删除所选档案失败', type: 'error', offset: 8 }) |
||||
|
} |
||||
|
}).catch(err => { |
||||
|
console.log(err) |
||||
|
}) |
||||
|
}).catch(() => { |
||||
|
}) |
||||
|
}, |
||||
|
closeDialog() { |
||||
|
this.formVisible = false |
||||
|
this.$refs.previewForm.miodDeptsTags = [] |
||||
|
this.$refs.previewForm.miodDeptsSelections = [] |
||||
|
this.$refs.previewForm.deptsValid = false |
||||
|
if (this.$refs.previewForm.$refs['addOrUpdateForm']) { |
||||
|
this.$refs.previewForm.$refs['addOrUpdateForm'].clearValidate() |
||||
|
this.$refs.previewForm.$refs['addOrUpdateForm'].resetFields() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
<style> |
||||
|
@media print { |
||||
|
html,body{ |
||||
|
height: inherit; |
||||
|
} |
||||
|
.print-container { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
} |
||||
|
@page { |
||||
|
size: A4; |
||||
|
margin: 2cm; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
|
<style lang='scss' scoped> |
||||
|
@import "~@/assets/styles/collect-reorganizi.scss"; |
||||
|
@mixin management-fixed-style{ |
||||
|
[data-theme="dark"] & { |
||||
|
background-color: #031435 !important; |
||||
|
-webkit-box-shadow: -5px 5px 10px 1px rgba(15,164,222,.16); |
||||
|
box-shadow: -5px 5px 10px 1px rgba(15,164,222,.16); |
||||
|
} |
||||
|
[data-theme="light"] & { |
||||
|
background-color: #fff; |
||||
|
} |
||||
|
} |
||||
|
.el-table { |
||||
|
::v-deep .el-table__fixed-right { |
||||
|
@include management-fixed-style; |
||||
|
} |
||||
|
} |
||||
|
.preview-dialog .el-dialog .preview-content { |
||||
|
height: calc(100vh - 264px) !important; |
||||
|
|
||||
|
} |
||||
|
.elect-cont-right .container-right { |
||||
|
min-height: calc(100vh - 284px); |
||||
|
} |
||||
|
.dialog-footer .el-button.el-button--primary, |
||||
|
.el-message-box__btns .el-button.el-button--primary{ |
||||
|
width: auto; |
||||
|
height: auto; |
||||
|
line-height:normal; |
||||
|
padding: 6px 20px; |
||||
|
} |
||||
|
|
||||
|
.tip-dialog{ |
||||
|
::v-deep .el-dialog{ |
||||
|
width: 504px; |
||||
|
.setting-dialog{ |
||||
|
padding: 10px 10px 0 10px; |
||||
|
} |
||||
|
.tip-content{ |
||||
|
padding-left: 34px; |
||||
|
font-size: 14px; |
||||
|
line-height: 24px; |
||||
|
color: #0C0E1E; |
||||
|
background: url("~@/assets/images/icon/tip-icon.png") no-repeat left top; |
||||
|
background-size: 24px 24px; |
||||
|
padding-bottom: 20px; |
||||
|
span{ |
||||
|
font-size: 12px; |
||||
|
color: #ED4A41; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.dialog-footer{ |
||||
|
margin-top: 27px; |
||||
|
} |
||||
|
} |
||||
|
.intoExamine{ |
||||
|
position: fixed; |
||||
|
left: 50%; |
||||
|
top: 50%; |
||||
|
transform: translate(-50%,-50%); |
||||
|
z-index: 99; |
||||
|
} |
||||
|
/* 基础样式 */ |
||||
|
.print-container { |
||||
|
position: fixed; |
||||
|
left: 50%; |
||||
|
top: 50%; |
||||
|
transform: translate(-50%,-50%); |
||||
|
z-index: 99; |
||||
|
} |
||||
|
|
||||
|
/* 标题样式 */ |
||||
|
.print-title { |
||||
|
font-size: 22pt; |
||||
|
font-family: "华文中宋"; |
||||
|
color: #ff0000; |
||||
|
margin: 20px 0; |
||||
|
} |
||||
|
|
||||
|
/* 表格样式 */ |
||||
|
.official-table { |
||||
|
border-collapse: collapse; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.label-cell, .content-cell { |
||||
|
border: 1px solid #ff0000; |
||||
|
padding: 8px; |
||||
|
} |
||||
|
|
||||
|
.label-cell { |
||||
|
background: #ffffff; |
||||
|
text-align: center; |
||||
|
font-size: 12pt; |
||||
|
color: #ff0000; |
||||
|
vertical-align: middle; |
||||
|
} |
||||
|
|
||||
|
.content-cell { |
||||
|
background: #ffffff; |
||||
|
font-size: 12pt; |
||||
|
vertical-align: top; |
||||
|
} |
||||
|
|
||||
|
.content-left { |
||||
|
text-align: left; |
||||
|
} |
||||
|
|
||||
|
.content-center { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.header-cell { |
||||
|
border: none; |
||||
|
padding: 20px 0; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.header-text { |
||||
|
font-family: "华文中宋"; |
||||
|
font-size: 22pt; |
||||
|
color: #ff0000; |
||||
|
margin: 0; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,290 @@ |
|||||
|
<template> |
||||
|
<div class="pdf-viewer-container"> |
||||
|
<!-- 加载状态 --> |
||||
|
<div v-if="loading" class="loading-overlay"> |
||||
|
<div class="loading-spinner"> |
||||
|
<i class="fa fa-spinner fa-spin fa-3x" /> |
||||
|
<p>{{ loadingMessage }}</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 错误提示 --> |
||||
|
<div v-else-if="error" class="error-message"> |
||||
|
<i class="fa fa-exclamation-triangle" /> |
||||
|
<p>{{ error }}</p> |
||||
|
</div> |
||||
|
|
||||
|
<!-- PDF 容器 --> |
||||
|
<div v-else class="pdf-container"> |
||||
|
<div class="pdf-controls"> |
||||
|
<button class="print-button" @click="printPdf"> |
||||
|
<i class="fa fa-print" /> 打印 PDF |
||||
|
</button> |
||||
|
<button class="download-button" @click="downloadPdf"> |
||||
|
<i class="fa fa-download" /> 下载 PDF |
||||
|
</button> |
||||
|
<div class="page-controls"> |
||||
|
<button :disabled="currentPage <= 1" class="page-button" @click="goToPrevPage"> |
||||
|
<i class="fa fa-chevron-left" /> |
||||
|
</button> |
||||
|
<span class="page-info"> |
||||
|
第 {{ currentPage }} 页,共 {{ totalPages }} 页 |
||||
|
</span> |
||||
|
<button :disabled="currentPage >= totalPages" class="page-button" @click="goToNextPage"> |
||||
|
<i class="fa fa-chevron-right" /> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div ref="pdfViewer" class="pdf-viewer" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
// import pdfjsLib from 'pdfjs-dist/webpack' |
||||
|
// import 'pdfjs-dist/web/pdf_viewer.css' |
||||
|
|
||||
|
// 中文语言包支持 |
||||
|
// import { getDocument, GlobalWorkerOptions } from 'pdfjs-dist' |
||||
|
|
||||
|
import * as PDFJS from 'pdfdist-mergeofd/build/pdf' |
||||
|
// import { degrees, PDFDocument, rgb, StandardFonts } from 'pdf-lib' |
||||
|
// import { TextLayerBuilder, EventBus } from 'pdfdist-mergeofd/web/pdf_viewer' |
||||
|
import 'pdfdist-mergeofd/web/pdf_viewer.css' |
||||
|
PDFJS.GlobalWorkerOptions.workerSrc = require('pdfdist-mergeofd/build/pdf.worker.entry.js') |
||||
|
// import { saveByteArray } from '@/utils/index' |
||||
|
// import { getInitWatermark } from '@/api/system/waterMask' |
||||
|
// import { FetchGetFilingsealFormat, FetchGetFilingsealFormatDtails } from '@/api/system/category/category' |
||||
|
// import fontkit from '@pdf-lib/fontkit' |
||||
|
|
||||
|
// 设置 PDF.js worker 路径 |
||||
|
// GlobalWorkerOptions.workerSrc = '//cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.worker.min.js' |
||||
|
|
||||
|
export default { |
||||
|
name: 'PdfViewer', |
||||
|
props: { |
||||
|
// 支持 URL 或 Blob 格式 |
||||
|
pdfSource: { |
||||
|
type: [String, Blob], |
||||
|
required: true |
||||
|
}, |
||||
|
// 后端请求配置(当 pdfSource 为 URL 时使用) |
||||
|
requestOptions: { |
||||
|
type: Object, |
||||
|
default: () => ({ |
||||
|
method: 'GET', |
||||
|
headers: {} |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
pdfDoc: null, |
||||
|
currentPage: 1, |
||||
|
totalPages: 0, |
||||
|
scale: 1.0, |
||||
|
loading: true, |
||||
|
loadingMessage: '正在准备 PDF...', |
||||
|
error: null, |
||||
|
pdfBlob: null // 存储 PDF Blob 对象 |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
this.loadPdf() |
||||
|
}, |
||||
|
methods: { |
||||
|
async loadPdf() { |
||||
|
try { |
||||
|
let pdfData |
||||
|
|
||||
|
// 处理不同类型的数据源 |
||||
|
if (typeof this.pdfSource === 'string') { |
||||
|
// 如果是 URL,发起请求获取数据流 |
||||
|
this.loadingMessage = '正在下载 PDF...' |
||||
|
const response = await fetch(this.pdfSource, this.requestOptions) |
||||
|
|
||||
|
if (!response.ok) { |
||||
|
throw new Error(`HTTP错误,状态码:${response.status}`) |
||||
|
} |
||||
|
|
||||
|
pdfData = await response.arrayBuffer() |
||||
|
this.pdfBlob = new Blob([pdfData], { type: 'application/pdf' }) |
||||
|
} else if (this.pdfSource instanceof Blob) { |
||||
|
// 如果是 Blob 直接处理 |
||||
|
this.loadingMessage = '正在处理 PDF...' |
||||
|
pdfData = await this.blobToArrayBuffer(this.pdfSource) |
||||
|
this.pdfBlob = this.pdfSource |
||||
|
} else { |
||||
|
throw new Error('不支持的 PDF 数据源类型') |
||||
|
} |
||||
|
|
||||
|
// 加载 PDF 文件 |
||||
|
const loadingTask = PDFJS.getDocument({ |
||||
|
data: pdfData, |
||||
|
cMapUrl: '//cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/cmaps/', |
||||
|
cMapPacked: true |
||||
|
}) |
||||
|
|
||||
|
// 监听加载进度 |
||||
|
loadingTask.onProgress = (progressData) => { |
||||
|
const percent = Math.round((progressData.loaded / progressData.total) * 100) |
||||
|
this.loadingMessage = `正在加载 PDF: ${percent}%` |
||||
|
} |
||||
|
|
||||
|
// 获取 PDF 文档对象 |
||||
|
this.pdfDoc = await loadingTask.promise |
||||
|
this.totalPages = this.pdfDoc.numPages |
||||
|
|
||||
|
// 渲染第一页 |
||||
|
this.renderPage(this.currentPage) |
||||
|
this.loading = false |
||||
|
} catch (err) { |
||||
|
console.error('加载 PDF 失败:', err) |
||||
|
this.error = `无法加载 PDF 文件: ${err.message}` |
||||
|
this.loading = false |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// Blob 转 ArrayBuffer |
||||
|
blobToArrayBuffer(blob) { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
const reader = new FileReader() |
||||
|
reader.onload = () => resolve(reader.result) |
||||
|
reader.onerror = reject |
||||
|
reader.readAsArrayBuffer(blob) |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
async renderPage(num) { |
||||
|
try { |
||||
|
const page = await this.pdfDoc.getPage(num) |
||||
|
|
||||
|
// 获取渲染上下文 |
||||
|
const viewport = page.getViewport({ scale: this.scale }) |
||||
|
const canvas = document.createElement('canvas') |
||||
|
const context = canvas.getContext('2d') |
||||
|
|
||||
|
// 设置 canvas 尺寸 |
||||
|
canvas.height = viewport.height |
||||
|
canvas.width = viewport.width |
||||
|
canvas.className = 'pdf-page' |
||||
|
|
||||
|
// 清空容器并添加新的 canvas |
||||
|
const viewer = this.$refs.pdfViewer |
||||
|
viewer.innerHTML = '' |
||||
|
viewer.appendChild(canvas) |
||||
|
|
||||
|
// 渲染 PDF 页面 |
||||
|
const renderContext = { |
||||
|
canvasContext: context, |
||||
|
viewport: viewport |
||||
|
} |
||||
|
|
||||
|
await page.render(renderContext).promise |
||||
|
} catch (err) { |
||||
|
console.error('渲染 PDF 页面失败:', err) |
||||
|
this.error = '无法渲染 PDF 页面。' |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
goToPrevPage() { |
||||
|
if (this.currentPage > 1) { |
||||
|
this.currentPage-- |
||||
|
this.renderPage(this.currentPage) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
goToNextPage() { |
||||
|
if (this.currentPage < this.totalPages) { |
||||
|
this.currentPage++ |
||||
|
this.renderPage(this.currentPage) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
async printPdf() { |
||||
|
try { |
||||
|
// 打印前确保 PDF 已加载 |
||||
|
if (!this.pdfDoc) return |
||||
|
|
||||
|
this.loading = true |
||||
|
this.loadingMessage = '正在准备打印...' |
||||
|
|
||||
|
// 创建临时打印容器 |
||||
|
const printContainer = document.createElement('div') |
||||
|
printContainer.className = 'pdf-print-container' |
||||
|
printContainer.style.display = 'none' |
||||
|
document.body.appendChild(printContainer) |
||||
|
|
||||
|
// 逐页渲染到打印容器 |
||||
|
for (let i = 1; i <= this.totalPages; i++) { |
||||
|
const page = await this.pdfDoc.getPage(i) |
||||
|
const viewport = page.getViewport({ scale: 1.5 }) // 使用较高的缩放比例以获得更好的打印质量 |
||||
|
|
||||
|
const canvas = document.createElement('canvas') |
||||
|
const context = canvas.getContext('2d') |
||||
|
|
||||
|
canvas.height = viewport.height |
||||
|
canvas.width = viewport.width |
||||
|
canvas.className = 'pdf-print-page' |
||||
|
canvas.style.pageBreakAfter = 'always' |
||||
|
if (i === this.totalPages) { |
||||
|
canvas.style.pageBreakAfter = 'avoid' |
||||
|
} |
||||
|
|
||||
|
printContainer.appendChild(canvas) |
||||
|
|
||||
|
const renderContext = { |
||||
|
canvasContext: context, |
||||
|
viewport: viewport |
||||
|
} |
||||
|
|
||||
|
await page.render(renderContext).promise |
||||
|
|
||||
|
// 更新加载进度 |
||||
|
const percent = Math.round((i / this.totalPages) * 100) |
||||
|
this.loadingMessage = `正在准备打印: ${percent}%` |
||||
|
} |
||||
|
|
||||
|
// 保存当前视图 |
||||
|
const currentScroll = window.scrollY |
||||
|
|
||||
|
// 打印 |
||||
|
window.print() |
||||
|
|
||||
|
// 清理 |
||||
|
setTimeout(() => { |
||||
|
document.body.removeChild(printContainer) |
||||
|
this.loading = false |
||||
|
window.scrollTo(0, currentScroll) |
||||
|
}, 1000) |
||||
|
} catch (err) { |
||||
|
console.error('打印 PDF 失败:', err) |
||||
|
this.error = '打印 PDF 时出错。' |
||||
|
this.loading = false |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
downloadPdf() { |
||||
|
if (!this.pdfBlob) { |
||||
|
this.error = '无法下载 PDF,数据不可用。' |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 创建下载链接 |
||||
|
const url = URL.createObjectURL(this.pdfBlob) |
||||
|
const link = document.createElement('a') |
||||
|
link.href = url |
||||
|
link.download = 'document.pdf' |
||||
|
link.click() |
||||
|
|
||||
|
// 释放 URL 对象 |
||||
|
setTimeout(() => URL.revokeObjectURL(url), 100) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
/* 样式部分保持不变... */ |
||||
|
</style> |
||||
@ -0,0 +1,610 @@ |
|||||
|
<template> |
||||
|
<div class="pdf-overlay-container"> |
||||
|
<div class="toolbar"> |
||||
|
<button class="btn" @click="loadPdf"> |
||||
|
<i class="fa fa-file-pdf-o" /> 加载PDF |
||||
|
</button> |
||||
|
<button class="btn" @click="addTextField"> |
||||
|
<i class="fa fa-font" /> 添加文本 |
||||
|
</button> |
||||
|
<button class="btn" @click="saveTemplate"> |
||||
|
<i class="fa fa-save" /> 保存模板 |
||||
|
</button> |
||||
|
<button class="btn" @click="loadTemplate"> |
||||
|
<i class="fa fa-folder-open-o" /> 加载模板 |
||||
|
</button> |
||||
|
<button class="btn" @click="exportPdf"> |
||||
|
<i class="fa fa-download" /> 导出PDF |
||||
|
</button> |
||||
|
<button class="btn" @click="printPdf"> |
||||
|
<i class="fa fa-print" /> 打印 |
||||
|
</button> |
||||
|
<div class="scale-controls"> |
||||
|
<button class="btn" @click="decreaseScale"> |
||||
|
<i class="fa fa-search-minus" /> |
||||
|
</button> |
||||
|
<span>{{ scale.toFixed(1) }}x</span> |
||||
|
<button class="btn" @click="increaseScale"> |
||||
|
<i class="fa fa-search-plus" /> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="pdf-container"> |
||||
|
<div ref="pdfViewer" class="pdf-viewer"> |
||||
|
<canvas ref="pdfCanvas" /> |
||||
|
</div> |
||||
|
|
||||
|
<div |
||||
|
ref="overlayContainer" |
||||
|
class="overlay-container" |
||||
|
@mousedown="startDrag" |
||||
|
@mousemove="onDrag" |
||||
|
@mouseup="stopDrag" |
||||
|
@mouseleave="stopDrag" |
||||
|
> |
||||
|
<div |
||||
|
v-for="(field, index) in formFields" |
||||
|
:key="index" |
||||
|
:style="getFieldStyle(field)" |
||||
|
class="form-field" |
||||
|
@click="selectField(index)" |
||||
|
@dblclick="editField(index)" |
||||
|
> |
||||
|
{{ field.content || '点击编辑' }} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div v-if="selectedFieldIndex !== -1" class="properties-panel"> |
||||
|
<h3>字段属性</h3> |
||||
|
<div class="property-row"> |
||||
|
<label>内容:</label> |
||||
|
<input v-model="formFields[selectedFieldIndex].content" type="text"> |
||||
|
</div> |
||||
|
<div class="property-row"> |
||||
|
<label>字体大小:</label> |
||||
|
<input v-model.number="formFields[selectedFieldIndex].fontSize" type="number"> |
||||
|
</div> |
||||
|
<div class="property-row"> |
||||
|
<label>颜色:</label> |
||||
|
<input v-model="formFields[selectedFieldIndex].color" type="color"> |
||||
|
</div> |
||||
|
<div class="property-row"> |
||||
|
<label>位置:</label> |
||||
|
<span>X: {{ formFields[selectedFieldIndex].x.toFixed(0) }}</span> |
||||
|
<span>Y: {{ formFields[selectedFieldIndex].y.toFixed(0) }}</span> |
||||
|
</div> |
||||
|
<button class="btn danger" @click="deleteSelectedField"> |
||||
|
<i class="fa fa-trash" /> 删除字段 |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
|
||||
|
import html2canvas from 'html2canvas' |
||||
|
|
||||
|
import * as PDFJS from 'pdfdist-mergeofd/build/pdf' |
||||
|
// import { degrees, PDFDocument, rgb, StandardFonts } from 'pdf-lib' |
||||
|
// import { TextLayerBuilder, EventBus } from 'pdfdist-mergeofd/web/pdf_viewer' |
||||
|
import 'pdfdist-mergeofd/web/pdf_viewer.css' |
||||
|
PDFJS.GlobalWorkerOptions.workerSrc = require('pdfdist-mergeofd/build/pdf.worker.entry.js') |
||||
|
// import { saveByteArray } from '@/utils/index' |
||||
|
// import fontkit from '@pdf-lib/fontkit' |
||||
|
import jsPDF from 'jspdf' |
||||
|
|
||||
|
export default { |
||||
|
name: 'PdfOverlay', |
||||
|
data() { |
||||
|
return { |
||||
|
pdfDoc: null, |
||||
|
currentPage: 1, |
||||
|
totalPages: 0, |
||||
|
scale: 1.0, |
||||
|
formFields: [], |
||||
|
draggingFieldIndex: -1, |
||||
|
lastX: 0, |
||||
|
lastY: 0, |
||||
|
selectedFieldIndex: -1, |
||||
|
templateData: null, |
||||
|
pdfPath: '', |
||||
|
isDraggingCanvas: false, |
||||
|
canvasDragStart: { x: 0, y: 0 }, |
||||
|
canvasOffset: { x: 0, y: 0 } |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
pdfViewerStyle() { |
||||
|
return { |
||||
|
transform: `translate(${this.canvasOffset.x}px, ${this.canvasOffset.y}px) scale(${this.scale})`, |
||||
|
transformOrigin: '0 0', |
||||
|
position: 'relative', |
||||
|
overflow: 'auto' |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
this.$refs.pdfViewer.addEventListener('wheel', this.zoomOnScroll) |
||||
|
this.$refs.pdfViewer.addEventListener('mousedown', this.startCanvasDrag) |
||||
|
window.addEventListener('mousemove', this.onCanvasDrag) |
||||
|
window.addEventListener('mouseup', this.stopCanvasDrag) |
||||
|
}, |
||||
|
beforeDestroy() { |
||||
|
this.$refs.pdfViewer.removeEventListener('wheel', this.zoomOnScroll) |
||||
|
this.$refs.pdfViewer.removeEventListener('mousedown', this.startCanvasDrag) |
||||
|
window.removeEventListener('mousemove', this.onCanvasDrag) |
||||
|
window.removeEventListener('mouseup', this.stopCanvasDrag) |
||||
|
}, |
||||
|
methods: { |
||||
|
// 修改后的printPdf方法 |
||||
|
async printPdf() { |
||||
|
if (!this.pdfDoc) { |
||||
|
alert('请先加载PDF') |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 临时显示所有字段以便打印 |
||||
|
const originalStyles = this.formFields.map(field => ({ |
||||
|
...field, |
||||
|
style: { ...field.style } |
||||
|
})) |
||||
|
|
||||
|
// 确保所有字段可见 |
||||
|
this.formFields.forEach(field => { |
||||
|
field.style = { |
||||
|
...field.style, |
||||
|
display: 'block', |
||||
|
opacity: 1 |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
// 强制更新DOM |
||||
|
await this.$nextTick() |
||||
|
|
||||
|
// 使用html2canvas捕获PDF和覆盖层 |
||||
|
const pdfContainer = this.$refs.pdfViewer |
||||
|
const canvas = await html2canvas(pdfContainer, { |
||||
|
scale: 2, |
||||
|
useCORS: true, |
||||
|
logging: false |
||||
|
}) |
||||
|
|
||||
|
// 恢复原始样式 |
||||
|
this.formFields = originalStyles |
||||
|
|
||||
|
// 将canvas转换为Blob |
||||
|
const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')) |
||||
|
const imgUrl = URL.createObjectURL(blob) |
||||
|
|
||||
|
// 创建打印窗口 |
||||
|
const printWindow = window.open('', '_blank', 'width=1000,height=800') |
||||
|
const doc = printWindow.document |
||||
|
|
||||
|
// 写入基本HTML结构 |
||||
|
doc.write(`<!DOCTYPE html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<title>打印PDF</title> |
||||
|
<style> |
||||
|
@media print { |
||||
|
body * { visibility: hidden; } |
||||
|
#print-area, #print-area * { visibility: visible; } |
||||
|
#print-area { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } |
||||
|
} |
||||
|
body { margin: 0; padding: 0; } |
||||
|
#print-area { display: flex; justify-content: center; align-items: center; padding: 20px; } |
||||
|
#print-image { max-width: 100%; max-height: 100%; } |
||||
|
.controls { position: fixed; bottom: 20px; left: 0; width: 100%; text-align: center; } |
||||
|
button { padding: 10px 20px; margin: 0 10px; } |
||||
|
</style> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="print-area"></div> |
||||
|
<div class="controls"></div> |
||||
|
</body> |
||||
|
</html>`) |
||||
|
|
||||
|
// 使用DOM API添加内容 |
||||
|
const printArea = doc.getElementById('print-area') |
||||
|
const img = doc.createElement('img') |
||||
|
img.src = imgUrl |
||||
|
img.id = 'print-image' |
||||
|
img.style.maxWidth = '100%' |
||||
|
img.style.maxHeight = '100%' |
||||
|
printArea.appendChild(img) |
||||
|
|
||||
|
// 添加控制按钮 |
||||
|
const controls = doc.querySelector('.controls') |
||||
|
|
||||
|
const printBtn = doc.createElement('button') |
||||
|
printBtn.textContent = '打印' |
||||
|
printBtn.onclick = () => printWindow.print() |
||||
|
controls.appendChild(printBtn) |
||||
|
|
||||
|
const closeBtn = doc.createElement('button') |
||||
|
closeBtn.textContent = '关闭' |
||||
|
closeBtn.onclick = () => { |
||||
|
URL.revokeObjectURL(imgUrl) |
||||
|
printWindow.close() |
||||
|
} |
||||
|
controls.appendChild(closeBtn) |
||||
|
|
||||
|
// 窗口关闭时释放对象URL |
||||
|
printWindow.addEventListener('unload', () => { |
||||
|
URL.revokeObjectURL(imgUrl) |
||||
|
}) |
||||
|
} catch (error) { |
||||
|
console.error('打印失败:', error) |
||||
|
alert('打印失败,请重试') |
||||
|
} |
||||
|
}, |
||||
|
async loadPdf() { |
||||
|
const fileInput = document.createElement('input') |
||||
|
fileInput.type = 'file' |
||||
|
fileInput.accept = '.pdf' |
||||
|
fileInput.onchange = async(e) => { |
||||
|
const file = e.target.files[0] |
||||
|
if (!file) return |
||||
|
|
||||
|
const fileReader = new FileReader() |
||||
|
fileReader.onload = async(event) => { |
||||
|
try { |
||||
|
const arrayBuffer = event.target.result |
||||
|
this.pdfDoc = await PDFJS.getDocument(arrayBuffer).promise |
||||
|
this.totalPages = this.pdfDoc.numPages |
||||
|
this.currentPage = 1 |
||||
|
this.pdfPath = URL.createObjectURL(file) |
||||
|
await this.renderPage() |
||||
|
} catch (error) { |
||||
|
console.error('加载PDF失败:', error) |
||||
|
alert('加载PDF失败,请确保文件格式正确') |
||||
|
} |
||||
|
} |
||||
|
fileReader.readAsArrayBuffer(file) |
||||
|
} |
||||
|
fileInput.click() |
||||
|
}, |
||||
|
async renderPage() { |
||||
|
if (!this.pdfDoc) return |
||||
|
|
||||
|
const page = await this.pdfDoc.getPage(this.currentPage) |
||||
|
const viewport = page.getViewport({ scale: 1.0 }) |
||||
|
|
||||
|
const canvas = this.$refs.pdfCanvas |
||||
|
const context = canvas.getContext('2d') |
||||
|
|
||||
|
// 设置canvas尺寸 |
||||
|
canvas.height = viewport.height |
||||
|
canvas.width = viewport.width |
||||
|
|
||||
|
// 渲染PDF页面 |
||||
|
const renderContext = { |
||||
|
canvasContext: context, |
||||
|
viewport: viewport |
||||
|
} |
||||
|
|
||||
|
const renderTask = page.render(renderContext) |
||||
|
await renderTask.promise |
||||
|
|
||||
|
// 调整覆盖层大小 |
||||
|
const overlayContainer = this.$refs.overlayContainer |
||||
|
overlayContainer.style.height = `${viewport.height}px` |
||||
|
overlayContainer.style.width = `${viewport.width}px` |
||||
|
}, |
||||
|
addTextField() { |
||||
|
this.formFields.push({ |
||||
|
x: 50, |
||||
|
y: 50, |
||||
|
width: 150, |
||||
|
height: 30, |
||||
|
fontSize: 14, |
||||
|
content: '输入文本', |
||||
|
color: '#000000', |
||||
|
textAlign: 'left' |
||||
|
}) |
||||
|
}, |
||||
|
getFieldStyle(field) { |
||||
|
return { |
||||
|
position: 'absolute', |
||||
|
left: `${field.x}px`, |
||||
|
top: `${field.y}px`, |
||||
|
width: `${field.width}px`, |
||||
|
height: `${field.height}px`, |
||||
|
fontSize: `${field.fontSize}px`, |
||||
|
color: field.color, |
||||
|
border: this.selectedFieldIndex === this.formFields.indexOf(field) ? '1px dashed #3b82f6' : 'none', |
||||
|
padding: '4px', |
||||
|
boxSizing: 'border-box', |
||||
|
overflow: 'hidden', |
||||
|
whiteSpace: 'nowrap', |
||||
|
textOverflow: 'ellipsis', |
||||
|
cursor: 'move', |
||||
|
textAlign: field.textAlign, |
||||
|
backgroundColor: 'rgba(255, 255, 255, 0.7)', |
||||
|
borderRadius: '2px', |
||||
|
userSelect: 'none' |
||||
|
} |
||||
|
}, |
||||
|
startDrag(e) { |
||||
|
if (!e.target.classList.contains('form-field')) return |
||||
|
|
||||
|
const fieldIndex = this.formFields.findIndex(field => |
||||
|
e.target.textContent.trim() === (field.content || '点击编辑').trim() |
||||
|
) |
||||
|
|
||||
|
if (fieldIndex === -1) return |
||||
|
|
||||
|
this.draggingFieldIndex = fieldIndex |
||||
|
this.lastX = e.clientX |
||||
|
this.lastY = e.clientY |
||||
|
this.selectField(fieldIndex) |
||||
|
|
||||
|
e.stopPropagation() |
||||
|
}, |
||||
|
onDrag(e) { |
||||
|
if (this.draggingFieldIndex === -1) return |
||||
|
|
||||
|
const dx = e.clientX - this.lastX |
||||
|
const dy = e.clientY - this.lastY |
||||
|
|
||||
|
this.formFields[this.draggingFieldIndex].x += dx |
||||
|
this.formFields[this.draggingFieldIndex].y += dy |
||||
|
|
||||
|
this.lastX = e.clientX |
||||
|
this.lastY = e.clientY |
||||
|
}, |
||||
|
stopDrag() { |
||||
|
this.draggingFieldIndex = -1 |
||||
|
}, |
||||
|
selectField(index) { |
||||
|
this.selectedFieldIndex = index |
||||
|
}, |
||||
|
editField(index) { |
||||
|
const field = this.formFields[index] |
||||
|
const content = prompt('编辑文本内容:', field.content || '') |
||||
|
if (content !== null) { |
||||
|
this.formFields[index].content = content |
||||
|
} |
||||
|
}, |
||||
|
saveTemplate() { |
||||
|
const template = { |
||||
|
formFields: this.formFields, |
||||
|
currentPage: this.currentPage |
||||
|
} |
||||
|
|
||||
|
const templateJson = JSON.stringify(template) |
||||
|
const blob = new Blob([templateJson], { type: 'application/json' }) |
||||
|
const url = URL.createObjectURL(blob) |
||||
|
|
||||
|
const a = document.createElement('a') |
||||
|
a.href = url |
||||
|
a.download = 'pdf_template.json' |
||||
|
a.click() |
||||
|
|
||||
|
URL.revokeObjectURL(url) |
||||
|
}, |
||||
|
loadTemplate() { |
||||
|
const fileInput = document.createElement('input') |
||||
|
fileInput.type = 'file' |
||||
|
fileInput.accept = '.json' |
||||
|
fileInput.onchange = (e) => { |
||||
|
const file = e.target.files[0] |
||||
|
if (!file) return |
||||
|
|
||||
|
const reader = new FileReader() |
||||
|
reader.onload = (event) => { |
||||
|
try { |
||||
|
const template = JSON.parse(event.target.result) |
||||
|
this.formFields = template.formFields || [] |
||||
|
this.currentPage = template.currentPage || 1 |
||||
|
this.renderPage() |
||||
|
} catch (error) { |
||||
|
console.error('加载模板失败:', error) |
||||
|
alert('加载模板失败,请确保文件格式正确') |
||||
|
} |
||||
|
} |
||||
|
reader.readAsText(file) |
||||
|
} |
||||
|
fileInput.click() |
||||
|
}, |
||||
|
async exportPdf() { |
||||
|
if (!this.pdfDoc) { |
||||
|
alert('请先加载PDF') |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 创建一个新的jsPDF实例 |
||||
|
const Pdf = new jsPDF({ |
||||
|
orientation: 'portrait', |
||||
|
unit: 'px', |
||||
|
format: [this.$refs.pdfCanvas.width, this.$refs.pdfCanvas.height] |
||||
|
}) |
||||
|
|
||||
|
// 将原始PDF转换为图像 |
||||
|
const pdfImage = await this.convertCanvasToImage(this.$refs.pdfCanvas) |
||||
|
|
||||
|
// 添加原始PDF图像 |
||||
|
Pdf.addImage(pdfImage, 'PNG', 0, 0, this.$refs.pdfCanvas.width, this.$refs.pdfCanvas.height) |
||||
|
|
||||
|
// 添加表单字段 |
||||
|
this.formFields.forEach(field => { |
||||
|
Pdf.setFontSize(field.fontSize) |
||||
|
Pdf.setTextColor(field.color) |
||||
|
|
||||
|
// 根据文本对齐方式设置位置 |
||||
|
let x = field.x |
||||
|
if (field.textAlign === 'center') { |
||||
|
x += field.width / 2 |
||||
|
} else if (field.textAlign === 'right') { |
||||
|
x += field.width |
||||
|
} |
||||
|
|
||||
|
Pdf.text(field.content || '', x, field.y + field.fontSize) |
||||
|
}) |
||||
|
|
||||
|
// 保存PDF |
||||
|
Pdf.save('document_with_overlay.pdf') |
||||
|
} catch (error) { |
||||
|
console.error('导出PDF失败:', error) |
||||
|
alert('导出PDF失败,请重试') |
||||
|
} |
||||
|
}, |
||||
|
convertCanvasToImage(canvas) { |
||||
|
return new Promise((resolve) => { |
||||
|
const image = new Image() |
||||
|
image.onload = () => resolve(image) |
||||
|
image.src = canvas.toDataURL('image/png') |
||||
|
}) |
||||
|
}, |
||||
|
increaseScale() { |
||||
|
this.scale = Math.min(this.scale + 0.1, 3.0) |
||||
|
}, |
||||
|
decreaseScale() { |
||||
|
this.scale = Math.max(this.scale - 0.1, 0.5) |
||||
|
}, |
||||
|
zoomOnScroll(e) { |
||||
|
e.preventDefault() |
||||
|
|
||||
|
if (e.ctrlKey) { |
||||
|
const delta = e.deltaY > 0 ? -0.1 : 0.1 |
||||
|
this.scale = Math.max(0.5, Math.min(this.scale + delta, 3.0)) |
||||
|
} |
||||
|
}, |
||||
|
startCanvasDrag(e) { |
||||
|
if (e.target === this.$refs.pdfViewer) { |
||||
|
this.isDraggingCanvas = true |
||||
|
this.canvasDragStart.x = e.clientX - this.canvasOffset.x |
||||
|
this.canvasDragStart.y = e.clientY - this.canvasOffset.y |
||||
|
e.preventDefault() |
||||
|
} |
||||
|
}, |
||||
|
onCanvasDrag(e) { |
||||
|
if (this.isDraggingCanvas) { |
||||
|
this.canvasOffset.x = e.clientX - this.canvasDragStart.x |
||||
|
this.canvasOffset.y = e.clientY - this.canvasDragStart.y |
||||
|
} |
||||
|
}, |
||||
|
stopCanvasDrag() { |
||||
|
this.isDraggingCanvas = false |
||||
|
}, |
||||
|
deleteSelectedField() { |
||||
|
if (this.selectedFieldIndex !== -1) { |
||||
|
this.formFields.splice(this.selectedFieldIndex, 1) |
||||
|
this.selectedFieldIndex = -1 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.pdf-overlay-container { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
height: 100vh; |
||||
|
overflow: hidden; |
||||
|
font-family: Arial, sans-serif; |
||||
|
} |
||||
|
|
||||
|
.toolbar { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
padding: 10px; |
||||
|
background-color: #f8fafc; |
||||
|
border-bottom: 1px solid #e2e8f0; |
||||
|
gap: 10px; |
||||
|
} |
||||
|
|
||||
|
.btn { |
||||
|
display: inline-flex; |
||||
|
align-items: center; |
||||
|
gap: 5px; |
||||
|
padding: 6px 12px; |
||||
|
background-color: #3b82f6; |
||||
|
color: white; |
||||
|
border: none; |
||||
|
border-radius: 4px; |
||||
|
cursor: pointer; |
||||
|
transition: background-color 0.2s; |
||||
|
} |
||||
|
|
||||
|
.btn:hover { |
||||
|
background-color: #2563eb; |
||||
|
} |
||||
|
|
||||
|
.btn.danger { |
||||
|
background-color: #ef4444; |
||||
|
} |
||||
|
|
||||
|
.btn.danger:hover { |
||||
|
background-color: #dc2626; |
||||
|
} |
||||
|
|
||||
|
.scale-controls { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-left: auto; |
||||
|
gap: 5px; |
||||
|
} |
||||
|
|
||||
|
.pdf-container { |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
position: relative; |
||||
|
overflow: auto; |
||||
|
} |
||||
|
|
||||
|
.pdf-viewer { |
||||
|
position: relative; |
||||
|
margin: 20px auto; |
||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); |
||||
|
} |
||||
|
|
||||
|
.overlay-container { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
.form-field { |
||||
|
pointer-events: all; |
||||
|
user-select: none; |
||||
|
transition: border-color 0.2s; |
||||
|
} |
||||
|
|
||||
|
.properties-panel { |
||||
|
position: absolute; |
||||
|
right: 10px; |
||||
|
top: 10px; |
||||
|
width: 250px; |
||||
|
background-color: white; |
||||
|
border: 1px solid #e2e8f0; |
||||
|
border-radius: 6px; |
||||
|
padding: 10px; |
||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); |
||||
|
z-index: 3; |
||||
|
} |
||||
|
|
||||
|
.property-row { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 10px; |
||||
|
} |
||||
|
|
||||
|
.property-row label { |
||||
|
width: 80px; |
||||
|
margin-right: 10px; |
||||
|
} |
||||
|
|
||||
|
.property-row input { |
||||
|
flex: 1; |
||||
|
padding: 4px; |
||||
|
border: 1px solid #e2e8f0; |
||||
|
border-radius: 4px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,293 @@ |
|||||
|
|
||||
|
// FormPreview.vue |
||||
|
<template> |
||||
|
<div class="app-container"> |
||||
|
<div v-loading="loading" class="cardWhite" element-loading-text="加载中..."> |
||||
|
<div class="topArea"> |
||||
|
<div class="backBox"> |
||||
|
<!-- <i class="el-icon-back" @click="goBack"></i> --> |
||||
|
</div> |
||||
|
|
||||
|
<el-alert :title="myTitle" type="warning" :closable="false" /> |
||||
|
</div> |
||||
|
|
||||
|
<div class="previewBox"> |
||||
|
<div v-for="pdf in pdfsArray" :key="pdf.id"> |
||||
|
<iframe |
||||
|
ref="myPrint" |
||||
|
:src="pdf.mypdf" |
||||
|
class="myPrint" |
||||
|
width="100%" |
||||
|
height="1500px" |
||||
|
/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import axios from 'axios' |
||||
|
import { getToken } from '@/utils/auth' |
||||
|
|
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
myTitle: '', |
||||
|
|
||||
|
pdfsArray: [], |
||||
|
allData: [], |
||||
|
loading: false |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
this.loading = true |
||||
|
this.getFile() |
||||
|
}, |
||||
|
|
||||
|
methods: { |
||||
|
goBack() { |
||||
|
this.$router.go(-1) |
||||
|
}, |
||||
|
|
||||
|
// 重置标题 |
||||
|
resetTitle() { |
||||
|
const arr = this.allData.map(k => { |
||||
|
return k.formName |
||||
|
}) |
||||
|
|
||||
|
const newArr = this.getArrCountInfo(arr) |
||||
|
// console.log("统计", arr, newArr); |
||||
|
let str = '' |
||||
|
for (var i of newArr) { |
||||
|
str = str + '《' + i.formName + '》' + i.count + '份;' |
||||
|
} |
||||
|
|
||||
|
if (str.charAt(str.length - 1) === ';') { |
||||
|
str = str.slice(0, str.length - 1) |
||||
|
} |
||||
|
|
||||
|
this.myTitle = '您当前预览的表单是' + str |
||||
|
}, |
||||
|
|
||||
|
// 数组元素去重 |
||||
|
uniqueArr(arr) { |
||||
|
var x = new Set(arr) |
||||
|
return [...x] |
||||
|
}, |
||||
|
|
||||
|
// 获取重复元素个数,输出info1 |
||||
|
getWordCnt(arr) { |
||||
|
return arr.reduce(function(prev, next) { |
||||
|
prev[next] = prev[next] + 1 || 1 |
||||
|
return prev |
||||
|
}, {}) |
||||
|
}, |
||||
|
|
||||
|
// 获取重复元素个数,输出info2 |
||||
|
getArrCountInfo(arr) { |
||||
|
var info2 = [] |
||||
|
const that = this |
||||
|
that.uniqueArr(arr).forEach(function(item) { |
||||
|
var countInfo = {} |
||||
|
countInfo.formName = item |
||||
|
|
||||
|
countInfo.count = that.getWordCnt(arr)[item] |
||||
|
info2.push(countInfo) |
||||
|
}) |
||||
|
return info2 |
||||
|
}, |
||||
|
|
||||
|
// 获取文件流 |
||||
|
async getFile() { |
||||
|
const arr = JSON.parse(sessionStorage.getItem('FormPreviewArray')) |
||||
|
|
||||
|
const pdfArr = [] |
||||
|
|
||||
|
for (var i = 0; i < arr.length; i++) { |
||||
|
const item = arr[i] |
||||
|
|
||||
|
axios({ |
||||
|
url: |
||||
|
`${window.g.API_URL}/resourceManage/export/v4/dlform/pdf/` + |
||||
|
item.id, |
||||
|
method: 'get', |
||||
|
params: { |
||||
|
// fileUrl: this.$route.query.fileUrl |
||||
|
}, |
||||
|
headers: { |
||||
|
Authorization: getToken() |
||||
|
}, |
||||
|
responseType: 'arraybuffer' |
||||
|
}) |
||||
|
.then(res => { |
||||
|
// console.log("文件流获取成功", res); |
||||
|
|
||||
|
pdfArr.push({ |
||||
|
formName: item.formName, |
||||
|
pdf: res.data |
||||
|
}) |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
console.log('文件流获取失败', err) |
||||
|
this.$message.error('获取文件信息失败,请稍后重试', 6000) |
||||
|
}) |
||||
|
.finally(() => { |
||||
|
// console.log("asd", i, arr.length); |
||||
|
if (i === arr.length) { |
||||
|
// console.log("文件流集合", pdfArr); |
||||
|
|
||||
|
this.pdfPreview(pdfArr) |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
// }); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
pdfPreview(binaryData) { |
||||
|
// data是一个ArrayBuffer格式,也是一个buffer流的数据 |
||||
|
// console.log("pdf流", binaryData); |
||||
|
const newArr = [] |
||||
|
|
||||
|
// 获取blob链接 |
||||
|
|
||||
|
for (var i = 0; i < binaryData.length; i++) { |
||||
|
const k = binaryData[i] |
||||
|
|
||||
|
const pdfUrl = window.URL.createObjectURL( |
||||
|
new Blob([k.pdf], { type: 'application/pdf' }) |
||||
|
) |
||||
|
// console.log("pdfUrl", pdfUrl); |
||||
|
|
||||
|
newArr.push({ |
||||
|
formName: k.formName, |
||||
|
mypdf: pdfUrl |
||||
|
}) |
||||
|
|
||||
|
if (i === binaryData.length - 1) { |
||||
|
// console.log("newArr---", newArr); |
||||
|
setTimeout(() => { |
||||
|
this.resetTitle() |
||||
|
}, 10) |
||||
|
this.pdfsArray = newArr |
||||
|
|
||||
|
this.allData = newArr |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.app-container { |
||||
|
height: 100%; |
||||
|
|
||||
|
margin-top: -16px; |
||||
|
position: relative; |
||||
|
overflow-y: hidden; |
||||
|
z-index: 99999 !important; |
||||
|
|
||||
|
.cardWhite { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
|
||||
|
// background: gray; |
||||
|
|
||||
|
.topArea { |
||||
|
display: flex; |
||||
|
position: relative; |
||||
|
margin-bottom: 20px; |
||||
|
// border: 1px solid red; |
||||
|
|
||||
|
width: 80%; |
||||
|
height: 40px; |
||||
|
z-index: 99999 !important; |
||||
|
// background: gray; |
||||
|
|
||||
|
position: fixed; |
||||
|
top: 4rem; |
||||
|
left: 15.625rem; |
||||
|
|
||||
|
.backBox { |
||||
|
margin-right: 4.5rem; |
||||
|
position: relative; |
||||
|
i { |
||||
|
cursor: pointer; |
||||
|
font-size: 20px; |
||||
|
color: #fff; |
||||
|
|
||||
|
position: absolute; |
||||
|
left: 20px; |
||||
|
top: 10px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.el-button { |
||||
|
margin-right: 1rem; |
||||
|
} |
||||
|
|
||||
|
.titleBox { |
||||
|
text-align: center; |
||||
|
background: #fff; |
||||
|
color: #000000; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 50%; |
||||
|
width: auto; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.previewBox { |
||||
|
// border: 1px solid blue; |
||||
|
|
||||
|
max-height: 1000px; |
||||
|
|
||||
|
overflow-y: auto; |
||||
|
|
||||
|
padding-top: 38px; |
||||
|
|
||||
|
::v-deep div { |
||||
|
margin-bottom: 20px; |
||||
|
.myPrint { |
||||
|
.vue-office-pdf-wrapper { |
||||
|
// font-size: 14px !important; |
||||
|
canvas { |
||||
|
// height: 100% !important; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//打印样式 |
||||
|
@media print { |
||||
|
body { |
||||
|
//设置一下边框,可以显示内容的范围,用于测试 |
||||
|
|
||||
|
//添加height:auto;才能打印多页,否则只能打印一页 |
||||
|
height: auto; |
||||
|
|
||||
|
// border: 1px solid red; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
header, |
||||
|
footer { |
||||
|
//隐藏页眉页脚,不然会多打印出首尾两页 |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
@page { |
||||
|
// overflow: visible; |
||||
|
// /* 纵向 */ |
||||
|
// size: portrait; |
||||
|
size: auto; |
||||
|
margin: 0mm 10mm; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
|
|
||||
@ -0,0 +1,146 @@ |
|||||
|
<template> |
||||
|
<div class="main"> |
||||
|
<div style="padding: 20px"> |
||||
|
<a-form layout="inline" style="color: black; margin-bottom: 22px"> |
||||
|
<a-row :gutter="48"> |
||||
|
<a-col> |
||||
|
<a-form-item label="运单号" style="margin-right: 30px"> |
||||
|
<a-input v-model="queryParam.waybillNo" placeholder="请输入运单号" allow-clear size="large" /> |
||||
|
</a-form-item> |
||||
|
<sava-button class="button" @click="doSearch">查询</sava-button> |
||||
|
</a-col> |
||||
|
</a-row> |
||||
|
</a-form> |
||||
|
<a-table |
||||
|
ref="table" |
||||
|
:columns="columns" |
||||
|
:data-source="loadData" |
||||
|
:loading="loading" |
||||
|
:row-key="(record) => record.id" |
||||
|
:pagination="pagination" |
||||
|
style="margin-top: 10px" |
||||
|
@change="handleTableChange" |
||||
|
> |
||||
|
<span slot="action" slot-scope="text, record"> |
||||
|
<!-- <a @click="handleEdit(record)" style="color: #2b79c2">编辑</a> --> |
||||
|
<a style="color: #2b79c2; margin-left: 10px" @click="viewDetail(record)">查看</a> |
||||
|
<a style="color: #2b79c2; margin-left: 10px" @click="printBill(record)">打印</a> |
||||
|
</span> |
||||
|
</a-table> |
||||
|
|
||||
|
<a-modal :visible="previewVisibleForAll" :footer="null" :width="800" @cancel="handleCancelAll"> |
||||
|
<div style="overflow-y: auto; overflow-x: hidden"> |
||||
|
<a-button shape="round" icon="file-pdf" size="small" @click="handlePrint(printData)">打印</a-button> |
||||
|
<div id="printFrom"> |
||||
|
<pdf ref="pdf" :src="previewFileSrc" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</a-modal> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
<script> |
||||
|
// 两个库引入 |
||||
|
import pdf from 'vue-pdf' |
||||
|
import printJS from 'print-js' |
||||
|
// 接口 |
||||
|
// import { reqWayBillList, reqBillReport } from '@/api/DigitalWayBill/DigitalWayBill' |
||||
|
|
||||
|
export default { |
||||
|
components: { |
||||
|
pdf |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
queryParam: { |
||||
|
waybillNo: '' |
||||
|
}, |
||||
|
columns: [ |
||||
|
], |
||||
|
loadData: [], |
||||
|
loading: false, |
||||
|
pagination: {}, |
||||
|
mdl: null, |
||||
|
enterpriseInfo: [], |
||||
|
inspectorInfo: [], |
||||
|
fenceParam: {}, |
||||
|
pdfUrl: '', // 你的 PDF 文件 URL |
||||
|
progress: 0, |
||||
|
printData: { |
||||
|
printable: 'printFrom', |
||||
|
header: '', |
||||
|
ignore: ['no-print'] |
||||
|
}, |
||||
|
previewVisibleForAll: false, |
||||
|
pageTotal: null, |
||||
|
previewFileSrc: '' |
||||
|
} |
||||
|
}, |
||||
|
created() { |
||||
|
this.doSearch() |
||||
|
}, |
||||
|
|
||||
|
methods: { |
||||
|
doSearch() { |
||||
|
this.loading = true |
||||
|
reqWayBillList(this.queryParam).then((res) => { |
||||
|
console.log('way bill list', res) |
||||
|
this.loadData = res.records |
||||
|
this.loading = false |
||||
|
}) |
||||
|
}, |
||||
|
handleTableChange(pagination) { |
||||
|
const pager = { ...this.pagination1 } |
||||
|
pager.current = pagination.current |
||||
|
this.pagination1 = pager |
||||
|
this.queryParam1.pageIndex = pagination.current |
||||
|
this.doSearch() |
||||
|
}, |
||||
|
|
||||
|
viewDetail(record) { |
||||
|
console.log('click view') |
||||
|
this.mdl = { ...record } |
||||
|
// 将获取的信息传递到新页面 |
||||
|
this.$router.push({ |
||||
|
path: '/bill/detail', |
||||
|
query: { |
||||
|
data: JSON.stringify(this.mdl) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
printBill(record) { |
||||
|
this.$message.success('生成文档需要一些时间, 请稍候...', 10) |
||||
|
reqBillReport(record.waybillNo) |
||||
|
.then((res) => { |
||||
|
console.log('pdf url', res) |
||||
|
this.previewFileSrc = res |
||||
|
this.previewVisibleForAll = true |
||||
|
}) |
||||
|
.catch((err) => { |
||||
|
this.$message.error(`获取文档失败: ${err}`) |
||||
|
}) |
||||
|
}, |
||||
|
handlePrint(params) { |
||||
|
printJS({ |
||||
|
printable: params.printable, // 'printFrom', // 标签元素id |
||||
|
type: params.type || 'html', |
||||
|
header: params.header, // '表单', |
||||
|
targetStyles: ['*'], |
||||
|
style: '@page {margin:0 10mm};', // 可选-打印时去掉眉页眉尾 |
||||
|
ignoreElements: params.ignore || [], // ['no-print'] |
||||
|
properties: params.properties || null |
||||
|
}) |
||||
|
}, |
||||
|
printPdf() { |
||||
|
this.$refs.pdf.print() |
||||
|
// window.print() |
||||
|
}, |
||||
|
handleCancel() { |
||||
|
this.previewVisible = false |
||||
|
}, |
||||
|
handleCancelAll() { |
||||
|
this.previewVisibleForAll = false |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue