feat(PdfViewer): 添加页码跳转功能
实现页码显示和跳转功能,包括当前页/总页数显示和输入框跳转 处理PDF初始化时的总页数获取和页码边界检查
Showing
1 changed file
with
118 additions
and
3 deletions
| ... | @@ -21,7 +21,7 @@ | ... | @@ -21,7 +21,7 @@ |
| 21 | :scrollThreshold="300" | 21 | :scrollThreshold="300" |
| 22 | :pdfWidth="`${Math.round(100 * zoomLevel)}%`" | 22 | :pdfWidth="`${Math.round(100 * zoomLevel)}%`" |
| 23 | :rowGap="8" | 23 | :rowGap="8" |
| 24 | - :page="1" | 24 | + :page="currentPage" |
| 25 | :cMapUrl="'https://unpkg.com/pdfjs-dist@3.7.107/cmaps/'" | 25 | :cMapUrl="'https://unpkg.com/pdfjs-dist@3.7.107/cmaps/'" |
| 26 | :withCredentials="false" | 26 | :withCredentials="false" |
| 27 | :useSystemFonts="true" | 27 | :useSystemFonts="true" |
| ... | @@ -56,7 +56,20 @@ | ... | @@ -56,7 +56,20 @@ |
| 56 | <div v-if="!loading && !loadingError" class="zoom-controls"> | 56 | <div v-if="!loading && !loadingError" class="zoom-controls"> |
| 57 | <van-button class="zoom-btn zoom-close-btn" type="default" icon="cross" round @click="handleClose" /> | 57 | <van-button class="zoom-btn zoom-close-btn" type="default" icon="cross" round @click="handleClose" /> |
| 58 | <van-button class="zoom-btn zoom-in-btn" type="default" icon="plus" round @click="zoomIn" /> | 58 | <van-button class="zoom-btn zoom-in-btn" type="default" icon="plus" round @click="zoomIn" /> |
| 59 | - <div class="zoom-level">{{ Math.round(zoomLevel * 100) }}%</div> | 59 | + <div class="page-jump" @click="focusPageInput"> |
| 60 | + <span class="page-display">{{ currentPage }}/{{ totalPages || 0 }}</span> | ||
| 61 | + <input | ||
| 62 | + ref="pageInputRef" | ||
| 63 | + class="page-input" | ||
| 64 | + type="number" | ||
| 65 | + :min="1" | ||
| 66 | + :max="totalPages || 1" | ||
| 67 | + v-model="pageInput" | ||
| 68 | + @blur="handlePageInputBlur" | ||
| 69 | + @keyup.enter="handlePageInputBlur" | ||
| 70 | + v-show="isEditingPage" | ||
| 71 | + /> | ||
| 72 | + </div> | ||
| 60 | <van-button class="zoom-btn zoom-out-btn" type="default" icon="minus" round @click="zoomOut" /> | 73 | <van-button class="zoom-btn zoom-out-btn" type="default" icon="minus" round @click="zoomOut" /> |
| 61 | <van-button class="zoom-btn zoom-reset-btn" type="default" round @click="resetZoom"> | 74 | <van-button class="zoom-btn zoom-reset-btn" type="default" round @click="resetZoom"> |
| 62 | <van-icon name="replay" size="16" /> | 75 | <van-icon name="replay" size="16" /> |
| ... | @@ -130,6 +143,42 @@ const zoomLevel = ref(1); // 缩放级别,1为100% | ... | @@ -130,6 +143,42 @@ const zoomLevel = ref(1); // 缩放级别,1为100% |
| 130 | const scrollPosition = ref({ x: 0, y: 0 }); // 记录滚动位置 | 143 | const scrollPosition = ref({ x: 0, y: 0 }); // 记录滚动位置 |
| 131 | const pdfRef = ref(null); // PDF组件引用 | 144 | const pdfRef = ref(null); // PDF组件引用 |
| 132 | 145 | ||
| 146 | +// 页码控制 | ||
| 147 | +const currentPage = ref(1); | ||
| 148 | +const totalPages = ref(0); | ||
| 149 | +const pageInput = ref(''); | ||
| 150 | +const isEditingPage = ref(false); | ||
| 151 | +const pageInputRef = ref(null); | ||
| 152 | + | ||
| 153 | +/** | ||
| 154 | + * 处理页码输入框失焦事件 | ||
| 155 | + */ | ||
| 156 | +const handlePageInputBlur = () => { | ||
| 157 | + const inputValue = parseInt(pageInput.value); | ||
| 158 | + if (!isNaN(inputValue)) { | ||
| 159 | + const validPage = Math.max(1, Math.min(inputValue, totalPages.value || 1)); | ||
| 160 | + currentPage.value = validPage; | ||
| 161 | + } | ||
| 162 | + pageInput.value = ''; | ||
| 163 | + isEditingPage.value = false; | ||
| 164 | +}; | ||
| 165 | + | ||
| 166 | +/** | ||
| 167 | + * 点击页码显示区域,切换到编辑模式 | ||
| 168 | + */ | ||
| 169 | +const focusPageInput = () => { | ||
| 170 | + if (!isEditingPage.value) { | ||
| 171 | + isEditingPage.value = true; | ||
| 172 | + pageInput.value = currentPage.value.toString(); | ||
| 173 | + nextTick(() => { | ||
| 174 | + if (pageInputRef.value) { | ||
| 175 | + pageInputRef.value.focus(); | ||
| 176 | + pageInputRef.value.select(); | ||
| 177 | + } | ||
| 178 | + }); | ||
| 179 | + } | ||
| 180 | +}; | ||
| 181 | + | ||
| 133 | // 超时处理相关 | 182 | // 超时处理相关 |
| 134 | const loadingTimeout = ref(30000); // 超时时间,默认30秒 | 183 | const loadingTimeout = ref(30000); // 超时时间,默认30秒 |
| 135 | const timeoutTimer = ref(null); // 超时计时器 | 184 | const timeoutTimer = ref(null); // 超时计时器 |
| ... | @@ -270,7 +319,8 @@ const handleScroll = (scrollOffset) => { | ... | @@ -270,7 +319,8 @@ const handleScroll = (scrollOffset) => { |
| 270 | * @param {number} page - 当前页码 | 319 | * @param {number} page - 当前页码 |
| 271 | */ | 320 | */ |
| 272 | const handlePageChange = (page) => { | 321 | const handlePageChange = (page) => { |
| 273 | - // 可以在这里处理页面变化事件 | 322 | + const p = Number(page) || 1; |
| 323 | + currentPage.value = p; | ||
| 274 | }; | 324 | }; |
| 275 | 325 | ||
| 276 | /** | 326 | /** |
| ... | @@ -283,6 +333,18 @@ const handlePdfInit = (pdf) => { | ... | @@ -283,6 +333,18 @@ const handlePdfInit = (pdf) => { |
| 283 | 333 | ||
| 284 | // PDF初始化完成后的处理 | 334 | // PDF初始化完成后的处理 |
| 285 | loadingError.value = false; | 335 | loadingError.value = false; |
| 336 | + | ||
| 337 | + // 记录总页数 | ||
| 338 | + try { | ||
| 339 | + const num = (pdf && typeof pdf.numPages === 'number') ? pdf.numPages : 0; | ||
| 340 | + if (num > 0) { | ||
| 341 | + totalPages.value = num; | ||
| 342 | + if (currentPage.value > num) currentPage.value = num; | ||
| 343 | + } | ||
| 344 | + } catch (e) { | ||
| 345 | + // 忽略无法获取页数的情况 | ||
| 346 | + } | ||
| 347 | + | ||
| 286 | nextTick(() => { | 348 | nextTick(() => { |
| 287 | if (props.preventSave) { | 349 | if (props.preventSave) { |
| 288 | // 添加防止保存的事件监听器 | 350 | // 添加防止保存的事件监听器 |
| ... | @@ -685,6 +747,59 @@ const addPreventSaveListeners = () => { | ... | @@ -685,6 +747,59 @@ const addPreventSaveListeners = () => { |
| 685 | line-height: 1.4; | 747 | line-height: 1.4; |
| 686 | } | 748 | } |
| 687 | 749 | ||
| 750 | +.page-jump { | ||
| 751 | + display: flex; | ||
| 752 | + align-items: center; | ||
| 753 | + justify-content: center; | ||
| 754 | + position: relative; | ||
| 755 | + cursor: pointer; | ||
| 756 | + min-width: 60px; | ||
| 757 | + height: 32px; | ||
| 758 | + background: rgba(0, 0, 0, 0.6); | ||
| 759 | + border-radius: 16px; | ||
| 760 | + padding: 0 12px; | ||
| 761 | +} | ||
| 762 | + | ||
| 763 | +.page-display { | ||
| 764 | + color: white; | ||
| 765 | + font-size: 14px; | ||
| 766 | + font-weight: 500; | ||
| 767 | + white-space: nowrap; | ||
| 768 | + user-select: none; | ||
| 769 | +} | ||
| 770 | + | ||
| 771 | +.page-input { | ||
| 772 | + position: absolute; | ||
| 773 | + top: 0; | ||
| 774 | + left: 0; | ||
| 775 | + right: 0; | ||
| 776 | + bottom: 0; | ||
| 777 | + width: 100%; | ||
| 778 | + height: 100%; | ||
| 779 | + background: rgba(255, 255, 255, 0.9); | ||
| 780 | + border: 2px solid #1989fa; | ||
| 781 | + border-radius: 16px; | ||
| 782 | + text-align: center; | ||
| 783 | + font-size: 14px; | ||
| 784 | + font-weight: 500; | ||
| 785 | + color: #333; | ||
| 786 | + outline: none; | ||
| 787 | +} | ||
| 788 | + | ||
| 789 | +.page-input::-webkit-outer-spin-button, | ||
| 790 | +.page-input::-webkit-inner-spin-button { | ||
| 791 | + -webkit-appearance: none; | ||
| 792 | + margin: 0; | ||
| 793 | +} | ||
| 794 | + | ||
| 795 | +.page-input[type=number] { | ||
| 796 | + -moz-appearance: textfield; | ||
| 797 | +} | ||
| 798 | + | ||
| 799 | +.page-jump:hover { | ||
| 800 | + background: rgba(0, 0, 0, 0.8); | ||
| 801 | +} | ||
| 802 | + | ||
| 688 | /* 错误状态样式 */ | 803 | /* 错误状态样式 */ |
| 689 | .error-container { | 804 | .error-container { |
| 690 | display: flex; | 805 | display: flex; | ... | ... |
-
Please register or login to post a comment