重构动物认领页面和导航菜单,统一使用SVG图标并优化交互体验
This commit is contained in:
590
website/js/enhanced-interactions.js
Normal file
590
website/js/enhanced-interactions.js
Normal file
@@ -0,0 +1,590 @@
|
||||
/**
|
||||
* 增强交互体验管理器
|
||||
* 统一管理整个网站的交互优化
|
||||
*/
|
||||
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);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user