commit 79b13fb280f59cae52cad5d22fa261c51f48c37e Author: Snowz <372492339@qq.com> Date: Sat Apr 12 15:38:09 2025 +0800 feat: 新增App图标搜索器增强版功能 引入完整的App图标搜索器增强版,包括前端页面、后端缓存系统、样式和脚本。主要功能包括实时搜索、多尺寸图标下载、图片预览、响应式设计和智能缓存机制。后端通过ImageCache类实现图片缓存,前端通过JavaScript优化搜索交互和图片加载体验。新增README.md提供详细的部署和开发指南。 diff --git a/README.md b/README.md new file mode 100644 index 0000000..4e925de --- /dev/null +++ b/README.md @@ -0,0 +1,164 @@ +# App图标搜索器增强版 + +一个高效、现代的 App 图标搜索和下载工具,基于 iTunes Search API 开发。支持多种尺寸图标下载,具有响应式设计和用户友好的界面。 + +## 功能特点 + +- 实时搜索:支持即时搜索结果展示 +- 多尺寸下载:支持 75px、100px、256px、512px 尺寸 +- 图片预览:支持图标大图预览 +- 响应式设计:完美适配移动端和桌面端 +- 智能缓存:支持服务端和客户端双重缓存机制 +- 用户友好:简洁的界面设计和流畅的交互体验 + +## 技术栈 + +### 前端 +- HTML5 + CSS3 +- 原生 JavaScript (ES6+) +- CSS Grid 和 Flexbox 布局 +- Service Worker 离线缓存 + +### 后端 +- PHP 7.4+ +- iTunes Search API +- 文件系统缓存 + +### 服务器 +- Apache/Nginx +- PHP-FPM +- 图片缓存系统 + +## 部署指南 + +### 环境要求 +- PHP >= 7.4 +- Apache/Nginx Web服务器 +- 允许外部API访问 +- PHP GD库(用于图片处理) +- 足够的磁盘空间用于缓存 + +### Apache 部署步骤 + +``` + + ServerName your-domain.com + DocumentRoot /path/to/icons-enhanced + + + Options -Indexes +FollowSymLinks + AllowOverride All + Require all granted + + + + Header set Cache-Control "max-age=31536000, public" + + +``` + +### Nginx 部署步骤 + +``` +server { + listen 80; + server_name your-domain.com; + root /path/to/icons-enhanced; + index index.php; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \.php$ { + fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + } + + location ~* \.(jpg|jpeg|png|gif)$ { + expires 1y; + add_header Cache-Control "public, no-transform"; + access_log off; + } +} +``` +### 配置权限 + +``` +chmod -R 755 . +chmod -R 777 cache/images +``` + +## 自定义开发指南 +### 前端定制 +1. 样式修改 +- 主题颜色:修改 css/styles.css 中的 CSS 变量 +- 布局调整:修改 Grid 和 Flexbox 相关样式 +- 响应式断点:调整 media queries 中的尺寸 +2. 交互优化 +- 搜索逻辑:修改 js/scripts.js 中的防抖函数 +- 动画效果:调整 transition 和 transform 相关属性 +- 预览功能:自定义模态框行为 +### 后端定制 +1. 缓存策略 +- 修改缓存时间:调整 ImageCache.php 中的 cacheDuration +- 自定义缓存目录:更改 cacheDir 配置 +- 添加缓存清理机制 +2. API 调整 +- 修改搜索参数:调整 iTunes API 请求参数 +- 添加错误处理:完善异常处理机制 +- 扩展搜索源:添加其他图标源支持 +### 性能优化 +1. 图片优化 +- 实现图片懒加载 +- 添加图片压缩功能 +- 使用 WebP 格式支持 +2. 缓存优化 +- 实现浏览器缓存 +- 添加 CDN 支持 +- 优化缓存策略 +## 常见问题 +1. 图片加载失败 +- 检查服务器权限设置 +- 验证缓存目录权限 +- 确认外部API访问是否正常 +2. 搜索响应慢 +- 调整防抖时间 +- 优化数据请求策略 +- 检查服务器性能 +3. 移动端适配问题 +- 检查视口设置 +- 调整响应式断点 +- 优化触摸交互 +## 维护建议 +1. 定期维护 +- 清理过期缓存 +- 更新依赖版本 +- 检查API可用性 +2. 性能监控 +- 监控服务器负载 +- 跟踪API响应时间 +- 分析用户使用数据 +## 贡献指南 +欢迎提交 Pull Request 或 Issue。在提交之前,请确保: + +1. 代码符合现有风格 +2. 添加必要的注释和文档 +3. 测试所有功能正常 +## 许可证 +MIT License - 详见根目录 LICENSE 文件 + +## 作者 +[Snowz] + +## 更新日志 +### v1.0.0 (2025-03-31) +- 初始版本发布 +- 基础搜索功能 +- 多尺寸下载支持 +### v1.1.0 (计划中) +- 添加图片懒加载 +- 优化搜索体验 +- 改进缓存机制 \ No newline at end of file diff --git a/cache/ImageCache.php b/cache/ImageCache.php new file mode 100644 index 0000000..b8312a2 --- /dev/null +++ b/cache/ImageCache.php @@ -0,0 +1,32 @@ +cacheDir = __DIR__ . '/images/'; + if (!file_exists($this->cacheDir)) { + mkdir($this->cacheDir, 0777, true); + } + } + + public function getCachedImage($url) { + $cacheFile = $this->cacheDir . md5($url); + + if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $this->cacheDuration)) { + return file_get_contents($cacheFile); + } + + $imageContent = file_get_contents($url); + if ($imageContent !== false) { + file_put_contents($cacheFile, $imageContent); + return $imageContent; + } + + return false; + } + + public function getCacheUrl($url) { + return 'cache/image.php?url=' . urlencode($url); + } +} \ No newline at end of file diff --git a/cache/image.php b/cache/image.php new file mode 100644 index 0000000..17c94d5 --- /dev/null +++ b/cache/image.php @@ -0,0 +1,37 @@ +getCachedImage($url); + +if ($image === false) { + header("HTTP/1.0 404 Not Found"); + exit; +} + +$extension = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION); +switch(strtolower($extension)) { + case 'jpg': + case 'jpeg': + header('Content-Type: image/jpeg'); + break; + case 'png': + header('Content-Type: image/png'); + break; + case 'gif': + header('Content-Type: image/gif'); + break; + default: + header('Content-Type: image/jpeg'); +} + +header('Cache-Control: public, max-age=31536000'); +header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', time() + 31536000)); + +echo $image; \ No newline at end of file diff --git a/css/styles.css b/css/styles.css new file mode 100644 index 0000000..4729538 --- /dev/null +++ b/css/styles.css @@ -0,0 +1,317 @@ +:root { + --primary-color: #4a90e2; + --secondary-color: #2c3e50; + --background-color: #f5f6fa; + --card-background: #ffffff; + --text-color: #2c3e50; + --border-radius: 12px; + --transition: all 0.3s ease; + --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 8px 15px rgba(0, 0, 0, 0.1); +} + +/* 基础样式 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + background-color: var(--background-color); + color: var(--text-color); + line-height: 1.6; +} + +/* 布局容器 */ +.container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; +} + +/* 搜索区域 */ +.search-container { + text-align: center; + margin-bottom: 3rem; + width: 100%; +} + +h1 { + font-size: 2.5rem; + color: var(--primary-color); + margin-bottom: 1.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} + +/* 搜索区域 */ +.search-box { + display: flex; + max-width: 600px; + margin: 0 auto 1rem; + position: relative; +} + +input[type="text"] { + flex: 1; + padding: 0.8rem 1rem; + font-size: 1.1rem; + border: 2px solid #e1e1e1; + border-radius: var(--border-radius) 0 0 var(--border-radius); + transition: var(--transition); + outline: none; +} + +input[type="text"]:focus { + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.1); +} + +button[type="submit"] { + padding: 0.8rem 1.5rem; + background-color: var(--primary-color); + color: white; + border: none; + border-radius: 0 var(--border-radius) var(--border-radius) 0; + cursor: pointer; + transition: var(--transition); + min-width: 60px; +} + +button[type="submit"]:hover { + background-color: #357abd; +} + +button[type="submit"]:active { + transform: translateY(1px); +} + +.search-options { + margin-top: 1rem; + display: flex; + justify-content: center; +} + +select { + padding: 0.5rem; + border-radius: var(--border-radius); + border: 2px solid #e1e1e1; + background-color: white; + cursor: pointer; +} + +/* 图标卡片 */ +.icon-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 2rem; + margin-top: 2rem; +} + +.icon-card { + background: var(--card-background); + border-radius: 16px; + padding: 1.5rem; + box-shadow: var(--shadow-md); + transition: var(--transition); + text-align: center; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.icon-card:hover { + transform: translateY(-5px); + box-shadow: var(--shadow-lg); +} + +/* 图标图片 */ +.icon-image { + text-align: center; + margin-bottom: 1rem; +} + +.icon-image img { + width: 100px; + height: 100px; + border-radius: 22px; + cursor: pointer; + transition: transform 0.3s ease; +} + +.icon-image img:hover { + transform: scale(1.05); +} + +/* 应用名称 */ +.app-name { + font-size: 1.1rem; + margin: 0.5rem 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: var(--text-color); +} + +/* 下载按钮 */ +.download-options { + display: flex; + flex-direction: column; + gap: 6px; + margin-top: 12px; + width: 100%; + padding: 0 2px; +} + +.download-options-row { + display: flex; + gap: 6px; + width: 100%; +} + +.size-btn { + background-color: var(--primary-color); + color: white; + text-decoration: none; + border-radius: 10px; + font-size: 0.85rem; + text-align: center; + transition: var(--transition); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + flex: 1; + border: none; + box-shadow: var(--shadow-sm); +} + +.size-btn:hover { + background-color: #357abd; + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} + +.size-btn:active { + transform: translateY(0); +} + +.size-btn-small { + padding: 8px 0; + min-height: 65px; + max-width: calc(33.33% - 4px); +} + +.size-btn-large { + padding: 8px 0; + min-height: 45px; +} + +/* 模态框 */ +.modal { + display: none; + position: fixed; + z-index: 1000; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.9); +} + +/* 返回顶部按钮 */ +#back-to-top { + position: fixed; + bottom: 20px; + right: 20px; + display: none; + padding: 1rem; + border-radius: 50%; + background-color: var(--primary-color); + color: white; + border: none; + cursor: pointer; + transition: var(--transition); +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .container { + padding: 1rem; + } + + h1 { + font-size: 2rem; + } + + .icon-grid { + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 1rem; + } + + .icon-card { + padding: 1.2rem; + gap: 0.8rem; + } + + .size-btn-small { + min-height: 50px; + padding: 6px 0; + } + + .size-btn-large { + min-height: 38px; + padding: 6px 0; + } + + .size-label { + font-size: 0.85rem; + } + + .size-desc { + font-size: 0.65rem; + } +} + +/* 页脚样式 */ +footer { + background-color: var(--secondary-color); + color: white; + padding: 2rem 0; + margin-top: 3rem; + width: 100%; +} + +.footer-content { + max-width: 1200px; + margin: 0 auto; + text-align: center; + padding: 0 1rem; + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; +} + +.social-links { + display: flex; + justify-content: center; + gap: 1rem; +} + +.social-links a { + color: white; + font-size: 1.5rem; + text-decoration: none; + transition: var(--transition); + padding: 0.5rem; +} + +.social-links a:hover { + color: var(--primary-color); +} \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..7fd675a --- /dev/null +++ b/index.php @@ -0,0 +1,119 @@ + + + + + + App图标搜索器 - 增强版 + + + + + + + +
+
+

App图标搜索器

+
+ +
+ +
+
+
+ +
+ '75x75bb', 'label' => '75px', 'desc' => '小图'], + ['size' => '100x100bb', 'label' => '100px', 'desc' => '标准'], + ['size' => '256x256bb', 'label' => '256px', 'desc' => '高清'], + ['size' => '512x512bb', 'label' => '512px', 'desc' => '超清'] + ]; + + echo '
'; + require_once 'cache/ImageCache.php'; + $imageCache = new ImageCache(); + + // 启用图片缓存 + echo '
'; + $cachedImageUrl = $imageCache->getCacheUrl($app['artworkUrl100']); + echo '' . htmlspecialchars($app['trackName']) . ''; + echo '
'; + echo '

' . htmlspecialchars($app['trackName']) . '

'; + echo '
'; + echo '
'; + // 前三个按钮 (75px, 100px, 256px) + foreach (array_slice($iconSizes, 0, 3) as $size) { + $sizeUrl = str_replace('100x100bb', $size['size'], $app['artworkUrl100']); + echo ''; + echo '' . $size['label'] . ''; + echo '' . $size['desc'] . ''; + echo ''; + } + echo '
'; + // 最后一个按钮 (512px) + $lastSize = end($iconSizes); + $lastSizeUrl = str_replace('100x100bb', $lastSize['size'], $app['artworkUrl100']); + echo ''; + echo '' . $lastSize['label'] . ''; + echo '' . $lastSize['desc'] . ''; + echo ''; + echo '
'; + echo '
'; + } + } else { + echo '

没有找到相关应用

'; + } + } + ?> +
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/js/scripts.js b/js/scripts.js new file mode 100644 index 0000000..294ca69 --- /dev/null +++ b/js/scripts.js @@ -0,0 +1,105 @@ +document.addEventListener('DOMContentLoaded', function() { + const backToTop = document.getElementById('back-to-top'); + const modal = document.getElementById('imagePreview'); + const modalImg = document.getElementById('previewImage'); + const closeBtn = document.getElementsByClassName('close')[0]; + const searchInput = document.getElementById('searchInput'); + const iconGrid = document.getElementById('iconGrid'); + + // 返回顶部按钮 + window.onscroll = function() { + if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) { + backToTop.style.display = 'block'; + } else { + backToTop.style.display = 'none'; + } + }; + + backToTop.onclick = function() { + window.scrollTo({ + top: 0, + behavior: 'smooth' + }); + }; + + // 图标预览模态框 + document.querySelectorAll('.icon-image img').forEach(img => { + img.onclick = function() { + modal.style.display = 'block'; + modalImg.src = this.getAttribute('data-high-res'); + modalImg.alt = this.alt; + }; + }); + + closeBtn.onclick = function() { + modal.style.display = 'none'; + }; + + window.onclick = function(event) { + if (event.target == modal) { + modal.style.display = 'none'; + } + }; + + // 搜索输入优化 + // 搜索防抖功能 + function debounce(func, wait) { + let timeout; + return function (...args) { + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(this, args), wait); + }; + } + + document.addEventListener('DOMContentLoaded', function() { + const searchForm = document.getElementById('searchForm'); + const searchInput = document.getElementById('searchInput'); + + // 防止表单自动提交 + searchForm.addEventListener('submit', function(e) { + e.preventDefault(); + if (searchInput.value.trim()) { + this.submit(); + } + }); + + // 输入防抖 + const debouncedSearch = debounce(function(value) { + if (value.trim().length >= 2) { + searchForm.submit(); + } + }, 800); + + // 监听输入事件 + searchInput.addEventListener('input', function(e) { + const value = e.target.value; + debouncedSearch(value); + }); + + // 添加加载状态 + searchForm.addEventListener('submit', function() { + const button = this.querySelector('button[type="submit"]'); + button.innerHTML = ''; + button.disabled = true; + + // 2秒后恢复按钮状态(如果请求完成会被新页面覆盖) + setTimeout(() => { + button.innerHTML = ''; + button.disabled = false; + }, 2000); + }); + }); + + // 图标加载动画 + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('fade-in'); + } + }); + }); + + document.querySelectorAll('.icon-card').forEach(card => { + observer.observe(card); + }); +}); \ No newline at end of file