You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

401 lines
14 KiB

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__;
}
}
});