FcDesigner/src/components/PrintForm.vue
2025-05-21 17:26:44 +08:00

399 lines
14 KiB
Vue

<template>
<div class="_fd-print">
<el-tooltip
effect="dark"
:content="t('designer.print.title')"
placement="top"
:hide-after="0"
>
<i class="fc-icon icon-print" @click="open"></i>
</el-tooltip>
<el-dialog class="_fd-print-dialog _fd-config-dialog" v-model="visible" destroy-on-close
:close-on-click-modal="false"
:title="t('designer.print.title')"
append-to-body
width="1080px">
<el-container class="_fd-print-con" style="height: 600px">
<el-aside style="width:255px;">
<el-container class="_fd-print-l">
<el-header class="_fd-print-head" height="40px">
{{ t('designer.print.config') }}
</el-header>
<el-main>
<el-form label-position="top" size="small">
<el-form-item :label="t('props.mode')">
<el-radio-group v-model="formData.type">
<el-radio-button value="form">{{ t('form.formMode') }}</el-radio-button>
<el-radio-button value="read">{{ t('form.previewMode') }}</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('props.style')">
<el-radio-group v-model="formData.style">
<el-radio-button value="default">{{
t('designer.print.defaultStyle')
}}
</el-radio-button>
<el-radio-button value="word">{{
t('designer.print.wordStyle')
}}
</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('style.width')">
<el-input-number :min="300" controls-position="right"
v-model="formData.width"></el-input-number>
</el-form-item>
<template v-for="item in padding" :key="item">
<el-form-item :label="t('designer.print.' + item)">
<el-input-number :min="0" controls-position="right"
v-model="formData[item]"></el-input-number>
</el-form-item>
</template>
</el-form>
</el-main>
</el-container>
</el-aside>
<el-main>
<el-container class="_fd-print-r">
<el-main>
<ViewForm class="_fd-print-form" :class="{'_fd-print-form-word': formData.style === 'word'}"
ref="form" :rule="rule" :option="options"
v-if="visible"
:style="{width: formData.width > 0 ? (formData.width + 'px') : '100%'}">
<template v-for="(_, name) in $slots" #[name]="scope">
<slot :name="name" v-bind="scope ?? {}"/>
</template>
</ViewForm>
</el-main>
</el-container>
</el-main>
</el-container>
<template #footer>
<div>
<el-button size="default" @click="visible=false">{{ t('props.cancel') }}</el-button>
<el-button size="default" @click="print(true)">{{
t('designer.print.export')
}}</el-button>
<el-button type="primary" size="default" @click="print(false)" :loading="printing">{{ t('props.print') }}
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script>
import {defineComponent, markRaw} from 'vue';
import Warning from './Warning.vue';
import viewForm from '../utils/form';
import loadjs from '../utils/loadjs/loadjs';
import SizeInput from './style/SizeInput.vue';
export default defineComponent({
name: 'PrintForm',
components: {
SizeInput,
Warning,
ViewForm: viewForm.$form(),
},
inject: ['designer'],
data() {
return {
visible: false,
printing: false,
frame: null,
rule: [],
options: {},
padding: ['top', 'bottom', 'left', 'right'],
formData: {
type: 'form',
style: 'default',
left: 20,
right: 20,
top: 20,
bottom: 20,
width: 780,
},
};
},
computed: {
t() {
return this.designer.setupState.t;
},
},
watch: {
visible(v) {
if (v) {
this.rule = viewForm.parseJson(this.designer.setupState.getPreviewRule());
this.options = viewForm.parseJson(this.designer.setupState.getOptionsJson());
this.options.submitBtn = false;
this.options.resetBtn = false;
} else {
this.printing = false;
if (this.frame) {
document.body.removeChild(this.frame);
this.frame = null;
}
}
},
'formData.type': function (n) {
this.options.preview = n === 'read';
}
},
methods: {
open() {
this.visible = true;
},
disableImageSmoothing(ctx) {
ctx.imageSmoothingEnabled = false;
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
},
print(flag) {
this.printing = true;
loadjs.ready(['html2canvas', 'jspdf'], () => {
window.html2canvas(this.$refs.form.$el, {
allowTaint: true,
useCORS: true,
}).then((canvas) => {
const pdf = new window.jspdf.jsPDF({
orientation: 'p',
unit: 'pt',
format: 'a4',
});
this.disableImageSmoothing(canvas.getContext('2d'));
const {
left: marginLeft,
right: marginRight,
top: marginTop,
bottom: marginBottom,
} = this.formData;
const pageWidth = pdf.internal.pageSize.getWidth();
const pageHeight = pdf.internal.pageSize.getHeight();
const contentWidth = pageWidth - marginLeft - marginRight;
const contentHeight = pageHeight - marginTop - marginBottom;
const scaledHeight = (canvas.height * contentWidth) / canvas.width;
if (scaledHeight <= contentHeight) {
pdf.addImage(
canvas.toDataURL('image/jpeg'),
'JPEG',
marginLeft,
marginTop,
contentWidth,
scaledHeight
);
} else {
let remainingHeight = scaledHeight;
let page = 0;
const clipHeight = canvas.width * contentHeight / contentWidth;
while (remainingHeight > 0) {
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
this.disableImageSmoothing(tempCtx);
const offsetY = page * clipHeight;
const actualClipHeight = Math.min(clipHeight, canvas.height - offsetY);
tempCanvas.width = canvas.width;
tempCanvas.height = actualClipHeight;
tempCtx.drawImage(
canvas,
0, offsetY, canvas.width, actualClipHeight,
0, 0, canvas.width, actualClipHeight
);
const imageHeight = (actualClipHeight / canvas.height) * scaledHeight;
pdf.addImage(
tempCanvas.toDataURL('image/jpeg'),
'JPEG',
marginLeft,
marginTop,
contentWidth,
imageHeight
);
remainingHeight -= contentHeight;
if (remainingHeight > 0) {
pdf.addPage();
page++;
}
}
}
if (flag) {
this.printing = false;
window.open(URL.createObjectURL(pdf.output('blob')));
} else {
this.printPdf(pdf);
}
}).catch((e) => {
this.printing = false;
});
});
},
printPdf(pdf) {
if (!this.frame) {
const frame = markRaw(document.createElement('iframe'));
frame.style.width = '0';
frame.style.position = 'absolute';
frame.style.height = '0';
frame.style.border = 'none';
frame.onload = function () {
setTimeout(() => {
frame.contentWindow.print();
}, 100);
}
document.body.appendChild(frame);
this.frame = frame;
}
this.frame.src = URL.createObjectURL(pdf.output('blob'));
this.printing = false;
},
},
created() {
if (window.html2canvas) {
loadjs.done('html2canvas');
} else if (!loadjs.isDefined('html2canvas')) {
loadjs.loadNpm('html2canvas@1.4.1/dist/html2canvas.min.js', 'html2canvas');
}
if (window.jspdf) {
loadjs.done('jspdf');
} else if (!loadjs.isDefined('jspdf')) {
loadjs.loadNpm('jspdf@3.0.1/dist/jspdf.umd.js', 'jspdf');
}
}
});
</script>
<style>
._fd-print .el-button {
font-weight: 400;
width: 100%;
}
._fd-print-con .el-main {
padding: 0;
}
._fd-print-l, ._fd-print-r {
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
border: 1px solid var(--fc-line-color-3);
}
._fd-print-head {
display: flex;
padding: 5px 15px;
border-bottom: 1px solid var(--fc-line-color-3);
background: var(--fc-bg-color-3);
align-items: center;
}
._fd-print-head .el-button.is-link {
color: var(--fc-style-color-1);
}
._fd-print-r {
border-left: 0 none;
}
._fd-print-r ._fd-print-head {
justify-content: flex-end;
}
._fd-print-l > .el-main, ._fd-print-r > .el-main {
display: flex;
flex-direction: column;
flex: 1;
flex-basis: auto;
box-sizing: border-box;
min-width: 0;
width: 100%;
padding: 10px;
}
._fd-print-l .el-form .el-radio-group, ._fd-print-l .el-form .el-radio-button__inner {
width: 100%;
}
._fd-print-l .el-form .el-radio-button {
flex: 1;
}
._fd-print-r > .el-main {
flex-direction: column;
padding: 20px;
position: relative;
}
._fd-print-form {
padding: 2px;
box-sizing: border-box;
}
._fd-print-form .el-input__wrapper, ._fd-print-form .el-textarea__inner, ._fd-print-form .el-select__wrapper {
box-shadow: none !important;
border: 1px solid var(--el-input-border-color, var(--el-border-color));
}
._fd-print-form .el-select__placeholder {
position: unset !important;
top: unset !important;
transform: unset !important;
}
._fd-print-form .is-disabled .el-input__wrapper {
background-color: unset !important;
}
._fd-print-form .is-disabled .el-input__inner {
color: unset !important;
}
._fd-print-form-word .el-input__wrapper, ._fd-print-form-word .el-textarea__inner, ._fd-print-form-word .el-select__wrapper {
border: none !important;
border-bottom: 1px solid var(--el-input-border-color, var(--el-border-color)) !important;
border-color: inherit !important;
border-radius: 0 !important;
}
._fd-print-form-word .el-input-number__decrease, ._fd-print-form-word .el-input-number__increase {
display: none !important;
}
._fd-print-form-word ._fc-read-view {
display: block;
width: 100%;
height: 1.5em;
padding: 0 4px;
line-height: 1.5em;
border-bottom: 1px solid var(--el-input-border-color, var(--el-border-color)) !important;
border-color: inherit !important;
}
._fd-print-page-line {
position: absolute;
border-bottom: 1px dashed var(--fc-line-color-3);
left: 0;
right: 0;
height: 1px;
color: var(--fc-text-color-3);
padding-left: 4px;
box-sizing: border-box;
font-size: 12px;
line-height: 2em;
z-index: 1;
}
</style>