Files

1406 lines
46 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
session_start();
// 配置
define('DATA_FILE', 'nav_data.json');
define('USERS_FILE', 'users.json');
define('APP_NAME', '个人导航');
define('VERSION', '1.0.0');
// 默认用户配置
$default_users = [
'admin' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi' // password: password
];
// 初始化用户文件
if (!file_exists(USERS_FILE)) {
file_put_contents(USERS_FILE, json_encode($default_users));
}
// 初始化导航数据文件
if (!file_exists(DATA_FILE)) {
$default_data = [
'quick_links' => [
['name' => 'Google', 'url' => 'https://www.google.com'],
['name' => 'GitHub', 'url' => 'https://github.com'],
['name' => 'YouTube', 'url' => 'https://www.youtube.com'],
['name' => '哔哩哔哩', 'url' => 'https://www.bilibili.com'],
['name' => '知乎', 'url' => 'https://www.zhihu.com'],
['name' => '微博', 'url' => 'https://www.weibo.com']
],
'categories' => [
[
'title' => '工作相关',
'links' => [
['name' => 'Gmail', 'url' => 'https://mail.google.com'],
['name' => 'Google Calendar', 'url' => 'https://calendar.google.com'],
['name' => 'Google Docs', 'url' => 'https://docs.google.com'],
['name' => 'Trello', 'url' => 'https://trello.com'],
['name' => 'Slack', 'url' => 'https://slack.com']
]
],
[
'title' => '开发工具',
'links' => [
['name' => 'Stack Overflow', 'url' => 'https://stackoverflow.com'],
['name' => 'MDN Web Docs', 'url' => 'https://developer.mozilla.org'],
['name' => 'CodePen', 'url' => 'https://codepen.io'],
['name' => 'CodeSandbox', 'url' => 'https://codesandbox.io'],
['name' => 'NPM', 'url' => 'https://npmjs.com']
]
],
[
'title' => '设计资源',
'links' => [
['name' => 'Dribbble', 'url' => 'https://dribbble.com'],
['name' => 'Behance', 'url' => 'https://behance.net'],
['name' => 'Unsplash', 'url' => 'https://unsplash.com'],
['name' => 'Figma', 'url' => 'https://figma.com'],
['name' => 'Coolors', 'url' => 'https://coolors.co']
]
],
[
'title' => '学习平台',
'links' => [
['name' => 'Coursera', 'url' => 'https://coursera.org'],
['name' => 'edX', 'url' => 'https://edx.org'],
['name' => 'Udemy', 'url' => 'https://udemy.com'],
['name' => 'Khan Academy', 'url' => 'https://khanacademy.org'],
['name' => 'freeCodeCamp', 'url' => 'https://freecodecamp.org']
]
],
[
'title' => '新闻资讯',
'links' => [
['name' => 'Hacker News', 'url' => 'https://news.ycombinator.com'],
['name' => 'Reddit', 'url' => 'https://www.reddit.com'],
['name' => 'Product Hunt', 'url' => 'https://producthunt.com'],
['name' => 'TechCrunch', 'url' => 'https://techcrunch.com'],
['name' => '36氪', 'url' => 'https://36kr.com']
]
],
[
'title' => '娱乐休闲',
'links' => [
['name' => 'Netflix', 'url' => 'https://www.netflix.com'],
['name' => 'Spotify', 'url' => 'https://www.spotify.com'],
['name' => 'SoundCloud', 'url' => 'https://soundcloud.com'],
['name' => 'Twitch', 'url' => 'https://www.twitch.tv'],
['name' => '豆瓣', 'url' => 'https://www.douban.com']
]
]
]
];
file_put_contents(DATA_FILE, json_encode($default_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
// 读取导航数据
function getNavData() {
if (!file_exists(DATA_FILE)) {
return ['quick_links' => [], 'categories' => []];
}
$json = file_get_contents(DATA_FILE);
$data = json_decode($json, true);
if (!$data) {
return ['quick_links' => [], 'categories' => []];
}
if (!isset($data['quick_links'])) $data['quick_links'] = [];
if (!isset($data['categories'])) $data['categories'] = [];
return $data;
}
// 保存导航数据
function saveNavData($data) {
return file_put_contents(DATA_FILE, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
// 验证用户
function verifyUser($username, $password) {
if (!file_exists(USERS_FILE)) {
return false;
}
$users = json_decode(file_get_contents(USERS_FILE), true);
if (!isset($users[$username])) {
return false;
}
return password_verify($password, $users[$username]);
}
// 检查是否已登录
function isLoggedIn() {
return isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true;
}
// 处理登录
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'login') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
if (verifyUser($username, $password)) {
$_SESSION['logged_in'] = true;
$_SESSION['username'] = $username;
header('Location: ?');
exit;
} else {
$error = '用户名或密码错误';
}
}
// 处理登出
if (isset($_GET['logout'])) {
session_destroy();
header('Location: ?');
exit;
}
// 处理API请求
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['api'])) {
if (!isLoggedIn()) {
http_response_code(401);
echo json_encode(['error' => '未授权']);
exit;
}
header('Content-Type: application/json');
$data = getNavData();
switch ($_POST['api']) {
case 'add_category':
$title = trim($_POST['title'] ?? '');
if ($title) {
if (!isset($data['categories'])) {
$data['categories'] = [];
}
$data['categories'][] = ['title' => $title, 'links' => []];
if (saveNavData($data)) {
echo json_encode(['success' => true, 'message' => '分类添加成功']);
} else {
echo json_encode(['error' => '保存失败']);
}
} else {
echo json_encode(['error' => '分类名称不能为空']);
}
break;
case 'edit_category':
$index = intval($_POST['index'] ?? -1);
$title = trim($_POST['title'] ?? '');
if ($index >= 0 && $title) {
if (isset($data['categories'][$index])) {
$data['categories'][$index]['title'] = $title;
if (saveNavData($data)) {
echo json_encode(['success' => true, 'message' => '分类修改成功']);
} else {
echo json_encode(['error' => '保存失败']);
}
} else {
echo json_encode(['error' => '分类不存在']);
}
} else {
echo json_encode(['error' => '参数错误']);
}
break;
case 'delete_category':
$index = intval($_POST['index'] ?? -1);
if ($index >= 0 && isset($data['categories'][$index])) {
array_splice($data['categories'], $index, 1);
if (saveNavData($data)) {
echo json_encode(['success' => true, 'message' => '分类删除成功']);
} else {
echo json_encode(['error' => '保存失败']);
}
} else {
echo json_encode(['error' => '分类不存在']);
}
break;
case 'add_link':
$type = $_POST['type'] ?? 'category';
$index = intval($_POST['index'] ?? -1);
$name = trim($_POST['name'] ?? '');
$url = trim($_POST['url'] ?? '');
if ($name && $url) {
if ($type === 'quick') {
if (!isset($data['quick_links'])) {
$data['quick_links'] = [];
}
$data['quick_links'][] = ['name' => $name, 'url' => $url];
if (saveNavData($data)) {
echo json_encode(['success' => true, 'message' => '快速链接添加成功']);
} else {
echo json_encode(['error' => '保存失败']);
}
} elseif ($type === 'category' && $index >= 0 && isset($data['categories'][$index])) {
if (!isset($data['categories'][$index]['links'])) {
$data['categories'][$index]['links'] = [];
}
$data['categories'][$index]['links'][] = ['name' => $name, 'url' => $url];
if (saveNavData($data)) {
echo json_encode(['success' => true, 'message' => '链接添加成功']);
} else {
echo json_encode(['error' => '保存失败']);
}
} else {
echo json_encode(['error' => '分类不存在']);
}
} else {
echo json_encode(['error' => '链接名称和地址不能为空']);
}
break;
case 'edit_link':
$type = $_POST['type'] ?? 'category';
$categoryIndex = intval($_POST['category_index'] ?? -1);
$linkIndex = intval($_POST['link_index'] ?? -1);
$name = trim($_POST['name'] ?? '');
$url = trim($_POST['url'] ?? '');
if ($name && $url) {
if ($type === 'quick' && $linkIndex >= 0 && isset($data['quick_links'][$linkIndex])) {
$data['quick_links'][$linkIndex] = ['name' => $name, 'url' => $url];
if (saveNavData($data)) {
echo json_encode(['success' => true, 'message' => '快速链接修改成功']);
} else {
echo json_encode(['error' => '保存失败']);
}
} elseif ($type === 'category' && $categoryIndex >= 0 && $linkIndex >= 0 && isset($data['categories'][$categoryIndex])) {
$data['categories'][$categoryIndex]['links'][$linkIndex] = ['name' => $name, 'url' => $url];
if (saveNavData($data)) {
echo json_encode(['success' => true, 'message' => '链接修改成功']);
} else {
echo json_encode(['error' => '保存失败']);
}
} else {
echo json_encode(['error' => '链接不存在']);
}
} else {
echo json_encode(['error' => '链接名称和地址不能为空']);
}
break;
case 'delete_link':
$type = $_POST['type'] ?? 'category';
$categoryIndex = intval($_POST['category_index'] ?? -1);
$linkIndex = intval($_POST['link_index'] ?? -1);
if ($type === 'quick' && $linkIndex >= 0 && isset($data['quick_links'][$linkIndex])) {
array_splice($data['quick_links'], $linkIndex, 1);
if (saveNavData($data)) {
echo json_encode(['success' => true, 'message' => '快速链接删除成功']);
} else {
echo json_encode(['error' => '保存失败']);
}
} elseif ($type === 'category' && $categoryIndex >= 0 && $linkIndex >= 0 && isset($data['categories'][$categoryIndex])) {
array_splice($data['categories'][$categoryIndex]['links'], $linkIndex, 1);
if (saveNavData($data)) {
echo json_encode(['success' => true, 'message' => '链接删除成功']);
} else {
echo json_encode(['error' => '保存失败']);
}
} else {
echo json_encode(['error' => '链接不存在']);
}
break;
case 'get_data':
echo json_encode($data);
break;
default:
echo json_encode(['error' => '未知操作']);
}
exit;
}
// 如果未登录,显示登录页面
if (!isLoggedIn()) {
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录 - <?php echo APP_NAME; ?></title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.login-container {
background: white;
padding: 40px;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
width: 100%;
max-width: 400px;
}
.login-header {
text-align: center;
margin-bottom: 30px;
}
.login-header h1 {
font-size: 28px;
color: #333;
margin-bottom: 10px;
}
.login-header p {
color: #666;
font-size: 14px;
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
margin-bottom: 8px;
color: #555;
font-size: 14px;
}
.form-input {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
transition: border-color 0.3s;
}
.form-input:focus {
outline: none;
border-color: #667eea;
}
.login-btn {
width: 100%;
padding: 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
transition: transform 0.2s;
}
.login-btn:hover {
transform: translateY(-2px);
}
.error {
background: #fee;
color: #c33;
padding: 10px;
border-radius: 6px;
margin-bottom: 20px;
font-size: 14px;
}
.default-info {
margin-top: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 6px;
font-size: 13px;
color: #666;
}
</style>
</head>
<body>
<div class="login-container">
<div class="login-header">
<h1><?php echo APP_NAME; ?></h1>
<p>请登录以管理您的导航</p>
</div>
<?php if (isset($error)): ?>
<div class="error"><?php echo $error; ?></div>
<?php endif; ?>
<form method="post">
<input type="hidden" name="action" value="login">
<div class="form-group">
<label class="form-label">用户名</label>
<input type="text" name="username" class="form-input" required>
</div>
<div class="form-group">
<label class="form-label">密码</label>
<input type="password" name="password" class="form-input" required>
</div>
<button type="submit" class="login-btn">登录</button>
</form>
<div class="default-info">
<strong>默认账号:</strong><br>
用户名:admin<br>
密码:password
</div>
</div>
</body>
</html>
<?php
exit;
}
// 已登录,显示导航页面
$navData = getNavData();
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo APP_NAME; ?></title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg-primary: #ffffff;
--bg-secondary: #f8f9fa;
--text-primary: #2c3e50;
--text-secondary: #6c757d;
--text-muted: #adb5bd;
--border-color: #e9ecef;
--accent-color: #3498db;
--hover-bg: #f1f3f5;
--shadow-sm: 0 2px 4px rgba(0,0,0,0.08);
--shadow-md: 0 4px 12px rgba(0,0,0,0.1);
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
[data-theme="dark"] {
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--text-primary: #e9ecef;
--text-secondary: #adb5bd;
--text-muted: #6c757d;
--border-color: #404040;
--accent-color: #5dade2;
--hover-bg: #343434;
--shadow-sm: 0 2px 4px rgba(0,0,0,0.3);
--shadow-md: 0 4px 12px rgba(0,0,0,0.4);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
transition: var(--transition);
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* Header */
header {
padding: 40px 0;
text-align: center;
border-bottom: 1px solid var(--border-color);
margin-bottom: 40px;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 800px;
margin: 0 auto;
}
h1 {
font-size: 2rem;
font-weight: 300;
letter-spacing: 2px;
color: var(--text-primary);
}
.header-actions {
display: flex;
gap: 10px;
align-items: center;
}
.theme-toggle, .logout-btn, .manage-btn {
background: none;
border: 1px solid var(--border-color);
color: var(--text-primary);
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
transition: var(--transition);
font-size: 14px;
text-decoration: none;
}
.theme-toggle:hover, .logout-btn:hover, .manage-btn:hover {
background: var(--hover-bg);
transform: translateY(-1px);
}
.manage-btn {
background: var(--accent-color);
color: white;
border-color: var(--accent-color);
}
.manage-btn:hover {
opacity: 0.9;
}
/* Search */
.search-section {
margin-bottom: 50px;
}
.search-container {
max-width: 600px;
margin: 0 auto;
position: relative;
}
.search-input {
width: 100%;
padding: 16px 20px;
font-size: 16px;
border: 1px solid var(--border-color);
border-radius: 8px;
background: var(--bg-secondary);
color: var(--text-primary);
transition: var(--transition);
}
.search-input:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
}
.search-hint {
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
color: var(--text-muted);
font-size: 14px;
pointer-events: none;
}
/* Quick Links */
.quick-links {
background: var(--bg-secondary);
border-radius: 12px;
padding: 30px;
margin-bottom: 40px;
border: 1px solid var(--border-color);
}
.quick-links-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.quick-links-title {
font-size: 20px;
font-weight: 500;
color: var(--text-primary);
}
.add-quick-link {
background: var(--accent-color);
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: var(--transition);
}
.add-quick-link:hover {
opacity: 0.9;
}
.quick-links-grid {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: center;
}
.quick-link {
color: var(--text-secondary);
text-decoration: none;
padding: 8px 16px;
border: 1px solid var(--border-color);
border-radius: 20px;
transition: var(--transition);
font-size: 14px;
position: relative;
}
.quick-link:hover {
background: var(--accent-color);
color: white;
border-color: var(--accent-color);
transform: scale(1.05);
}
.quick-link .delete-btn {
display: none;
position: absolute;
top: -8px;
right: -8px;
background: #e74c3c;
color: white;
border: none;
border-radius: 50%;
width: 20px;
height: 20px;
font-size: 12px;
cursor: pointer;
}
.quick-link:hover .delete-btn {
display: block;
}
/* Navigation Grid */
.nav-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 30px;
margin-bottom: 50px;
}
.nav-category {
background: var(--bg-secondary);
border-radius: 12px;
padding: 24px;
transition: var(--transition);
border: 1px solid var(--border-color);
position: relative;
}
.nav-category:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.category-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.category-title {
font-size: 18px;
font-weight: 500;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 8px;
}
.category-title::before {
content: '';
width: 3px;
height: 18px;
background: var(--accent-color);
border-radius: 2px;
}
.category-actions {
display: flex;
gap: 5px;
}
.category-btn {
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
font-size: 14px;
padding: 4px;
transition: var(--transition);
}
.category-btn:hover {
color: var(--accent-color);
}
.link-list {
list-style: none;
}
.link-item {
margin-bottom: 12px;
display: flex;
justify-content: space-between;
align-items: center;
}
.link-item:last-child {
margin-bottom: 0;
}
.link {
color: var(--text-secondary);
text-decoration: none;
font-size: 15px;
display: inline-block;
transition: var(--transition);
position: relative;
padding: 4px 0;
flex: 1;
}
.link::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 1px;
background: var(--accent-color);
transition: width 0.3s ease;
}
.link:hover {
color: var(--accent-color);
transform: translateX(4px);
}
.link:hover::after {
width: 100%;
}
.link-actions {
display: none;
gap: 5px;
}
.link-item:hover .link-actions {
display: flex;
}
.link-btn {
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
font-size: 12px;
padding: 2px;
transition: var(--transition);
}
.link-btn:hover {
color: var(--accent-color);
}
.link-btn.delete {
color: #e74c3c;
}
.link-btn.delete:hover {
color: #c0392b;
}
/* Footer */
footer {
text-align: center;
padding: 30px 0;
color: var(--text-muted);
font-size: 14px;
border-top: 1px solid var(--border-color);
}
/* Modal */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
animation: fadeIn 0.3s ease;
}
.modal.active {
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: var(--bg-primary);
border-radius: 12px;
padding: 30px;
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
animation: slideUp 0.3s ease;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.modal-title {
font-size: 20px;
font-weight: 500;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: var(--text-secondary);
transition: var(--transition);
}
.close-btn:hover {
color: var(--text-primary);
transform: rotate(90deg);
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-size: 14px;
color: var(--text-secondary);
}
.form-input {
width: 100%;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: 6px;
background: var(--bg-secondary);
color: var(--text-primary);
font-size: 14px;
}
.form-input:focus {
outline: none;
border-color: var(--accent-color);
}
.btn-primary {
background: var(--accent-color);
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
cursor: pointer;
transition: var(--transition);
font-size: 14px;
}
.btn-primary:hover {
opacity: 0.9;
transform: translateY(-1px);
}
/* Notification */
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
border-radius: 6px;
color: white;
z-index: 2000;
animation: slideInRight 0.3s ease;
display: none;
}
.notification.success {
background: #27ae60;
}
.notification.error {
background: #e74c3c;
}
/* Animations */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from {
transform: translateY(20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* Responsive */
@media (max-width: 768px) {
h1 {
font-size: 1.5rem;
}
.header-content {
flex-direction: column;
gap: 20px;
}
.nav-grid {
grid-template-columns: 1fr;
gap: 20px;
}
.quick-links-grid {
gap: 12px;
}
.quick-link {
font-size: 13px;
padding: 6px 12px;
}
}
</style>
</head>
<body>
<header>
<div class="container">
<div class="header-content">
<h1><?php echo APP_NAME; ?></h1>
<div class="header-actions">
<button class="theme-toggle" onclick="toggleTheme()">🌙 深色</button>
<a href="?logout" class="logout-btn">退出</a>
<button class="manage-btn" onclick="toggleManageMode()">管理</button>
</div>
</div>
</div>
</header>
<main class="container">
<!-- Search Section -->
<section class="search-section">
<div class="search-container">
<input
type="text"
class="search-input"
id="searchInput"
placeholder="搜索网站..."
onkeyup="searchLinks(event)"
>
<span class="search-hint">Ctrl+K</span>
</div>
</section>
<!-- Quick Links -->
<section class="quick-links">
<div class="quick-links-header">
<h2 class="quick-links-title">快速访问</h2>
<button class="add-quick-link" onclick="addQuickLink()" style="display:none;">+ 添加</button>
</div>
<div class="quick-links-grid" id="quickLinks">
<?php foreach ($navData['quick_links'] as $index => $link): ?>
<a href="<?php echo htmlspecialchars($link['url']); ?>" class="quick-link" target="_blank">
<?php echo htmlspecialchars($link['name']); ?>
<button class="delete-btn" onclick="deleteQuickLink(event, <?php echo $index; ?>)" style="display:none;">×</button>
</a>
<?php endforeach; ?>
</div>
</section>
<!-- Navigation Grid -->
<div class="nav-grid" id="navGrid">
<?php foreach ($navData['categories'] as $categoryIndex => $category): ?>
<div class="nav-category">
<div class="category-header">
<h3 class="category-title"><?php echo htmlspecialchars($category['title']); ?></h3>
<div class="category-actions" style="display:none;">
<button class="category-btn" onclick="editCategory(<?php echo $categoryIndex; ?>)">✏️</button>
<button class="category-btn delete" onclick="deleteCategory(<?php echo $categoryIndex; ?>)">🗑️</button>
<button class="category-btn" onclick="addLink(<?php echo $categoryIndex; ?>)"></button>
</div>
</div>
<ul class="link-list">
<?php foreach ($category['links'] as $linkIndex => $link): ?>
<li class="link-item">
<a href="<?php echo htmlspecialchars($link['url']); ?>" class="link" target="_blank">
<?php echo htmlspecialchars($link['name']); ?>
</a>
<div class="link-actions" style="display:none;">
<button class="link-btn" onclick="editLink(<?php echo $categoryIndex; ?>, <?php echo $linkIndex; ?>)">✏️</button>
<button class="link-btn delete" onclick="deleteLink(<?php echo $categoryIndex; ?>, <?php echo $linkIndex; ?>)">🗑️</button>
</div>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endforeach; ?>
<!-- Add Category Button -->
<div class="nav-category" id="addCategoryBtn" style="display:none; border: 2px dashed var(--border-color); cursor: pointer;" onclick="addCategory()">
<div style="display: flex; align-items: center; justify-content: center; height: 100%; min-height: 200px; color: var(--text-muted);">
<div style="text-align: center;">
<div style="font-size: 48px; margin-bottom: 10px;"></div>
<div>添加新分类</div>
</div>
</div>
</div>
</div>
</main>
<footer>
<div class="container">
<p>© 2024 <?php echo APP_NAME; ?> v<?php echo VERSION; ?> | 用心收集,高效访问</p>
</div>
</footer>
<!-- Modal -->
<div class="modal" id="editModal">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title" id="modalTitle">编辑</h3>
<button class="close-btn" onclick="closeModal()">×</button>
</div>
<form id="editForm" onsubmit="handleSubmit(event)">
<div class="form-group">
<label class="form-label" id="label1">名称</label>
<input type="text" class="form-input" id="input1" name="input1" required>
</div>
<div class="form-group" id="urlGroup">
<label class="form-label" id="label2">链接</label>
<input type="url" class="form-input" id="input2" name="input2">
</div>
<button type="submit" class="btn-primary">保存</button>
</form>
</div>
</div>
<!-- Notification -->
<div class="notification" id="notification"></div>
<script>
let manageMode = false;
let currentEdit = {
type: '',
index: -1,
categoryIndex: -1,
linkIndex: -1
};
// Theme Toggle
function toggleTheme() {
const body = document.body;
const themeToggle = document.querySelector('.theme-toggle');
if (body.getAttribute('data-theme') === 'dark') {
body.removeAttribute('data-theme');
themeToggle.textContent = '🌙 深色';
localStorage.setItem('theme', 'light');
} else {
body.setAttribute('data-theme', 'dark');
themeToggle.textContent = '☀️ 浅色';
localStorage.setItem('theme', 'dark');
}
}
// Load saved theme
window.addEventListener('DOMContentLoaded', () => {
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
document.body.setAttribute('data-theme', 'dark');
document.querySelector('.theme-toggle').textContent = '☀️ 浅色';
}
});
// Search functionality
function searchLinks(event) {
const searchTerm = event.target.value.toLowerCase();
const allLinks = document.querySelectorAll('.link');
const allCategories = document.querySelectorAll('.nav-category');
const quickLinks = document.querySelectorAll('.quick-link');
if (searchTerm === '') {
allLinks.forEach(link => {
link.parentElement.style.display = 'flex';
});
allCategories.forEach(category => {
category.style.display = 'block';
});
quickLinks.forEach(link => {
link.parentElement.style.display = 'inline-flex';
});
return;
}
allCategories.forEach(category => {
let hasVisibleLinks = false;
const links = category.querySelectorAll('.link');
links.forEach(link => {
const text = link.textContent.toLowerCase();
if (text.includes(searchTerm)) {
link.parentElement.style.display = 'flex';
hasVisibleLinks = true;
} else {
link.parentElement.style.display = 'none';
}
});
category.style.display = hasVisibleLinks ? 'block' : 'none';
});
quickLinks.forEach(link => {
const text = link.textContent.toLowerCase();
link.parentElement.style.display = text.includes(searchTerm) ? 'inline-flex' : 'none';
});
}
// Keyboard shortcut for search
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
document.getElementById('searchInput').focus();
}
});
// Toggle manage mode
function toggleManageMode() {
manageMode = !manageMode;
const manageBtn = document.querySelector('.manage-btn');
const hiddenElements = document.querySelectorAll('.category-actions, .link-actions, .delete-btn, .add-quick-link, #addCategoryBtn');
if (manageMode) {
manageBtn.textContent = '完成';
manageBtn.style.background = '#e74c3c';
hiddenElements.forEach(el => el.style.display = '');
} else {
manageBtn.textContent = '管理';
manageBtn.style.background = '';
hiddenElements.forEach(el => el.style.display = 'none');
}
}
// Show notification
function showNotification(message, type = 'success') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = `notification ${type}`;
notification.style.display = 'block';
setTimeout(() => {
notification.style.display = 'none';
}, 3000);
}
// Modal functions
function openModal(title, showUrl = true) {
document.getElementById('modalTitle').textContent = title;
const urlGroup = document.getElementById('urlGroup');
const urlInput = document.getElementById('input2');
if (showUrl) {
urlGroup.style.display = 'block';
urlInput.required = true;
} else {
urlGroup.style.display = 'none';
urlInput.required = false;
urlInput.value = ''; // 清空URL输入框
}
document.getElementById('editModal').classList.add('active');
}
function closeModal() {
document.getElementById('editModal').classList.remove('active');
document.getElementById('editForm').reset();
}
// Category functions
function addCategory() {
currentEdit = { type: 'add_category' };
openModal('添加分类', false);
document.getElementById('label1').textContent = '分类名称';
}
function editCategory(index) {
currentEdit = { type: 'edit_category', index };
const title = document.querySelectorAll('.category-title')[index].textContent.trim();
openModal('编辑分类', false);
document.getElementById('label1').textContent = '分类名称';
document.getElementById('input1').value = title;
}
function deleteCategory(index) {
if (confirm('确定要删除这个分类吗?')) {
apiRequest('delete_category', { index: index });
}
}
// Link functions
function addLink(categoryIndex) {
currentEdit = { type: 'add_link', categoryIndex };
openModal('添加链接', true);
document.getElementById('label1').textContent = '链接名称';
document.getElementById('label2').textContent = '链接地址';
}
function editLink(categoryIndex, linkIndex) {
currentEdit = { type: 'edit_link', categoryIndex, linkIndex };
const link = document.querySelectorAll('.nav-category')[categoryIndex].querySelectorAll('.link')[linkIndex];
openModal('编辑链接', true);
document.getElementById('label1').textContent = '链接名称';
document.getElementById('label2').textContent = '链接地址';
document.getElementById('input1').value = link.textContent.trim();
document.getElementById('input2').value = link.href;
}
function deleteLink(categoryIndex, linkIndex) {
if (confirm('确定要删除这个链接吗?')) {
apiRequest('delete_link', { type: 'category', category_index: categoryIndex, link_index: linkIndex });
}
}
// Quick link functions
function addQuickLink() {
currentEdit = { type: 'add_quick_link' };
openModal('添加快速链接', true);
document.getElementById('label1').textContent = '链接名称';
document.getElementById('label2').textContent = '链接地址';
}
function deleteQuickLink(event, index) {
event.preventDefault();
event.stopPropagation();
if (confirm('确定要删除这个快速链接吗?')) {
apiRequest('delete_link', { type: 'quick', link_index: index });
}
}
// Handle form submit
function handleSubmit(event) {
event.preventDefault();
const input1 = document.getElementById('input1').value;
const input2 = document.getElementById('input2').value;
switch (currentEdit.type) {
case 'add_category':
apiRequest('add_category', { title: input1 });
break;
case 'edit_category':
apiRequest('edit_category', { index: currentEdit.index, title: input1 });
break;
case 'add_link':
apiRequest('add_link', { type: 'category', index: currentEdit.categoryIndex, name: input1, url: input2 });
break;
case 'edit_link':
apiRequest('edit_link', { type: 'category', category_index: currentEdit.categoryIndex, link_index: currentEdit.linkIndex, name: input1, url: input2 });
break;
case 'add_quick_link':
apiRequest('add_link', { type: 'quick', name: input1, url: input2 });
break;
}
closeModal();
}
// API request
function apiRequest(action, data) {
data.api = action;
const params = new URLSearchParams();
for (const key in data) {
params.append(key, data[key]);
}
fetch('', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params
})
.then(response => response.json())
.then(result => {
if (result.success) {
showNotification(result.message || '操作成功');
setTimeout(() => {
location.reload();
}, 1000);
} else {
showNotification(result.error || '操作失败', 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('请求失败', 'error');
});
}
// Close modal when clicking outside
document.getElementById('editModal').addEventListener('click', (e) => {
if (e.target === e.currentTarget) {
closeModal();
}
});
</script>
</body>
</html>
<?php
// End of file
?>