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图标搜索器 - 增强版
+
+
+
+
+
+
+
+
+
+
+
+ '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']) . '](' . htmlspecialchars($cachedImageUrl) . ')
';
+ echo '
';
+ echo '
' . htmlspecialchars($app['trackName']) . '
';
+ 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