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