import Vue from 'vue'; import store from '@/store' // v-dialogDrag: 弹窗拖拽属性 Vue.directive('drag', { bind(el, binding, vnode, oldVnode) { const dialogHeaderEl = el.querySelector('.el-dialog__header'); const dragDom = el.querySelector('.el-dialog'); dialogHeaderEl.style.cssText += ';cursor:move;' dragDom.style.cssText += ';top:0px;' // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null); const sty = (() => { if (window.document.currentStyle) { return (dom, attr) => dom.currentStyle[attr]; } else { return (dom, attr) => getComputedStyle(dom, false)[attr]; } })() dialogHeaderEl.onmousedown = (e) => { // 鼠标按下,计算当前元素距离可视区的距离 const disX = e.clientX - dialogHeaderEl.offsetLeft; const disY = e.clientY - dialogHeaderEl.offsetTop; const screenWidth = document.body.clientWidth; // body当前宽度 const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度,可某些环境下无法获取) const dragDomWidth = dragDom.offsetWidth; // 对话框宽度 const dragDomheight = dragDom.offsetHeight; // 对话框高度 const minDragDomLeft = dragDom.offsetLeft; const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth; const minDragDomTop = dragDom.offsetTop; const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight; // 获取到的值带px 正则匹配替换 let styL = sty(dragDom, 'left'); let styT = sty(dragDom, 'top'); // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px if (styL.includes('%')) { styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100); styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100); } else { styL = +styL.replace(/\px/g, ''); styT = +styT.replace(/\px/g, ''); }; document.onmousemove = function (e) { // 通过事件委托,计算移动的距离 let left = e.clientX - disX; let top = e.clientY - disY; // 边界处理 if (-(left) > minDragDomLeft) { left = -(minDragDomLeft); } else if (left > maxDragDomLeft) { left = maxDragDomLeft; } if (-(top) > minDragDomTop) { top = -(minDragDomTop); } else if (top > maxDragDomTop) { top = maxDragDomTop; } // 移动当前元素 dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`; }; document.onmouseup = function (e) { document.onmousemove = null; document.onmouseup = null; }; } } }) //---------------以下都可删--------------------- // v-fireworks: 容器内按钮点击烟花特效(全局可复用) Vue.directive('fireworks', { bind(el, binding) { const colors = ['#FFD700', '#DAA520', '#B8860B', '#C9A227', '#E6B800']; const getSkyConfig = () => { let type = 'rain'; let enabled = true; const val = binding && binding.value; if (typeof val === 'string') { if (val === 'rain' || val === 'meteor') type = val; if (val === 'none') enabled = false; } else if (typeof val === 'object' && val) { if (val.sky === 'rain' || val.sky === 'meteor') type = val.sky; if (val.sky === 'none') enabled = false; if (typeof val.skyEnabled === 'boolean') enabled = val.skyEnabled; if (typeof val.enabled === 'boolean') enabled = val.enabled; } else if (val === false) { enabled = false; } const ds = el.getAttribute && el.getAttribute('data-sky'); if (ds === 'rain' || ds === 'meteor') type = ds; if (ds === 'none') enabled = false; const dse = el.getAttribute && el.getAttribute('data-sky-enabled'); if (dse === 'false' || dse === '0') enabled = false; if (dse === 'true' || dse === '1') enabled = true; return { type, enabled }; } // ===== 全屏天空特效(雨/流星) ===== let skyCanvas = null; let skyCtx = null; let skyRaf = null; let skyTimer = null; let skyDelayTimer = null; let skyParticles = []; const ensureSkyCanvas = () => { if (skyCanvas) return; skyCanvas = document.createElement('canvas'); skyCanvas.className = 'sky-effect-canvas'; skyCanvas.style.position = 'fixed'; skyCanvas.style.left = '0'; skyCanvas.style.top = '0'; skyCanvas.style.width = '100vw'; skyCanvas.style.height = '100vh'; skyCanvas.style.pointerEvents = 'none'; skyCanvas.style.zIndex = '99998'; document.body.appendChild(skyCanvas); skyCtx = skyCanvas.getContext('2d'); const resize = () => { skyCanvas.width = window.innerWidth; skyCanvas.height = window.innerHeight; }; resize(); window.addEventListener('resize', resize); skyCanvas.__resize__ = resize; }; const clearSky = () => { if (skyRaf) cancelAnimationFrame(skyRaf); skyRaf = null; if (skyTimer) clearTimeout(skyTimer); skyTimer = null; if (skyDelayTimer) clearTimeout(skyDelayTimer); skyDelayTimer = null; if (skyCanvas) { window.removeEventListener('resize', skyCanvas.__resize__); if (skyCanvas.parentNode) skyCanvas.parentNode.removeChild(skyCanvas); } skyCanvas = null; skyCtx = null; skyParticles = []; }; const startRain = (duration = 3000) => { ensureSkyCanvas(); const count = Math.min(140, Math.floor((window.innerWidth * window.innerHeight) / 12000)); skyParticles = new Array(count).fill(0).map(() => ({ x: Math.random() * skyCanvas.width, y: Math.random() * skyCanvas.height, len: 10 + Math.random() * 14, speed: 3 + Math.random() * 6, wind: 1 + Math.random() * 2 })); const stroke = 'rgba(255, 215, 0, 0.8)'; const draw = () => { if (!skyCtx) return; skyCtx.clearRect(0, 0, skyCanvas.width, skyCanvas.height); skyCtx.strokeStyle = stroke; skyCtx.lineWidth = 1.2; skyCtx.lineCap = 'round'; for (const p of skyParticles) { skyCtx.beginPath(); skyCtx.moveTo(p.x, p.y); skyCtx.lineTo(p.x + p.wind, p.y + p.len); skyCtx.stroke(); p.x += p.wind; p.y += p.speed; if (p.y > skyCanvas.height || p.x > skyCanvas.width) { p.x = Math.random() * skyCanvas.width; p.y = -20; } } skyRaf = requestAnimationFrame(draw); }; draw(); skyTimer = setTimeout(clearSky, duration); }; const startMeteor = (duration = 3000) => { ensureSkyCanvas(); const count = 8; skyParticles = new Array(count).fill(0).map(() => newMeteor()); function newMeteor() { const startX = Math.random() * skyCanvas.width; const startY = Math.random() * (skyCanvas.height * 0.3); const speed = 6 + Math.random() * 6; const angle = Math.PI / 3; // 60° 斜向 const vx = Math.cos(angle) * speed; const vy = Math.sin(angle) * speed; const len = 120 + Math.random() * 120; const life = 40 + Math.random() * 40; return { x: startX, y: startY, vx, vy, len, life, maxLife: life }; } const draw = () => { if (!skyCtx) return; skyCtx.clearRect(0, 0, skyCanvas.width, skyCanvas.height); for (let i = 0; i < skyParticles.length; i++) { const m = skyParticles[i]; const grad = skyCtx.createLinearGradient(m.x, m.y, m.x - m.vx * 10, m.y - m.vy * 10); grad.addColorStop(0, 'rgba(255,215,0,0.9)'); grad.addColorStop(1, 'rgba(255,215,0,0.0)'); skyCtx.strokeStyle = grad; skyCtx.lineWidth = 2; skyCtx.beginPath(); skyCtx.moveTo(m.x, m.y); skyCtx.lineTo(m.x - (m.vx * (m.len / 10)), m.y - (m.vy * (m.len / 10))); skyCtx.stroke(); m.x += m.vx; m.y += m.vy; m.life -= 1; if (m.life <= 0 || m.x > skyCanvas.width + 100 || m.y > skyCanvas.height + 100) { skyParticles[i] = newMeteor(); } } skyRaf = requestAnimationFrame(draw); }; draw(); skyTimer = setTimeout(clearSky, duration); }; const startSkyEffect = () => { const cfg = getSkyConfig(); if (!cfg.enabled) return; // 若已有画布或定时器,刷新计时即可,避免叠加 if (skyTimer) { clearTimeout(skyTimer); skyTimer = null; } if (skyDelayTimer) { clearTimeout(skyDelayTimer); skyDelayTimer = null; } if (cfg.type === 'meteor') { startMeteor(3500); } else { // 雨滴延迟1秒出现,持续时间为1秒 skyDelayTimer = setTimeout(() => { startRain(1000); }, 1000); } }; const isButtonLike = (node) => { if (!node) return false; if (node.closest) { const match = node.closest('button, .el-button, [role="button"], input[type="button"], input[type="submit"], a.el-button, a'); return !!match; } return false; } const spawnFireworks = (x, y) => { const count = 14; for (let i = 0; i < count; i++) { const particle = document.createElement('span'); particle.className = 'firework-particle'; const angle = (Math.PI * 2 * i) / count + Math.random() * 0.3; const distance = 60 + Math.random() * 60; const dx = Math.cos(angle) * distance; const dy = Math.sin(angle) * distance; particle.style.left = x + 'px'; particle.style.top = y + 'px'; particle.style.background = colors[i % colors.length]; particle.style.setProperty('--dx', dx + 'px'); particle.style.setProperty('--dy', dy + 'px'); document.body.appendChild(particle); setTimeout(() => { if (particle && particle.parentNode) { particle.parentNode.removeChild(particle); } }, 700); } }; const showOverlayImage = (x, y) => { const img = document.createElement('img'); img.className = 'click-overlay-image'; img.src = '/static/img/保密资源.png'; img.style.left = x + 'px'; img.style.top = y + 'px'; document.body.appendChild(img); setTimeout(() => { if (img && img.parentNode) { img.parentNode.removeChild(img); } }, 1000); }; const handler = (e) => { if (store.state.common.effectsEnabled==='N') return; if (!isButtonLike(e.target)) return; const x = e.clientX; const y = e.clientY; spawnFireworks(x, y); startSkyEffect(); showOverlayImage(x, y); }; el.__fireworksHandler__ = handler; el.addEventListener('click', handler, false); }, unbind(el) { if (el.__fireworksHandler__) { el.removeEventListener('click', el.__fireworksHandler__, false); delete el.__fireworksHandler__; } } }) // v-click-image: 点击显示图片浮层(不拦截交互,可全局复用) Vue.directive('click-image', { bind(el, binding) { const defaults = { src: '/static/img/保密资源.png', duration: 1000, width: '90px', height: '90px', any: false // false: 仅按钮/a 元素触发;true: 任意元素点击触发 }; const resolveConfig = () => { const val = binding && binding.value; const cfg = { ...defaults }; if (typeof val === 'string') { cfg.src = val || cfg.src; } else if (val && typeof val === 'object') { if (val.src) cfg.src = val.src; if (val.duration != null) cfg.duration = Number(val.duration) || cfg.duration; if (val.width) cfg.width = typeof val.width === 'number' ? (val.width + 'px') : val.width; if (val.height) cfg.height = typeof val.height === 'number' ? (val.height + 'px') : val.height; if (typeof val.any === 'boolean') cfg.any = val.any; } // data-* 兜底 const ds = el.getAttribute && el.getAttribute('data-click-image'); if (ds) cfg.src = ds; const dsd = el.getAttribute && el.getAttribute('data-click-image-duration'); if (dsd) cfg.duration = Number(dsd) || cfg.duration; const dsw = el.getAttribute && el.getAttribute('data-click-image-width'); if (dsw) cfg.width = dsw.match(/^\d+$/) ? (dsw + 'px') : dsw; const dsh = el.getAttribute && el.getAttribute('data-click-image-height'); if (dsh) cfg.height = dsh.match(/^\d+$/) ? (dsh + 'px') : dsh; const dsa = el.getAttribute && el.getAttribute('data-click-image-any'); if (dsa === 'true' || dsa === '1') cfg.any = true; if (dsa === 'false' || dsa === '0') cfg.any = false; return cfg; }; const isButtonLike = (node) => { if (!node) return false; if (node.closest) { const match = node.closest('button, .el-button, [role="button"], input[type="button"], input[type="submit"], a.el-button, a'); return !!match; } return false; }; const show = (x, y, cfg) => { const img = document.createElement('img'); img.className = 'click-overlay-image'; img.src = cfg.src; img.style.left = x + 'px'; img.style.top = y + 'px'; if (cfg.width) img.style.width = cfg.width; if (cfg.height) img.style.height = cfg.height; document.body.appendChild(img); setTimeout(() => { if (img && img.parentNode) img.parentNode.removeChild(img); }, cfg.duration); }; const handler = (e) => { if (store.state.common.effectsEnabled==='N') return; const cfg = resolveConfig(); if (!cfg.any && !isButtonLike(e.target)) return; const x = e.clientX; const y = e.clientY; show(x, y, cfg); }; el.__clickImageHandler__ = handler; el.addEventListener('click', handler, false); }, unbind(el) { if (el.__clickImageHandler__) { el.removeEventListener('click', el.__clickImageHandler__, false); delete el.__clickImageHandler__; } } });