feat: 新增App图标搜索器增强版功能
引入完整的App图标搜索器增强版,包括前端页面、后端缓存系统、样式和脚本。主要功能包括实时搜索、多尺寸图标下载、图片预览、响应式设计和智能缓存机制。后端通过ImageCache类实现图片缓存,前端通过JavaScript优化搜索交互和图片加载体验。新增README.md提供详细的部署和开发指南。
This commit is contained in:
commit
79b13fb280
164
README.md
Normal file
164
README.md
Normal file
@ -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 部署步骤
|
||||
|
||||
```
|
||||
<VirtualHost *:80>
|
||||
ServerName your-domain.com
|
||||
DocumentRoot /path/to/icons-enhanced
|
||||
|
||||
<Directory /path/to/icons-enhanced>
|
||||
Options -Indexes +FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
<FilesMatch "\.(jpg|jpeg|png|gif)$">
|
||||
Header set Cache-Control "max-age=31536000, public"
|
||||
</FilesMatch>
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
### 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 (计划中)
|
||||
- 添加图片懒加载
|
||||
- 优化搜索体验
|
||||
- 改进缓存机制
|
32
cache/ImageCache.php
vendored
Normal file
32
cache/ImageCache.php
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
class ImageCache {
|
||||
private $cacheDir;
|
||||
private $cacheDuration = 604800; // 7天缓存
|
||||
|
||||
public function __construct() {
|
||||
$this->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);
|
||||
}
|
||||
}
|
37
cache/image.php
vendored
Normal file
37
cache/image.php
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
require_once 'ImageCache.php';
|
||||
|
||||
$url = isset($_GET['url']) ? $_GET['url'] : '';
|
||||
if (empty($url)) {
|
||||
header("HTTP/1.0 404 Not Found");
|
||||
exit;
|
||||
}
|
||||
|
||||
$imageCache = new ImageCache();
|
||||
$image = $imageCache->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;
|
317
css/styles.css
Normal file
317
css/styles.css
Normal file
@ -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);
|
||||
}
|
119
index.php
Normal file
119
index.php
Normal file
@ -0,0 +1,119 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>App图标搜索器 - 增强版</title>
|
||||
<meta name="description" content="一款可以在线搜索APP图标并下载小工具">
|
||||
<meta name="keywords" content="photo8,App图标搜索器">
|
||||
<link rel="shortcut icon" href="https://api.photo8.site/path/img/icon-64@3x.png" type="image/png">
|
||||
<link rel="stylesheet" href="css/styles.css">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="search-container">
|
||||
<h1><i class="fas fa-search"></i> App图标搜索器</h1>
|
||||
<form method="GET" action="" id="searchForm">
|
||||
<div class="search-box">
|
||||
<input type="text" name="term" id="searchInput" placeholder="输入APP名字..." value="<?php echo htmlspecialchars($searchTerm ?? ''); ?>">
|
||||
<button type="submit"><i class="fas fa-search"></i></button>
|
||||
</div>
|
||||
<div class="search-options">
|
||||
<select name="limit" id="limitSelect">
|
||||
<option value="20">20个结果</option>
|
||||
<option value="50">50个结果</option>
|
||||
<option value="100">100个结果</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="icon-grid" id="iconGrid">
|
||||
<?php
|
||||
function fetchAppData($term, $limit, $offset) {
|
||||
$url = "https://itunes.apple.com/search?term=" . urlencode($term) . "&country=CN&entity=software&limit=" . $limit . "&offset=" . $offset;
|
||||
$json = @file_get_contents($url);
|
||||
if ($json === false) {
|
||||
return [];
|
||||
}
|
||||
$data = json_decode($json, true);
|
||||
return $data['results'] ?? [];
|
||||
}
|
||||
|
||||
$searchTerm = isset($_GET['term']) ? trim($_GET['term']) : '';
|
||||
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 20;
|
||||
|
||||
if ($searchTerm) {
|
||||
$apps = fetchAppData($searchTerm, $limit, 0);
|
||||
if (!empty($apps)) {
|
||||
foreach ($apps as $app) {
|
||||
$iconSizes = [
|
||||
['size' => '75x75bb', 'label' => '75px', 'desc' => '小图'],
|
||||
['size' => '100x100bb', 'label' => '100px', 'desc' => '标准'],
|
||||
['size' => '256x256bb', 'label' => '256px', 'desc' => '高清'],
|
||||
['size' => '512x512bb', 'label' => '512px', 'desc' => '超清']
|
||||
];
|
||||
|
||||
echo '<div class="icon-card">';
|
||||
require_once 'cache/ImageCache.php';
|
||||
$imageCache = new ImageCache();
|
||||
|
||||
// 启用图片缓存
|
||||
echo '<div class="icon-image">';
|
||||
$cachedImageUrl = $imageCache->getCacheUrl($app['artworkUrl100']);
|
||||
echo '<img src="' . htmlspecialchars($cachedImageUrl) . '" alt="' . htmlspecialchars($app['trackName']) . '"
|
||||
data-high-res="' . str_replace('100x100bb', '512x512bb', $cachedImageUrl) . '">';
|
||||
echo '</div>';
|
||||
echo '<h3 class="app-name">' . htmlspecialchars($app['trackName']) . '</h3>';
|
||||
echo '<div class="download-options">';
|
||||
echo '<div class="download-options-row">';
|
||||
// 前三个按钮 (75px, 100px, 256px)
|
||||
foreach (array_slice($iconSizes, 0, 3) as $size) {
|
||||
$sizeUrl = str_replace('100x100bb', $size['size'], $app['artworkUrl100']);
|
||||
echo '<a href="' . htmlspecialchars($sizeUrl) . '" class="size-btn size-btn-small" target="_blank">';
|
||||
echo '<span class="size-label">' . $size['label'] . '</span>';
|
||||
echo '<span class="size-desc">' . $size['desc'] . '</span>';
|
||||
echo '</a>';
|
||||
}
|
||||
echo '</div>';
|
||||
// 最后一个按钮 (512px)
|
||||
$lastSize = end($iconSizes);
|
||||
$lastSizeUrl = str_replace('100x100bb', $lastSize['size'], $app['artworkUrl100']);
|
||||
echo '<a href="' . htmlspecialchars($lastSizeUrl) . '" class="size-btn size-btn-large" target="_blank">';
|
||||
echo '<span class="size-label">' . $lastSize['label'] . '</span>';
|
||||
echo '<span class="size-desc">' . $lastSize['desc'] . '</span>';
|
||||
echo '</a>';
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
} else {
|
||||
echo '<div class="no-results"><i class="fas fa-exclamation-circle"></i><p>没有找到相关应用</p></div>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="imagePreview" class="modal">
|
||||
<span class="close">×</span>
|
||||
<img class="modal-content" id="previewImage">
|
||||
<div id="imageCaption"></div>
|
||||
</div>
|
||||
|
||||
<?php if ($searchTerm): ?>
|
||||
<footer>
|
||||
<div class="footer-content">
|
||||
<p>© 2024 PHOTO8 - App图标搜索器</p>
|
||||
<div class="social-links">
|
||||
<a href="#" title="微博"><i class="fab fa-weibo"></i></a>
|
||||
<a href="#" title="微信"><i class="fab fa-weixin"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<?php endif; ?>
|
||||
|
||||
<button id="back-to-top" title="返回顶部"><i class="fas fa-arrow-up"></i></button>
|
||||
<script src="js/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
105
js/scripts.js
Normal file
105
js/scripts.js
Normal file
@ -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 = '<i class="fas fa-spinner fa-spin"></i>';
|
||||
button.disabled = true;
|
||||
|
||||
// 2秒后恢复按钮状态(如果请求完成会被新页面覆盖)
|
||||
setTimeout(() => {
|
||||
button.innerHTML = '<i class="fas fa-search"></i>';
|
||||
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);
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user