vue cli上简单的功能,在js上太难弄了,这个弹窗功能时常用到,保存起来备用吧
备注:deepseek这个人工智障写一堆有问题的我,还老服务器繁忙
效果图:
html代码:
<div class="modal-mask" v-show="qrcodeShow" @click.self="closeModal"><div class="modal-container" ref="modal" :style="modalStyle"><divclass="modal-header"@mousedown="startDrag"@touchstart.prevent="startDrag"@mouseup="stopDrag"@touchend="stopDrag"><span>获取app</span><span class="close-btn" @click="closeModal">×</span></div><div class="image-container"><img :src="qrcodeImgUrl" class="modal-image" alt="弹窗图片" /></div></div>
</div>
js代码:
data: {scanCodeList: [],qrcodeShow: false,qrcodeImgUrl: "**图片地址**",isDragging: false,startX: 0,startY: 0,translateX: 0,translateY: 0,modalRect: null,
},created() {this.$nextTick(() => {// 使用jQuery添加动画效果$(".modal-container").hide();// 监听弹窗状态变化this.$watch("qrcodeShow", (newVal) => {if (newVal) {$(".modal-container").fadeIn(300);} else {$(".modal-container").fadeOut(300);}});});},computed: {modalStyle() {return {transform: `translate(${this.translateX}px, ${this.translateY}px)`,};},},methods: {showModal() {this.qrcodeShow = true;},closeModal() {this.qrcodeShow = false;},// 开始拖动startDrag(e) {this.isDragging = true;const clientX = e.touches ? e.touches[0].clientX : e.clientX;const clientY = e.touches ? e.touches[0].clientY : e.clientY;// 记录初始位置this.startX = clientX - this.translateX;this.startY = clientY - this.translateY;// 获取弹窗尺寸this.modalRect = this.$refs.modal.getBoundingClientRect();// 添加事件监听document.addEventListener("mousemove", this.onDrag);document.addEventListener("touchmove", this.onDrag, { passive: false });document.addEventListener("mouseup", this.stopDrag);document.addEventListener("touchend", this.stopDrag);// 优化拖动体验document.body.style.cursor = "grabbing";document.body.style.userSelect = "none";},// 拖动处理onDrag(e) {if (!this.isDragging) return;// 获取坐标const clientX = e.touches ? e.touches[0].clientX : e.clientX;const clientY = e.touches ? e.touches[0].clientY : e.clientY;// 计算新位置let newX = clientX - this.startX;let newY = clientY - this.startY;// 计算边界const viewportWidth = document.documentElement.clientWidth;const viewportHeight = document.documentElement.clientHeight;const modalWidth = this.modalRect.width;const modalHeight = this.modalRect.height;// 有效边界const minX = -(viewportWidth - modalWidth) / 2;const minY = -(viewportHeight - modalHeight) / 2;const maxX = (viewportWidth - modalWidth) / 2;const maxY = (viewportHeight - modalHeight) / 2;// 应用约束newX = Math.max(minX, Math.min(newX, maxX));newY = Math.max(minY, Math.min(newY, maxY));// 更新位置this.translateX = newX;this.translateY = newY;},// 停止拖动stopDrag() {this.isDragging = false;// 移除事件监听document.removeEventListener("mousemove", this.onDrag);document.removeEventListener("touchmove", this.onDrag);document.removeEventListener("mouseup", this.stopDrag);document.removeEventListener("touchend", this.stopDrag);// 恢复样式document.body.style.cursor = "";document.body.style.userSelect = "";},// 重置位置到屏幕中央resetPosition() {this.$nextTick(() => {const modal = this.$refs.modal;if (modal) {const rect = modal.getBoundingClientRect();const viewportWidth = document.documentElement.clientWidth;const viewportHeight = document.documentElement.clientHeight;this.translateX = (viewportWidth - rect.width) / 2;this.translateY = (viewportHeight - rect.height) / 2;}});},},
css代码:
/* 遮罩层样式 */.modal-mask {position: fixed;top: 0;left: 0;width: 100%;height: 100%;background: rgba(0, 0, 0, 0.5);z-index: 9998;display: flex;justify-content: center;align-items: center;/* 弹窗容器 */.modal-container {display: none;background: white;border-radius: 8px;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);width: 500px;height: 500px;z-index: 9999;position: relative;/* 弹窗头部 */.modal-header {height: 50px;padding: 15px;border-bottom: 1px solid #eee;display: flex;justify-content: space-between;align-items: center;cursor: move;user-select: none; /* 防止文字被选中 */span {font-size: 18px;font-weight: bold;}/* 关闭按钮样式 */.close-btn {cursor: pointer;font-size: 20px;color: #666;padding: 0 5px;}}/* 图片容器 */.image-container {padding: 20px;width: 100%;height: calc(100% - 50px);overflow: auto;img {width: 100%;height: 100%;object-fit: cover;}}}}