<?php
/**
* 现代网址导航
*
* @package custom
* @description 现代化响应式网址导航页,支持分类筛选与搜索
*/
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
// --- 1. 处理内容解析 ---
$content = $this->content;
// 将 <br> <p> 等标签转换为换行符
$content = preg_replace('/<(br|p|div)[^>]*>/', "\n", $content);
// 去除 HTML 标签
$content = strip_tags($content);
// 按行分割
$lines = explode("\n", $content);
// --- 2. 解析数据 ---
$navItems = [];
foreach ($lines as $line) {
$line = trim($line);
// 检查是否以 --- 开头
if (strpos($line, '---') === 0) {
$line = substr($line, 3); // 去掉 ---
$parts = explode('|', $line);
if (count($parts) >= 3) {
$title = trim($parts[0]);
$desc = trim($parts[1]);
$url = trim($parts[2]);
// 第4个参数是手动指定的图标URL(可选),第5个是分类
$customIcon = isset($parts[3]) ? trim($parts[3]) : '';
$category = isset($parts[4]) ? trim($parts[4]) : '默认';
if (strpos($url, 'http://') === 0 || strpos($url, 'https://') === 0) {
$navItems[] = [
'title' => $title,
'desc' => $desc,
'url' => $url,
'icon' => $customIcon, // 存入自定义图标
'category' => $category
];
}
}
}
}
// --- 3. 随机渐变函数 ---
function getRandomGradient() {
$h1 = mt_rand(0, 360);
$h2 = ($h1 + mt_rand(30, 180)) % 360;
$s = mt_rand(60, 85);
$l = mt_rand(88, 96); // 提高亮度,保证文字清晰
return "linear-gradient(135deg, hsl($h1, {$s}%, {$l}%) 0%, hsl($h2, {$s}%, {$l}%) 100%)";
}
// --- 新增:统计分类数量 ---
$categories = ['全部' => count($navItems)]; // 默认“全部”,数量为总数量
foreach ($navItems as $item) {
$catName = $item['category'];
if (isset($categories[$catName])) {
$categories[$catName]++;
} else {
$categories[$catName] = 1;
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<?php $this->need('module/head.php'); ?>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-color: #4f46e5;
--bg-color: #f3f4f6;
--text-main: #1f2937;
--text-sub: #6b7280;
--card-border: #e5e7eb;
--card-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
}
/* 夜间模式适配 */
body[data-theme="dark"], body.dark {
--bg-color: #121212;
--text-main: #e5e5e5;
--text-sub: #a3a3a3;
--card-border: #333333;
--card-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
body { background-color: var(--bg-color); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; color: var(--text-main); margin: 0; transition: background 0.3s; }
.nav-container { max-width: 1200px; margin: 0 auto; padding: 40px 20px; }
.header-section { text-align: center; margin-bottom: 40px; }
.site-title { font-size: 2.5rem; font-weight: 800; background: linear-gradient(135deg, var(--primary-color), #818cf8); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 10px; }
.search-box { position: relative; max-width: 600px; margin: 0 auto 30px; }
.search-input { width: 100%; padding: 16px 20px 16px 50px; border-radius: 50px; border: 2px solid var(--card-border); background: var(--bg-color); color: var(--text-main); font-size: 1rem; outline: none; box-sizing: border-box; }
.search-icon { position: absolute; left: 20px; top: 50%; transform: translateY(-50%); color: var(--text-sub); }
.grid-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; }
.nav-card {
/* 背景由内联样式动态控制 */
border-radius: 12px;
padding: 20px;
text-decoration: none;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid rgba(0,0,0,0.05);
display: flex;
align-items: center;
box-shadow: var(--card-shadow);
}
/* 夜间模式下的边框微调 */
body.dark .nav-card { border: 1px solid rgba(255,255,255,0.1); }
.nav-card:hover { transform: translateY(-3px); box-shadow: 0 10px 20px rgba(0,0,0,0.15); }
.card-icon {
width: 44px; height: 44px;
border-radius: 10px;
background: rgba(255,255,255,0.5); /* 半透明白底 */
backdrop-filter: blur(4px);
display: flex; align-items: center; justify-content: center;
margin-right: 15px;
flex-shrink: 0;
overflow: hidden;
}
.card-icon img { width: 28px; height: 28px; object-fit: contain; display: block; }
/* 当图片加载失败时显示的默认图标样式 */
.card-icon i { font-size: 20px; color: var(--primary-color); }
.card-info { flex: 1; }
.card-title { font-weight: 700; font-size: 1.1rem; margin-bottom: 4px; color: var(--text-main); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.card-desc { font-size: 0.85rem; color: var(--text-sub); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tag-badge { font-size: 0.75rem; background: rgba(255,255,255,0.4); padding: 2px 8px; border-radius: 4px; color: var(--text-main); }
@media (max-width: 768px) { .grid-container { grid-template-columns: 1fr; } }
/* --- 分类筛选栏样式 --- */
.category-filter {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 10px;
margin-top: 20px;
padding: 10px;
}
.cat-item {
padding: 6px 14px;
border-radius: 20px;
background: var(--card-bg);
border: 1px solid var(--card-border);
color: var(--text-sub);
font-size: 0.9rem;
cursor: pointer;
transition: all 0.2s;
display: inline-flex;
align-items: center;
user-select: none;
}
.cat-item:hover {
border-color: var(--primary-color);
color: var(--primary-color);
transform: translateY(-1px);
}
.cat-item.active {
background: var(--primary-color);
color: #fff;
border-color: var(--primary-color);
box-shadow: 0 4px 10px rgba(79, 70, 229, 0.3);
}
.cat-count {
background: rgba(0,0,0,0.05);
padding: 2px 6px;
border-radius: 10px;
font-size: 0.75rem;
margin-left: 6px;
min-width: 18px;
text-align: center;
}
/* 夜间模式下的数字背景微调 */
body.dark .cat-count {
background: rgba(255,255,255,0.1);
}
body.dark .cat-item.active .cat-count {
background: rgba(255,255,255,0.2);
color: #fff;
}
</style>
</head>
<body>
<?php $this->need('module/header.php'); ?>
<div id="Joe">
<div class="joe_container">
<main class="joe_main">
<div class="nav-container">
<div class="header-section">
<h1 class="site-title"><?php $this->title(); ?></h1>
<div class="search-box">
<i class="fas fa-search search-icon"></i>
<input type="text" id="searchInput" class="search-input" placeholder="搜索网站名称或描述...">
</div>
<!-- 新增:分类筛选栏 -->
<div class="category-filter">
<?php foreach ($categories as $name => $count) : ?>
<span class="cat-item <?php echo ($name === '全部') ? 'active' : ''; ?>"
data-cat="<?php echo $name; ?>">
<?php echo $name; ?> <span class="cat-count"><?php echo $count; ?></span>
</span>
<?php endforeach; ?>
</div>
</div>
<div class="grid-container" id="navGrid">
<?php if (empty($navItems)) : ?>
<div class="no-result" style="grid-column:1/-1;text-align:center;padding:40px;">
未找到数据。<br>请检查正文格式是否为 <code>---标题|描述|链接</code>
</div>
<?php else : ?>
<?php foreach ($navItems as $item) : ?>
<?php
// 生成随机背景
$randomBg = getRandomGradient();
// 获取域名
$domain = parse_url($item['url'], PHP_URL_HOST);
$iconSrc = $item['icon'] ? $item['icon'] : "https://" . $domain . "/favicon.ico";
?>
<a href="<?php echo $item['url']; ?>"
class="nav-card"
style="background: <?php echo $randomBg; ?>;"
target="_blank"
rel="noopener noreferrer">
<div class="card-icon">
<!-- 图片加载失败时,显示 globe 图标 -->
<img data-src="<?php echo $iconSrc; ?>" data-timeout="3000" onerror="this.style.display='none'; this.parentNode.innerHTML='<i class=\'fas fa-globe\'></i>'" alt="" class="lazy-favicon">
</div>
<div class="card-info">
<div class="card-title"><?php echo $item['title']; ?></div>
<div class="card-desc"><?php echo $item['desc']; ?></div>
</div>
<span class="tag-badge"><?php echo $item['category']; ?></span>
</a>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</main>
<?php joe\isPc() ? $this->need('module/aside.php') : null ?>
</div>
<?php $this->need('module/bottom.php'); ?>
</div>
<?php $this->need('module/footer.php') ?>
<script>
function loadFaviconWithTimeout() {
// 获取所有带有 data-src 的图片
const images = document.querySelectorAll('img.lazy-favicon');
images.forEach(img => {
const url = img.getAttribute('data-src');
const timeout = parseInt(img.getAttribute('data-timeout')) || 3000; // 默认3秒
// 创建一个临时的 Image 对象
const loader = new Image();
// 超时逻辑
const timer = setTimeout(() => {
// 超时后,移除加载中的占位符或错误处理
if (img.parentNode) {
img.style.display = 'none';
img.parentNode.innerHTML = '<i class="fas fa-globe"></i>';
}
console.warn('Favicon 加载超时:', url);
}, timeout);
// 加载成功
loader.onload = function() {
clearTimeout(timer);
img.src = url; // 将 URL 赋值给真正的 src
img.style.display = 'block'; // 显示图片
};
// 加载失败 (404/500等)
loader.onerror = function() {
clearTimeout(timer);
// 触发 onerror 逻辑,显示地球图标
if (img.onerro) img.onerror();
};
// 开始加载
loader.src = url;
});
}
document.addEventListener('DOMContentLoaded', function() {
const searchInput = document.getElementById('searchInput');
const catItems = document.querySelectorAll('.cat-item');
const cards = document.querySelectorAll('.nav-card');
// 1. 分类点击事件
catItems.forEach(item => {
item.addEventListener('click', function() {
// 移除其他项的 active 状态
catItems.forEach(i => i.classList.remove('active'));
// 给当前项添加 active 状态
this.classList.add('active');
const selectedCat = this.getAttribute('data-cat');
const searchVal = searchInput.value.toLowerCase();
// 执行筛选
filterCards(selectedCat, searchVal);
});
});
// 2. 搜索框输入事件
searchInput.addEventListener('input', function(e) {
const val = e.target.value.toLowerCase();
// 获取当前选中的分类
const activeCatItem = document.querySelector('.cat-item.active');
const selectedCat = activeCatItem ? activeCatItem.getAttribute('data-cat') : '全部';
filterCards(selectedCat, val);
});
// 3. 统一的筛选函数
function filterCards(category, keyword) {
cards.forEach(card => {
const cardCat = card.querySelector('.tag-badge').innerText;
const title = card.querySelector('.card-title').innerText.toLowerCase();
const desc = card.querySelector('.card-desc').innerText.toLowerCase();
// 判断分类是否匹配
const isCatMatch = (category === '全部' || cardCat === category);
// 判断搜索词是否匹配
const isSearchMatch = (title.includes(keyword) || desc.includes(keyword));
// 同时满足才显示
if (isCatMatch && isSearchMatch) {
card.style.display = 'flex';
} else {
card.style.display = 'none';
}
});
}
loadFaviconWithTimeout();
});
</script>
</body>
</html>