【Vue&JS】解决鼠标拖拽与点击事件冲突&元素在指定div中拖拽移动场景

时间:2025-02-07 18:49:44
<template> <fin-main v-if="subAppActive" class="portal-sub-app-wrapper"> <license-info :message="licenseMessage" :isShow="isShowLicense" @close="closeLicense" > </license-info> <!-- 系统左侧菜单容器 --> <div class="boyun-menu" id="boyun-menu" v-if="isBoYun" :style="{ height: subAppHeight }" ></div> <div class="portal-sub-app" :class="{ 'boyun-sub-app': isBoYun }" id="portal-sub-app" :style="{ height: subAppHeight }" > </div> <!-- 帮助悬浮按钮 --> <div class="drag-area"> <div v-if="isShowHelp" class="help-control" ref="helpControl" draggable="true" @mousedown.stop="moveLineFun" @click="openWin"> 帮助 </div> </div> </fin-main> </template> <script> import { mapState, mapMutations, mapGetters } from 'vuex'; import LicenseInfo from '@c/license-info'; import dayjs from 'dayjs'; // import DragDoc from '@c/drag-modules/'; import { checkLicense } from '@/modules/sys/service'; import { start, isMicroSub, getMicroSub } from '@/micro'; import { aesDecrypt, random } from '@/utils/crypto'; export default { data() { return { licenseMessage: '', subAppHeight: '100%', isBoYun: false, myArray: [1], createEle: null, }; }, components: { LicenseInfo, // DragDoc, }, computed: { ...mapState('app', ['subAppActive', 'isShowLicense', 'isOpenDocWin']), ...mapState('user', ['subApps', '']), ...mapGetters('user', ['isShowHelp']), }, watch: { $route(val) { this.handleRouteChange(val); }, isShowLicense(val) { if (val && this.licenseMessage.length) { this.subAppHeight = 'calc( 100% - 36px )'; } else { this.subAppHeight = '100%'; } }, }, beforeRouteEnter(to, from, next) { next((vm) => { vm.handleRouteChange.apply(vm, [to]); }); }, mounted() { const { register } = this.$store.state.app; if (!register) { this.$store.commit('app/TOGGLE_REGISTER'); const appList = this.buildAppList(); console.log(appList); start({ prefetch: false, appList, }); } }, methods: { ...mapMutations('user', ['setIsOpenDocWin']), // 打开拖拽窗 openWin() { // 解决鼠标拖拽与点击事件冲突 const isDrag = this.$refs.helpControl.getAttribute('drag-flag'); if (isDrag === 'true') { return false; } this.setIsOpenDocWin(true); }, // 第一次进入以及切换其它子系统触发该方法 // 判断是否访问的是子系统,如果为子系统收起portal菜单以及处理全屏路由、license校验 handleRouteChange($route) { if (this.$route.path.startsWith('/boc')) { this.isBoYun = true; } else { this.isBoYun = false; } const appList = this.buildAppList(); const bol = isMicroSub(appList, this.$route.path); this.$store.commit('app/TOGGLE_SUBAPPACTIVE', bol); // 如果是子应用关闭系统菜单 if (bol) { this.$store.commit('app/TOGGLE_SIDEBAR', true); const microSub = getMicroSub(appList, $route.path); // if (EnableLicense) { this.validLicense(microSub); // } // 添加 hash模式全屏 if ( $route.path.startsWith(`${microSub.entry}/full`) || ($route.hash && $route.hash.startsWith('#/full')) ) { // mounted后执行 setTimeout(() => { window.eventCenter.emit('SYSTEM_FULL_SCREEN'); }); // ('SYSTEM_FULL_SCREEN'); // eslint-disable-next-line no-underscore-dangle } else if (window.__IS_FULL_SCREEN) { window.eventCenter.emit('SYSTEM_EXIT_FULL_SCREEN'); } } else { this.$router.replace({ path: '/404', }); } }, buildAppList() { if (!process.env.isProduction) { return process.env.appList; } const singleSpaList = []; this.subApps.map((app) => { const { name, url, extend } = app; try { const info = JSON.parse(extend); const { appName, entry, container } = info; singleSpaList.push({ name: appName, entry, context: `/${appName}`, container, }); } catch (error) { const entry = url || `/${name}`; singleSpaList.push({ name, entry, context: entry, }); } }); return singleSpaList; }, validLicense(app) { // 随机数 const seq = random(100000000); checkLicense({ key: app.context, seq, }) .then((res) => { try { res = res.replace(/[\r\n]/g, ''); const resStr = aesDecrypt(res); const data = JSON.parse(resStr); const { result, seq: seqNext, list = [] } = data; if (seq + 1 !== seqNext) { this.$message.error('license校验数据格式错误!'); return this.$router.replace({ name: 'home', }); } // 过期 if (result === 0) { return this.$router.replace({ name: 'exception_expire', }); } // 未到有效期 if (result === -1) { const str = list.reduce((pre, current) => { const { productName, startDate, endDate } = current; return `${pre}${productName} 有效期为 ${dayjs(startDate).format( 'YYYY-MM-DD', )}~${dayjs(endDate).format('YYYY-MM-DD')}`; }, ''); return this.$router.replace({ name: 'not_valid', params: { desc: str, }, }); } if (list.length) { const str = list.reduce((pre, current) => { const { productName, endDate } = current; return `${pre}${productName} 将于 ${dayjs(endDate).format( 'YYYY-MM-DD', )} 到期,`; }, ''); this.licenseMessage = `${str}到期后产品将不能使用,请联系管理员续约!`; } } catch (error) { console.log(error); } }) .catch((err) => { console.error(err); this.$router.replace({ name: 'home', }); }); }, closeLicense() { this.$store.commit('app/setLicenseState', false); }, // 拖拽监听鼠标按下 moveLineFun(e) { const { target } = e; const moveBoxObj = document.getElementsByClassName('drag-area')[0]; // 最大的框,自带相对定位属性 const moveObj = document.getElementsByClassName('help-control')[0]; // 移动的元素 const moveBoxObjMaxWidth = moveBoxObj.clientWidth; // 得到点击时该元素所在大容器的宽 const moveBoxObjMaxHeight = moveBoxObj.clientHeight; // 得到点击时该元素所在大容器的高 const moveLineObjOffsetLeft = moveObj.offsetLeft; // 得到点击时该元素的左边距 const moveLineObjOffsetTop = moveObj.offsetTop; // 得到点击时该元素的上边距 const moveStartX = e.clientX; // 得到当前鼠标点击的x位置 const moveStartY = e.clientY; // 得到当前鼠标点击的x位置 const leftMin = 0; const leftMax = moveBoxObjMaxWidth - moveObj.clientWidth; // 左边距最大值 const topMax = moveBoxObjMaxHeight - moveObj.clientHeight; // 上边距最大值 // 解决鼠标拖拽与点击事件冲突:时间差 moveObj.setAttribute('drag-flag', false); const dragStartTime = new Date().getTime(); document.onmousemove = () => { const dragEndTime = new Date().getTime(); if (dragEndTime - dragStartTime > 200) { target.setAttribute('drag-flag', true); } }; // 绑定鼠标移动时的计算 function moveFun(e1) { e1.preventDefault(); const mouseMoveDistance = e1.clientX - moveStartX; // 鼠标滑动距离(正则是往右;负则是往左) const mouseMoveDistanceY = e1.clientY - moveStartY; // 鼠标滑动距离(正则是往下;负则是往上) moveObj.style.positon = 'absolute'; // 给元素添加绝对定位属性 let styleLeft = moveLineObjOffsetLeft + mouseMoveDistance; // 左边距 = 元素初始(左边距)位置 + 鼠标滑动的距离 let styleTop = moveLineObjOffsetTop + mouseMoveDistanceY; // 左边距 = 元素初始(左边距)位置 + 鼠标滑动的距离 console.log('move'); if (styleLeft <= leftMin) { styleLeft = leftMin; } else if (styleLeft > leftMax) { styleLeft = leftMax; } if (styleTop <= moveObj.clientHeight) { styleTop = 0; } else if (styleTop > topMax) { styleTop = topMax; } moveObj.style.top = `${styleTop}px`; // 赋值拖动的线的上边距离 moveObj.style.left = `${styleLeft}px`; // 赋值拖动的线的左边距离 } // 取消计算绑定 function stopFun() { document.removeEventListener('mousemove', moveFun); // 取消监听事件,鼠标开始移动 document.removeEventListener('mouseup', stopFun); // 取消监听事件,鼠标停止移动 moveObj.style.left = null; // 清除left行内样式, 否则鼠标移开时收不回去 } document.addEventListener('mousemove', moveFun); // 添加监听事件,鼠标开始移动 document.addEventListener('mouseup', stopFun); // 添加监听事件,鼠标停止移动 }, }, }; </script> <style lang="less" scoped> .portal-sub-app-wrapper { height: 100%; padding: 0 !important; overflow-x: hidden; .drag-area { position: absolute; top: 56px; right: 0; z-index: 999; width: 52px; height: calc(~"100% - 56px"); .help-control { position: absolute; top: 50%; right: -24px !important; z-index: 999; width: 44px; height: 44px; padding: 10px; border-radius: 50%; font-size: 12px; line-height: 24px; color: #fff; background: #2c68ff; cursor: pointer; } } .help-control:hover { right: 8px !important; font-size: 12px; } .license-info { position: relative; padding: 8px 16px; color: #fff; background-color: #ff9c00; &-content { display: table-cell; padding: 0 8px; .title { font-size: 14px; line-height: 18px; } } .close-icon { position: absolute; top: 12px; right: 16px; font-size: 12px; color: #fff; opacity: 1; cursor: pointer; } } .boyun-menu { width: 180px; overflow: hidden; float: left; } .boyun-sub-app { width: calc(~"100% - 180px"); float: left; } } </style>