vue3-components-plus 3.0.25 → 3.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ComponentDemo/PdfDemo copy.vue +234 -0
- package/dist/ComponentDemo/PdfDemo.vue +35 -8
- package/dist/ComponentDemo/pdfReade copy.vue +369 -0
- package/dist/ComponentDemo/pdfReade.vue +590 -0
- package/dist/vue3-components-plus.css +1 -1
- package/dist/vue3-components-plus.js +128 -55
- package/dist/vue3-components-plus.umd.cjs +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="demo-container">
|
|
3
|
+
<div class="control-panel">
|
|
4
|
+
<h3>PDF 文档预览演示</h3>
|
|
5
|
+
|
|
6
|
+
<!-- 本地PDF文件 -->
|
|
7
|
+
<div class="local-section">
|
|
8
|
+
<h4>本地PDF文件</h4>
|
|
9
|
+
<span class="file-name">驿云通 (11).pdf</span>
|
|
10
|
+
<button @click="loadLocalPdf">加载本地文件</button>
|
|
11
|
+
<button @click="printPdf" :disabled="!currentUrl">打印</button>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<!-- 文件上传方式 -->
|
|
15
|
+
<div class="upload-section">
|
|
16
|
+
<h4>方式一:文件上传</h4>
|
|
17
|
+
<input
|
|
18
|
+
type="file"
|
|
19
|
+
@change="importPdf(($event.target as any)?.files?.[0])"
|
|
20
|
+
accept=".pdf"
|
|
21
|
+
/>
|
|
22
|
+
<button @click="clearFile" :disabled="!file">清除文件</button>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<!-- URL方式 -->
|
|
26
|
+
<div class="url-section">
|
|
27
|
+
<h4>方式二:URL地址</h4>
|
|
28
|
+
<input
|
|
29
|
+
v-model="pdfUrl"
|
|
30
|
+
type="text"
|
|
31
|
+
placeholder="请输入PDF文档的URL地址"
|
|
32
|
+
class="url-input"
|
|
33
|
+
/>
|
|
34
|
+
<button @click="loadFromUrl" :disabled="!pdfUrl.trim()">加载URL</button>
|
|
35
|
+
<button @click="clearUrl" :disabled="!pdfUrl">清除URL</button>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<!-- 搜索功能 -->
|
|
39
|
+
<div class="search-section">
|
|
40
|
+
<h4>文档搜索</h4>
|
|
41
|
+
<input
|
|
42
|
+
v-model="searchKeyword"
|
|
43
|
+
type="text"
|
|
44
|
+
placeholder="输入要搜索的关键字"
|
|
45
|
+
class="search-input"
|
|
46
|
+
/>
|
|
47
|
+
<button @click="clickPdf" :disabled="!searchKeyword.trim()">搜索</button>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<!-- PDF组件 -->
|
|
52
|
+
<div class="pdf-container">
|
|
53
|
+
<NsPdf
|
|
54
|
+
v-if="counts"
|
|
55
|
+
ref="pdfRef"
|
|
56
|
+
:url="currentUrl"
|
|
57
|
+
:hasTool="true">
|
|
58
|
+
</NsPdf>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
62
|
+
|
|
63
|
+
<script setup lang="ts">
|
|
64
|
+
import { ref, nextTick } from 'vue'
|
|
65
|
+
import localPdfUrl from '@/assets/驿云通 (11).pdf'
|
|
66
|
+
|
|
67
|
+
const counts = ref(true)
|
|
68
|
+
const file = ref()
|
|
69
|
+
const pdfRef = ref()
|
|
70
|
+
const pdfUrl = ref('')
|
|
71
|
+
const currentUrl = ref('')
|
|
72
|
+
const searchKeyword = ref('')
|
|
73
|
+
|
|
74
|
+
function loadLocalPdf() {
|
|
75
|
+
pdfUrl.value = ''
|
|
76
|
+
file.value = null
|
|
77
|
+
currentUrl.value = localPdfUrl
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function printPdf() {
|
|
81
|
+
if (!currentUrl.value) return
|
|
82
|
+
window.print()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function importPdf(f: any) {
|
|
86
|
+
pdfUrl.value = ''
|
|
87
|
+
file.value = f
|
|
88
|
+
|
|
89
|
+
if (f && f.name.endsWith('.pdf')) {
|
|
90
|
+
const fileUrl = URL.createObjectURL(f)
|
|
91
|
+
currentUrl.value = fileUrl
|
|
92
|
+
} else if (f) {
|
|
93
|
+
alert('请选择PDF文件')
|
|
94
|
+
clearFile()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function loadFromUrl() {
|
|
99
|
+
if (pdfUrl.value.trim()) {
|
|
100
|
+
clearFile()
|
|
101
|
+
currentUrl.value = pdfUrl.value.trim()
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function clearFile() {
|
|
106
|
+
file.value = null
|
|
107
|
+
if (currentUrl.value.startsWith('blob:')) {
|
|
108
|
+
URL.revokeObjectURL(currentUrl.value)
|
|
109
|
+
}
|
|
110
|
+
currentUrl.value = ''
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function clearUrl() {
|
|
114
|
+
pdfUrl.value = ''
|
|
115
|
+
currentUrl.value = ''
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function clickPdf() {
|
|
119
|
+
if (searchKeyword.value.trim() && pdfRef.value) {
|
|
120
|
+
pdfRef.value.search(searchKeyword.value.trim())
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function reloadComponent() {
|
|
125
|
+
counts.value = false
|
|
126
|
+
nextTick(() => {
|
|
127
|
+
counts.value = true
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
</script>
|
|
131
|
+
|
|
132
|
+
<style scoped lang="scss">
|
|
133
|
+
.demo-container {
|
|
134
|
+
padding: 20px;
|
|
135
|
+
max-width: 1400px;
|
|
136
|
+
margin: 0 auto;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.control-panel {
|
|
140
|
+
background: #f5f5f5;
|
|
141
|
+
padding: 20px;
|
|
142
|
+
border-radius: 8px;
|
|
143
|
+
margin-bottom: 20px;
|
|
144
|
+
|
|
145
|
+
h3 {
|
|
146
|
+
margin: 0 0 20px 0;
|
|
147
|
+
color: #333;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
h4 {
|
|
151
|
+
margin: 15px 0 10px 0;
|
|
152
|
+
color: #666;
|
|
153
|
+
font-size: 14px;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.local-section,
|
|
158
|
+
.upload-section,
|
|
159
|
+
.url-section,
|
|
160
|
+
.search-section {
|
|
161
|
+
margin-bottom: 20px;
|
|
162
|
+
padding: 15px;
|
|
163
|
+
background: white;
|
|
164
|
+
border-radius: 6px;
|
|
165
|
+
border: 1px solid #e0e0e0;
|
|
166
|
+
|
|
167
|
+
input[type='file'] {
|
|
168
|
+
margin-right: 10px;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.url-input,
|
|
172
|
+
.search-input {
|
|
173
|
+
width: 300px;
|
|
174
|
+
padding: 8px 12px;
|
|
175
|
+
border: 1px solid #ddd;
|
|
176
|
+
border-radius: 4px;
|
|
177
|
+
margin-right: 10px;
|
|
178
|
+
font-size: 14px;
|
|
179
|
+
|
|
180
|
+
&:focus {
|
|
181
|
+
outline: none;
|
|
182
|
+
border-color: #409eff;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
button {
|
|
187
|
+
padding: 8px 16px;
|
|
188
|
+
background: #409eff;
|
|
189
|
+
color: white;
|
|
190
|
+
border: none;
|
|
191
|
+
border-radius: 4px;
|
|
192
|
+
cursor: pointer;
|
|
193
|
+
margin-right: 10px;
|
|
194
|
+
font-size: 14px;
|
|
195
|
+
|
|
196
|
+
&:hover:not(:disabled) {
|
|
197
|
+
background: #337ecc;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
&:disabled {
|
|
201
|
+
background: #c0c4cc;
|
|
202
|
+
cursor: not-allowed;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.pdf-container {
|
|
208
|
+
height: 600px;
|
|
209
|
+
border: 1px solid #e0e0e0;
|
|
210
|
+
border-radius: 8px;
|
|
211
|
+
overflow: hidden;
|
|
212
|
+
margin-bottom: 20px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@media (max-width: 768px) {
|
|
216
|
+
.url-input,
|
|
217
|
+
.search-input {
|
|
218
|
+
width: 100% !important;
|
|
219
|
+
margin-bottom: 10px;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
@media print {
|
|
224
|
+
.control-panel {
|
|
225
|
+
display: none !important;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.pdf-container {
|
|
229
|
+
height: auto !important;
|
|
230
|
+
border: none !important;
|
|
231
|
+
overflow: visible !important;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
</style>
|
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
<div class="control-panel">
|
|
4
4
|
<h3>PDF 文档预览演示</h3>
|
|
5
5
|
|
|
6
|
+
<!-- 本地PDF文件 -->
|
|
7
|
+
<div class="local-section">
|
|
8
|
+
<h4>本地PDF文件</h4>
|
|
9
|
+
<span class="file-name">驿云通 (11).pdf</span>
|
|
10
|
+
<button @click="loadLocalPdf">加载本地文件</button>
|
|
11
|
+
<button @click="printPdf" :disabled="!currentUrl">打印</button>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
6
14
|
<!-- 文件上传方式 -->
|
|
7
15
|
<div class="upload-section">
|
|
8
16
|
<h4>方式一:文件上传</h4>
|
|
@@ -54,23 +62,31 @@
|
|
|
54
62
|
|
|
55
63
|
<script setup lang="ts">
|
|
56
64
|
import { ref, nextTick } from 'vue'
|
|
65
|
+
import localPdfUrl from '@/assets/驿云通 (11).pdf'
|
|
57
66
|
|
|
58
67
|
const counts = ref(true)
|
|
59
68
|
const file = ref()
|
|
60
69
|
const pdfRef = ref()
|
|
61
|
-
const pdfUrl = ref(
|
|
62
|
-
|
|
63
|
-
)
|
|
64
|
-
const currentUrl = ref(pdfUrl.value)
|
|
70
|
+
const pdfUrl = ref('')
|
|
71
|
+
const currentUrl = ref('')
|
|
65
72
|
const searchKeyword = ref('')
|
|
66
73
|
|
|
74
|
+
function loadLocalPdf() {
|
|
75
|
+
pdfUrl.value = ''
|
|
76
|
+
file.value = null
|
|
77
|
+
currentUrl.value = localPdfUrl
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function printPdf() {
|
|
81
|
+
if (!currentUrl.value) return
|
|
82
|
+
window.print()
|
|
83
|
+
}
|
|
84
|
+
|
|
67
85
|
function importPdf(f: any) {
|
|
68
|
-
// 清除URL,使用文件上传
|
|
69
86
|
pdfUrl.value = ''
|
|
70
87
|
file.value = f
|
|
71
88
|
|
|
72
89
|
if (f && f.name.endsWith('.pdf')) {
|
|
73
|
-
// 创建文件URL
|
|
74
90
|
const fileUrl = URL.createObjectURL(f)
|
|
75
91
|
currentUrl.value = fileUrl
|
|
76
92
|
} else if (f) {
|
|
@@ -81,7 +97,6 @@ function importPdf(f: any) {
|
|
|
81
97
|
|
|
82
98
|
function loadFromUrl() {
|
|
83
99
|
if (pdfUrl.value.trim()) {
|
|
84
|
-
// 清除文件,使用URL
|
|
85
100
|
clearFile()
|
|
86
101
|
currentUrl.value = pdfUrl.value.trim()
|
|
87
102
|
}
|
|
@@ -106,7 +121,6 @@ function clickPdf() {
|
|
|
106
121
|
}
|
|
107
122
|
}
|
|
108
123
|
|
|
109
|
-
// 重新加载组件
|
|
110
124
|
function reloadComponent() {
|
|
111
125
|
counts.value = false
|
|
112
126
|
nextTick(() => {
|
|
@@ -140,6 +154,7 @@ function reloadComponent() {
|
|
|
140
154
|
}
|
|
141
155
|
}
|
|
142
156
|
|
|
157
|
+
.local-section,
|
|
143
158
|
.upload-section,
|
|
144
159
|
.url-section,
|
|
145
160
|
.search-section {
|
|
@@ -204,4 +219,16 @@ function reloadComponent() {
|
|
|
204
219
|
margin-bottom: 10px;
|
|
205
220
|
}
|
|
206
221
|
}
|
|
222
|
+
|
|
223
|
+
@media print {
|
|
224
|
+
.control-panel {
|
|
225
|
+
display: none !important;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.pdf-container {
|
|
229
|
+
height: auto !important;
|
|
230
|
+
border: none !important;
|
|
231
|
+
overflow: visible !important;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
207
234
|
</style>
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="pdf-reader-page">
|
|
3
|
+
<div class="toolbar">
|
|
4
|
+
<div class="file-info">
|
|
5
|
+
<strong>{{ fileName }}</strong>
|
|
6
|
+
<span>{{ statusText }}</span>
|
|
7
|
+
</div>
|
|
8
|
+
<button type="button" :disabled="isPrinting || !pdfData || !totalPages" @click="printPdf">
|
|
9
|
+
{{ isPrinting ? 'Loading...' : 'Print' }}
|
|
10
|
+
</button>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div ref="pdfWrapperRef" class="pdf-wrapper">
|
|
14
|
+
<PDF
|
|
15
|
+
v-if="pdfData"
|
|
16
|
+
:key="viewerKey"
|
|
17
|
+
:src="pdfData"
|
|
18
|
+
pdf-width="100%"
|
|
19
|
+
:row-gap="12"
|
|
20
|
+
:show-progress="true"
|
|
21
|
+
:show-page-tooltip="true"
|
|
22
|
+
:show-back-to-top-btn="false"
|
|
23
|
+
@onPdfInit="handlePdfInit"
|
|
24
|
+
@onProgress="handleProgress"
|
|
25
|
+
@onComplete="handleDownloadComplete"
|
|
26
|
+
/>
|
|
27
|
+
<div v-else class="loading-state">
|
|
28
|
+
{{ statusText }}
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script setup lang="ts">
|
|
35
|
+
import { computed, nextTick, onBeforeMount, onMounted, ref } from 'vue'
|
|
36
|
+
import PDF from 'pdf-vue3'
|
|
37
|
+
|
|
38
|
+
const fileName = 'yiyuntong-11.pdf'
|
|
39
|
+
const pdfWrapperRef = ref<HTMLElement | null>(null)
|
|
40
|
+
const pdfData = ref<Uint8Array | null>(null)
|
|
41
|
+
const totalPages = ref(0)
|
|
42
|
+
const loadProgress = ref(0)
|
|
43
|
+
const isDownloadComplete = ref(false)
|
|
44
|
+
const isPrinting = ref(false)
|
|
45
|
+
const loadError = ref('')
|
|
46
|
+
const viewerKey = ref(0)
|
|
47
|
+
const pdfAssetUrl = new URL('../assets/驿云通 (11).pdf', import.meta.url).href
|
|
48
|
+
|
|
49
|
+
const statusText = computed(() => {
|
|
50
|
+
if (loadError.value) {
|
|
51
|
+
return loadError.value
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!pdfData.value) {
|
|
55
|
+
return `Reading PDF ${Math.floor(loadProgress.value)}%`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!totalPages.value) {
|
|
59
|
+
return `Loading ${Math.floor(loadProgress.value)}%`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (isPrinting.value) {
|
|
63
|
+
return 'Waiting for all pages to render'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return `${totalPages.value} pages`
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
onBeforeMount(() => {
|
|
70
|
+
resetLegacyPdfWorker()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
onMounted(() => {
|
|
74
|
+
loadLocalPdf()
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
async function loadLocalPdf() {
|
|
78
|
+
loadError.value = ''
|
|
79
|
+
loadProgress.value = 10
|
|
80
|
+
totalPages.value = 0
|
|
81
|
+
isDownloadComplete.value = false
|
|
82
|
+
pdfData.value = null
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
resetLegacyPdfWorker()
|
|
86
|
+
|
|
87
|
+
const response = await fetch(pdfAssetUrl)
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
throw new Error(`HTTP ${response.status}`)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
loadProgress.value = 60
|
|
93
|
+
const buffer = await response.arrayBuffer()
|
|
94
|
+
|
|
95
|
+
resetLegacyPdfWorker()
|
|
96
|
+
viewerKey.value += 1
|
|
97
|
+
pdfData.value = new Uint8Array(buffer)
|
|
98
|
+
loadProgress.value = 100
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error(error)
|
|
101
|
+
loadError.value = 'PDF file read failed'
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function resetLegacyPdfWorker() {
|
|
106
|
+
delete (window as Window & { pdfjsWorker?: unknown }).pdfjsWorker
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function handlePdfInit(pdf: { numPages?: number }) {
|
|
110
|
+
totalPages.value = pdf.numPages ?? 0
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function handleProgress(progress: number) {
|
|
114
|
+
loadProgress.value = progress
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function handleDownloadComplete() {
|
|
118
|
+
isDownloadComplete.value = true
|
|
119
|
+
loadProgress.value = 100
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function printPdf() {
|
|
123
|
+
if (isPrinting.value) return
|
|
124
|
+
|
|
125
|
+
isPrinting.value = true
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
await waitForAllPagesRendered()
|
|
129
|
+
window.print()
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error(error)
|
|
132
|
+
window.alert('PDF page loading timed out, please try again later')
|
|
133
|
+
} finally {
|
|
134
|
+
isPrinting.value = false
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function waitForAllPagesRendered(timeout = 30000) {
|
|
139
|
+
const startTime = Date.now()
|
|
140
|
+
|
|
141
|
+
while (Date.now() - startTime < timeout) {
|
|
142
|
+
await nextTick()
|
|
143
|
+
await waitFrame()
|
|
144
|
+
|
|
145
|
+
if (isDownloadComplete.value && totalPages.value > 0 && areAllCanvasesPainted()) {
|
|
146
|
+
await waitForStableLayout()
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
await sleep(100)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
throw new Error('PDF render timeout')
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function waitForStableLayout() {
|
|
157
|
+
let previousHeight = -1
|
|
158
|
+
|
|
159
|
+
for (let index = 0; index < 5; index += 1) {
|
|
160
|
+
await waitFrame()
|
|
161
|
+
await sleep(80)
|
|
162
|
+
|
|
163
|
+
const currentHeight = getCanvasContainerHeight()
|
|
164
|
+
if (currentHeight > 0 && currentHeight === previousHeight) {
|
|
165
|
+
await waitFrame()
|
|
166
|
+
await waitFrame()
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
previousHeight = currentHeight
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function areAllCanvasesPainted() {
|
|
175
|
+
const canvases = getCanvases()
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
canvases.length >= totalPages.value &&
|
|
179
|
+
canvases.every((canvas) => canvas.width > 0 && canvas.height > 0 && isCanvasPainted(canvas))
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function getCanvases() {
|
|
184
|
+
return Array.from(
|
|
185
|
+
pdfWrapperRef.value?.querySelectorAll<HTMLCanvasElement>(
|
|
186
|
+
'.pdf-vue3-canvas-container canvas',
|
|
187
|
+
) ?? [],
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getCanvasContainerHeight() {
|
|
192
|
+
return pdfWrapperRef.value?.querySelector('.pdf-vue3-canvas-container')?.scrollHeight ?? 0
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function isCanvasPainted(canvas: HTMLCanvasElement) {
|
|
196
|
+
const context = canvas.getContext('2d')
|
|
197
|
+
if (!context) return false
|
|
198
|
+
|
|
199
|
+
const points = [
|
|
200
|
+
[0.5, 0.5],
|
|
201
|
+
[0.25, 0.25],
|
|
202
|
+
[0.75, 0.25],
|
|
203
|
+
[0.25, 0.75],
|
|
204
|
+
[0.75, 0.75],
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
return points.some(([xRatio, yRatio]) => {
|
|
209
|
+
const x = Math.max(0, Math.min(canvas.width - 1, Math.floor(canvas.width * xRatio)))
|
|
210
|
+
const y = Math.max(0, Math.min(canvas.height - 1, Math.floor(canvas.height * yRatio)))
|
|
211
|
+
return context.getImageData(x, y, 1, 1).data[3] > 0
|
|
212
|
+
})
|
|
213
|
+
} catch {
|
|
214
|
+
return canvas.width > 0 && canvas.height > 0
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function waitFrame() {
|
|
219
|
+
return new Promise<void>((resolve) => {
|
|
220
|
+
requestAnimationFrame(() => resolve())
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function sleep(duration: number) {
|
|
225
|
+
return new Promise<void>((resolve) => {
|
|
226
|
+
window.setTimeout(resolve, duration)
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
</script>
|
|
230
|
+
|
|
231
|
+
<style scoped lang="scss">
|
|
232
|
+
.pdf-reader-page {
|
|
233
|
+
display: flex;
|
|
234
|
+
flex-direction: column;
|
|
235
|
+
height: 100%;
|
|
236
|
+
min-height: 720px;
|
|
237
|
+
background: #f3f5f8;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.toolbar {
|
|
241
|
+
flex: none;
|
|
242
|
+
display: flex;
|
|
243
|
+
align-items: center;
|
|
244
|
+
justify-content: space-between;
|
|
245
|
+
gap: 16px;
|
|
246
|
+
padding: 14px 18px;
|
|
247
|
+
border-bottom: 1px solid #d8dee8;
|
|
248
|
+
background: #ffffff;
|
|
249
|
+
|
|
250
|
+
.file-info {
|
|
251
|
+
display: flex;
|
|
252
|
+
flex-direction: column;
|
|
253
|
+
gap: 4px;
|
|
254
|
+
min-width: 0;
|
|
255
|
+
color: #1f2937;
|
|
256
|
+
|
|
257
|
+
strong,
|
|
258
|
+
span {
|
|
259
|
+
overflow: hidden;
|
|
260
|
+
text-overflow: ellipsis;
|
|
261
|
+
white-space: nowrap;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
span {
|
|
265
|
+
color: #64748b;
|
|
266
|
+
font-size: 13px;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
button {
|
|
271
|
+
flex: none;
|
|
272
|
+
min-width: 88px;
|
|
273
|
+
height: 36px;
|
|
274
|
+
padding: 0 16px;
|
|
275
|
+
border: 1px solid #2563eb;
|
|
276
|
+
border-radius: 4px;
|
|
277
|
+
background: #2563eb;
|
|
278
|
+
color: #ffffff;
|
|
279
|
+
cursor: pointer;
|
|
280
|
+
|
|
281
|
+
&:disabled {
|
|
282
|
+
border-color: #b8c2d6;
|
|
283
|
+
background: #b8c2d6;
|
|
284
|
+
cursor: not-allowed;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.pdf-wrapper {
|
|
290
|
+
flex: 1;
|
|
291
|
+
min-height: 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.loading-state {
|
|
295
|
+
display: flex;
|
|
296
|
+
align-items: center;
|
|
297
|
+
justify-content: center;
|
|
298
|
+
height: 100%;
|
|
299
|
+
min-height: 360px;
|
|
300
|
+
color: #475569;
|
|
301
|
+
font-size: 14px;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
:deep(.pdf-vue3-scroller) {
|
|
305
|
+
background: #e9edf3;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
:deep(.pdf-vue3-canvas-container) {
|
|
309
|
+
padding: 18px 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
@media (max-width: 768px) {
|
|
313
|
+
.pdf-reader-page {
|
|
314
|
+
min-height: 100vh;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.toolbar {
|
|
318
|
+
align-items: stretch;
|
|
319
|
+
flex-direction: column;
|
|
320
|
+
|
|
321
|
+
button {
|
|
322
|
+
width: 100%;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
@media print {
|
|
328
|
+
.pdf-reader-page {
|
|
329
|
+
display: block;
|
|
330
|
+
height: auto;
|
|
331
|
+
min-height: auto;
|
|
332
|
+
background: #ffffff;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.toolbar {
|
|
336
|
+
display: none !important;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.pdf-wrapper,
|
|
340
|
+
:deep(.pdf-vue3-main),
|
|
341
|
+
:deep(.pdf-vue3-container),
|
|
342
|
+
:deep(.pdf-vue3-scroller) {
|
|
343
|
+
height: auto !important;
|
|
344
|
+
max-height: none !important;
|
|
345
|
+
overflow: visible !important;
|
|
346
|
+
background: #ffffff !important;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
:deep(.pdf-vue3-canvas-container) {
|
|
350
|
+
width: 100% !important;
|
|
351
|
+
padding: 0 !important;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
:deep(.pdf-vue3-canvas-container canvas) {
|
|
355
|
+
width: 100% !important;
|
|
356
|
+
height: auto !important;
|
|
357
|
+
margin: 0 auto !important;
|
|
358
|
+
box-shadow: none !important;
|
|
359
|
+
break-after: page;
|
|
360
|
+
page-break-after: always;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
:deep(.pdf-vue3-progress),
|
|
364
|
+
:deep(.pdf-vue3-pageTooltip),
|
|
365
|
+
:deep(.pdf-vue3-backToTopBtn) {
|
|
366
|
+
display: none !important;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
</style>
|