<script lang="ts" setup>
|
import { computed, ref } from 'vue';
|
|
import { BasicModal, useModalInner } from '@jnpf/ui/modal';
|
|
import { Button, Space } from 'ant-design-vue';
|
|
import CustomPrintPreviewPane from './CustomPrintPreviewPane.vue';
|
import type { CustomPrintConfig, CustomPrintPreviewExpose } from './types';
|
|
const emit = defineEmits(['register', 'print', 'downloadPdf']);
|
|
interface State {
|
template: string;
|
type: CustomPrintConfig['type'];
|
data: any;
|
title: string;
|
showPdfBtn: boolean;
|
loading: boolean;
|
error: null | string;
|
}
|
|
const state = ref<State>({
|
template: '',
|
type: 'html',
|
data: {},
|
title: '打印预览',
|
showPdfBtn: true,
|
loading: false,
|
error: null,
|
});
|
|
const previewRef = ref<CustomPrintPreviewExpose | null>(null);
|
const isPrinting = ref(false);
|
|
const [registerModal, { closeModal }] = useModalInner(init);
|
|
// 是否是 HTML 文件模式
|
const isHtmlFileMode = computed(() => state.value.type === 'html-file');
|
const canPrint = computed(() => !state.value.loading && !state.value.error && !isPrinting.value);
|
|
const previewConfig = computed<CustomPrintConfig>(() => ({
|
data: state.value.data,
|
template: state.value.template,
|
title: state.value.title,
|
type: state.value.type,
|
}));
|
|
// 打印样式注入
|
const printStyles = computed(() => {
|
return `
|
<style>
|
@media print {
|
@page {
|
size: A4;
|
margin: 0;
|
}
|
body {
|
background: white !important;
|
padding: 0 !important;
|
margin: 0 !important;
|
-webkit-print-color-adjust: exact;
|
print-color-adjust: exact;
|
}
|
.custom-print-modal__content {
|
padding: 0 !important;
|
background: white !important;
|
}
|
.no-print {
|
display: none !important;
|
}
|
.print-page {
|
break-after: page;
|
page-break-after: always;
|
}
|
.print-page:last-child {
|
break-after: auto;
|
page-break-after: auto;
|
}
|
}
|
</style>
|
`;
|
});
|
|
async function init(data: Partial<State>) {
|
// 标题模板:使用传入的 title 填充,默认显示"打印预览"
|
const titleValue = data.title || '';
|
const finalTitle = titleValue ? `打印预览 - ${titleValue}` : '打印预览';
|
|
state.value = {
|
template: data.template || '',
|
type: data.type || 'html',
|
data: data.data || {},
|
title: finalTitle,
|
showPdfBtn: data.showPdfBtn !== false,
|
loading: false,
|
error: null,
|
};
|
}
|
|
// 打印
|
function handlePrint() {
|
if (!canPrint.value) return;
|
|
// 使用独立的 printing 状态,避免影响模板显示
|
isPrinting.value = true;
|
|
const originalTitle = document.title;
|
document.title = state.value.title || '打印';
|
|
// 监听打印对话框关闭事件
|
const cleanup = () => {
|
document.title = originalTitle;
|
isPrinting.value = false;
|
emit('print', state.value.data);
|
window.removeEventListener('afterprint', cleanup);
|
};
|
window.addEventListener('afterprint', cleanup);
|
|
// 使用延迟确保 UI 先更新再调用打印(window.print 会阻塞主线程)
|
setTimeout(() => {
|
// 如果是 iframe 模式,直接调用 iframe 的打印
|
if (isHtmlFileMode.value) {
|
printIframeContent();
|
} else {
|
window.print();
|
}
|
// 打印对话框关闭后清理(afterprint 事件不稳定,使用较短的备用延迟)
|
setTimeout(cleanup, 500);
|
}, 50);
|
}
|
|
// 打印 iframe 内容
|
function printIframeContent() {
|
previewRef.value?.print();
|
}
|
|
// 导出PDF(通过打印为PDF实现)
|
function handleDownloadPdf() {
|
if (!canPrint.value) return;
|
|
// PDF 导出功能和打印相同,都是调用浏览器打印对话框
|
handlePrint();
|
emit('downloadPdf', state.value.data);
|
}
|
|
// 关闭弹窗
|
function handleClose() {
|
closeModal();
|
}
|
|
// 重新加载模板
|
function reloadTemplate() {
|
state.value.error = null;
|
previewRef.value?.reload();
|
}
|
|
function handlePreviewError(message: string) {
|
state.value.error = message;
|
state.value.loading = false;
|
}
|
|
function handlePreviewLoaded() {
|
state.value.error = null;
|
state.value.loading = false;
|
}
|
|
function handlePreviewLoading(loading: boolean) {
|
state.value.loading = loading;
|
}
|
</script>
|
|
<template>
|
<BasicModal
|
v-bind="$attrs"
|
:title="state.title"
|
:default-fullscreen="true"
|
:closable="false"
|
:keyboard="true"
|
:footer="null"
|
class="custom-print-modal"
|
@register="registerModal">
|
<template #title>
|
<div class="custom-print-modal__header">
|
<span class="header-title">{{ state.title }}</span>
|
<Space class="header-actions no-print" :size="10">
|
<Button v-if="state.error" :loading="state.loading" @click="reloadTemplate"> 重新加载 </Button>
|
<Button v-if="state.showPdfBtn" :disabled="!canPrint" :loading="isPrinting" @click="handleDownloadPdf">
|
导出PDF
|
</Button>
|
<Button type="primary" :disabled="!canPrint" :loading="isPrinting" @click="handlePrint"> 打印 </Button>
|
<Button @click="handleClose">关闭</Button>
|
</Space>
|
</div>
|
</template>
|
|
<div class="custom-print-modal__content">
|
<CustomPrintPreviewPane
|
ref="previewRef"
|
:config="previewConfig"
|
@error="handlePreviewError"
|
@loaded="handlePreviewLoaded"
|
@loading="handlePreviewLoading" />
|
|
<!-- 注入打印样式 -->
|
<div v-if="state.type === 'html'" v-html="printStyles"></div>
|
</div>
|
</BasicModal>
|
</template>
|
|
<style lang="scss" scoped>
|
// 打印时隐藏弹窗的一些元素
|
@media print {
|
:deep(.ant-modal-header) {
|
display: none !important;
|
}
|
|
:deep(.ant-modal-close) {
|
display: none !important;
|
}
|
|
:deep(.ant-modal-body) {
|
padding: 0 !important;
|
}
|
|
:deep(.scrollbar__view) {
|
overflow: visible !important;
|
}
|
|
.custom-print-modal__content {
|
padding: 0 !important;
|
background: white !important;
|
}
|
|
// iframe 容器在打印时显示完整内容
|
:deep(.iframe-container) {
|
overflow: visible !important;
|
}
|
}
|
|
.custom-print-modal {
|
&__header {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
width: 100%;
|
|
.header-title {
|
font-size: 16px;
|
font-weight: 500;
|
}
|
|
.header-actions {
|
display: flex;
|
gap: 8px;
|
}
|
}
|
|
&__content {
|
position: relative;
|
height: 100%;
|
padding: 20px;
|
overflow: auto;
|
background: #f0f0f0;
|
|
:deep(.custom-print-preview-pane) {
|
height: 100%;
|
}
|
}
|
}
|
</style>
|