功能特性: - 3D地球动画与中国地图可视化 - 省份/城市/企业搜索功能 - 308家企业数据展示 - 响应式设计(PC端和移动端) - 企业详情页面与业务板块展示 - 官网新闻轮播图 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
202 lines
6.0 KiB
JavaScript
202 lines
6.0 KiB
JavaScript
/* ===================================
|
||
UI工具函数
|
||
=================================== */
|
||
|
||
import { CONFIG } from '../config.js';
|
||
|
||
export class UIUtils {
|
||
/**
|
||
* 切换界面显示/隐藏
|
||
* @param {HTMLElement} hideElement - 要隐藏的元素
|
||
* @param {HTMLElement} showElement - 要显示的元素
|
||
* @param {Function} onComplete - 完成回调
|
||
*/
|
||
static switchInterface(hideElement, showElement, onComplete) {
|
||
const duration = CONFIG.animation.ui.fadeDuration;
|
||
|
||
gsap.to(hideElement, {
|
||
opacity: 0,
|
||
duration: duration,
|
||
onComplete: () => {
|
||
hideElement.style.display = 'none';
|
||
showElement.style.display = 'block';
|
||
gsap.to(showElement, {
|
||
opacity: 1,
|
||
duration: duration,
|
||
onComplete: onComplete
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 批量显示元素(带渐入动画)
|
||
* @param {string} selector - CSS选择器
|
||
* @param {object} options - 动画选项
|
||
*/
|
||
static staggerShow(selector, options = {}) {
|
||
const defaults = {
|
||
opacity: 1,
|
||
y: 0,
|
||
duration: 0.6,
|
||
stagger: CONFIG.animation.ui.cardStagger,
|
||
delay: CONFIG.animation.ui.cardDelay,
|
||
ease: "power2.out"
|
||
};
|
||
|
||
gsap.to(selector, { ...defaults, ...options });
|
||
}
|
||
|
||
/**
|
||
* 移动端触摸事件适配
|
||
* @param {HTMLElement} element - 目标元素
|
||
* @param {object} handlers - 事件处理器 { onTap, onSwipe }
|
||
*/
|
||
static bindTouchEvents(element, handlers) {
|
||
let startX = 0;
|
||
let startY = 0;
|
||
let startTime = 0;
|
||
|
||
element.addEventListener('touchstart', (e) => {
|
||
startX = e.touches[0].clientX;
|
||
startY = e.touches[0].clientY;
|
||
startTime = Date.now();
|
||
});
|
||
|
||
element.addEventListener('touchend', (e) => {
|
||
const endX = e.changedTouches[0].clientX;
|
||
const endY = e.changedTouches[0].clientY;
|
||
const endTime = Date.now();
|
||
|
||
const deltaX = endX - startX;
|
||
const deltaY = endY - startY;
|
||
const deltaTime = endTime - startTime;
|
||
|
||
// 判断是点击还是滑动
|
||
if (Math.abs(deltaX) < 10 && Math.abs(deltaY) < 10 && deltaTime < 300) {
|
||
// 点击
|
||
if (handlers.onTap) handlers.onTap(e);
|
||
} else {
|
||
// 滑动
|
||
if (handlers.onSwipe) {
|
||
const direction = Math.abs(deltaX) > Math.abs(deltaY)
|
||
? (deltaX > 0 ? 'right' : 'left')
|
||
: (deltaY > 0 ? 'down' : 'up');
|
||
handlers.onSwipe(direction, e);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 防抖函数
|
||
* @param {Function} func - 要防抖的函数
|
||
* @param {number} wait - 等待时间(毫秒)
|
||
* @returns {Function}
|
||
*/
|
||
static debounce(func, wait = 300) {
|
||
let timeout;
|
||
return function executedFunction(...args) {
|
||
const later = () => {
|
||
clearTimeout(timeout);
|
||
func(...args);
|
||
};
|
||
clearTimeout(timeout);
|
||
timeout = setTimeout(later, wait);
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 节流函数
|
||
* @param {Function} func - 要节流的函数
|
||
* @param {number} limit - 限制间隔(毫秒)
|
||
* @returns {Function}
|
||
*/
|
||
static throttle(func, limit = 100) {
|
||
let inThrottle;
|
||
return function executedFunction(...args) {
|
||
if (!inThrottle) {
|
||
func(...args);
|
||
inThrottle = true;
|
||
setTimeout(() => inThrottle = false, limit);
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 检测是否为移动设备
|
||
* @returns {boolean}
|
||
*/
|
||
static isMobile() {
|
||
return window.innerWidth <= CONFIG.mobile.breakpoint;
|
||
}
|
||
|
||
/**
|
||
* 获取安全区域内边距
|
||
* @returns {object} { top, bottom, left, right }
|
||
*/
|
||
static getSafeAreaInsets() {
|
||
const computed = getComputedStyle(document.documentElement);
|
||
return {
|
||
top: parseInt(computed.getPropertyValue('env(safe-area-inset-top)')) || 0,
|
||
bottom: parseInt(computed.getPropertyValue('env(safe-area-inset-bottom)')) || 0,
|
||
left: parseInt(computed.getPropertyValue('env(safe-area-inset-left)')) || 0,
|
||
right: parseInt(computed.getPropertyValue('env(safe-area-inset-right)')) || 0
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 初始化陀螺仪控制(移动端视差)
|
||
* @param {Function} callback - 回调函数,接收 (beta, gamma) 参数
|
||
*/
|
||
static initGyroscope(callback) {
|
||
if (!this.isMobile()) return;
|
||
|
||
if (window.DeviceOrientationEvent) {
|
||
window.addEventListener('deviceorientation', (e) => {
|
||
const beta = e.beta || 0; // 前后倾斜(-180 ~ 180)
|
||
const gamma = e.gamma || 0; // 左右倾斜(-90 ~ 90)
|
||
callback(beta, gamma);
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 显示加载提示
|
||
* @param {string} text - 提示文本
|
||
*/
|
||
static showLoading(text = '加载中...') {
|
||
const existing = document.getElementById('loading-overlay');
|
||
if (existing) return;
|
||
|
||
const overlay = document.createElement('div');
|
||
overlay.id = 'loading-overlay';
|
||
overlay.style.cssText = `
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 9999;
|
||
color: #38bdf8;
|
||
font-size: 1rem;
|
||
`;
|
||
overlay.innerText = text;
|
||
document.body.appendChild(overlay);
|
||
}
|
||
|
||
/**
|
||
* 隐藏加载提示
|
||
*/
|
||
static hideLoading() {
|
||
const overlay = document.getElementById('loading-overlay');
|
||
if (overlay) {
|
||
overlay.remove();
|
||
}
|
||
}
|
||
}
|