Files
n8n_Demo/web_frontend/web_result/js/main.js

713 lines
23 KiB
JavaScript
Raw Normal View History

// 2024长三角国际新能源汽车与智能交通产业博览会 - 交互脚本
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
// 隐藏页面加载器
hidePageLoader();
initNavbar();
initAnimations();
initCounters();
initScrollEffects();
initRippleEffect();
initLazyLoading();
initFormValidation();
initCharts();
initTimeline();
initInteractiveElements();
handleImageErrors();
});
// 隐藏页面加载器(兼容新的加载器)
function hidePageLoader() {
const loader = document.getElementById('pageLoader') || document.getElementById('page-loader');
if (loader) {
// 如果是新的加载器,使用其自带的隐藏方法
if (loader.id === 'page-loader' && window.PageLoader) {
window.PageLoader.hideLoader();
return;
}
// 旧的加载器逻辑
loader.style.opacity = '0';
loader.style.transition = 'opacity 0.5s ease-out';
setTimeout(() => {
loader.style.display = 'none';
document.body.style.overflow = 'auto';
document.body.classList.remove('loading');
}, 500);
} else {
// 如果没有找到加载器,也要确保恢复滚动
document.body.style.overflow = 'auto';
document.body.classList.remove('loading');
}
}
// 如果页面加载超过3秒强制隐藏加载器
window.addEventListener('load', function() {
setTimeout(() => {
hidePageLoader();
}, 3000);
});
// 导航栏交互
function initNavbar() {
const navbar = document.querySelector('.navbar');
const navLinks = document.querySelectorAll('.nav-link');
const mobileMenuBtn = document.querySelector('.mobile-menu-btn');
const mobileMenu = document.querySelector('.mobile-menu');
// 滚动时改变导航栏样式
window.addEventListener('scroll', () => {
if (window.scrollY > 100) {
navbar?.classList.add('scrolled');
} else {
navbar?.classList.remove('scrolled');
}
});
// 高亮当前页面链接
const currentPath = window.location.pathname;
navLinks.forEach(link => {
if (link.getAttribute('href') === currentPath) {
link.classList.add('active');
}
});
// 移动端菜单切换
mobileMenuBtn?.addEventListener('click', () => {
mobileMenu?.classList.toggle('open');
mobileMenuBtn.classList.toggle('active');
});
// 平滑滚动到锚点
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
}
// 动画初始化
function initAnimations() {
// Intersection Observer for fade-in animations
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-in');
observer.unobserve(entry.target);
}
});
}, observerOptions);
// 观察所有需要动画的元素
document.querySelectorAll('.fade-in, .slide-in-left, .slide-in-right, .grid-item').forEach(el => {
observer.observe(el);
});
}
// 数字计数器动画
function initCounters() {
const counters = document.querySelectorAll('.counter');
const speed = 200; // 动画速度
const countUp = (counter) => {
const target = +counter.getAttribute('data-target');
const increment = target / speed;
const updateCount = () => {
const count = +counter.innerText.replace(/[^0-9]/g, '');
if (count < target) {
counter.innerText = Math.ceil(count + increment).toLocaleString();
setTimeout(updateCount, 1);
} else {
counter.innerText = target.toLocaleString();
// 添加单位
const unit = counter.getAttribute('data-unit');
if (unit) {
counter.innerText += unit;
}
}
};
updateCount();
};
// 使用 Intersection Observer 触发计数
const counterObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
countUp(entry.target);
counterObserver.unobserve(entry.target);
}
});
}, { threshold: 0.5 });
counters.forEach(counter => {
counter.innerText = '0';
counterObserver.observe(counter);
});
}
// 滚动效果
function initScrollEffects() {
let lastScrollTop = 0;
const header = document.querySelector('header');
window.addEventListener('scroll', () => {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
// 隐藏/显示导航栏
if (scrollTop > lastScrollTop && scrollTop > 300) {
header?.classList.add('hide');
} else {
header?.classList.remove('hide');
}
lastScrollTop = scrollTop <= 0 ? 0 : scrollTop;
// 视差效果
const parallaxElements = document.querySelectorAll('.parallax');
parallaxElements.forEach(el => {
const speed = el.getAttribute('data-speed') || 0.5;
const yPos = -(scrollTop * speed);
el.style.transform = `translateY(${yPos}px)`;
});
// 进度条
const progressBar = document.querySelector('.progress-bar');
if (progressBar) {
const winScroll = document.body.scrollTop || document.documentElement.scrollTop;
const height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
const scrolled = (winScroll / height) * 100;
progressBar.style.width = scrolled + '%';
}
});
}
// 波纹效果
function initRippleEffect() {
document.querySelectorAll('.btn, .card').forEach(element => {
element.addEventListener('click', function(e) {
const ripple = document.createElement('span');
ripple.classList.add('ripple');
const rect = this.getBoundingClientRect();
const size = Math.max(rect.width, rect.height);
const x = e.clientX - rect.left - size / 2;
const y = e.clientY - rect.top - size / 2;
ripple.style.width = ripple.style.height = size + 'px';
ripple.style.left = x + 'px';
ripple.style.top = y + 'px';
this.appendChild(ripple);
setTimeout(() => {
ripple.remove();
}, 600);
});
});
}
// 懒加载
function initLazyLoading() {
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
imageObserver.unobserve(img);
// 添加加载完成动画
img.addEventListener('load', () => {
img.classList.add('loaded');
});
}
});
}, {
rootMargin: '50px 0px'
});
images.forEach(img => imageObserver.observe(img));
}
// 表单验证
function initFormValidation() {
const forms = document.querySelectorAll('form');
forms.forEach(form => {
form.addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
const inputs = form.querySelectorAll('input[required], textarea[required]');
inputs.forEach(input => {
if (!input.value.trim()) {
isValid = false;
input.classList.add('error');
showError(input, '此字段为必填项');
} else {
input.classList.remove('error');
clearError(input);
}
// 邮箱验证
if (input.type === 'email' && input.value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(input.value)) {
isValid = false;
input.classList.add('error');
showError(input, '请输入有效的邮箱地址');
}
}
// 电话验证
if (input.type === 'tel' && input.value) {
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(input.value)) {
isValid = false;
input.classList.add('error');
showError(input, '请输入有效的手机号码');
}
}
});
if (isValid) {
// 显示成功消息
showSuccess('表单提交成功!');
form.reset();
}
});
// 实时验证
form.querySelectorAll('input, textarea').forEach(input => {
input.addEventListener('blur', function() {
validateInput(this);
});
input.addEventListener('input', function() {
if (this.classList.contains('error')) {
validateInput(this);
}
});
});
});
}
// 显示错误信息
function showError(input, message) {
const errorEl = input.nextElementSibling;
if (errorEl && errorEl.classList.contains('error-message')) {
errorEl.textContent = message;
} else {
const error = document.createElement('span');
error.classList.add('error-message');
error.textContent = message;
input.parentNode.insertBefore(error, input.nextSibling);
}
}
// 清除错误信息
function clearError(input) {
const errorEl = input.nextElementSibling;
if (errorEl && errorEl.classList.contains('error-message')) {
errorEl.remove();
}
}
// 验证输入
function validateInput(input) {
if (input.hasAttribute('required') && !input.value.trim()) {
input.classList.add('error');
showError(input, '此字段为必填项');
return false;
}
input.classList.remove('error');
clearError(input);
return true;
}
// 显示成功消息
function showSuccess(message) {
const toast = document.createElement('div');
toast.classList.add('toast', 'success');
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.classList.add('show');
}, 100);
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => {
toast.remove();
}, 300);
}, 3000);
}
// 初始化图表
function initCharts() {
// 预算分配饼图
const budgetChart = document.getElementById('budgetChart');
if (budgetChart) {
const data = [
{ label: '场地租赁', value: 35, color: '#10b981' },
{ label: '营销推广', value: 25, color: '#3b82f6' },
{ label: '运营服务', value: 20, color: '#8b5cf6' },
{ label: '人员成本', value: 15, color: '#f59e0b' },
{ label: '其他费用', value: 5, color: '#ef4444' }
];
drawPieChart(budgetChart, data);
}
// 参展商类别分布
const exhibitorChart = document.getElementById('exhibitorChart');
if (exhibitorChart) {
const data = [
{ label: '整车制造', value: 40 },
{ label: '零部件', value: 30 },
{ label: '充电设施', value: 15 },
{ label: '智能驾驶', value: 10 },
{ label: '其他', value: 5 }
];
drawBarChart(exhibitorChart, data);
}
}
// 绘制饼图
function drawPieChart(canvas, data) {
const ctx = canvas.getContext('2d');
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = Math.min(centerX, centerY) - 20;
let currentAngle = -Math.PI / 2;
const total = data.reduce((sum, item) => sum + item.value, 0);
data.forEach(item => {
const sliceAngle = (item.value / total) * 2 * Math.PI;
// 绘制扇形
ctx.beginPath();
ctx.arc(centerX, centerY, radius, currentAngle, currentAngle + sliceAngle);
ctx.lineTo(centerX, centerY);
ctx.fillStyle = item.color;
ctx.fill();
// 绘制标签
const labelX = centerX + Math.cos(currentAngle + sliceAngle / 2) * (radius * 0.7);
const labelY = centerY + Math.sin(currentAngle + sliceAngle / 2) * (radius * 0.7);
ctx.fillStyle = '#fff';
ctx.font = '14px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(item.value + '%', labelX, labelY);
currentAngle += sliceAngle;
});
}
// 绘制柱状图
function drawBarChart(canvas, data) {
const ctx = canvas.getContext('2d');
const barWidth = canvas.width / (data.length * 2);
const maxValue = Math.max(...data.map(item => item.value));
const chartHeight = canvas.height - 40;
data.forEach((item, index) => {
const barHeight = (item.value / maxValue) * chartHeight;
const x = (index * 2 + 0.5) * barWidth;
const y = canvas.height - barHeight - 20;
// 绘制柱子
const gradient = ctx.createLinearGradient(x, y, x, y + barHeight);
gradient.addColorStop(0, '#10b981');
gradient.addColorStop(1, '#3b82f6');
ctx.fillStyle = gradient;
ctx.fillRect(x, y, barWidth, barHeight);
// 绘制数值
ctx.fillStyle = '#333';
ctx.font = '12px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(item.value + '%', x + barWidth / 2, y - 5);
});
}
// 初始化时间线
function initTimeline() {
const timelineItems = document.querySelectorAll('.timeline-item');
const timelineObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('active');
}
});
}, { threshold: 0.5 });
timelineItems.forEach(item => {
timelineObserver.observe(item);
});
}
// 交互元素初始化
function initInteractiveElements() {
// 标签页切换
const tabs = document.querySelectorAll('.tab');
const tabContents = document.querySelectorAll('.tab-content');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
const target = tab.dataset.tab;
// 切换标签状态
tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
// 切换内容显示
tabContents.forEach(content => {
if (content.id === target) {
content.classList.add('active');
content.style.display = 'block';
} else {
content.classList.remove('active');
content.style.display = 'none';
}
});
});
});
// 手风琴效果
const accordionHeaders = document.querySelectorAll('.accordion-header');
accordionHeaders.forEach(header => {
header.addEventListener('click', () => {
const content = header.nextElementSibling;
const isOpen = header.classList.contains('active');
// 关闭其他项
accordionHeaders.forEach(h => {
h.classList.remove('active');
h.nextElementSibling.style.maxHeight = null;
});
// 切换当前项
if (!isOpen) {
header.classList.add('active');
content.style.maxHeight = content.scrollHeight + 'px';
}
});
});
// 模态框
const modalTriggers = document.querySelectorAll('[data-modal]');
const modals = document.querySelectorAll('.modal');
const modalCloses = document.querySelectorAll('.modal-close');
modalTriggers.forEach(trigger => {
trigger.addEventListener('click', () => {
const modalId = trigger.dataset.modal;
const modal = document.getElementById(modalId);
if (modal) {
modal.classList.add('open');
document.body.style.overflow = 'hidden';
}
});
});
modalCloses.forEach(close => {
close.addEventListener('click', () => {
const modal = close.closest('.modal');
modal.classList.remove('open');
document.body.style.overflow = '';
});
});
// 点击背景关闭模态框
modals.forEach(modal => {
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.classList.remove('open');
document.body.style.overflow = '';
}
});
});
// 工具提示
const tooltips = document.querySelectorAll('[data-tooltip]');
tooltips.forEach(element => {
const tooltip = document.createElement('div');
tooltip.classList.add('tooltip');
tooltip.textContent = element.dataset.tooltip;
element.addEventListener('mouseenter', () => {
document.body.appendChild(tooltip);
const rect = element.getBoundingClientRect();
tooltip.style.left = rect.left + rect.width / 2 - tooltip.offsetWidth / 2 + 'px';
tooltip.style.top = rect.top - tooltip.offsetHeight - 10 + 'px';
tooltip.classList.add('show');
});
element.addEventListener('mouseleave', () => {
tooltip.classList.remove('show');
setTimeout(() => {
tooltip.remove();
}, 300);
});
});
}
// 复制到剪贴板
function copyToClipboard(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
showSuccess('已复制到剪贴板');
}
// 分享功能
function shareContent(platform) {
const url = encodeURIComponent(window.location.href);
const title = encodeURIComponent(document.title);
let shareUrl = '';
switch(platform) {
case 'wechat':
// 生成微信分享二维码
generateQRCode(window.location.href);
break;
case 'weibo':
shareUrl = `https://service.weibo.com/share/share.php?url=${url}&title=${title}`;
break;
case 'linkedin':
shareUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${url}`;
break;
case 'twitter':
shareUrl = `https://twitter.com/intent/tweet?url=${url}&text=${title}`;
break;
}
if (shareUrl) {
window.open(shareUrl, '_blank', 'width=600,height=400');
}
}
// 生成二维码
function generateQRCode(text) {
const modal = document.createElement('div');
modal.classList.add('qr-modal');
modal.innerHTML = `
<div class="qr-content">
<h3>微信扫码分享</h3>
<div id="qrcode"></div>
<button class="btn btn-secondary" onclick="this.parentElement.parentElement.remove()">关闭</button>
</div>
`;
document.body.appendChild(modal);
// 这里可以集成实际的二维码生成库
document.getElementById('qrcode').innerHTML = '二维码生成区域';
}
// 主题切换
function initThemeToggle() {
const themeToggle = document.getElementById('themeToggle');
const currentTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', currentTheme);
themeToggle?.addEventListener('click', () => {
const theme = document.documentElement.getAttribute('data-theme') === 'light' ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
});
}
// 导出为 PDF
function exportToPDF() {
window.print();
}
// 性能监控
function initPerformanceMonitoring() {
// 页面加载时间
window.addEventListener('load', () => {
const loadTime = performance.timing.loadEventEnd - performance.timing.navigationStart;
console.log(`页面加载时间: ${loadTime}ms`);
// 发送到分析服务
if (window.gtag) {
gtag('event', 'page_load_time', {
value: loadTime,
page_path: window.location.pathname
});
}
});
// 监控长任务
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('Long Task detected:', entry);
}
});
observer.observe({ entryTypes: ['longtask'] });
}
}
// 处理图片加载错误
function handleImageErrors() {
const images = document.querySelectorAll('img');
const defaultImage = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZTJlOGYwIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxOCIgZmlsbD0iIzk0YTNiOCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuWbvueJh+WKoOi9veS4rTwvdGV4dD48L3N2Zz4=';
images.forEach(img => {
// 添加错误处理
img.addEventListener('error', function() {
console.warn('图片加载失败:', this.src);
// 设置默认占位图
this.src = defaultImage;
this.alt = '图片加载失败';
this.classList.add('image-error');
});
// 添加加载成功处理
img.addEventListener('load', function() {
this.classList.add('image-loaded');
});
});
}
// 初始化性能监控
initPerformanceMonitoring();
initThemeToggle();