feat: 优化会展网站首页用户体验和视觉效果
主要改进: - 修复星形图标可见性问题,改为黄色高对比度显示 - 移除'立即参展'按钮,清理冗余CTA元素 - 修正背景图片路径并优化透明度(opacity-10) - 为所有容器区域添加hover提示文字 - 删除'立即参与,共创绿色出行未来'整个CTA区块 - 优化页面加载器和图片错误处理 - 修复展会信息卡片文字对比度问题 - 增强多层背景遮罩效果 影响文件: - web_frontend/web_result/index.html - web_frontend/web_result/css/animations.css - web_frontend/web_result/js/main.js - 新增多个页面和样式文件 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
709
web_frontend/web_result/js/main.js
Normal file
709
web_frontend/web_result/js/main.js
Normal file
@@ -0,0 +1,709 @@
|
||||
// 2024长三角国际新能源汽车与智能交通产业博览会 - 交互脚本
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 隐藏页面加载器
|
||||
hidePageLoader();
|
||||
|
||||
initNavbar();
|
||||
initAnimations();
|
||||
initCounters();
|
||||
initScrollEffects();
|
||||
initRippleEffect();
|
||||
initLazyLoading();
|
||||
initFormValidation();
|
||||
initCharts();
|
||||
initTimeline();
|
||||
initInteractiveElements();
|
||||
handleImageErrors();
|
||||
});
|
||||
|
||||
// 隐藏页面加载器
|
||||
function hidePageLoader() {
|
||||
const loader = document.getElementById('pageLoader');
|
||||
if (loader) {
|
||||
// 添加淡出动画
|
||||
loader.style.opacity = '0';
|
||||
loader.style.transition = 'opacity 0.5s ease-out';
|
||||
|
||||
// 500ms后完全移除
|
||||
setTimeout(() => {
|
||||
loader.style.display = 'none';
|
||||
// 恢复body滚动
|
||||
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();
|
||||
Reference in New Issue
Block a user