|
@@ -17,13 +17,13 @@
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div ref="pdfContainer" class="pdf-container">
|
|
<div ref="pdfContainer" class="pdf-container">
|
|
|
- <!-- Canvas 元素将在这里动态生成 -->
|
|
|
|
|
|
|
+ <!-- Canvas 元素将在这里动态生成,每个页面包含水印覆盖层 -->
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
-import { ref, onMounted, watch, onUnmounted } from 'vue';
|
|
|
|
|
|
|
+import { ref, computed, onMounted, watch, onUnmounted } from 'vue';
|
|
|
import * as pdfjsLib from 'pdfjs-dist';
|
|
import * as pdfjsLib from 'pdfjs-dist';
|
|
|
|
|
|
|
|
// 设置 worker
|
|
// 设置 worker
|
|
@@ -39,17 +39,29 @@ const props = defineProps({
|
|
|
url: {
|
|
url: {
|
|
|
type: String,
|
|
type: String,
|
|
|
required: true
|
|
required: true
|
|
|
|
|
+ },
|
|
|
|
|
+ watermarkConfig: {
|
|
|
|
|
+ type: Object,
|
|
|
|
|
+ default: null
|
|
|
|
|
+ // { username: string, account: string, date: string }
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+// 计算水印文本
|
|
|
|
|
+const watermarkText = computed(() => {
|
|
|
|
|
+ if (!props.watermarkConfig) return ''
|
|
|
|
|
+ const { username, account, date } = props.watermarkConfig
|
|
|
|
|
+ return `${username || ''} ${account || ''} ${date || ''}`.trim()
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
const loading = ref(true);
|
|
const loading = ref(true);
|
|
|
const error = ref(null);
|
|
const error = ref(null);
|
|
|
const progress = ref(0);
|
|
const progress = ref(0);
|
|
|
const pdfContainer = ref(null);
|
|
const pdfContainer = ref(null);
|
|
|
const currentUrl = ref('');
|
|
const currentUrl = ref('');
|
|
|
-const isLoading = ref(false); // 防止重复加载的标志
|
|
|
|
|
-const retryCount = ref(0); // 重试次数
|
|
|
|
|
-const maxRetries = 3; // 最大重试次数
|
|
|
|
|
|
|
+const isLoading = ref(false);
|
|
|
|
|
+const retryCount = ref(0);
|
|
|
|
|
+const maxRetries = 3;
|
|
|
let pdfDoc = null;
|
|
let pdfDoc = null;
|
|
|
|
|
|
|
|
const loadPdf = async (isRetry = false) => {
|
|
const loadPdf = async (isRetry = false) => {
|
|
@@ -98,6 +110,8 @@ const loadPdf = async (isRetry = false) => {
|
|
|
if (pdfDoc.numPages > 0) {
|
|
if (pdfDoc.numPages > 0) {
|
|
|
// 第一页优先加载
|
|
// 第一页优先加载
|
|
|
await renderPage(1);
|
|
await renderPage(1);
|
|
|
|
|
+ // 第一页渲染完成后立即隐藏加载提示
|
|
|
|
|
+ loading.value = false;
|
|
|
progress.value = Math.floor((1 / pdfDoc.numPages) * 100);
|
|
progress.value = Math.floor((1 / pdfDoc.numPages) * 100);
|
|
|
|
|
|
|
|
// 加载剩余页面
|
|
// 加载剩余页面
|
|
@@ -141,6 +155,12 @@ const renderPage = async (pageNum) => {
|
|
|
const page = await pdfDoc.getPage(pageNum);
|
|
const page = await pdfDoc.getPage(pageNum);
|
|
|
const viewport = page.getViewport({ scale: 1.5 });
|
|
const viewport = page.getViewport({ scale: 1.5 });
|
|
|
|
|
|
|
|
|
|
+ // 创建页面容器
|
|
|
|
|
+ const pageWrapper = document.createElement('div');
|
|
|
|
|
+ pageWrapper.className = 'page-wrapper';
|
|
|
|
|
+ pageWrapper.style.position = 'relative';
|
|
|
|
|
+ pageWrapper.style.marginBottom = '10px';
|
|
|
|
|
+
|
|
|
const canvas = document.createElement('canvas');
|
|
const canvas = document.createElement('canvas');
|
|
|
const context = canvas.getContext('2d');
|
|
const context = canvas.getContext('2d');
|
|
|
|
|
|
|
@@ -149,11 +169,27 @@ const renderPage = async (pageNum) => {
|
|
|
canvas.style.width = '100%';
|
|
canvas.style.width = '100%';
|
|
|
canvas.style.height = 'auto';
|
|
canvas.style.height = 'auto';
|
|
|
canvas.style.display = 'block';
|
|
canvas.style.display = 'block';
|
|
|
- canvas.style.marginBottom = '10px';
|
|
|
|
|
canvas.style.boxShadow = '0 2px 5px rgba(0,0,0,0.1)';
|
|
canvas.style.boxShadow = '0 2px 5px rgba(0,0,0,0.1)';
|
|
|
|
|
|
|
|
|
|
+ pageWrapper.appendChild(canvas);
|
|
|
|
|
+
|
|
|
|
|
+ // 添加水印覆盖层
|
|
|
|
|
+ if (props.watermarkConfig && watermarkText.value) {
|
|
|
|
|
+ console.log('📄 [MobilePdfViewer] 添加水印:', watermarkText.value);
|
|
|
|
|
+ const watermarkOverlay = document.createElement('div');
|
|
|
|
|
+ watermarkOverlay.className = 'watermark-overlay';
|
|
|
|
|
+ watermarkOverlay.innerHTML = `
|
|
|
|
|
+ <div class="watermark-grid">
|
|
|
|
|
+ ${Array(15).fill(`<span class="watermark-item">${watermarkText.value}</span>`).join('')}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ `;
|
|
|
|
|
+ pageWrapper.appendChild(watermarkOverlay);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.log('📄 [MobilePdfViewer] 无水印配置:', { watermarkConfig: props.watermarkConfig, watermarkText: watermarkText.value });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (pdfContainer.value) {
|
|
if (pdfContainer.value) {
|
|
|
- pdfContainer.value.appendChild(canvas);
|
|
|
|
|
|
|
+ pdfContainer.value.appendChild(pageWrapper);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const renderContext = {
|
|
const renderContext = {
|
|
@@ -319,3 +355,43 @@ onUnmounted(() => {
|
|
|
100% { transform: rotate(360deg); }
|
|
100% { transform: rotate(360deg); }
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|
|
|
|
|
+
|
|
|
|
|
+<style>
|
|
|
|
|
+/* 水印覆盖层样式 - 非scoped以支持动态创建的DOM */
|
|
|
|
|
+.mobile-pdf-viewer .page-wrapper {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.mobile-pdf-viewer .watermark-overlay {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ pointer-events: none;
|
|
|
|
|
+ z-index: 10;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.mobile-pdf-viewer .watermark-grid {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
|
|
+ grid-template-rows: repeat(5, 1fr);
|
|
|
|
|
+ gap: 20px;
|
|
|
|
|
+ padding: 30px;
|
|
|
|
|
+ transform: rotate(-30deg) scale(1.5);
|
|
|
|
|
+ transform-origin: center center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.mobile-pdf-viewer .watermark-item {
|
|
|
|
|
+ color: rgba(120, 120, 120, 0.15);
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ user-select: none;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|