<script lang="ts" setup>
|
import type { PreviewOrientation } from './preview';
|
|
import type { WatermarkConfig, WatermarkPosition } from '#/api/x/dms/watermark';
|
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
import { getWatermarkPreview } from '#/api/x/dms/watermark';
|
|
import { previewContent, previewPage } from './preview';
|
|
const props = defineProps<{ config: WatermarkConfig; orientation: PreviewOrientation; position: WatermarkPosition }>();
|
const PIXELS_PER_MM = 2;
|
const canvas = ref<HTMLCanvasElement>();
|
const error = ref('');
|
const loading = ref(false);
|
const page = computed(() => previewPage(props.position, props.orientation));
|
const width = computed(() => Math.round(page.value.widthMm * PIXELS_PER_MM));
|
const height = computed(() => Math.round(page.value.heightMm * PIXELS_PER_MM));
|
const orientationLabel = computed(() => (props.orientation === 'landscape' ? '横向' : '纵向'));
|
let generation = 0;
|
|
async function render() {
|
const current = ++generation;
|
const surface = canvas.value;
|
const context = surface?.getContext('2d');
|
if (!surface || !context) return;
|
context.clearRect(0, 0, surface.width, surface.height);
|
error.value = '';
|
loading.value = true;
|
let url: string | undefined;
|
try {
|
const blob = await getWatermarkPreview(
|
page.value.widthMm,
|
page.value.heightMm,
|
{ ...props.config, positions: [props.position] },
|
previewContent(props.config),
|
);
|
if (!blob.type.startsWith('image/')) {
|
const result = JSON.parse(await blob.text());
|
throw new Error(result.msg || '水印预览失败');
|
}
|
if (current !== generation) return;
|
url = URL.createObjectURL(blob);
|
const image = await new Promise<HTMLImageElement>((resolve, reject) => {
|
const image = new Image();
|
image.addEventListener('load', () => resolve(image));
|
image.addEventListener('error', () => reject(new Error('水印预览图加载失败')));
|
image.src = url!;
|
});
|
if (current === generation) context.drawImage(image, 0, 0, surface.width, surface.height);
|
} catch (error_) {
|
if (current === generation) error.value = error_ instanceof Error ? error_.message : '水印预览失败';
|
} finally {
|
if (url) URL.revokeObjectURL(url);
|
if (current === generation) loading.value = false;
|
}
|
}
|
|
onMounted(render);
|
watch(() => [props.config, props.position, props.orientation], render, { deep: true });
|
onBeforeUnmount(() => generation++);
|
</script>
|
|
<template>
|
<section class="paper-preview">
|
<div class="paper-title">
|
<h2>{{ orientationLabel }}</h2>
|
<span>{{ page.widthMm }} × {{ page.heightMm }} mm</span>
|
</div>
|
<div class="paper-frame">
|
<canvas ref="canvas" :width="width" :height="height" :aria-label="`${position.paperType} ${orientationLabel}水印效果`" role="img"></canvas>
|
</div>
|
<div v-if="loading" class="render-status">正在生成预览…</div>
|
<div v-if="error" class="render-error">{{ error }}</div>
|
</section>
|
</template>
|
|
<style scoped>
|
.paper-preview {
|
min-width: 0;
|
}
|
|
.paper-title {
|
display: flex;
|
align-items: baseline;
|
gap: 12px;
|
margin-bottom: 16px;
|
}
|
|
.paper-title h2 {
|
margin: 0;
|
font-size: 16px;
|
font-weight: 600;
|
}
|
|
.paper-title span {
|
color: #6d7a81;
|
font-size: 13px;
|
}
|
|
.paper-frame {
|
display: flex;
|
align-items: flex-start;
|
justify-content: center;
|
}
|
|
.paper-frame canvas {
|
display: block;
|
width: auto;
|
max-width: 100%;
|
height: auto;
|
max-height: max(260px, calc(100vh - 260px));
|
background: #fff;
|
box-shadow:
|
0 2px 16px #24323a20,
|
0 0 0 1px #ccd3d6;
|
}
|
|
.render-error {
|
margin-top: 12px;
|
color: #b42318;
|
}
|
|
.render-status {
|
margin-top: 12px;
|
color: #6d7a81;
|
}
|
</style>
|