hookehuyr

feat(PdfViewer): 添加控制栏拖动功能

为PDF查看器添加控制栏拖动功能,支持鼠标和触摸屏操作
添加阻尼效果和拖动阈值,优化用户体验
更新样式以反映拖动状态
...@@ -52,6 +52,7 @@ declare module 'vue' { ...@@ -52,6 +52,7 @@ declare module 'vue' {
52 VanDropdownMenu: typeof import('vant/es')['DropdownMenu'] 52 VanDropdownMenu: typeof import('vant/es')['DropdownMenu']
53 VanEmpty: typeof import('vant/es')['Empty'] 53 VanEmpty: typeof import('vant/es')['Empty']
54 VanField: typeof import('vant/es')['Field'] 54 VanField: typeof import('vant/es')['Field']
55 + VanFloatingBubble: typeof import('vant/es')['FloatingBubble']
55 VanForm: typeof import('vant/es')['Form'] 56 VanForm: typeof import('vant/es')['Form']
56 VanIcon: typeof import('vant/es')['Icon'] 57 VanIcon: typeof import('vant/es')['Icon']
57 VanImage: typeof import('vant/es')['Image'] 58 VanImage: typeof import('vant/es')['Image']
......
1 <!-- 1 <!--
2 * @Date: 2025-01-21 2 * @Date: 2025-01-21
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-10-22 13:49:33 4 + * @LastEditTime: 2025-10-22 14:11:50
5 * @FilePath: /mlaj/src/components/ui/PdfViewer.vue 5 * @FilePath: /mlaj/src/components/ui/PdfViewer.vue
6 * @Description: PDF预览组件 - 使用pdf-vue3库 6 * @Description: PDF预览组件 - 使用pdf-vue3库
7 --> 7 -->
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
10 :style="{ height: '100%', width: '100%' }"> 10 :style="{ height: '100%', width: '100%' }">
11 <div class="pdf-viewer-container"> 11 <div class="pdf-viewer-container">
12 <!-- PDF内容区域 --> 12 <!-- PDF内容区域 -->
13 - <div class="pdf-content" :class="{ 'pdf-no-select': preventSave }"> 13 + <div id="pdf-content" class="pdf-content" :class="{ 'pdf-no-select': preventSave }">
14 <PDF v-if="url && show && !loadingError" 14 <PDF v-if="url && show && !loadingError"
15 ref="pdfRef" 15 ref="pdfRef"
16 :src="url" 16 :src="url"
...@@ -53,7 +53,16 @@ ...@@ -53,7 +53,16 @@
53 <!-- 已移除顶部关闭按钮,关闭功能迁移至控制栏 --> 53 <!-- 已移除顶部关闭按钮,关闭功能迁移至控制栏 -->
54 54
55 <!-- 缩放控制按钮 --> 55 <!-- 缩放控制按钮 -->
56 - <div v-if="!loading && !loadingError" class="zoom-controls"> 56 + <div
57 + v-if="!loading && !loadingError"
58 + class="zoom-controls"
59 + :class="{ 'dragging': isDragging }"
60 + :style="{ bottom: controlsY + 'px' }"
61 + @mousedown="handleDragStart"
62 + @touchstart="handleTouchStart"
63 + @touchmove="handleTouchMove"
64 + @touchend="handleTouchEnd"
65 + >
57 <van-button class="zoom-btn zoom-close-btn" type="default" icon="cross" round @click="handleClose" /> 66 <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" /> --> 67 <!-- <van-button class="zoom-btn zoom-in-btn" type="default" icon="plus" round @click="zoomIn" /> -->
59 <font-awesome-icon icon="magnifying-glass-plus" class="text-2xl text-gray-600 ml-2 mr-2" @click="zoomIn" /> 68 <font-awesome-icon icon="magnifying-glass-plus" class="text-2xl text-gray-600 ml-2 mr-2" @click="zoomIn" />
...@@ -152,6 +161,14 @@ const pageInput = ref(''); ...@@ -152,6 +161,14 @@ const pageInput = ref('');
152 const isEditingPage = ref(false); 161 const isEditingPage = ref(false);
153 const pageInputRef = ref(null); 162 const pageInputRef = ref(null);
154 163
164 +// 拖动控制相关变量
165 +const isDragging = ref(false); // 是否正在拖动
166 +const dragStartY = ref(0); // 拖动开始时的Y坐标
167 +const controlsY = ref(20); // 控制栏距离底部的距离(px)
168 +const dragOffset = ref(0); // 拖动偏移量
169 +const initialControlsY = ref(20); // 拖动开始时控制栏的初始位置
170 +const dragThreshold = 5; // 拖动阈值,超过这个距离才认为是拖动
171 +
155 /** 172 /**
156 * 处理页码输入框失焦事件 173 * 处理页码输入框失焦事件
157 */ 174 */
...@@ -181,6 +198,110 @@ const focusPageInput = () => { ...@@ -181,6 +198,110 @@ const focusPageInput = () => {
181 } 198 }
182 }; 199 };
183 200
201 +/**
202 + * 处理拖动开始事件(鼠标)
203 + */
204 +const handleDragStart = (e) => {
205 + // 如果点击的是按钮,不处理拖动
206 + if (e.target.closest('.zoom-btn') || e.target.closest('.page-jump')) {
207 + return;
208 + }
209 +
210 + dragStartY.value = e.clientY;
211 + initialControlsY.value = controlsY.value; // 记录拖动开始时的位置
212 + dragOffset.value = 0;
213 + document.addEventListener('mousemove', handleDragMove);
214 + document.addEventListener('mouseup', handleDragEnd);
215 + // 不立即调用preventDefault,等确认是拖动后再阻止
216 +};
217 +
218 +/**
219 + * 处理拖动移动事件(鼠标)
220 + */
221 +const handleDragMove = (e) => {
222 + const deltaY = Math.abs(dragStartY.value - e.clientY);
223 +
224 + // 只有移动距离超过阈值才开始拖动
225 + if (!isDragging.value && deltaY > dragThreshold) {
226 + isDragging.value = true;
227 + e.preventDefault();
228 + }
229 +
230 + if (!isDragging.value) return;
231 +
232 + const actualDeltaY = dragStartY.value - e.clientY; // 向上为正,向下为负
233 + const dampingFactor = 0.3; // 阻尼系数,降低移动速度
234 + const dampedDelta = actualDeltaY * dampingFactor;
235 +
236 + const newY = Math.max(20, Math.min(window.innerHeight - 100, initialControlsY.value + dampedDelta));
237 + controlsY.value = newY;
238 + dragOffset.value = dampedDelta;
239 + e.preventDefault();
240 +};
241 +
242 +/**
243 + * 处理拖动结束事件(鼠标)
244 + */
245 +const handleDragEnd = () => {
246 + isDragging.value = false;
247 + dragStartY.value = 0;
248 + dragOffset.value = 0;
249 + document.removeEventListener('mousemove', handleDragMove);
250 + document.removeEventListener('mouseup', handleDragEnd);
251 +};
252 +
253 +/**
254 + * 处理触摸开始事件(移动端)
255 + */
256 +const handleTouchStart = (e) => {
257 + if (e.touches.length === 1) {
258 + // 如果触摸的是按钮,不处理拖动
259 + if (e.target.closest('.zoom-btn') || e.target.closest('.page-jump')) {
260 + return;
261 + }
262 +
263 + dragStartY.value = e.touches[0].clientY;
264 + initialControlsY.value = controlsY.value; // 记录拖动开始时的位置
265 + dragOffset.value = 0;
266 + // 不立即调用preventDefault,等确认是拖动后再阻止
267 + }
268 +};
269 +
270 +/**
271 + * 处理触摸移动事件(移动端)
272 + */
273 +const handleTouchMove = (e) => {
274 + if (e.touches.length !== 1) return;
275 +
276 + const deltaY = Math.abs(dragStartY.value - e.touches[0].clientY);
277 +
278 + // 只有移动距离超过阈值才开始拖动
279 + if (!isDragging.value && deltaY > dragThreshold) {
280 + isDragging.value = true;
281 + e.preventDefault();
282 + }
283 +
284 + if (!isDragging.value) return;
285 +
286 + const actualDeltaY = dragStartY.value - e.touches[0].clientY; // 向上为正,向下为负
287 + const dampingFactor = 0.4; // 移动端稍微高一点的阻尼系数
288 + const dampedDelta = actualDeltaY * dampingFactor;
289 +
290 + const newY = Math.max(20, Math.min(window.innerHeight - 100, initialControlsY.value + dampedDelta));
291 + controlsY.value = newY;
292 + dragOffset.value = dampedDelta;
293 + e.preventDefault();
294 +};
295 +
296 +/**
297 + * 处理触摸结束事件(移动端)
298 + */
299 +const handleTouchEnd = () => {
300 + isDragging.value = false;
301 + dragStartY.value = 0;
302 + dragOffset.value = 0;
303 +};
304 +
184 // 超时处理相关 305 // 超时处理相关
185 const loadingTimeout = ref(30000); // 超时时间,默认30秒 306 const loadingTimeout = ref(30000); // 超时时间,默认30秒
186 const timeoutTimer = ref(null); // 超时计时器 307 const timeoutTimer = ref(null); // 超时计时器
...@@ -749,6 +870,8 @@ const applyPinchZoom = (newZoom, centerX, centerY, container) => { ...@@ -749,6 +870,8 @@ const applyPinchZoom = (newZoom, centerX, centerY, container) => {
749 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); 870 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
750 backdrop-filter: blur(8px); 871 backdrop-filter: blur(8px);
751 transition: all 0.3s ease; 872 transition: all 0.3s ease;
873 + cursor: grab;
874 + user-select: none;
752 } 875 }
753 876
754 .zoom-controls:hover { 877 .zoom-controls:hover {
...@@ -757,6 +880,14 @@ const applyPinchZoom = (newZoom, centerX, centerY, container) => { ...@@ -757,6 +880,14 @@ const applyPinchZoom = (newZoom, centerX, centerY, container) => {
757 transform: translateX(-50%) translateY(-2px); 880 transform: translateX(-50%) translateY(-2px);
758 } 881 }
759 882
883 +.zoom-controls.dragging {
884 + cursor: grabbing;
885 + transform: translateX(-50%) scale(1.05);
886 + box-shadow: 0 8px 40px rgba(0, 0, 0, 0.25);
887 + background: rgba(255, 255, 255, 1);
888 + transition: none;
889 +}
890 +
760 .zoom-btn { 891 .zoom-btn {
761 width: 40px; 892 width: 40px;
762 height: 40px; 893 height: 40px;
......