590 lines
18 KiB
JavaScript
590 lines
18 KiB
JavaScript
/**
|
|
* 增强交互体验管理器
|
|
* 统一管理整个网站的交互优化
|
|
*/
|
|
class EnhancedInteractionManager {
|
|
constructor() {
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.setupGlobalInteractions();
|
|
this.setupLoadingStates();
|
|
this.setupFormEnhancements();
|
|
this.setupScrollEffects();
|
|
this.setupTooltips();
|
|
this.setupNotifications();
|
|
this.setupKeyboardNavigation();
|
|
this.setupMobileOptimizations();
|
|
}
|
|
|
|
// 全局交互设置
|
|
setupGlobalInteractions() {
|
|
// 平滑滚动
|
|
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'
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
// 返回顶部按钮
|
|
this.createBackToTopButton();
|
|
|
|
// 页面加载进度条
|
|
this.createLoadingBar();
|
|
|
|
// 全局错误处理
|
|
this.setupGlobalErrorHandling();
|
|
}
|
|
|
|
// 创建返回顶部按钮
|
|
createBackToTopButton() {
|
|
const backToTop = document.createElement('button');
|
|
backToTop.innerHTML = '<i class="fa fa-chevron-up"></i>';
|
|
backToTop.className = 'btn-back-to-top';
|
|
backToTop.setAttribute('aria-label', '返回顶部');
|
|
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
.btn-back-to-top {
|
|
position: fixed;
|
|
bottom: 30px;
|
|
right: 30px;
|
|
width: 50px;
|
|
height: 50px;
|
|
background: #007bff;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
transition: all 0.3s ease;
|
|
z-index: 1000;
|
|
box-shadow: 0 4px 12px rgba(0,123,255,0.3);
|
|
}
|
|
.btn-back-to-top:hover {
|
|
background: #0056b3;
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 16px rgba(0,123,255,0.4);
|
|
}
|
|
.btn-back-to-top.show {
|
|
opacity: 1;
|
|
visibility: visible;
|
|
}
|
|
@media (max-width: 768px) {
|
|
.btn-back-to-top {
|
|
bottom: 20px;
|
|
right: 20px;
|
|
width: 45px;
|
|
height: 45px;
|
|
}
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
document.body.appendChild(backToTop);
|
|
|
|
// 滚动显示/隐藏
|
|
window.addEventListener('scroll', () => {
|
|
if (window.pageYOffset > 300) {
|
|
backToTop.classList.add('show');
|
|
} else {
|
|
backToTop.classList.remove('show');
|
|
}
|
|
});
|
|
|
|
// 点击返回顶部
|
|
backToTop.addEventListener('click', () => {
|
|
window.scrollTo({
|
|
top: 0,
|
|
behavior: 'smooth'
|
|
});
|
|
});
|
|
}
|
|
|
|
// 创建加载进度条
|
|
createLoadingBar() {
|
|
const loadingBar = document.createElement('div');
|
|
loadingBar.className = 'loading-bar';
|
|
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
.loading-bar {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 0%;
|
|
height: 3px;
|
|
background: linear-gradient(90deg, #007bff, #28a745);
|
|
z-index: 9999;
|
|
transition: width 0.3s ease;
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
document.body.appendChild(loadingBar);
|
|
|
|
// 页面加载进度
|
|
let progress = 0;
|
|
const interval = setInterval(() => {
|
|
progress += Math.random() * 30;
|
|
if (progress > 90) progress = 90;
|
|
loadingBar.style.width = progress + '%';
|
|
}, 200);
|
|
|
|
window.addEventListener('load', () => {
|
|
clearInterval(interval);
|
|
loadingBar.style.width = '100%';
|
|
setTimeout(() => {
|
|
loadingBar.style.opacity = '0';
|
|
setTimeout(() => loadingBar.remove(), 300);
|
|
}, 200);
|
|
});
|
|
}
|
|
|
|
// 设置加载状态
|
|
setupLoadingStates() {
|
|
// 为所有按钮添加加载状态
|
|
document.addEventListener('click', (e) => {
|
|
if (e.target.matches('button[type="submit"], .btn-primary, .btn-flower, .btn-animal')) {
|
|
this.showButtonLoading(e.target);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 显示按钮加载状态
|
|
showButtonLoading(button) {
|
|
const originalText = button.innerHTML;
|
|
const originalDisabled = button.disabled;
|
|
|
|
button.disabled = true;
|
|
button.innerHTML = '<i class="fa fa-spinner fa-spin me-2"></i>处理中...';
|
|
|
|
// 模拟加载时间
|
|
setTimeout(() => {
|
|
button.disabled = originalDisabled;
|
|
button.innerHTML = originalText;
|
|
}, 2000);
|
|
}
|
|
|
|
// 表单增强
|
|
setupFormEnhancements() {
|
|
// 实时验证
|
|
document.querySelectorAll('input, textarea, select').forEach(field => {
|
|
field.addEventListener('blur', () => this.validateField(field));
|
|
field.addEventListener('input', () => this.clearFieldError(field));
|
|
});
|
|
|
|
// 表单提交增强
|
|
document.querySelectorAll('form').forEach(form => {
|
|
form.addEventListener('submit', (e) => {
|
|
if (!this.validateForm(form)) {
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// 验证单个字段
|
|
validateField(field) {
|
|
const value = field.value.trim();
|
|
let isValid = true;
|
|
let message = '';
|
|
|
|
// 必填验证
|
|
if (field.hasAttribute('required') && !value) {
|
|
isValid = false;
|
|
message = '此字段为必填项';
|
|
}
|
|
|
|
// 邮箱验证
|
|
if (field.type === 'email' && value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
|
isValid = false;
|
|
message = '请输入有效的邮箱地址';
|
|
}
|
|
|
|
// 手机号验证
|
|
if (field.type === 'tel' && value && !/^1[3-9]\d{9}$/.test(value)) {
|
|
isValid = false;
|
|
message = '请输入有效的手机号码';
|
|
}
|
|
|
|
// 显示验证结果
|
|
if (!isValid) {
|
|
this.showFieldError(field, message);
|
|
} else {
|
|
this.showFieldSuccess(field);
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
// 显示字段错误
|
|
showFieldError(field, message) {
|
|
this.clearFieldFeedback(field);
|
|
|
|
field.classList.add('is-invalid');
|
|
const feedback = document.createElement('div');
|
|
feedback.className = 'invalid-feedback';
|
|
feedback.textContent = message;
|
|
field.parentNode.appendChild(feedback);
|
|
}
|
|
|
|
// 显示字段成功
|
|
showFieldSuccess(field) {
|
|
this.clearFieldFeedback(field);
|
|
field.classList.add('is-valid');
|
|
}
|
|
|
|
// 清除字段错误
|
|
clearFieldError(field) {
|
|
field.classList.remove('is-invalid');
|
|
const feedback = field.parentNode.querySelector('.invalid-feedback');
|
|
if (feedback) feedback.remove();
|
|
}
|
|
|
|
// 清除字段反馈
|
|
clearFieldFeedback(field) {
|
|
field.classList.remove('is-invalid', 'is-valid');
|
|
const feedback = field.parentNode.querySelector('.invalid-feedback, .valid-feedback');
|
|
if (feedback) feedback.remove();
|
|
}
|
|
|
|
// 验证整个表单
|
|
validateForm(form) {
|
|
let isValid = true;
|
|
const fields = form.querySelectorAll('input, textarea, select');
|
|
|
|
fields.forEach(field => {
|
|
if (!this.validateField(field)) {
|
|
isValid = false;
|
|
}
|
|
});
|
|
|
|
return isValid;
|
|
}
|
|
|
|
// 滚动效果
|
|
setupScrollEffects() {
|
|
// 导航栏滚动效果
|
|
const navbar = document.querySelector('.navbar');
|
|
if (navbar) {
|
|
window.addEventListener('scroll', () => {
|
|
if (window.scrollY > 100) {
|
|
navbar.classList.add('navbar-scrolled');
|
|
} else {
|
|
navbar.classList.remove('navbar-scrolled');
|
|
}
|
|
});
|
|
|
|
// 添加滚动样式
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
.navbar-scrolled {
|
|
background-color: rgba(255, 255, 255, 0.95) !important;
|
|
backdrop-filter: blur(10px);
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
transition: all 0.3s ease;
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
// 视差滚动效果
|
|
this.setupParallaxEffect();
|
|
}
|
|
|
|
// 视差滚动效果
|
|
setupParallaxEffect() {
|
|
const parallaxElements = document.querySelectorAll('.hero-section');
|
|
|
|
window.addEventListener('scroll', () => {
|
|
const scrolled = window.pageYOffset;
|
|
parallaxElements.forEach(element => {
|
|
const rate = scrolled * -0.5;
|
|
element.style.transform = `translateY(${rate}px)`;
|
|
});
|
|
});
|
|
}
|
|
|
|
// 工具提示
|
|
setupTooltips() {
|
|
// 为所有带有title属性的元素添加工具提示
|
|
document.querySelectorAll('[title]').forEach(element => {
|
|
element.addEventListener('mouseenter', (e) => {
|
|
this.showTooltip(e.target, e.target.getAttribute('title'));
|
|
});
|
|
|
|
element.addEventListener('mouseleave', () => {
|
|
this.hideTooltip();
|
|
});
|
|
});
|
|
}
|
|
|
|
// 显示工具提示
|
|
showTooltip(element, text) {
|
|
const tooltip = document.createElement('div');
|
|
tooltip.className = 'custom-tooltip';
|
|
tooltip.textContent = text;
|
|
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
.custom-tooltip {
|
|
position: absolute;
|
|
background: #333;
|
|
color: white;
|
|
padding: 8px 12px;
|
|
border-radius: 4px;
|
|
font-size: 14px;
|
|
z-index: 1000;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
.custom-tooltip.show {
|
|
opacity: 1;
|
|
}
|
|
`;
|
|
if (!document.querySelector('style[data-tooltip]')) {
|
|
style.setAttribute('data-tooltip', 'true');
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
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';
|
|
|
|
setTimeout(() => tooltip.classList.add('show'), 10);
|
|
}
|
|
|
|
// 隐藏工具提示
|
|
hideTooltip() {
|
|
const tooltip = document.querySelector('.custom-tooltip');
|
|
if (tooltip) {
|
|
tooltip.classList.remove('show');
|
|
setTimeout(() => tooltip.remove(), 300);
|
|
}
|
|
}
|
|
|
|
// 通知系统
|
|
setupNotifications() {
|
|
this.createNotificationContainer();
|
|
}
|
|
|
|
// 创建通知容器
|
|
createNotificationContainer() {
|
|
const container = document.createElement('div');
|
|
container.id = 'notification-container';
|
|
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
#notification-container {
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
z-index: 9999;
|
|
max-width: 400px;
|
|
}
|
|
.notification {
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 16px;
|
|
margin-bottom: 10px;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
border-left: 4px solid #007bff;
|
|
opacity: 0;
|
|
transform: translateX(100%);
|
|
transition: all 0.3s ease;
|
|
}
|
|
.notification.show {
|
|
opacity: 1;
|
|
transform: translateX(0);
|
|
}
|
|
.notification.success { border-left-color: #28a745; }
|
|
.notification.error { border-left-color: #dc3545; }
|
|
.notification.warning { border-left-color: #ffc107; }
|
|
.notification.info { border-left-color: #17a2b8; }
|
|
`;
|
|
document.head.appendChild(style);
|
|
document.body.appendChild(container);
|
|
}
|
|
|
|
// 显示通知
|
|
showNotification(message, type = 'info', duration = 5000) {
|
|
const notification = document.createElement('div');
|
|
notification.className = `notification ${type}`;
|
|
notification.innerHTML = `
|
|
<div class="d-flex justify-content-between align-items-start">
|
|
<div>
|
|
<strong>${this.getNotificationTitle(type)}</strong>
|
|
<div>${message}</div>
|
|
</div>
|
|
<button type="button" class="btn-close" onclick="this.parentElement.parentElement.remove()"></button>
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('notification-container').appendChild(notification);
|
|
|
|
setTimeout(() => notification.classList.add('show'), 10);
|
|
|
|
if (duration > 0) {
|
|
setTimeout(() => {
|
|
notification.classList.remove('show');
|
|
setTimeout(() => notification.remove(), 300);
|
|
}, duration);
|
|
}
|
|
}
|
|
|
|
// 获取通知标题
|
|
getNotificationTitle(type) {
|
|
const titles = {
|
|
success: '成功',
|
|
error: '错误',
|
|
warning: '警告',
|
|
info: '提示'
|
|
};
|
|
return titles[type] || '通知';
|
|
}
|
|
|
|
// 键盘导航
|
|
setupKeyboardNavigation() {
|
|
document.addEventListener('keydown', (e) => {
|
|
// ESC键关闭模态框
|
|
if (e.key === 'Escape') {
|
|
const modals = document.querySelectorAll('.modal.show');
|
|
modals.forEach(modal => {
|
|
const modalInstance = bootstrap.Modal.getInstance(modal);
|
|
if (modalInstance) modalInstance.hide();
|
|
});
|
|
}
|
|
|
|
// Tab键焦点管理
|
|
if (e.key === 'Tab') {
|
|
this.manageFocus(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 焦点管理
|
|
manageFocus(e) {
|
|
const focusableElements = document.querySelectorAll(
|
|
'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled])'
|
|
);
|
|
|
|
const firstElement = focusableElements[0];
|
|
const lastElement = focusableElements[focusableElements.length - 1];
|
|
|
|
if (e.shiftKey && document.activeElement === firstElement) {
|
|
e.preventDefault();
|
|
lastElement.focus();
|
|
} else if (!e.shiftKey && document.activeElement === lastElement) {
|
|
e.preventDefault();
|
|
firstElement.focus();
|
|
}
|
|
}
|
|
|
|
// 移动端优化
|
|
setupMobileOptimizations() {
|
|
// 触摸反馈
|
|
document.addEventListener('touchstart', (e) => {
|
|
if (e.target.matches('button, .btn, .card')) {
|
|
e.target.style.transform = 'scale(0.98)';
|
|
}
|
|
});
|
|
|
|
document.addEventListener('touchend', (e) => {
|
|
if (e.target.matches('button, .btn, .card')) {
|
|
setTimeout(() => {
|
|
e.target.style.transform = '';
|
|
}, 150);
|
|
}
|
|
});
|
|
|
|
// 防止双击缩放
|
|
let lastTouchEnd = 0;
|
|
document.addEventListener('touchend', (e) => {
|
|
const now = (new Date()).getTime();
|
|
if (now - lastTouchEnd <= 300) {
|
|
e.preventDefault();
|
|
}
|
|
lastTouchEnd = now;
|
|
}, false);
|
|
|
|
// 移动端导航优化
|
|
this.setupMobileNavigation();
|
|
}
|
|
|
|
// 移动端导航优化
|
|
setupMobileNavigation() {
|
|
const navbarToggler = document.querySelector('.navbar-toggler');
|
|
const navbarCollapse = document.querySelector('.navbar-collapse');
|
|
|
|
if (navbarToggler && navbarCollapse) {
|
|
// 点击导航链接后自动关闭菜单
|
|
navbarCollapse.querySelectorAll('a').forEach(link => {
|
|
link.addEventListener('click', () => {
|
|
if (window.innerWidth < 992) {
|
|
const collapse = new bootstrap.Collapse(navbarCollapse, {
|
|
hide: true
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
// 全局错误处理
|
|
setupGlobalErrorHandling() {
|
|
window.addEventListener('error', (e) => {
|
|
console.error('全局错误:', e.error);
|
|
this.showNotification('页面出现错误,请刷新重试', 'error');
|
|
});
|
|
|
|
window.addEventListener('unhandledrejection', (e) => {
|
|
console.error('未处理的Promise错误:', e.reason);
|
|
this.showNotification('网络请求失败,请检查网络连接', 'error');
|
|
});
|
|
}
|
|
|
|
// 性能监控
|
|
setupPerformanceMonitoring() {
|
|
// 页面加载性能
|
|
window.addEventListener('load', () => {
|
|
const perfData = performance.getEntriesByType('navigation')[0];
|
|
const loadTime = perfData.loadEventEnd - perfData.loadEventStart;
|
|
|
|
if (loadTime > 3000) {
|
|
console.warn('页面加载时间过长:', loadTime + 'ms');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// 全局实例
|
|
let enhancedInteractionManager;
|
|
|
|
// 页面加载完成后初始化
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
enhancedInteractionManager = new EnhancedInteractionManager();
|
|
});
|
|
|
|
// 导出全局方法供其他脚本使用
|
|
window.showNotification = function(message, type, duration) {
|
|
if (enhancedInteractionManager) {
|
|
enhancedInteractionManager.showNotification(message, type, duration);
|
|
}
|
|
};
|
|
|
|
window.showButtonLoading = function(button) {
|
|
if (enhancedInteractionManager) {
|
|
enhancedInteractionManager.showButtonLoading(button);
|
|
}
|
|
}; |