
index.php
<?php
// PHP 代码部分
// =========================================================================
// Function to read the JSON data file
function readData($filePath) {
if (file_exists($filePath)) {
$jsonData = file_get_contents($filePath);
return json_decode($jsonData, true);
}
// Return empty data if file doesn't exist
return ['categories' => [], 'websites' => []];
}
// 读取数据文件
$data = readData('data.json');
$categories = $data['categories'];
$websites = $data['websites'];
// 获取当前分类ID,默认为 'all'
$currentCategory = $_GET['cat'] ?? 'all';
// Simple helper function to find child categories
function findChildren($allCategories, $parentId) {
$children = [];
foreach ($allCategories as $category) {
if (isset($category['parentId']) && $category['parentId'] === $parentId) {
$children[] = $category['id'];
$children = array_merge($children, findChildren($allCategories, $category['id']));
}
}
return $children;
}
// Function to find ancestor IDs of a category
function findAncestors($allCategories, $categoryId) {
$ancestors = [];
$current = array_values(array_filter($allCategories, function($cat) use ($categoryId) {
return $cat['id'] === $categoryId;
}));
if (empty($current)) {
return [];
}
$current = $current[0];
while (isset($current['parentId'])) {
$parentId = $current['parentId'];
$ancestors[] = $parentId;
$current = array_values(array_filter($allCategories, function($cat) use ($parentId) {
return $cat['id'] === $parentId;
}));
if (!empty($current)) {
$current = $current[0];
} else {
$current = null;
}
}
return $ancestors;
}
// Filter websites based on current category
$websitesToDisplay = [];
if ($currentCategory === 'all') {
$websitesToDisplay = $websites;
} else {
$categoryIds = array_merge([$currentCategory], findChildren($categories, $currentCategory));
foreach ($websites as $website) {
if (in_array($website['categoryId'], $categoryIds)) {
$websitesToDisplay[] = $website;
}
}
}
// Get the list of all categories that should be open in the sidebar
$activeCategories = [];
if ($currentCategory !== 'all') {
$activeCategories = array_merge(
[$currentCategory],
findChildren($categories, $currentCategory),
findAncestors($categories, $currentCategory)
);
}
// Function to get the domain from a URL
function getDomain($url) {
// 检查网址是否为占位符 #
if ($url === '#') {
return '待添加网址';
}
$host = parse_url($url, PHP_URL_HOST);
// Remove "www." prefix if it exists
if (strpos($host, 'www.') === 0) {
return substr($host, 4);
}
return $host ?: '';
}
// Helper to build a tree for sidebar display
function buildCategoryTree($allCategories, $parentId = null) {
$branch = [];
foreach ($allCategories as $category) {
if ((!isset($category['parentId']) || $category['parentId'] === null) && $parentId === null ||
(isset($category['parentId']) && $category['parentId'] === $parentId)) {
$category['children'] = buildCategoryTree($allCategories, $category['id']);
$branch[] = $category;
}
}
return $branch;
}
// Helper to render the categories with indentation and toggle functionality for the sidebar
function renderCategoryList($categoryTree, $currentCategory, $activeCategories) {
echo '<ul>';
foreach ($categoryTree as $category) {
$hasChildren = !empty($category['children']);
$isCurrent = $currentCategory === $category['id'];
$isCurrentOrDescendant = in_array($category['id'], $activeCategories);
$activeClass = $isCurrent ? 'bg-indigo-500 text-white shadow-md' : 'hover:bg-indigo-50 hover:text-indigo-600 text-gray-700';
$expandedClass = $isCurrentOrDescendant ? '' : 'hidden';
$rotateClass = $isCurrentOrDescendant ? 'rotate-90' : '';
echo '<li data-category-id="' . htmlspecialchars($category['id']) . '">';
echo '<div class="flex items-center justify-between">';
echo '<a href="index.php?cat=' . $category['id'] . '"';
echo ' class="flex-1 block text-left p-3 rounded-xl transition-colors duration-200 focus:outline-none font-medium ' . $activeClass . '">';
echo htmlspecialchars($category['name']);
echo '</a>';
if ($hasChildren) {
echo '<button class="category-toggle-btn p-2 rounded-full text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500">';
echo '<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 transition-transform duration-200 ' . $rotateClass . '" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" /></svg>';
echo '</button>';
}
echo '</div>';
if ($hasChildren) {
echo '<ul class="ml-4 mt-2 space-y-2 ' . $expandedClass . '">';
renderCategoryList($category['children'], $currentCategory, $activeCategories);
echo '</ul>';
}
echo '</li>';
}
echo '</ul>';
}
// Get favicon URL from the website's root domain
function getFaviconUrl($url) {
$parts = parse_url($url);
$scheme = isset($parts['scheme']) ? $parts['scheme'] : 'http';
$host = isset($parts['host']) ? $parts['host'] : '';
if (empty($host)) {
return '#';
}
return "$scheme://$host/favicon.ico";
}
$categoryTree = buildCategoryTree($categories);
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>网址导航</title>
<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #f7f9fc;
}
/* 自定义滚动条样式 */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f5f9;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
</style>
</head>
<body class="bg-gray-100 flex flex-col min-h-screen">
<!-- 顶部自定义横向导航栏 -->
<nav class="sticky top-0 z-50 bg-white shadow-lg p-4 mb-8">
<div class="container mx-auto flex flex-wrap items-center justify-center gap-4">
<!-- 网站Logo和名称 -->
<a href="index.php" class="flex items-center gap-2 mr-6">
<img src="https://pan.iqiyi.com/file/sns_comment_v2/oTil7ORS4zry7IYmcNDm1Kj1qIyhjV_dk8DHIaISyQ9F5Mkav4WeEHsKjT_eS8EmG2q6QOJ6WUblIay5aGrfKQ.png"
alt="网站Logo"
class="h-10 w-10 rounded-full shadow-md">
<span class="text-xl font-bold text-indigo-600">网址导航</span>
</a>
<!-- 自定义导航链接 -->
<a href="index.php"
class="px-6 py-2 rounded-full font-medium transition-colors duration-200 text-slate-600 hover:bg-sky-50 hover:text-sky-600">
主页
</a>
<a href="#"
class="px-6 py-2 rounded-full font-medium transition-colors duration-200 text-slate-600 hover:bg-sky-50 hover:text-sky-600">
关于我们
</a>
<a href="#"
class="px-6 py-2 rounded-full font-medium transition-colors duration-200 text-slate-600 hover:bg-sky-50 hover:text-sky-600">
服务
</a>
<a href="#"
class="px-6 py-2 rounded-full font-medium transition-colors duration-200 text-slate-600 hover:bg-sky-50 hover:text-sky-600">
联系我们
</a>
</div>
</nav>
<!-- 主内容区 -->
<div class="container mx-auto p-4 md:p-8 flex flex-col md:flex-row gap-8 flex-1">
<!-- 侧边栏:网站分类 -->
<aside class="w-full md:w-1/4 bg-white p-4 md:p-6 rounded-2xl shadow-lg border border-gray-200 h-fit">
<h2 class="text-xl md:text-2xl font-bold text-gray-800 mb-4 pb-2 border-b-2 border-indigo-500">网站分类</h2>
<nav>
<ul id="category-list">
<!-- '全部' category button -->
<li>
<a href="index.php?cat=all"
class="w-full block text-left p-3 rounded-xl transition-colors duration-200 focus:outline-none font-medium <?php echo $currentCategory === 'all' ? 'bg-indigo-500 text-white shadow-md' : 'hover:bg-indigo-50 hover:text-indigo-600 text-gray-700'; ?>">
全部
</a>
</li>
<!-- Dynamically rendered categories from data.json -->
<?php renderCategoryList($categoryTree, $currentCategory, $activeCategories); ?>
</ul>
</nav>
</aside>
<!-- 主内容区:网址列表 -->
<main>
<div class="bg-white p-4 md:p-6 rounded-2xl shadow-lg border border-gray-200">
<h1 class="text-2xl md:text-3xl font-bold text-gray-800 mb-6">
<?php
if ($currentCategory === 'all') {
echo '所有网址';
} else {
$currentCategoryName = '所有网址';
foreach ($categories as $category) {
if ($category['id'] === $currentCategory) {
$currentCategoryName = $category['name'];
break;
}
}
echo htmlspecialchars($currentCategoryName);
}
?>
</h1>
<!-- Website grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<?php if (count($websitesToDisplay) > 0): ?>
<?php foreach ($websitesToDisplay as $website): ?>
<a href="<?php echo htmlspecialchars($website['url']); ?>" target="_blank"
class="bg-white p-6 rounded-2xl shadow-md transition-transform transform hover:scale-105 hover:shadow-xl border border-gray-200 cursor-pointer">
<div class="flex items-center mb-2">
<!-- 网站图标 -->
<div class="w-10 h-10 flex-shrink-0 rounded-full mr-4 border border-indigo-200 overflow-hidden bg-gray-100 flex items-center justify-center">
<img src="<?php echo htmlspecialchars(getFaviconUrl($website['url'])); ?>"
alt="<?php echo htmlspecialchars($website['name']); ?> Logo"
class="w-full h-full object-contain p-1"
onerror="this.onerror=null;this.src='https://placehold.co/40x40/4f46e5/ffffff?text=<?php echo urlencode(mb_substr(htmlspecialchars($website['name']), 0, 1, 'UTF-8')); ?>'">
</div>
<h3 class="text-lg font-semibold text-indigo-600 truncate"><?php echo htmlspecialchars($website['name']); ?></h3>
</div>
<p class="text-sm text-gray-500 line-clamp-2"><?php echo htmlspecialchars($website['description']); ?></p>
</a>
<?php endforeach; ?>
<?php else: ?>
<p class="text-gray-500 text-center col-span-3">此分类下暂无网址。</p>
<?php endif; ?>
</div>
</div>
</main>
</div>
<!-- JavaScript to handle category toggle -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const toggleButtons = document.querySelectorAll('.category-toggle-btn');
toggleButtons.forEach(button => {
button.addEventListener('click', (e) => {
// 阻止点击事件冒泡到父级a标签
e.preventDefault();
e.stopPropagation();
const parentLi = button.closest('li');
const childUl = parentLi.querySelector('ul');
if (childUl) {
childUl.classList.toggle('hidden');
button.querySelector('svg').classList.toggle('rotate-90');
}
});
});
});
</script>
</body>
</html>
admin.php 密码在第三行设置,默认为admin
<?php
// Set a password for admin access. IMPORTANT: CHANGE THIS TO A SECURE PASSWORD!
$password = 'admin';
session_start();
// Handle login
if (isset($_POST['login_password'])) {
if ($_POST['login_password'] === $password) {
$_SESSION['authenticated'] = true;
} else {
$loginError = "密码错误!";
}
}
// Check if the user is authenticated
if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
echo '
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>后台登录</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>body { font-family: "Inter", sans-serif; background-color: #f7f9fc; }</style>
</head>
<body class="bg-gray-100 flex items-center justify-center min-h-screen">
<div class="w-full max-w-sm p-8 bg-white rounded-2xl shadow-lg border border-gray-200">
<h1 class="text-3xl font-bold text-gray-800 text-center mb-6">后台登录</h1>
<form method="post">
<input type="password" name="login_password" placeholder="请输入管理密码" required
class="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
<button type="submit" class="w-full p-3 bg-indigo-500 text-white rounded-lg hover:bg-indigo-600 transition-colors duration-200">
登录
</button>
</form>
' . (isset($loginError) ? '<p class="text-red-500 mt-4 text-center">' . $loginError . '</p>' : '') . '
</div>
</body>
</html>
';
exit;
}
// --- Admin Panel Logic ---
$filePath = 'data.json';
// Read JSON data
function readData($filePath) {
if (file_exists($filePath)) {
$jsonData = file_get_contents($filePath);
return json_decode($jsonData, true);
}
return ['categories' => [], 'websites' => []];
}
// Write JSON data
function writeData($filePath, $data) {
$jsonData = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
file_put_contents($filePath, $jsonData);
}
/**
* Function to automatically fetch the website description from a URL using cURL and DOMDocument.
* This version is more robust by also checking for the 'og:description' property.
* @param string $url The URL of the website to fetch.
* @return string The meta description content or an empty string if not found.
*/
function fetchWebsiteDescription($url) {
// Check if the URL is valid
if (!filter_var($url, FILTER_VALIDATE_URL)) {
return '';
}
// Initialize cURL session
$ch = curl_init();
// Set cURL options
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // Follow redirects
curl_setopt($ch, CURLOPT_TIMEOUT, 10); // 10 second timeout
// Set a user agent to mimic a browser, which helps prevent some websites from blocking the request
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36');
// Execute cURL session
$html = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// If the request failed or returned a bad status code, return an empty string
if ($html === false || $httpCode >= 400) {
return '';
}
// Create a new DOMDocument to parse the HTML
$dom = new DOMDocument();
// Suppress warnings from malformed HTML
@$dom->loadHTML($html);
$metaDescription = '';
// Find all meta tags
$metaTags = $dom->getElementsByTagName('meta');
foreach ($metaTags as $meta) {
// Check for standard 'description' meta tag
if ($meta->getAttribute('name') === 'description') {
$metaDescription = $meta->getAttribute('content');
break; // Found the standard description, break the loop
}
// Check for Open Graph 'og:description' meta tag
if (empty($metaDescription) && $meta->getAttribute('property') === 'og:description') {
$metaDescription = $meta->getAttribute('content');
}
}
return $metaDescription;
}
/**
* Function to get the favicon URL from the website's root domain.
* @param string $url The URL of the website.
* @return string The favicon URL.
*/
function getFaviconUrl($url) {
$parts = parse_url($url);
$scheme = isset($parts['scheme']) ? $parts['scheme'] : 'http';
$host = isset($parts['host']) ? $parts['host'] : '';
if (empty($host)) {
return '#';
}
return "$scheme://$host/favicon.ico";
}
/**
* Recursively find all child categories of a given category.
* @param array $allCategories The full array of all categories.
* @param string $parentId The ID of the parent category.
* @return array An array of child category IDs.
*/
function getChildCategoryIds($allCategories, $parentId) {
$childIds = [];
foreach ($allCategories as $category) {
if (isset($category['parentId']) && $category['parentId'] === $parentId) {
$childIds[] = $category['id'];
$childIds = array_merge($childIds, getChildCategoryIds($allCategories, $category['id']));
}
}
return $childIds;
}
/**
* Builds a hierarchical tree structure from a flat list of categories.
* @param array $allCategories The full array of categories.
* @param string $parentId The ID of the parent category to start from.
* @return array The hierarchical tree.
*/
function buildCategoryTree($allCategories, $parentId = null) {
$branch = [];
foreach ($allCategories as $category) {
// Check if the category has a parentId, and if it matches the current parentId
if ((!isset($category['parentId']) || $category['parentId'] === null) && $parentId === null ||
(isset($category['parentId']) && $category['parentId'] === $parentId)) {
$category['children'] = buildCategoryTree($allCategories, $category['id']);
$branch[] = $category;
}
}
return $branch;
}
/**
* Recursively gets the full category path as a string.
* @param array $allCategories The full array of categories.
* @param string $categoryId The ID of the category.
* @return string The category path string (e.g., "一级分类 > 二级分类").
*/
function getCategoryPath($allCategories, $categoryId) {
$path = [];
$currentCategory = array_values(array_filter($allCategories, function($cat) use ($categoryId) {
return $cat['id'] === $categoryId;
}));
if (empty($currentCategory)) {
return '';
}
$currentCategory = $currentCategory[0];
// Build path by traversing up the parent chain
while ($currentCategory) {
$path[] = $currentCategory['name'];
if (isset($currentCategory['parentId'])) {
$parentId = $currentCategory['parentId'];
$parentCategory = array_values(array_filter($allCategories, function($cat) use ($parentId) {
return $cat['id'] === $parentId;
}));
$currentCategory = !empty($parentCategory) ? $parentCategory[0] : null;
} else {
$currentCategory = null;
}
}
return implode(' > ', array_reverse($path));
}
$data = readData($filePath);
// Handle category actions
if (isset($_POST['category_action'])) {
if ($_POST['category_action'] === 'add') {
$newCategoryName = trim($_POST['category_name']);
$parentCategoryId = isset($_POST['parent_id']) && $_POST['parent_id'] !== '' ? $_POST['parent_id'] : null;
if (!empty($newCategoryName)) {
$newCategoryId = uniqid('cat_');
$data['categories'][] = ['id' => $newCategoryId, 'name' => $newCategoryName, 'parentId' => $parentCategoryId];
writeData($filePath, $data);
}
} elseif ($_POST['category_action'] === 'edit') {
$categoryId = $_POST['category_id'];
$newCategoryName = trim($_POST['category_name']);
$parentCategoryId = isset($_POST['parent_id']) && $_POST['parent_id'] !== '' ? $_POST['parent_id'] : null;
if (!empty($newCategoryName)) {
foreach ($data['categories'] as &$category) {
if ($category['id'] === $categoryId) {
$category['name'] = $newCategoryName;
$category['parentId'] = $parentCategoryId;
break;
}
}
writeData($filePath, $data);
}
} elseif ($_POST['category_action'] === 'delete') {
$categoryId = $_POST['category_id'];
// Get all child category IDs (including sub-children)
$childIds = getChildCategoryIds($data['categories'], $categoryId);
$idsToDelete = array_merge([$categoryId], $childIds);
// Remove category and its children
$data['categories'] = array_filter($data['categories'], function($cat) use ($idsToDelete) {
return !in_array($cat['id'], $idsToDelete);
});
// Remove websites in the deleted categories
$data['websites'] = array_filter($data['websites'], function($web) use ($idsToDelete) {
return !in_array($web['categoryId'], $idsToDelete);
});
writeData($filePath, $data);
}
header('Location: admin.php');
exit;
}
// Handle website actions
if (isset($_POST['website_action'])) {
$websiteUrl = trim($_POST['website_url']);
$websiteDescription = trim($_POST['website_description']);
$websiteIconUrl = trim($_POST['website_iconUrl']); // New field
// Automatically fetch description if the field is empty
if (empty($websiteDescription)) {
$websiteDescription = fetchWebsiteDescription($websiteUrl);
}
if ($_POST['website_action'] === 'add') {
$newWebsite = [
'id' => uniqid('web_'),
'categoryId' => $_POST['website_categoryId'],
'name' => trim($_POST['website_name']),
'url' => $websiteUrl,
'description' => $websiteDescription,
'iconUrl' => $websiteIconUrl, // Add the new field
];
if (!empty($newWebsite['name']) && !empty($newWebsite['url']) && !empty($newWebsite['categoryId'])) {
$data['websites'][] = $newWebsite;
writeData($filePath, $data);
}
} elseif ($_POST['website_action'] === 'edit') {
$websiteId = $_POST['website_id'];
$updatedWebsite = [
'id' => $websiteId,
'categoryId' => $_POST['website_categoryId'],
'name' => trim($_POST['website_name']),
'url' => $websiteUrl,
'description' => $websiteDescription,
'iconUrl' => $websiteIconUrl, // Update the new field
];
foreach ($data['websites'] as &$website) {
if ($website['id'] === $websiteId) {
$website = $updatedWebsite;
break;
}
}
writeData($filePath, $data);
} elseif ($_POST['website_action'] === 'delete') {
$websiteId = $_POST['website_id'];
$data['websites'] = array_filter($data['websites'], function($web) use ($websiteId) {
return $web['id'] !== $websiteId;
});
writeData($filePath, $data);
}
header('Location: admin.php');
exit;
}
// Get the category and website to edit, if any
$editCategory = null;
if (isset($_GET['edit_cat'])) {
foreach ($data['categories'] as $cat) {
if ($cat['id'] === $_GET['edit_cat']) {
$editCategory = $cat;
break;
}
}
}
$editWebsite = null;
if (isset($_GET['edit_web'])) {
foreach ($data['websites'] as $web) {
if ($web['id'] === $_GET['edit_web']) {
$editWebsite = $web;
break;
}
}
}
// Helper function to render hierarchical categories
function renderCategoryOptions($categories, $level = 0, $selectedId = null) {
$indent = str_repeat('— ', $level);
foreach ($categories as $category) {
$isSelected = ($selectedId !== null && $selectedId === $category['id']) ? 'selected' : '';
echo '<option value="' . htmlspecialchars($category['id']) . '" ' . $isSelected . '>' . $indent . htmlspecialchars($category['name']) . '</option>';
if (isset($category['children']) && !empty($category['children'])) {
renderCategoryOptions($category['children'], $level + 1, $selectedId);
}
}
}
function renderCategoryList($categories) {
echo '<ul>';
foreach ($categories as $category) {
echo '<li class="flex justify-between items-center p-3 bg-gray-50 rounded-lg border border-gray-200">';
echo '<span>' . htmlspecialchars($category['name']) . '</span>';
echo '<div class="flex gap-2">';
echo '<a href="admin.php?edit_cat=' . htmlspecialchars($category['id']) . '" class="text-sm text-indigo-500 hover:text-indigo-700 font-medium">编辑</a>';
echo '<form method="post" style="display:inline;">';
echo '<input type="hidden" name="category_action" value="delete">';
echo '<input type="hidden" name="category_id" value="' . htmlspecialchars($category['id']) . '">';
echo '<button type="submit" class="text-sm text-red-500 hover:text-red-700 font-medium">删除</button>';
echo '</form>';
echo '</div>';
echo '</li>';
if (isset($category['children']) && !empty($category['children'])) {
echo '<li>';
renderCategoryList($category['children']);
echo '</li>';
}
}
echo '</ul>';
}
$categoryTree = buildCategoryTree($data['categories']);
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>后台管理</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body { font-family: 'Inter', sans-serif; background-color: #f7f9fc; }
</style>
</head>
<body class="bg-gray-100 flex flex-col min-h-screen">
<!-- Top Navigation Bar -->
<header class="bg-white p-4 md:p-6 shadow-lg rounded-b-2xl mb-4">
<div class="container mx-auto flex justify-between items-center">
<h1 class="text-2xl md:text-3xl font-bold text-gray-800">后台管理面板</h1>
<a href="index.php" class="text-sm text-indigo-500 hover:text-indigo-700 font-medium transition-colors duration-200">
返回首页
</a>
</div>
</header>
<div class="container mx-auto p-4 md:p-8 flex-1">
<div class="bg-white p-4 md:p-6 rounded-2xl shadow-lg border border-gray-200">
<!-- Categories Management -->
<h2 class="text-xl md:text-2xl font-bold text-gray-800 mb-4">分类管理</h2>
<form method="post" class="mb-8 p-4 border border-gray-200 rounded-xl">
<input type="hidden" name="category_action" value="<?php echo $editCategory ? 'edit' : 'add'; ?>">
<?php if ($editCategory): ?>
<input type="hidden" name="category_id" value="<?php echo htmlspecialchars($editCategory['id']); ?>">
<?php endif; ?>
<div class="flex flex-col md:flex-row items-center gap-4">
<input type="text" name="category_name" placeholder="分类名称" required
value="<?php echo $editCategory ? htmlspecialchars($editCategory['name']) : ''; ?>"
class="flex-1 p-2 w-full md:w-auto border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
<select name="parent_id" class="flex-1 p-2 w-full md:w-auto border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="">顶级分类</option>
<?php renderCategoryOptions($categoryTree, 0, $editCategory ? ($editCategory['parentId'] ?? null) : null); ?>
</select>
<button type="submit" class="w-full md:w-auto px-4 py-2 bg-indigo-500 text-white rounded-lg hover:bg-indigo-600 transition-colors duration-200">
<?php echo $editCategory ? '更新分类' : '添加分类'; ?>
</button>
<?php if ($editCategory): ?>
<a href="admin.php" class="w-full md:w-auto px-4 py-2 bg-gray-300 text-gray-700 rounded-lg hover:bg-gray-400 transition-colors duration-200 text-center">取消</a>
<?php endif; ?>
</div>
</form>
<?php renderCategoryList($categoryTree); ?>
<!-- Websites Management -->
<h2 class="text-xl md::text-2xl font-bold text-gray-800 mt-8 mb-4">网址管理</h2>
<form method="post" class="p-4 border border-gray-200 rounded-xl">
<input type="hidden" name="website_action" value="<?php echo $editWebsite ? 'edit' : 'add'; ?>">
<?php if ($editWebsite): ?>
<input type="hidden" name="website_id" value="<?php echo htmlspecialchars($editWebsite['id']); ?>">
<?php endif; ?>
<div>
<select name="website_categoryId" required
class="w-full p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="">选择分类...</option>
<?php renderCategoryOptions($categoryTree, 0, $editWebsite ? $editWebsite['categoryId'] : null); ?>
</select>
<input type="text" name="website_name" placeholder="网址名称" required
value="<?php echo $editWebsite ? htmlspecialchars($editWebsite['name']) : ''; ?>"
class="w-full p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
<input type="url" name="website_url" placeholder="网址 URL" required
value="<?php echo $editWebsite ? htmlspecialchars($editWebsite['url']) : ''; ?>"
class="w-full p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
<input type="text" name="website_description" placeholder="网址描述 (留空则自动获取)"
value="<?php echo $editWebsite ? htmlspecialchars($editWebsite['description']) : ''; ?>"
class="w-full p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
<input type="url" name="website_iconUrl" placeholder="自定义图标URL (可选)"
value="<?php echo $editWebsite ? htmlspecialchars($editWebsite['iconUrl'] ?? '') : ''; ?>"
class="w-full p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
<div class="flex gap-2">
<button type="submit" class="flex-1 px-4 py-2 bg-indigo-500 text-white rounded-lg hover:bg-indigo-600 transition-colors duration-200">
<?php echo $editWebsite ? '更新网址' : '添加网址'; ?>
</button>
<?php if ($editWebsite): ?>
<a href="admin.php" class="flex-1 px-4 py-2 bg-gray-300 text-gray-700 rounded-lg hover:bg-gray-400 transition-colors duration-200 text-center">取消</a>
<?php endif; ?>
</div>
</div>
</form>
<ul class="space-y-2 mt-8">
<?php foreach ($data['websites'] as $website): ?>
<li class="flex items-center justify-between p-3 bg-gray-50 rounded-lg border border-gray-200">
<div class="flex items-center gap-4">
<!-- Icon Display -->
<div class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-200 overflow-hidden flex-shrink-0">
<img src="<?php echo htmlspecialchars($website['iconUrl'] ?? getFaviconUrl($website['url'])); ?>"
alt="<?php echo htmlspecialchars($website['name']); ?> icon"
class="w-full h-full object-contain"
onerror="this.onerror=null;this.src='https://placehold.co/32x32/4f46e5/ffffff?text=<?php echo urlencode(mb_substr(htmlspecialchars($website['name']), 0, 1, 'UTF-8')); ?>'">
</div>
<div class="flex flex-col">
<span class="font-medium text-gray-700"><?php echo htmlspecialchars($website['name']); ?></span>
<span class="text-sm text-gray-500"><?php echo htmlspecialchars($website['url']); ?></span>
<?php if (isset($website['categoryId'])): ?>
<span class="text-xs text-gray-400">分类: <?php echo htmlspecialchars(getCategoryPath($data['categories'], $website['categoryId'])); ?></span>
<?php endif; ?>
</div>
</div>
<div class="flex gap-2">
<a href="admin.php?edit_web=<?php echo htmlspecialchars($website['id']); ?>" class="text-sm text-indigo-500 hover:text-indigo-700 font-medium">编辑</a>
<!-- Using a form for delete to ensure a POST request -->
<form method="post" style="display:inline;">
<input type="hidden" name="website_action" value="delete">
<input type="hidden" name="website_id" value="<?php echo htmlspecialchars($website['id']); ?>">
<button type="submit" class="text-sm text-red-500 hover:text-red-700 font-medium">删除</button>
</form>
</div>
</li>
<?php endforeach; ?>
</ul>
</div>
</div>
</body>
</html>
data.json
{
"categories": [
{
"id": "cat_social_media",
"name": "社交媒体",
"parentId": null
},
{
"id": "cat_news_info",
"name": "新闻资讯",
"parentId": null
},
{
"id": "cat_search_engines",
"name": "搜索引擎",
"parentId": null
},
{
"id": "cat_online_shopping",
"name": "在线购物",
"parentId": null
},
{
"id": "cat_entertainment",
"name": "影音娱乐",
"parentId": null
},
{
"id": "cat_video_platforms",
"name": "视频平台",
"parentId": "cat_entertainment"
},
{
"id": "cat_music_platforms",
"name": "音乐平台",
"parentId": "cat_entertainment"
},
{
"id": "cat_dev_tools",
"name": "开发工具",
"parentId": null
},
{
"id": "cat_tech_blogs",
"name": "技术博客",
"parentId": "cat_dev_tools"
},
{
"id": "cat_mail",
"name": "电子邮箱",
"parentId": null
},
{
"id": "cat_edu",
"name": "学习教育",
"parentId": null
},
{
"id": "cat_life_services",
"name": "生活服务",
"parentId": null
},
{
"id": "cat_finance",
"name": "金融理财",
"parentId": null
},
{
"id": "cat_travel",
"name": "旅游出行",
"parentId": null
}
],
"websites": [
{
"id": "web_weibo",
"categoryId": "cat_social_media",
"name": "微博",
"url": "https://weibo.com",
"description": "中国最大的社交媒体平台之一,提供公开分享、评论和互动的功能。"
},
{
"id": "web_zcool",
"categoryId": "cat_social_media",
"name": "站酷",
"url": "https://www.zcool.com.cn",
"description": "国内领先的原创设计师社区,汇聚了大量平面设计师、插画师等创意人群。"
},
{
"id": "web_douban",
"categoryId": "cat_social_media",
"name": "豆瓣",
"url": "https://www.douban.com",
"description": "提供图书、电影、音乐、活动信息,并鼓励用户分享评论和创建社交圈子。"
},
{
"id": "web_zhihu",
"categoryId": "cat_social_media",
"name": "知乎",
"url": "https://www.zhihu.com",
"description": "一个高质量的中文问答社区,用户可以分享知识、经验和见解。"
},
{
"id": "web_hupu",
"categoryId": "cat_social_media",
"name": "虎扑",
"url": "https://www.hupu.com",
"description": "专注于体育和流行文化的综合性社区,以篮球和足球讨论为主。"
},
{
"id": "web_v2ex",
"categoryId": "cat_social_media",
"name": "V2EX",
"url": "https://www.v2ex.com",
"description": "一个以程序员和设计师为主的分享和交流社区。"
},
{
"id": "web_douyin",
"categoryId": "cat_social_media",
"name": "抖音",
"url": "https://www.douyin.com",
"description": "热门的短视频社交应用,用户可以拍摄、分享和观看短视频。"
},
{
"id": "web_xiaohongshu",
"categoryId": "cat_social_media",
"name": "小红书",
"url": "https://www.xiaohongshu.com",
"description": "一个生活方式分享社区,用户可以分享购物、旅行、美食等生活经验。"
},
{
"id": "web_baidunews",
"categoryId": "cat_news_info",
"name": "百度新闻",
"url": "https://news.baidu.com",
"description": "聚合海量新闻资讯,提供实时热点和深度报道。"
},
{
"id": "web_sina_news",
"categoryId": "cat_news_info",
"name": "新浪新闻",
"url": "https://news.sina.com.cn",
"description": "中国权威新闻资讯平台,提供国内外时事、财经、体育等新闻。"
},
{
"id": "web_netease_news",
"categoryId": "cat_news_info",
"name": "网易新闻",
"url": "https://news.163.com",
"description": "网易旗下的新闻门户,提供独家评论和深度分析。"
},
{
"id": "web_ifeng",
"categoryId": "cat_news_info",
"name": "凤凰网",
"url": "https://www.ifeng.com",
"description": "提供全球华人社会的新闻资讯,以独特的视角进行报道。"
},
{
"id": "web_thepaper",
"categoryId": "cat_news_info",
"name": "澎湃新闻",
"url": "https://www.thepaper.cn",
"description": "专注于时政、思想、文化领域的原创性时事新闻和思想分析。"
},
{
"id": "web_cctv",
"categoryId": "cat_news_info",
"name": "央视网",
"url": "https://www.cctv.com",
"description": "中国中央电视台官方网站,提供电视节目和新闻资讯。"
},
{
"id": "web_baidu",
"categoryId": "cat_search_engines",
"name": "百度",
"url": "https://www.baidu.com",
"description": "全球最大的中文搜索引擎,提供网页、图片、视频等多种搜索服务。"
},
{
"id": "web_google",
"categoryId": "cat_search_engines",
"name": "谷歌",
"url": "https://www.google.com",
"description": "全球领先的搜索引擎,提供快速、相关的搜索结果。"
},
{
"id": "web_bing",
"categoryId": "cat_search_engines",
"name": "必应",
"url": "https://cn.bing.com",
"description": "微软旗下的搜索引擎,提供高质量的搜索结果和每日高清背景图。"
},
{
"id": "web_sogou",
"categoryId": "cat_search_engines",
"name": "搜狗",
"url": "https://www.sogou.com",
"description": "提供网页、微信、知乎、新闻等特色搜索服务。"
},
{
"id": "web_360",
"categoryId": "cat_search_engines",
"name": "360搜索",
"url": "https://www.so.com",
"description": "360公司推出的搜索引擎,致力于提供安全、干净的搜索体验。"
},
{
"id": "web_taobao",
"categoryId": "cat_online_shopping",
"name": "淘宝",
"url": "https://www.taobao.com",
"description": "亚洲最大的在线零售平台,提供种类繁多的商品。"
},
{
"id": "web_jd",
"categoryId": "cat_online_shopping",
"name": "京东",
"url": "https://www.jd.com",
"description": "中国领先的自营式电商企业,以正品保障和快速物流闻名。"
},
{
"id": "web_tmall",
"categoryId": "cat_online_shopping",
"name": "天猫",
"url": "https://www.tmall.com",
"description": "阿里巴巴旗下的综合性购物网站,主要以品牌商品为主。"
},
{
"id": "web_amazon",
"categoryId": "cat_online_shopping",
"name": "亚马逊",
"url": "https://www.amazon.cn",
"description": "全球最大的电子商务公司,提供图书、电子产品、家居用品等。"
},
{
"id": "web_pinduoduo",
"categoryId": "cat_online_shopping",
"name": "拼多多",
"url": "https://www.pinduoduo.com",
"description": "以拼团模式为主的社交电商平台,提供物美价廉的商品。"
},
{
"id": "web_suning",
"categoryId": "cat_online_shopping",
"name": "苏宁易购",
"url": "https://www.suning.com",
"description": "提供家电、数码、百货等商品的在线零售平台。"
},
{
"id": "web_youku",
"categoryId": "cat_video_platforms",
"name": "优酷",
"url": "https://www.youku.com",
"description": "提供海量电影、电视剧、综艺、动漫等视频内容,是国内领先的视频分享平台。"
},
{
"id": "web_qqvideo",
"categoryId": "cat_video_platforms",
"name": "腾讯视频",
"url": "https://v.qq.com",
"description": "腾讯旗下的视频媒体平台,提供丰富的影视剧、综艺和体育赛事直播。"
},
{
"id": "web_iqiyi",
"categoryId": "cat_video_platforms",
"name": "爱奇艺",
"url": "https://www.iqiyi.com",
"description": "中国领先的在线视频媒体,提供高清、正版的电影、电视剧和综艺节目。"
},
{
"id": "web_bilibili",
"categoryId": "cat_video_platforms",
"name": "哔哩哔哩",
"url": "https://www.bilibili.com",
"description": "中国知名的视频弹幕网站,拥有丰富的动漫、番剧、游戏和原创视频内容。"
},
{
"id": "web_tudou",
"categoryId": "cat_video_platforms",
"name": "土豆",
"url": "https://www.tudou.com",
"description": "提供短视频、动漫、搞笑等多元化视频内容的分享平台。"
},
{
"id": "web_migu",
"categoryId": "cat_video_platforms",
"name": "咪咕视频",
"url": "https://migu.cn",
"description": "中国移动旗下的视频平台,提供体育赛事、影视剧和综艺节目。"
},
{
"id": "web_netease_music",
"categoryId": "cat_music_platforms",
"name": "网易云音乐",
"url": "https://music.163.com",
"description": "专注于发现与分享的音乐产品,提供个性化推荐和丰富的用户评论。"
},
{
"id": "web_qq_music",
"categoryId": "cat_music_platforms",
"name": "QQ音乐",
"url": "https://y.qq.com",
"description": "腾讯旗下的正版数字音乐平台,拥有海量音乐库和独家版权。"
},
{
"id": "web_kugou",
"categoryId": "cat_music_platforms",
"name": "酷狗音乐",
"url": "https://www.kugou.com",
"description": "老牌音乐播放软件,提供高品质音乐下载和在线播放。"
},
{
"id": "web_kuwo",
"categoryId": "cat_music_platforms",
"name": "酷我音乐",
"url": "https://www.kuwo.cn",
"description": "提供在线音乐播放、下载、MV观看等服务。"
},
{
"id": "web_github",
"categoryId": "cat_dev_tools",
"name": "GitHub",
"url": "https://github.com",
"description": "全球最大的代码托管平台,开发者可以在此进行版本控制和协作。"
},
{
"id": "web_stackoverflow",
"categoryId": "cat_dev_tools",
"name": "Stack Overflow",
"url": "https://stackoverflow.com",
"description": "全球最大的程序员问答社区,解决编程难题的首选平台。"
},
{
"id": "web_gitee",
"categoryId": "cat_dev_tools",
"name": "Gitee",
"url": "https://gitee.com",
"description": "国内领先的代码托管和研发协作平台,提供私有仓库和持续集成服务。"
},
{
"id": "web_npm",
"categoryId": "cat_dev_tools",
"name": "NPM",
"url": "https://www.npmjs.com",
"description": "JavaScript包管理器,是世界上最大的软件注册表。"
},
{
"id": "web_docker",
"categoryId": "cat_dev_tools",
"name": "Docker Hub",
"url": "https://hub.docker.com",
"description": "Docker官方镜像仓库,提供大量预构建的Docker镜像。"
},
{
"id": "web_segmentfault",
"categoryId": "cat_tech_blogs",
"name": "思否",
"url": "https://segmentfault.com",
"description": "一个面向开发者的技术问答和博客社区。"
},
{
"id": "web_juejin",
"categoryId": "cat_tech_blogs",
"name": "掘金",
"url": "https://juejin.cn",
"description": "一个帮助开发者成长、分享技术经验的社区。"
},
{
"id": "web_csdn",
"categoryId": "cat_tech_blogs",
"name": "CSDN",
"url": "https://www.csdn.net",
"description": "中国最大的IT技术社区之一,提供博客、论坛、下载等服务。"
},
{
"id": "web_oschina",
"categoryId": "cat_tech_blogs",
"name": "开源中国",
"url": "https://www.oschina.net",
"description": "国内领先的开源技术社区,提供开源项目、技术资讯和博客。"
},
{
"id": "web_infoq",
"categoryId": "cat_tech_blogs",
"name": "InfoQ",
"url": "https://www.infoq.cn",
"description": "提供高质量的技术文章和大会视频,专注于软件开发领域。"
},
{
"id": "web_tencent_mail",
"categoryId": "cat_mail",
"name": "QQ邮箱",
"url": "https://mail.qq.com",
"description": "腾讯旗下的免费邮箱服务,支持大容量存储和多功能管理。"
},
{
"id": "web_163_mail",
"categoryId": "cat_mail",
"name": "网易邮箱",
"url": "https://mail.163.com",
"description": "网易旗下的免费邮箱服务,是中国最早、最大的邮箱服务商之一。"
},
{
"id": "web_gmail",
"categoryId": "cat_mail",
"name": "Gmail",
"url": "https://mail.google.com",
"description": "谷歌提供的电子邮件服务,界面简洁,功能强大。"
},
{
"id": "web_outlook",
"categoryId": "cat_mail",
"name": "Outlook",
"url": "https://outlook.live.com",
"description": "微软旗下的免费电子邮件服务,整合了日历和联系人功能。"
},
{
"id": "web_coursera",
"categoryId": "cat_edu",
"name": "Coursera",
"url": "https://www.coursera.org",
"description": "提供来自世界顶尖大学和机构的在线课程和学位项目。"
},
{
"id": "web_edx",
"categoryId": "cat_edu",
"name": "edX",
"url": "https://www.edx.org",
"description": "由麻省理工学院和哈佛大学创立的在线学习平台,提供高质量的慕课课程。"
},
{
"id": "web_imooc",
"categoryId": "cat_edu",
"name": "慕课网",
"url": "https://www.imooc.com",
"description": "国内知名的IT技能学习平台,提供大量的编程和开发课程。"
},
{
"id": "web_xuexi",
"categoryId": "cat_edu",
"name": "学习强国",
"url": "https://www.xuexi.cn",
"description": "提供丰富学习资源的综合性平台,涵盖政治、文化、科学等多个领域。"
},
{
"id": "web_dianping",
"categoryId": "cat_life_services",
"name": "大众点评",
"url": "https://www.dianping.com",
"description": "提供餐饮、休闲、娱乐等本地生活服务信息和用户评论。"
},
{
"id": "web_meituan",
"categoryId": "cat_life_services",
"name": "美团",
"url": "https://www.meituan.com",
"description": "一站式生活服务平台,涵盖外卖、酒店、旅游、电影票等。"
},
{
"id": "web_58tongcheng",
"categoryId": "cat_life_services",
"name": "58同城",
"url": "https://www.58.com",
"description": "提供招聘、房产、二手交易等本地生活服务信息。"
},
{
"id": "web_ganji",
"categoryId": "cat_life_services",
"name": "赶集网",
"url": "https://www.ganji.com",
"description": "提供招聘、房产、二手车等生活信息服务。"
},
{
"id": "web_alipay",
"categoryId": "cat_finance",
"name": "支付宝",
"url": "https://www.alipay.com",
"description": "全球领先的第三方支付平台,提供支付、理财、生活缴费等服务。"
},
{
"id": "web_jrjd",
"categoryId": "cat_finance",
"name": "京东金融",
"url": "https://jr.jd.com",
"description": "京东旗下的金融科技服务平台,提供理财、保险、借贷等服务。"
},
{
"id": "web_bankofchina",
"categoryId": "cat_finance",
"name": "中国银行",
"url": "https://www.boc.cn",
"description": "中国国有商业银行之一,提供个人和企业金融服务。"
},
{
"id": "web_ctrip",
"categoryId": "cat_travel",
"name": "携程",
"url": "https://www.ctrip.com",
"description": "国内领先的在线旅游服务平台,提供机票、酒店、火车票等预订服务。"
},
{
"id": "web_qunar",
"categoryId": "cat_travel",
"name": "去哪儿",
"url": "https://www.qunar.com",
"description": "提供机票、酒店、旅游度假、火车票的预订服务。"
},
{
"id": "web_mafengwo",
"categoryId": "cat_travel",
"name": "马蜂窝",
"url": "https://www.mafengwo.cn",
"description": "专注于自由行的旅游社区,提供旅行攻略和用户游记。"
},
{
"id": "web_feizhu",
"categoryId": "cat_travel",
"name": "飞猪",
"url": "https://www.fliggy.com",
"description": "阿里巴巴旗下的在线旅游平台,提供机票、酒店、度假等服务。"
}
]
}