2192 lines
81 KiB
JavaScript
2192 lines
81 KiB
JavaScript
addEventListener('fetch', (event) => {
|
||
event.respondWith(handleRequest(event.request));
|
||
});
|
||
|
||
async function handleRequest(request) {
|
||
const url = new URL(request.url);
|
||
const { pathname } = url;
|
||
|
||
try {
|
||
// 登录请求处理
|
||
if (pathname === '/login' && request.method === 'POST') {
|
||
const formData = new URLSearchParams(await request.text());
|
||
const username = formData.get('username');
|
||
const password = formData.get('password');
|
||
|
||
if (username === envusername && password === envpassword) {
|
||
return new Response(await renderNavigationPage(), {
|
||
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
||
});
|
||
}
|
||
// 登录失败时直接返回登录页面并显示错误
|
||
return new Response(await renderLoginPage(true), {
|
||
headers: { 'Content-Type': 'text/html; charset=utf-8' }
|
||
});
|
||
}
|
||
|
||
// 路由处理
|
||
switch (pathname) {
|
||
case '/':
|
||
case '/login.html':
|
||
return new Response(await renderLoginPage(), {
|
||
headers: { 'Content-Type': 'text/html; charset=utf-8' }
|
||
});
|
||
|
||
case '/privacy':
|
||
return serveStaticFile('privacy.html');
|
||
|
||
case '/data':
|
||
return fetchNavigationData();
|
||
|
||
case '/add-category':
|
||
if (request.method === 'POST') return addCategory(request);
|
||
break;
|
||
|
||
case '/add-site':
|
||
if (request.method === 'POST') return addSite(request);
|
||
break;
|
||
|
||
case '/delete-category':
|
||
if (request.method === 'POST') return deleteCategory(request);
|
||
break;
|
||
|
||
case '/delete-site':
|
||
if (request.method === 'POST') return deleteSite(request);
|
||
break;
|
||
|
||
case '/edit-site':
|
||
if (request.method === 'POST') return editSite(request);
|
||
break;
|
||
case '/edit-category':
|
||
if (request.method === 'POST') return editCategory(request);
|
||
break;
|
||
// 在handleRequest中添加切换分类状态的路由
|
||
case '/toggle-category':
|
||
if (request.method === 'POST') return toggleCategory(request);
|
||
break;
|
||
// 在handleRequest的switch语句中添加新路由
|
||
case '/reorder-site':
|
||
if (request.method === 'POST') return reorderSite(request);
|
||
break;
|
||
case '/load-notification':
|
||
return loadNotification();
|
||
case '/export':
|
||
return exportData(); // 导出接口
|
||
|
||
case '/import':
|
||
if (request.method === 'POST') return importData(request);
|
||
break;
|
||
}
|
||
|
||
// 未匹配的路由返回404
|
||
return serveStaticFile('NotFound.html', 404);
|
||
} catch (error) {
|
||
console.error('Error handling request:', error);
|
||
return new Response('Internal Server Error', { status: 500 });
|
||
}
|
||
}
|
||
|
||
// KV 数据操作函数
|
||
async function getNavigationData() {
|
||
const data = await NAVIGATION_DATA.get('data');
|
||
const parsedData = data ? JSON.parse(data) : { categories: [] };
|
||
|
||
// 初始化折叠状态
|
||
parsedData.categories.forEach(category => {
|
||
if (typeof category.collapsed === 'undefined') {
|
||
category.collapsed = false; // 默认展开状态
|
||
}
|
||
});
|
||
|
||
return parsedData;
|
||
}
|
||
// 导出导航数据为 JSON 文件
|
||
async function exportData() {
|
||
const navigationData = await getNavigationData(); // 复用已有函数
|
||
const jsonStr = JSON.stringify(navigationData, null, 2);
|
||
|
||
return new Response(jsonStr, {
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Content-Disposition': 'attachment; filename="navigation_backup.json"',
|
||
},
|
||
});
|
||
}
|
||
// 从上传的 JSON 文件导入导航数据
|
||
async function importData(request) {
|
||
try {
|
||
const formData = await request.formData();
|
||
const file = formData.get('file');
|
||
|
||
if (!file) {
|
||
return new Response(JSON.stringify({ error: '请选择要导入的文件' }), {
|
||
status: 400,
|
||
headers: { 'Content-Type': 'application/json' },
|
||
});
|
||
}
|
||
|
||
// 读取文件内容
|
||
const fileText = await file.text();
|
||
const importedData = JSON.parse(fileText);
|
||
|
||
// 简单验证格式:必须包含 categories 数组
|
||
if (!importedData || !Array.isArray(importedData.categories)) {
|
||
return new Response(JSON.stringify({ error: '无效的备份文件格式,缺少 categories 数组' }), {
|
||
status: 400,
|
||
headers: { 'Content-Type': 'application/json' },
|
||
});
|
||
}
|
||
|
||
// 可选:进一步验证每个分类和站点结构(这里略,可根据需要补充)
|
||
|
||
// 写入 KV
|
||
await NAVIGATION_DATA.put('data', JSON.stringify(importedData));
|
||
|
||
return new Response(JSON.stringify({ message: '数据导入成功' }), {
|
||
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
||
});
|
||
} catch (error) {
|
||
return new Response(JSON.stringify({ error: '导入失败:' + error.message }), {
|
||
status: 500,
|
||
headers: { 'Content-Type': 'application/json' },
|
||
});
|
||
}
|
||
}
|
||
// 添加站点重新排序功能
|
||
async function reorderSite(request) {
|
||
const requestBody = await request.json();
|
||
const { categoryIndex, oldIndex, newIndex } = requestBody;
|
||
const navigationData = await getNavigationData();
|
||
|
||
// 验证分类索引
|
||
if (categoryIndex < 0 || categoryIndex >= navigationData.categories.length) {
|
||
return new Response(JSON.stringify({ error: '无效分类索引' }), {
|
||
status: 400,
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
}
|
||
|
||
const category = navigationData.categories[categoryIndex];
|
||
// 验证站点索引
|
||
if (oldIndex < 0 || oldIndex >= category.sites.length ||
|
||
newIndex < 0 || newIndex >= category.sites.length) {
|
||
return new Response(JSON.stringify({ error: '无效站点索引' }), {
|
||
status: 400,
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
}
|
||
|
||
// 移动站点位置
|
||
const siteToMove = category.sites.splice(oldIndex, 1)[0];
|
||
category.sites.splice(newIndex, 0, siteToMove);
|
||
|
||
await setNavigationData(navigationData);
|
||
return new Response(JSON.stringify({ message: '站点顺序已更新' }), {
|
||
headers: { 'Content-Type': 'application/json; charset=utf-8' }
|
||
});
|
||
}
|
||
// 添加编辑分类的API处理函数
|
||
async function editCategory(request) {
|
||
const requestBody = await request.json();
|
||
const navigationData = await getNavigationData();
|
||
|
||
// 验证索引有效性
|
||
if (requestBody.categoryIndex < 0 ||
|
||
requestBody.categoryIndex >= navigationData.categories.length) {
|
||
return new Response(JSON.stringify({ error: '无效分类索引' }), {
|
||
status: 400,
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
}
|
||
|
||
// 检查新名称是否已存在
|
||
const newName = requestBody.newName.trim();
|
||
const categoryExists = navigationData.categories.some(
|
||
(cat, index) => index !== requestBody.categoryIndex && cat.name === newName
|
||
);
|
||
|
||
if (categoryExists) {
|
||
return new Response(JSON.stringify({ error: '分类名称已存在' }), {
|
||
status: 400,
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
}
|
||
|
||
// 更新分类名称
|
||
navigationData.categories[requestBody.categoryIndex].name = newName;
|
||
|
||
await setNavigationData(navigationData);
|
||
return new Response(JSON.stringify({ message: '分类名称修改成功' }), {
|
||
headers: { 'Content-Type': 'application/json; charset=utf-8' }
|
||
});
|
||
}
|
||
// 添加切换分类折叠状态的API处理函数
|
||
async function toggleCategory(request) {
|
||
const requestBody = await request.json();
|
||
const navigationData = await getNavigationData();
|
||
|
||
// 验证索引有效性
|
||
if (requestBody.categoryIndex < 0 ||
|
||
requestBody.categoryIndex >= navigationData.categories.length) {
|
||
return new Response(JSON.stringify({ error: '无效分类索引' }), {
|
||
status: 400,
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
}
|
||
|
||
// 切换折叠状态
|
||
const category = navigationData.categories[requestBody.categoryIndex];
|
||
category.collapsed = !category.collapsed;
|
||
|
||
await setNavigationData(navigationData);
|
||
return new Response(JSON.stringify({
|
||
message: '状态更新成功',
|
||
collapsed: category.collapsed
|
||
}), {
|
||
headers: { 'Content-Type': 'application/json; charset=utf-8' }
|
||
});
|
||
}
|
||
|
||
// 切换分类折叠状态
|
||
async function toggleCategoryState(categoryIndex) {
|
||
const response = await fetch('/toggle-category', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ categoryIndex })
|
||
});
|
||
|
||
if (response.ok) {
|
||
const result = await response.json();
|
||
const categoryElement = document.getElementById(`category-sites-${categoryIndex}`);
|
||
const toggleIcon = document.querySelector(`.toggle-btn[onclick="toggleCategoryState(${categoryIndex})"] .iconify`);
|
||
|
||
if (result.collapsed) {
|
||
categoryElement.style.display = 'none';
|
||
toggleIcon.setAttribute('data-icon', 'mdi:chevron-right');
|
||
} else {
|
||
categoryElement.style.display = 'flex';
|
||
toggleIcon.setAttribute('data-icon', 'mdi:chevron-down');
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
async function setNavigationData(data) {
|
||
await NAVIGATION_DATA.put('data', JSON.stringify(data));
|
||
}
|
||
|
||
// 静态文件服务
|
||
async function serveStaticFile(filename, status = 200) {
|
||
const content = await STATIC_FILES.get(filename);
|
||
if (content) {
|
||
return new Response(content, {
|
||
status,
|
||
headers: { 'Content-Type': 'text/html; charset=utf-8' }
|
||
});
|
||
}
|
||
return new Response(`${filename} not found`, { status: 404 });
|
||
}
|
||
|
||
// API 端点处理函数
|
||
async function fetchNavigationData() {
|
||
const navigationData = await getNavigationData();
|
||
return new Response(JSON.stringify(navigationData), {
|
||
headers: {
|
||
'Content-Type': 'application/json; charset=utf-8',
|
||
'Cache-Control': 'no-cache'
|
||
}
|
||
});
|
||
}
|
||
|
||
async function addCategory(request) {
|
||
const requestBody = await request.json();
|
||
const navigationData = await getNavigationData();
|
||
|
||
// 检查分类是否已存在
|
||
const categoryExists = navigationData.categories.some(
|
||
cat => cat.name === requestBody.name
|
||
);
|
||
|
||
if (categoryExists) {
|
||
return new Response(JSON.stringify({ error: '分类已存在' }), {
|
||
status: 400,
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
}
|
||
|
||
navigationData.categories.push({
|
||
name: requestBody.name,
|
||
sites: []
|
||
});
|
||
|
||
await setNavigationData(navigationData);
|
||
return new Response(JSON.stringify({ message: '分类添加成功' }), {
|
||
headers: { 'Content-Type': 'application/json; charset=utf-8' }
|
||
});
|
||
}
|
||
|
||
async function addSite(request) {
|
||
const requestBody = await request.json();
|
||
const navigationData = await getNavigationData();
|
||
|
||
// 验证索引有效性
|
||
if (requestBody.categoryIndex < 0 ||
|
||
requestBody.categoryIndex >= navigationData.categories.length) {
|
||
return new Response(JSON.stringify({ error: '无效分类索引' }), {
|
||
status: 400,
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
}
|
||
|
||
// 添加站点描述字段
|
||
navigationData.categories[requestBody.categoryIndex].sites.push({
|
||
name: requestBody.siteName,
|
||
url: requestBody.siteUrl,
|
||
icon: requestBody.siteIcon,
|
||
info: requestBody.siteInfo || '' // 添加站点描述
|
||
});
|
||
|
||
await setNavigationData(navigationData);
|
||
return new Response(JSON.stringify({ message: '站点添加成功' }), {
|
||
headers: { 'Content-Type': 'application/json; charset=utf-8' }
|
||
});
|
||
}
|
||
|
||
async function editSite(request) {
|
||
const requestBody = await request.json();
|
||
const navigationData = await getNavigationData();
|
||
|
||
// 获取原始分类索引和新分类索引
|
||
const originalCategoryIndex = requestBody.originalCategoryIndex;
|
||
const newCategoryIndex = requestBody.newCategoryIndex;
|
||
const siteIndex = requestBody.siteIndex;
|
||
|
||
// 验证索引有效性
|
||
if (originalCategoryIndex < 0 ||
|
||
originalCategoryIndex >= navigationData.categories.length ||
|
||
newCategoryIndex < 0 ||
|
||
newCategoryIndex >= navigationData.categories.length) {
|
||
return new Response(JSON.stringify({ error: '无效分类索引' }), {
|
||
status: 400,
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
}
|
||
|
||
const originalCategory = navigationData.categories[originalCategoryIndex];
|
||
if (siteIndex < 0 || siteIndex >= originalCategory.sites.length) {
|
||
return new Response(JSON.stringify({ error: '无效站点索引' }), {
|
||
status: 400,
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
}
|
||
|
||
// 获取站点对象
|
||
const site = originalCategory.sites[siteIndex];
|
||
|
||
// 更新站点信息
|
||
site.name = requestBody.siteName;
|
||
site.url = requestBody.siteUrl;
|
||
site.icon = requestBody.siteIcon;
|
||
site.info = requestBody.siteInfo || '';
|
||
|
||
// 如果分类发生变化,移动站点到新分类
|
||
if (originalCategoryIndex !== newCategoryIndex) {
|
||
// 从原分类移除
|
||
originalCategory.sites.splice(siteIndex, 1);
|
||
|
||
// 添加到新分类
|
||
navigationData.categories[newCategoryIndex].sites.push(site);
|
||
}
|
||
|
||
await setNavigationData(navigationData);
|
||
return new Response(JSON.stringify({ message: '站点更新成功' }), {
|
||
headers: { 'Content-Type': 'application/json; charset=utf-8' }
|
||
});
|
||
}
|
||
|
||
async function deleteCategory(request) {
|
||
const requestBody = await request.json();
|
||
const navigationData = await getNavigationData();
|
||
|
||
// 验证索引有效性
|
||
if (requestBody.categoryIndex < 0 ||
|
||
requestBody.categoryIndex >= navigationData.categories.length) {
|
||
return new Response(JSON.stringify({ error: '无效分类索引' }), {
|
||
status: 400,
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
}
|
||
|
||
navigationData.categories.splice(requestBody.categoryIndex, 1);
|
||
await setNavigationData(navigationData);
|
||
return new Response(JSON.stringify({ message: '分类删除成功' }), {
|
||
headers: { 'Content-Type': 'application/json; charset=utf-8' }
|
||
});
|
||
}
|
||
|
||
async function deleteSite(request) {
|
||
const requestBody = await request.json();
|
||
const navigationData = await getNavigationData();
|
||
|
||
// 验证索引有效性
|
||
if (requestBody.categoryIndex < 0 ||
|
||
requestBody.categoryIndex >= navigationData.categories.length) {
|
||
return new Response(JSON.stringify({ error: '无效分类索引' }), {
|
||
status: 400,
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
}
|
||
|
||
const category = navigationData.categories[requestBody.categoryIndex];
|
||
if (requestBody.siteIndex < 0 || requestBody.siteIndex >= category.sites.length) {
|
||
return new Response(JSON.stringify({ error: '无效站点索引' }), {
|
||
status: 400,
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
}
|
||
|
||
category.sites.splice(requestBody.siteIndex, 1);
|
||
await setNavigationData(navigationData);
|
||
return new Response(JSON.stringify({ message: '站点删除成功' }), {
|
||
headers: { 'Content-Type': 'application/json; charset=utf-8' }
|
||
});
|
||
}
|
||
|
||
async function loadNotification() {
|
||
const notification = await NAVIGATION_DATA.get('info');
|
||
return new Response(notification || '', {
|
||
headers: { 'Cache-Control': 'no-cache' }
|
||
});
|
||
}
|
||
|
||
// 页面渲染函数
|
||
async function renderLoginPage(showError = false) {
|
||
return `
|
||
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Dispark|点滴星火登录页</title>
|
||
<style>
|
||
body {
|
||
margin: 0;
|
||
height: 100vh;
|
||
background-image: url('https://cdn.pixabay.com/photo/2024/09/06/02/03/hill-9026381_960_720.png');
|
||
background-size: cover;
|
||
background-position: center;
|
||
font-family: Arial, sans-serif;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
flex-direction: column;
|
||
}
|
||
h1 {
|
||
position: absolute;
|
||
top: 20px;
|
||
left: 20px;
|
||
font-size: 36px;
|
||
margin: 0;
|
||
color: #fff;
|
||
}
|
||
.login-container {
|
||
background-color: rgba(255, 255, 255, 0.8);
|
||
padding: 30px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||
width: 100%;
|
||
max-width: 350px;
|
||
text-align: center;
|
||
}
|
||
.login-container input {
|
||
width: 100%;
|
||
padding: 10px;
|
||
margin: 5px 0 15px 0;
|
||
border: 1px solid #ddd;
|
||
border-radius: 5px;
|
||
font-size: 14px;
|
||
background-color: rgba(255, 255, 255, 0.5);
|
||
}
|
||
.login-container button {
|
||
width: 100%;
|
||
padding: 12px;
|
||
background-color: #007BFF;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
}
|
||
.login-container button:hover {
|
||
background-color: #0056b3;
|
||
}
|
||
footer {
|
||
position: absolute;
|
||
bottom: 10px;
|
||
width: 100%;
|
||
text-align: center;
|
||
font-size: 14px;
|
||
color: #fff;
|
||
}
|
||
footer a {
|
||
color: #fff;
|
||
text-decoration: none;
|
||
}
|
||
footer a:hover {
|
||
text-decoration: underline;
|
||
}
|
||
.error-message {
|
||
color: #ff0000;
|
||
margin-bottom: 15px;
|
||
display: ${showError ? 'block' : 'none'};
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1 style="display: flex; align-items: center; justify-content: center; gap: 10px; font-size: 24px;">
|
||
<a href="https://www.9872991.xyz/">
|
||
<img src="https://api.iconify.design/devicon/prometheus.svg?width=64&height=64&color=%23FFFFFF"
|
||
alt="Prometheus Icon" style="filter: invert(1);">
|
||
</a>
|
||
<span>Dispark|点滴星火</span>
|
||
</h1>
|
||
|
||
<div class="login-container">
|
||
<div class="error-message">用户名或密码错误,请重试</div>
|
||
<form action="/login" method="POST">
|
||
<input type="text" id="username" name="username" placeholder="用户名" required>
|
||
<input type="password" id="password" name="password" placeholder="密码" required>
|
||
<button type="submit" id="login-button">登录</button>
|
||
</form>
|
||
</div>
|
||
|
||
<footer>
|
||
<p>
|
||
<span>© 2025</span>
|
||
<a href="https://www.9912987.xyz/" target="_blank">Dispark|点滴星火</a>
|
||
<a href="/privacy">隐私政策</a>
|
||
<a href="mailto:i@dispark.top">联系方式</a>
|
||
<a href="https://wwww.9872991.xyz">更多书签</a>
|
||
</p>
|
||
</footer>
|
||
</body>
|
||
</html>`;
|
||
}
|
||
|
||
async function renderNavigationPage() {
|
||
const navigationData = await getNavigationData();
|
||
|
||
const html = `
|
||
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Dispark|点滴星火</title>
|
||
<link rel="icon" href="https://api.iconify.design/devicon/prometheus.svg" type="image/png">
|
||
<script src="https://code.iconify.design/2/2.0.3/iconify.min.js"></script>
|
||
<style>
|
||
body {
|
||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||
padding: 20px;
|
||
background-color: #f4f4f9;
|
||
color: #333;
|
||
margin: 0;
|
||
}
|
||
/* 搜索框容器 - 顶部居中位置 */
|
||
.search-container {
|
||
position: relative;
|
||
max-width: 200px;
|
||
margin: 0 auto;
|
||
padding: 15px 0;
|
||
}
|
||
|
||
/* 搜索框样式 */
|
||
#searchInput {
|
||
width: 100%;
|
||
padding: 14px 20px 14px 50px;
|
||
font-size: 16px;
|
||
border: none;
|
||
border-radius: 50px;
|
||
background: #fff;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||
transition: all 0.3s ease;
|
||
color: #333;
|
||
outline: none;
|
||
}
|
||
|
||
#searchInput:focus {
|
||
box-shadow: 0 4px 16px rgba(74, 144, 226, 0.3);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
/* 搜索图标 */
|
||
.search-icon {
|
||
position: absolute;
|
||
left: 20px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
color: #999;
|
||
pointer-events: none;
|
||
transition: color 0.3s;
|
||
}
|
||
|
||
#searchInput:focus + .search-icon {
|
||
color: #4A90E2;
|
||
}
|
||
|
||
/* 清除按钮 */
|
||
.clear-btn {
|
||
position: absolute;
|
||
right: 20px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
background: none;
|
||
border: none;
|
||
color: #999;
|
||
cursor: pointer;
|
||
display: none;
|
||
}
|
||
|
||
#searchInput:not(:placeholder-shown) + .search-icon + .clear-btn {
|
||
display: block;
|
||
}
|
||
|
||
/* 搜索结果计数 */
|
||
.search-results {
|
||
text-align: center;
|
||
margin: 10px 0 20px;
|
||
color: #666;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 优化后的 header 样式 */
|
||
header {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
position: sticky;
|
||
top: 0;
|
||
width: 100%;
|
||
background-color: #fff;
|
||
z-index: 100;
|
||
padding: 10px 15px;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
transition: all 0.3s ease;
|
||
}
|
||
/* 添加拖拽样式 */
|
||
.drag-handle {
|
||
cursor: move;
|
||
opacity: 0.5;
|
||
transition: opacity 0.3s;
|
||
margin-right: 5px;
|
||
}
|
||
.drag-handle:hover {
|
||
opacity: 1;
|
||
}
|
||
.dragging {
|
||
opacity: 0.7;
|
||
transform: scale(1.02);
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||
}
|
||
|
||
/* 移动端优化 - 关键改进 */
|
||
@media (max-width: 768px) {
|
||
header {
|
||
flex-direction: column;
|
||
padding: 5px 8px; /* 更紧凑的内边距 */
|
||
}
|
||
header > div {
|
||
width: 100%;
|
||
max-width: 100% !important;
|
||
min-width: 100% !important;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.search-container {
|
||
max-width: 100% !important;
|
||
}
|
||
|
||
.header-buttons {
|
||
justify-content: center !important;
|
||
}
|
||
.header-top {
|
||
flex-direction: column;
|
||
align-items: center;
|
||
width: 100%;
|
||
}
|
||
|
||
.header-left {
|
||
text-align: center;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.header-left h1 {
|
||
font-size: 1.1rem !important; /* 更小的字体 */
|
||
}
|
||
|
||
.main-title {
|
||
font-size: 1.2rem !important; /* 更小的标题 */
|
||
margin: 3px 0 !important;
|
||
}
|
||
|
||
.title-subtext {
|
||
font-size: 0.6rem !important; /* 更小的副标题 */
|
||
margin-top: 0 !important;
|
||
}
|
||
|
||
#datetime {
|
||
font-size: 0.7rem;
|
||
margin: 2px 0;
|
||
}
|
||
|
||
.header-buttons {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 5px;
|
||
width: 100%;
|
||
margin: 5px 0 0 0 !important;
|
||
}
|
||
|
||
.header-buttons button {
|
||
padding: 5px 8px;
|
||
font-size: 11px;
|
||
min-height: 32px;
|
||
}
|
||
|
||
/* 滚动时进一步缩小 header */
|
||
body.scrolled header {
|
||
padding: 3px 5px;
|
||
height: 50px; /* 固定高度 */
|
||
overflow: hidden;
|
||
}
|
||
|
||
body.scrolled .header-middle {
|
||
display: none; /* 滚动时隐藏中间部分 */
|
||
}
|
||
|
||
body.scrolled .header-top {
|
||
width: 100%;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
body.scrolled .header-left {
|
||
flex: 1;
|
||
text-align: left;
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
body.scrolled .header-buttons {
|
||
display: none; /* 滚动时隐藏按钮 */
|
||
}
|
||
|
||
/* 调整主内容区域 */
|
||
main {
|
||
margin-top: 120px; /* 更小的初始高度 */
|
||
transition: margin-top 0.3s ease;
|
||
}
|
||
|
||
body.scrolled main {
|
||
margin-top: 60px; /* 滚动后更小的空间 */
|
||
}
|
||
footer {
|
||
font-size: 12px;
|
||
flex-direction: column;
|
||
height: auto;
|
||
padding: 8px 0;
|
||
}
|
||
footer p {
|
||
flex-direction: column;
|
||
gap: 5px;
|
||
}
|
||
footer a::after {
|
||
content: none;
|
||
}
|
||
}
|
||
|
||
/* 桌面端优化 */
|
||
.header-top {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
width: 100%;
|
||
}
|
||
|
||
.header-middle {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
margin: 5px 0;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.header-buttons {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-right: 20px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
/* 滚动时缩小 header */
|
||
body.scrolled header {
|
||
padding: 5px 15px;
|
||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
body.scrolled .main-title {
|
||
font-size: 2rem;
|
||
}
|
||
|
||
body.scrolled .title-subtext {
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
body.scrolled .header-buttons button {
|
||
padding: 6px 12px;
|
||
}
|
||
|
||
/* 主内容区域调整 */
|
||
main {
|
||
margin-top: 140px;
|
||
transition: margin-top 0.3s ease;
|
||
}
|
||
|
||
body.scrolled main {
|
||
margin-top: 70px;
|
||
}
|
||
main {
|
||
margin-top: 80px;
|
||
padding: 20px;
|
||
}
|
||
section {
|
||
margin-bottom: 30px;
|
||
}
|
||
h2 {
|
||
margin: 0;
|
||
font-size: 1.5rem;
|
||
color: #4A90E2;
|
||
border-bottom: 2px solid #4A90E2;
|
||
padding-bottom: 5px;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
ul {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
gap: 10px;
|
||
}
|
||
li {
|
||
width: 120px;
|
||
padding: 10px;
|
||
background: white;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
|
||
text-align: center;
|
||
transition: all 0.3s ease;
|
||
position: relative;
|
||
}
|
||
li:hover {
|
||
transform: translateY(-5px);
|
||
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
|
||
}
|
||
.item-icon {
|
||
cursor: pointer;
|
||
}
|
||
a {
|
||
display: block;
|
||
margin-top: 8px;
|
||
color: #333;
|
||
font-size: 14px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
text-decoration: none;
|
||
}
|
||
a:hover {
|
||
color: #ff6700;
|
||
}
|
||
.opts {
|
||
margin-top: 8px;
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 5px;
|
||
}
|
||
button {
|
||
cursor: pointer;
|
||
background: none;
|
||
border: none;
|
||
padding: 0;
|
||
}
|
||
#addCategoryForm {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 20px;
|
||
gap: 15px;
|
||
}
|
||
#notification-banner {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
}
|
||
#notification-message {
|
||
display: inline-block;
|
||
padding-left: 100%;
|
||
animation: scroll-left 20s linear infinite;
|
||
}
|
||
@keyframes scroll-left {
|
||
0% { transform: translateX(0); }
|
||
100% { transform: translateX(-100%); }
|
||
}
|
||
.modal {
|
||
display: none;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0,0,0,0.5);
|
||
z-index: 1000;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
/* 新增标题样式 */
|
||
.main-title {
|
||
font-size: 2.5rem;
|
||
font-weight: 700;
|
||
background: linear-gradient(45deg, #4A90E2, #5E60CE, #7B68EE);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
text-fill-color: transparent;
|
||
text-shadow: 0 4px 10px rgba(74, 144, 226, 0.2);
|
||
letter-spacing: 1px;
|
||
position: relative;
|
||
display: inline-block;
|
||
margin: 0 0 10px 0;
|
||
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
|
||
}
|
||
|
||
.main-title::after {
|
||
content: "";
|
||
position: absolute;
|
||
bottom: -10px;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 3px;
|
||
background: linear-gradient(90deg, #4A90E2, #7B68EE);
|
||
border-radius: 2px;
|
||
transform: scaleX(0.8);
|
||
}
|
||
|
||
.title-subtext {
|
||
font-size: 1rem;
|
||
color: #666;
|
||
font-weight: 400;
|
||
letter-spacing: 2px;
|
||
text-transform: uppercase;
|
||
margin-top: 5px;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
/* 添加标题动画 */
|
||
@keyframes titleGlow {
|
||
0% { text-shadow: 0 4px 10px rgba(74, 144, 226, 0.2); }
|
||
50% { text-shadow: 0 4px 20px rgba(123, 104, 238, 0.4); }
|
||
100% { text-shadow: 0 4px 10px rgba(74, 144, 226, 0.2); }
|
||
}
|
||
|
||
.main-title {
|
||
animation: titleGlow 4s ease-in-out infinite;
|
||
}
|
||
/* 修改模态框为不透明背景 */
|
||
.modal-content {
|
||
background: #fff; /* 改为纯白色不透明背景 */
|
||
padding: 25px;
|
||
border-radius: 10px;
|
||
width: 90%;
|
||
max-width: 500px;
|
||
position: relative;
|
||
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
||
}
|
||
/* 添加分类模态框特定样式 */
|
||
/* 优化添加分类模态框为水平布局 */
|
||
#addCategoryModal .modal-content {
|
||
background: #fff;
|
||
border-radius: 10px;
|
||
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
|
||
padding: 25px;
|
||
max-width: 500px;
|
||
width: 90%;
|
||
}
|
||
|
||
#addCategoryModal h2 {
|
||
font-size: 20px;
|
||
color: #333;
|
||
margin-top: 0;
|
||
margin-bottom: 20px;
|
||
font-weight: 600;
|
||
text-align: center;
|
||
}
|
||
|
||
#addCategoryForm {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
#addCategoryForm input {
|
||
flex: 1;
|
||
padding: 12px 15px;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 6px;
|
||
font-size: 15px;
|
||
transition: all 0.3s;
|
||
height: 46px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
#addCategoryForm input:focus {
|
||
border-color: #ff9800;
|
||
box-shadow: 0 0 0 2px rgba(255, 152, 0, 0.2);
|
||
outline: none;
|
||
}
|
||
|
||
#addCategoryForm button[type="submit"] {
|
||
background: #ff9800;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 6px;
|
||
padding: 0 20px;
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
height: 46px;
|
||
min-width: 100px;
|
||
}
|
||
|
||
#addCategoryForm button[type="submit"]:hover {
|
||
background: #e68a00;
|
||
}
|
||
|
||
/* 所有模态框的统一关闭按钮样式 */
|
||
.modal-close {
|
||
position: absolute;
|
||
top: 15px;
|
||
right: 15px;
|
||
width: 24px;
|
||
height: 24px;
|
||
cursor: pointer;
|
||
opacity: 0.6;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.modal-close:hover {
|
||
opacity: 1;
|
||
transform: rotate(90deg);
|
||
}
|
||
|
||
.modal-close::before, .modal-close::after {
|
||
content: "";
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
width: 18px;
|
||
height: 2px;
|
||
background: #666;
|
||
transform-origin: center;
|
||
}
|
||
|
||
.modal-close::before {
|
||
transform: translate(-50%, -50%) rotate(45deg);
|
||
}
|
||
|
||
.modal-close::after {
|
||
transform: translate(-50%, -50%) rotate(-45deg);
|
||
}
|
||
|
||
/* 添加站点和编辑站点模态框的样式 */
|
||
#myModal .modal-content,
|
||
#editSiteModal .modal-content {
|
||
background: #fff;
|
||
border-radius: 10px;
|
||
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
|
||
padding: 25px;
|
||
max-width: 500px;
|
||
width: 90%;
|
||
}
|
||
|
||
#myModal h2,
|
||
#editSiteModal h2 {
|
||
font-size: 20px;
|
||
color: #333;
|
||
margin-top: 0;
|
||
margin-bottom: 20px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
#addSiteForm,
|
||
#editSiteForm {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
}
|
||
|
||
#addSiteForm input,
|
||
#addSiteForm select,
|
||
#addSiteForm textarea,
|
||
#editSiteForm input,
|
||
#editSiteForm select,
|
||
#editSiteForm textarea {
|
||
padding: 12px 15px;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 6px;
|
||
font-size: 15px;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
#addSiteForm input:focus,
|
||
#addSiteForm select:focus,
|
||
#addSiteForm textarea:focus,
|
||
#editSiteForm input:focus,
|
||
#editSiteForm select:focus,
|
||
#editSiteForm textarea:focus {
|
||
border-color: #4A90E2;
|
||
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
|
||
outline: none;
|
||
}
|
||
|
||
#addSiteForm button[type="submit"],
|
||
#editSiteForm button[type="submit"] {
|
||
background: #4A90E2;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 6px;
|
||
padding: 12px;
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
#addSiteForm button[type="submit"]:hover {
|
||
background: #3a7bc8;
|
||
}
|
||
|
||
#editSiteForm button[type="submit"] {
|
||
background: #ff9800;
|
||
}
|
||
|
||
#editSiteForm button[type="submit"]:hover {
|
||
background: #e68a00;
|
||
}
|
||
form {
|
||
display: grid;
|
||
gap: 10px;
|
||
}
|
||
input, select, textarea {
|
||
padding: 8px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
font-family: inherit;
|
||
background: #f9f9f9;
|
||
}
|
||
button[type="submit"] {
|
||
padding: 10px;
|
||
background: #007BFF;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
}
|
||
/* 新增悬停提示框样式 */
|
||
/* 添加站点描述提示框的悬停样式 */
|
||
.info-icon {
|
||
position: absolute;
|
||
top: 5px;
|
||
right: 5px;
|
||
color: #999;
|
||
cursor: help;
|
||
font-size: 12px;
|
||
opacity: 0.7;
|
||
transition: all 0.3s;
|
||
z-index: 10;
|
||
}
|
||
|
||
.info-icon:hover {
|
||
opacity: 1;
|
||
color: #4A90E2;
|
||
}
|
||
|
||
.info-tooltip {
|
||
visibility: hidden;
|
||
position: absolute;
|
||
top: 100%;
|
||
right: 0;
|
||
background-color: #333;
|
||
color: #fff;
|
||
padding: 10px 15px;
|
||
border-radius: 6px;
|
||
white-space: normal;
|
||
width: max-content;
|
||
max-width: 280px;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||
z-index: 100;
|
||
opacity: 0;
|
||
transform: translateY(10px);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.info-icon:hover .info-tooltip {
|
||
visibility: visible;
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.info-tooltip::after {
|
||
content: "";
|
||
position: absolute;
|
||
bottom: 100%;
|
||
right: 10px;
|
||
border-width: 5px;
|
||
border-style: solid;
|
||
border-color: transparent transparent #333 transparent;
|
||
}
|
||
|
||
/* 修复站点链接点击问题 */
|
||
.item-icon {
|
||
cursor: pointer;
|
||
}
|
||
|
||
.site-link {
|
||
display: block;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
}
|
||
|
||
.site-name {
|
||
display: block;
|
||
margin-top: 8px;
|
||
color: #333;
|
||
font-size: 14px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.site-name:hover {
|
||
color: #ff6700;
|
||
}
|
||
/* 自定义确认对话框样式 */
|
||
#customConfirmModal {
|
||
display: none;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0,0,0,0.5);
|
||
z-index: 2000;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.confirm-content {
|
||
background: #fff;
|
||
border-radius: 10px;
|
||
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
|
||
padding: 25px;
|
||
max-width: 400px;
|
||
width: 90%;
|
||
text-align: center;
|
||
}
|
||
|
||
.confirm-message {
|
||
font-size: 18px;
|
||
margin-bottom: 25px;
|
||
color: #333;
|
||
}
|
||
|
||
.confirm-buttons {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 15px;
|
||
}
|
||
|
||
.confirm-button {
|
||
padding: 10px 25px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.confirm-ok {
|
||
background: #ff5252;
|
||
color: white;
|
||
}
|
||
|
||
.confirm-ok:hover {
|
||
background: #e04545;
|
||
}
|
||
|
||
.confirm-cancel {
|
||
background: #e0e0e0;
|
||
color: #333;
|
||
}
|
||
|
||
.confirm-cancel:hover {
|
||
background: #d0d0d0;
|
||
}
|
||
/* 页脚样式优化 - 开始 */
|
||
footer {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 40px; /* 增加高度 */
|
||
background: #f0f0f0;
|
||
text-align: center;
|
||
font-size: 13px;
|
||
z-index: 999;
|
||
box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.1);
|
||
display: flex;
|
||
align-items: center; /* 垂直居中 */
|
||
justify-content: center; /* 水平居中 */
|
||
}
|
||
|
||
footer p {
|
||
margin: 0;
|
||
padding: 0 15px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
footer a {
|
||
color: #3498db;
|
||
text-decoration: none;
|
||
transition: color 0.2s;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
}
|
||
|
||
footer a:hover {
|
||
color: #2980b9;
|
||
text-decoration: underline;
|
||
}
|
||
|
||
footer a::after {
|
||
content: "|";
|
||
margin-left: 10px;
|
||
color: #ccc;
|
||
}
|
||
|
||
footer a:last-child::after {
|
||
content: none;
|
||
}
|
||
/* 页脚样式优化 - 结束 */
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header style="display: flex; justify-content: space-between; align-items: center; width: 100%; padding: 15px; background: rgba(255,255,255,0.95); box-shadow: 0 2px 10px rgba(0,0,0,0.05); position: fixed; top: 0; left: 0; z-index: 1000; box-sizing: border-box;">
|
||
<!-- 左侧:Logo和搜索框组合 -->
|
||
<div style="display: flex; align-items: center; flex: 1; min-width: 40%;">
|
||
<!-- Logo和标题 -->
|
||
<div style="display: flex; flex-direction: column; margin-right: 20px; flex-shrink: 0;">
|
||
<div style="display: flex; align-items: center;">
|
||
<a href="https://www.9872991.xyz/" target="_blank" style="display: flex; align-items: center;">
|
||
<img src="https://api.iconify.design/devicon/prometheus.svg?width=48&height=48" alt="Logo">
|
||
<h1 style="margin-left: 10px; margin-bottom: 0; font-size: 1.5rem; color: #333; white-space: nowrap;">Dispark</h1>
|
||
</a>
|
||
</div>
|
||
<div id="datetime" style="font-size: 0.9rem; margin-top: 5px; color: #666; white-space: nowrap;">
|
||
正在加载时间...
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 搜索框 - 靠近Logo -->
|
||
<div class="search-container" style="flex: 1; min-width: 30px; max-width: 300px;">
|
||
<input type="text" id="searchInput" placeholder="搜索站点名称或描述...">
|
||
<span class="search-icon iconify" data-icon="mdi:magnify" data-width="24" data-height="24"></span>
|
||
<button class="clear-btn" onclick="clearSearch()">
|
||
<span class="iconify" data-icon="mdi:close" data-width="20" data-height="20"></span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右侧:主标题和按钮 -->
|
||
<div style="display: flex; flex-direction: column; align-items: center; flex: 1; min-width: 20%; overflow: hidden;">
|
||
<!-- 主标题 -->
|
||
<div class="main-title" style="font-size: 1.8rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">点滴星火导航站</div>
|
||
<div class="title-subtext" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">探索 · 连接 · 发现</div>
|
||
</div>
|
||
|
||
<!-- 右侧按钮组 -->
|
||
<div style="display: flex; gap: 10px; flex-shrink: 1; flex-wrap: wrap; justify-content: flex-end; max-width: 40%;">
|
||
<button id="addCategoryBtn" style="background-color: #ff9800; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer; display: flex; align-items: center; gap: 5px; white-space: nowrap;">
|
||
<span class="iconify" data-icon="carbon:add-filled" data-width="16px" data-height="16px"></span>
|
||
添加分类
|
||
</button>
|
||
|
||
<button onclick="window.open('https://xinsheng.9912987.xyz/', '_blank')" style="background-color: #28a745; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer; display: flex; align-items: center; gap: 5px; white-space: nowrap;">
|
||
<span class="iconify" data-icon="mdi:message-text" data-width="16px" data-height="16px"></span>
|
||
留言板
|
||
</button>
|
||
|
||
<button onclick="window.open('https://news.9912987.xyz/', '_blank')" style="background-color: #28a745; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer; display: flex; align-items: center; gap: 5px; white-space: nowrap;">
|
||
<span class="iconify" data-icon="mdi:newspaper" data-width="16px" data-height="16px"></span>
|
||
新闻
|
||
</button>
|
||
<!-- 导出按钮 -->
|
||
<button onclick="exportData()" style="background-color: #17a2b8; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer; display: flex; align-items: center; gap: 5px; white-space: nowrap;">
|
||
<span class="iconify" data-icon="mdi:export" data-width="16px" data-height="16px"></span>
|
||
导出
|
||
</button>
|
||
|
||
<!-- 导入按钮(触发隐藏 file input) -->
|
||
<button onclick="document.getElementById('importFile').click()" style="background-color: #6c757d; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer; display: flex; align-items: center; gap: 5px; white-space: nowrap;">
|
||
<span class="iconify" data-icon="mdi:import" data-width="16px" data-height="16px"></span>
|
||
导入
|
||
</button>
|
||
<input type="file" id="importFile" accept=".json,application/json" style="display: none;" onchange="importData(this.files[0])">
|
||
<button onclick="location.href='/login.html'" style="background-color: #007BFF; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer; display: flex; align-items: center; gap: 5px; white-space: nowrap;">
|
||
<span class="iconify" data-icon="mdi:logout" data-width="16px" data-height="16px"></span>
|
||
退出
|
||
</button>
|
||
</div>
|
||
</header>
|
||
<main>
|
||
<!-- 搜索结果计数显示 -->
|
||
<div class="search-results" id="searchResults"></div>
|
||
${renderCategories(navigationData.categories)} <!-- 确保这里调用了renderCategories -->
|
||
|
||
</main>
|
||
<!-- 自定义确认对话框 -->
|
||
<div id="customConfirmModal">
|
||
<div class="confirm-content">
|
||
<div class="confirm-message" id="confirmMessage"></div>
|
||
<div class="confirm-buttons">
|
||
<button class="confirm-button confirm-ok" id="confirmOk">确定</button>
|
||
<button class="confirm-button confirm-cancel" id="confirmCancel">取消</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 修复后的添加分类模态框 -->
|
||
<div id="addCategoryModal" class="modal" style="display: none;">
|
||
<div class="modal-content">
|
||
<div class="modal-close" onclick="closeModal('addCategoryModal')"></div>
|
||
<h2>添加新分类</h2>
|
||
<form id="addCategoryForm">
|
||
<input type="text" name="categoryName" placeholder="输入分类名称" required>
|
||
<button type="submit">添加分类</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<!-- 添加站点模态框 -->
|
||
<div id="myModal" class="modal" style="display: none;">
|
||
<div class="modal-content">
|
||
<div class="modal-close" onclick="closeModal('myModal')"></div>
|
||
<h2>添加新站点</h2>
|
||
<form id="addSiteForm">
|
||
<select name="categoryIndex" required>
|
||
${navigationData.categories.map((category, index) =>
|
||
`<option value="${index}">${category.name}</option>`
|
||
).join('')}
|
||
</select>
|
||
<input type="text" name="siteName" placeholder="站点名称" required>
|
||
<input type="url" name="siteUrl" placeholder="站点链接" required>
|
||
<input type="text" name="siteIcon" placeholder="图标名称" required value="noto:fire">
|
||
<textarea name="siteInfo" placeholder="站点描述 (可选)" rows="3"></textarea>
|
||
<button type="submit">添加站点</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<!-- 添加分类名称修改模态框到HTML -->
|
||
<div id="editCategoryModal" class="modal" style="display: none;">
|
||
<div class="modal-content">
|
||
<div class="modal-close" onclick="closeModal('editCategoryModal')"></div>
|
||
<h2>修改分类名称</h2>
|
||
<form id="editCategoryForm">
|
||
<input type="hidden" name="categoryIndex">
|
||
<input type="text" name="categoryName" placeholder="分类名称" required>
|
||
<button type="submit">保存修改</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 在编辑站点模态框中添加分类选择 -->
|
||
<!-- 编辑站点模态框 -->
|
||
<div id="editSiteModal" class="modal" style="display: none;">
|
||
<div class="modal-content">
|
||
<div class="modal-close" onclick="closeModal('editSiteModal')"></div>
|
||
<h2>修改站点</h2>
|
||
<form id="editSiteForm">
|
||
<input type="hidden" name="originalCategoryIndex">
|
||
<input type="hidden" name="siteIndex">
|
||
|
||
<!-- 添加分类选择下拉菜单 -->
|
||
<select name="newCategoryIndex" required>
|
||
${navigationData.categories.map((category, index) =>
|
||
`<option value="${index}">${category.name}</option>`
|
||
).join('')}
|
||
</select>
|
||
|
||
<input type="text" name="siteName" placeholder="站点名称" required>
|
||
<input type="url" name="siteUrl" placeholder="站点链接" required>
|
||
<input type="text" name="siteIcon" placeholder="图标名称" required>
|
||
<textarea name="siteInfo" placeholder="站点描述 (可选)" rows="3"></textarea>
|
||
<button type="submit">保存修改</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<script>
|
||
// 在script标签中添加拖拽功能
|
||
let draggedItem = null;
|
||
|
||
function onDragStart(event, categoryIndex, siteIndex) {
|
||
event.dataTransfer.setData("text/plain", JSON.stringify({
|
||
categoryIndex: categoryIndex,
|
||
siteIndex: siteIndex
|
||
}));
|
||
event.currentTarget.classList.add('dragging');
|
||
draggedItem = event.currentTarget;
|
||
}
|
||
|
||
function onDragOver(event) {
|
||
event.preventDefault();
|
||
event.dataTransfer.dropEffect = "move";
|
||
}
|
||
|
||
async function onDrop(event, targetCategoryIndex, targetSiteIndex) {
|
||
event.preventDefault();
|
||
|
||
if (!draggedItem) return;
|
||
|
||
const data = JSON.parse(event.dataTransfer.getData("text/plain"));
|
||
const sourceCategoryIndex = data.categoryIndex;
|
||
const sourceSiteIndex = data.siteIndex;
|
||
|
||
// 移除拖拽样式
|
||
draggedItem.classList.remove('dragging');
|
||
draggedItem = null;
|
||
|
||
// 如果是同一分类内的移动
|
||
if (sourceCategoryIndex === targetCategoryIndex) {
|
||
try {
|
||
const response = await fetch('/reorder-site', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
categoryIndex: sourceCategoryIndex,
|
||
oldIndex: sourceSiteIndex,
|
||
newIndex: targetSiteIndex
|
||
})
|
||
});
|
||
|
||
if (response.ok) {
|
||
location.reload();
|
||
} else {
|
||
const error = await response.json();
|
||
alert(error.error || '移动失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('移动失败:', error);
|
||
alert('站点移动失败,请重试');
|
||
}
|
||
} else {
|
||
alert('站点只能在同一个分类内移动位置');
|
||
}
|
||
}
|
||
// 添加切换分类折叠状态函数
|
||
async function toggleCategoryState(categoryIndex) {
|
||
const response = await fetch('/toggle-category', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ categoryIndex })
|
||
});
|
||
|
||
if (response.ok) {
|
||
const result = await response.json();
|
||
const categoryElement = document.getElementById('category-sites-' + categoryIndex);
|
||
const toggleIcon = document.getElementById('toggle-icon-' + categoryIndex);
|
||
|
||
if (result.collapsed) {
|
||
categoryElement.style.display = 'none';
|
||
toggleIcon.setAttribute('data-icon', 'mdi:chevron-right');
|
||
} else {
|
||
categoryElement.style.display = 'flex';
|
||
toggleIcon.setAttribute('data-icon', 'mdi:chevron-down');
|
||
}
|
||
}
|
||
}
|
||
|
||
// 修复站点链接点击问题
|
||
function openSiteLink(url) {
|
||
window.open(url, '_blank');
|
||
}
|
||
// 自定义确认函数
|
||
let confirmCallback = null;
|
||
let currentCategoryIndex = null;
|
||
let currentSiteIndex = null;
|
||
let isSiteDeletion = false;
|
||
|
||
function showConfirm(message, callback, categoryIndex, siteIndex = null) {
|
||
document.getElementById('confirmMessage').textContent = message;
|
||
confirmCallback = callback;
|
||
currentCategoryIndex = categoryIndex;
|
||
currentSiteIndex = siteIndex;
|
||
isSiteDeletion = siteIndex !== null;
|
||
document.getElementById('customConfirmModal').style.display = "flex";
|
||
}
|
||
|
||
// 打开编辑分类模态框
|
||
function openEditCategoryModal(categoryIndex) {
|
||
const categoryName = document.getElementById(\`category-name-\${categoryIndex}\`).textContent;
|
||
document.getElementById('editCategoryForm').categoryIndex.value = categoryIndex;
|
||
document.getElementById('editCategoryForm').categoryName.value = categoryName;
|
||
document.getElementById('editCategoryModal').style.display = "flex";
|
||
}
|
||
// 添加编辑分类表单提交事件
|
||
document.getElementById('editCategoryForm')?.addEventListener('submit', async function(event) {
|
||
event.preventDefault();
|
||
const formData = new FormData(event.target);
|
||
const data = {
|
||
categoryIndex: parseInt(formData.get('categoryIndex')),
|
||
newName: formData.get('categoryName')
|
||
};
|
||
|
||
const response = await fetch('/edit-category', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data)
|
||
});
|
||
|
||
if (response.ok) {
|
||
closeModal('editCategoryModal');
|
||
// 更新分类名称显示
|
||
const categoryNameElement = document.getElementById(\`category-name-\${data.categoryIndex}\`);
|
||
if (categoryNameElement) {
|
||
categoryNameElement.textContent = data.newName;
|
||
}
|
||
} else {
|
||
const error = await response.json();
|
||
alert(error.error || '修改失败');
|
||
}
|
||
});
|
||
|
||
// 绑定确认对话框按钮事件
|
||
document.getElementById('confirmOk').addEventListener('click', function() {
|
||
if (confirmCallback) {
|
||
confirmCallback(currentCategoryIndex, currentSiteIndex);
|
||
}
|
||
document.getElementById('customConfirmModal').style.display = "none";
|
||
});
|
||
|
||
document.getElementById('confirmCancel').addEventListener('click', function() {
|
||
document.getElementById('customConfirmModal').style.display = "none";
|
||
});
|
||
|
||
// 修改删除分类函数 - 修复字符串问题
|
||
function deleteCategory(categoryIndex) {
|
||
const categoryName = navigationData.categories[categoryIndex]?.name || '该分类';
|
||
showConfirm('确定要删除分类 "' + categoryName + '" 吗?此操作不可恢复。',
|
||
performDeleteCategory,
|
||
categoryIndex);
|
||
}
|
||
|
||
async function performDeleteCategory(categoryIndex) {
|
||
const response = await fetch('/delete-category', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ categoryIndex })
|
||
});
|
||
|
||
if (response.ok) location.reload();
|
||
}
|
||
|
||
// 修改删除站点函数 - 修复字符串问题
|
||
function deleteSite(categoryIndex, siteIndex) {
|
||
const siteName = navigationData.categories[categoryIndex]?.sites[siteIndex]?.name || '该站点';
|
||
showConfirm('确定要删除站点 "' + siteName + '" 吗?此操作不可恢复。',
|
||
performDeleteSite,
|
||
categoryIndex,
|
||
siteIndex);
|
||
}
|
||
|
||
async function performDeleteSite(categoryIndex, siteIndex) {
|
||
const response = await fetch('/delete-site', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ categoryIndex, siteIndex })
|
||
});
|
||
|
||
if (response.ok) location.reload();
|
||
}
|
||
// 初始化导航数据(从服务器获取)
|
||
let navigationData = { categories: [] };
|
||
|
||
// 关闭模态框函数
|
||
function closeModal(modalId) {
|
||
document.getElementById(modalId).style.display = "none";
|
||
}
|
||
|
||
// 添加分类按钮事件
|
||
document.getElementById('addCategoryBtn')?.addEventListener('click', function() {
|
||
document.getElementById('addCategoryModal').style.display = "flex";
|
||
});
|
||
|
||
// 添加分类表单提交事件
|
||
document.getElementById('addCategoryForm')?.addEventListener('submit', async function(event) {
|
||
event.preventDefault();
|
||
const categoryName = event.target.categoryName.value;
|
||
|
||
const response = await fetch('/data');
|
||
const navigationData = await response.json();
|
||
|
||
const categoryExists = navigationData.categories.some(
|
||
cat => cat.name === categoryName
|
||
);
|
||
|
||
if (!categoryExists) {
|
||
const addResponse = await fetch('/add-category', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ name: categoryName })
|
||
});
|
||
|
||
if (addResponse.ok) {
|
||
document.getElementById('addCategoryModal').style.display = "none";
|
||
location.reload();
|
||
}
|
||
} else {
|
||
alert('该分类已经存在,请输入不同的分类名称。');
|
||
}
|
||
});
|
||
|
||
|
||
// 添加站点表单提交事件
|
||
document.getElementById('addSiteForm')?.addEventListener('submit', async function(event) {
|
||
event.preventDefault();
|
||
const formData = new FormData(event.target);
|
||
const data = {
|
||
categoryIndex: parseInt(formData.get('categoryIndex')),
|
||
siteName: formData.get('siteName'),
|
||
siteUrl: formData.get('siteUrl'),
|
||
siteIcon: formData.get('siteIcon'),
|
||
siteInfo: formData.get('siteInfo')
|
||
};
|
||
|
||
const response = await fetch('/add-site', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data)
|
||
});
|
||
|
||
if (response.ok) {
|
||
closeModal('myModal');
|
||
location.reload();
|
||
}
|
||
});
|
||
|
||
// 编辑站点表单提交事件
|
||
document.getElementById('editSiteForm')?.addEventListener('submit', async function(event) {
|
||
event.preventDefault();
|
||
const formData = new FormData(event.target);
|
||
const data = {
|
||
originalCategoryIndex: parseInt(formData.get('originalCategoryIndex')),
|
||
newCategoryIndex: parseInt(formData.get('newCategoryIndex')),
|
||
siteIndex: parseInt(formData.get('siteIndex')),
|
||
siteName: formData.get('siteName'),
|
||
siteUrl: formData.get('siteUrl'),
|
||
siteIcon: formData.get('siteIcon'),
|
||
siteInfo: formData.get('siteInfo')
|
||
};
|
||
|
||
const response = await fetch('/edit-site', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data)
|
||
});
|
||
|
||
if (response.ok) {
|
||
closeModal('editSiteModal');
|
||
location.reload();
|
||
}
|
||
});
|
||
|
||
// 模态框控制
|
||
const modal = document.getElementById("myModal");
|
||
const editModal = document.getElementById("editSiteModal");
|
||
const infoModal = document.getElementById("infoModal");
|
||
|
||
// 打开添加站点模态框的函数
|
||
function openAddModal(categoryIndex) {
|
||
document.getElementById('myModal').style.display = "flex";
|
||
document.querySelector("#addSiteForm select").value = categoryIndex;
|
||
}
|
||
|
||
// 打开编辑站点模态框的函数
|
||
function openEditModal(categoryIndex, siteIndex, siteName, siteUrl, siteIcon, siteInfo) {
|
||
const modal = document.getElementById('editSiteModal');
|
||
modal.style.display = "flex";
|
||
|
||
const form = document.getElementById('editSiteForm');
|
||
form.originalCategoryIndex.value = categoryIndex;
|
||
form.newCategoryIndex.value = categoryIndex;
|
||
form.siteIndex.value = siteIndex;
|
||
form.siteName.value = siteName;
|
||
form.siteUrl.value = siteUrl;
|
||
form.siteIcon.value = siteIcon;
|
||
form.siteInfo.value = siteInfo || '';
|
||
}
|
||
|
||
// 显示站点描述
|
||
function showSiteInfo(siteName, siteInfo) {
|
||
if (!siteInfo) return;
|
||
|
||
document.getElementById('info-title').textContent = siteName + ' - 描述';
|
||
document.getElementById('info-text').textContent = siteInfo;
|
||
infoModal.style.display = "flex";
|
||
}
|
||
|
||
// 关闭所有模态框
|
||
function closeModals() {
|
||
document.getElementById('myModal').style.display = "none";
|
||
document.getElementById('editSiteModal').style.display = "none";
|
||
document.getElementById('infoModal').style.display = "none";
|
||
document.getElementById('addCategoryModal').style.display = "none";
|
||
}
|
||
|
||
// 关闭按钮事件
|
||
document.querySelectorAll('.close, .info-close').forEach(btn => {
|
||
btn.addEventListener('click', closeModals);
|
||
});
|
||
|
||
// 点击模态框外部关闭
|
||
window.addEventListener('click', function(event) {
|
||
if (event.target.classList.contains('modal')) {
|
||
// 关闭所有模态框
|
||
document.querySelectorAll('.modal').forEach(modal => {
|
||
modal.style.display = "none";
|
||
});
|
||
}
|
||
});
|
||
|
||
|
||
// 日期显示
|
||
function updateDate() {
|
||
const now = new Date();
|
||
const options = {
|
||
weekday: 'long',
|
||
year: 'numeric',
|
||
month: 'long',
|
||
day: 'numeric'
|
||
};
|
||
document.getElementById('datetime').textContent =
|
||
now.toLocaleDateString('zh-CN', options);
|
||
}
|
||
// 添加悬停事件处理函数
|
||
function setupTooltips() {
|
||
// 为所有信息图标添加悬停事件
|
||
document.querySelectorAll('.info-icon').forEach(icon => {
|
||
const tooltip = icon.querySelector('.info-tooltip');
|
||
|
||
icon.addEventListener('mouseenter', () => {
|
||
tooltip.style.visibility = 'visible';
|
||
tooltip.style.opacity = '1';
|
||
tooltip.style.transform = 'translateY(0)';
|
||
});
|
||
|
||
icon.addEventListener('mouseleave', () => {
|
||
tooltip.style.visibility = 'hidden';
|
||
tooltip.style.opacity = '0';
|
||
tooltip.style.transform = 'translateY(10px)';
|
||
});
|
||
});
|
||
}
|
||
// 优化后的滚动处理
|
||
window.addEventListener('scroll', function() {
|
||
const scrollPosition = window.scrollY;
|
||
const header = document.querySelector('header');
|
||
const main = document.querySelector('main');
|
||
const isMobile = window.innerWidth <= 768;
|
||
|
||
if (scrollPosition > 30) {
|
||
document.body.classList.add('scrolled');
|
||
} else {
|
||
document.body.classList.remove('scrolled');
|
||
}
|
||
|
||
// 移动设备上进一步优化
|
||
if (isMobile) {
|
||
if (scrollPosition > 50) {
|
||
header.style.position = 'fixed';
|
||
header.style.height = '50px';
|
||
main.style.marginTop = '60px';
|
||
} else {
|
||
header.style.position = 'sticky';
|
||
header.style.height = 'auto';
|
||
main.style.marginTop = '120px';
|
||
}
|
||
}
|
||
});
|
||
// 添加搜索功能
|
||
document.getElementById('searchInput').addEventListener('input', function() {
|
||
const searchTerm = this.value.trim().toLowerCase();
|
||
performSearch(searchTerm);
|
||
});
|
||
// 清除搜索
|
||
function clearSearch() {
|
||
document.getElementById('searchInput').value = '';
|
||
performSearch('');
|
||
}
|
||
|
||
// 搜索执行函数
|
||
function performSearch(term) {
|
||
const resultsContainer = document.getElementById('searchResults');
|
||
|
||
// 清除之前的高亮
|
||
document.querySelectorAll('.highlight').forEach(el => {
|
||
const parent = el.parentNode;
|
||
parent.replaceChild(document.createTextNode(el.textContent), el);
|
||
});
|
||
|
||
// 重置所有元素的显示状态
|
||
document.querySelectorAll('section').forEach(section => {
|
||
section.style.display = 'block';
|
||
});
|
||
|
||
document.querySelectorAll('section ul li').forEach(li => {
|
||
li.style.display = 'flex';
|
||
});
|
||
|
||
// 如果没有搜索词,显示所有内容
|
||
if (!term) {
|
||
resultsContainer.textContent = '';
|
||
return;
|
||
}
|
||
|
||
// 搜索逻辑
|
||
let matchCount = 0;
|
||
|
||
// 遍历所有站点
|
||
document.querySelectorAll('section ul li').forEach(li => {
|
||
const nameElement = li.querySelector('.site-name');
|
||
const infoElement = li.querySelector('.info-tooltip');
|
||
|
||
const name = nameElement ? nameElement.textContent.toLowerCase() : '';
|
||
const info = infoElement ? infoElement.textContent.toLowerCase() : '';
|
||
|
||
// 检查是否匹配
|
||
if (name.includes(term) || info.includes(term)) {
|
||
matchCount++;
|
||
|
||
// 高亮匹配文本
|
||
if (name.includes(term)) {
|
||
highlightText(nameElement, term);
|
||
}
|
||
|
||
if (info.includes(term) && infoElement) {
|
||
highlightText(infoElement, term);
|
||
}
|
||
} else {
|
||
// 隐藏不匹配的站点
|
||
li.style.display = 'none';
|
||
}
|
||
});
|
||
|
||
// 隐藏没有匹配站点的分类
|
||
document.querySelectorAll('section').forEach(section => {
|
||
const visibleSites = section.querySelectorAll('ul li[style="display: flex;"]');
|
||
if (visibleSites.length === 0) {
|
||
section.style.display = 'none';
|
||
}
|
||
});
|
||
|
||
// 显示结果计数
|
||
resultsContainer.textContent = matchCount > 0 ?
|
||
'找到 ' + matchCount + ' 个匹配结果' :
|
||
'没有找到匹配的站点';
|
||
|
||
// 如果有匹配结果,滚动到第一个结果
|
||
if (matchCount > 0) {
|
||
const firstMatch = document.querySelector('section:not([style="display: none;"])');
|
||
if (firstMatch) {
|
||
firstMatch.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
}
|
||
}
|
||
}
|
||
// 导出数据
|
||
async function exportData() {
|
||
// 直接跳转到导出接口,浏览器会自动下载
|
||
window.location.href = '/export';
|
||
}
|
||
|
||
// 导入数据
|
||
async function importData(file) {
|
||
if (!file) return;
|
||
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
|
||
const response = await fetch('/import', {
|
||
method: 'POST',
|
||
body: formData,
|
||
});
|
||
|
||
const result = await response.json();
|
||
if (response.ok) {
|
||
alert('导入成功,页面即将刷新');
|
||
location.reload();
|
||
} else {
|
||
alert('导入失败:' + (result.error || '未知错误'));
|
||
}
|
||
}
|
||
// 高亮文本函数
|
||
function highlightText(element, term) {
|
||
const text = element.textContent;
|
||
const regex = new RegExp('(' + escapeRegExp(term) + ')', 'gi');
|
||
|
||
const newContent = text.replace(regex, '<span class="highlight">$1</span>');
|
||
element.innerHTML = newContent;
|
||
}
|
||
|
||
// 转义正则表达式特殊字符
|
||
function escapeRegExp(string) {
|
||
return string.replace(/[.*+?^$()|[\]\\]/g, '\\$&');
|
||
}
|
||
|
||
// 初始化
|
||
window.addEventListener('load', async function() {
|
||
updateDate();
|
||
|
||
// 加载通知
|
||
const response = await fetch('/load-notification');
|
||
const notification = await response.text();
|
||
document.getElementById('notification-message').textContent =
|
||
notification || '暂无新通知';
|
||
|
||
// 设置工具提示
|
||
// setupTooltips();
|
||
// 移动设备上设置更紧凑的布局
|
||
if (window.innerWidth <= 768) {
|
||
document.querySelector('header').classList.add('mobile');
|
||
document.querySelector('main').style.marginTop = '120px';
|
||
}
|
||
});
|
||
</script>
|
||
<footer>
|
||
<p>
|
||
<a>© 2025</a>
|
||
<a href="https://www.9912987.xyz/" target="_blank">Dispark|点滴星火</a>
|
||
<a href="/privacy">隐私政策</a>
|
||
<a href="mailto:i@dispark.top">联系方式</a>
|
||
<a href="http://113.44.203.109:36615" target="_blank" rel="noopener noreferrer">更多书签</a>
|
||
</p>
|
||
</footer>
|
||
</body>
|
||
</html>`;
|
||
|
||
return html;
|
||
}
|
||
|
||
/// 修复renderCategories函数 - 关键修改
|
||
function renderCategories(categories) {
|
||
return categories.map((category, categoryIndex) => `
|
||
<section>
|
||
<h2 style="display: flex; align-items: center;">
|
||
<!-- 折叠/展开按钮 -->
|
||
<button class="toggle-btn" onclick="toggleCategoryState(${categoryIndex})"
|
||
style="background: none; border: none; cursor: pointer; margin-right: 8px;">
|
||
<span id="toggle-icon-${categoryIndex}" class="iconify"
|
||
data-icon="${category.collapsed ? 'mdi:chevron-right' : 'mdi:chevron-down'}"
|
||
data-width="24px" data-height="24px"></span>
|
||
</button>
|
||
|
||
<span id="category-name-${categoryIndex}">${category.name}</span>
|
||
|
||
<!-- 编辑分类名称按钮 -->
|
||
<button class="edit-category-btn" onclick="openEditCategoryModal(${categoryIndex})"
|
||
style="background: none; border: none; cursor: pointer; margin-left: 8px;">
|
||
<span class="iconify" data-icon="mdi:pencil" data-width="20px" data-height="20px"></span>
|
||
</button>
|
||
|
||
<!-- 删除分类按钮 -->
|
||
<button class="delete-ca-btn" onclick="deleteCategory(${categoryIndex})">
|
||
<span class="iconify" data-icon="material-symbols:delete-outline" data-width="24px" data-height="24px"></span>
|
||
</button>
|
||
|
||
<!-- 添加站点按钮 -->
|
||
<button class="add-site-btn" onclick="openAddModal(${categoryIndex})">
|
||
<span class="iconify" data-icon="carbon:add-filled" data-width="24px" data-height="24px"></span>
|
||
</button>
|
||
</h2>
|
||
|
||
<ul id="category-sites-${categoryIndex}"
|
||
style="${category.collapsed ? 'display: none;' : 'display: flex;'}">
|
||
${category.sites.map((site, siteIndex) => `
|
||
<li draggable="true" ondragstart="onDragStart(event, ${categoryIndex}, ${siteIndex})" ondragover="onDragOver(event)" ondrop="onDrop(event, ${categoryIndex}, ${siteIndex})">
|
||
<div class="site-block" onclick="openSiteLink('${escapeString(site.url)}')">
|
||
<!-- 添加拖拽手柄 -->
|
||
<div class="drag-handle" onclick="event.stopPropagation()">
|
||
<span class="iconify" data-icon="mdi:drag" data-width="16px" data-height="16px"></span>
|
||
</div>
|
||
<div class="item-icon">
|
||
<span class="iconify" data-icon="${site.icon}" data-width="40px" data-height="40px"></span>
|
||
</div>
|
||
<a href="${site.url}" target="_blank" class="site-link" onclick="event.stopPropagation(); return true;">
|
||
<span class="site-name">${site.name}</span>
|
||
</a>
|
||
|
||
${site.info ? `
|
||
<div class="info-icon" onclick="event.stopPropagation()">
|
||
<span class="iconify" data-icon="mdi:information-outline" data-width="16px" data-height="16px"></span>
|
||
<div class="info-tooltip">
|
||
${site.info}
|
||
</div>
|
||
</div>
|
||
` : ''}
|
||
|
||
<div class="opts">
|
||
<button onclick="event.stopPropagation(); deleteSite(${categoryIndex}, ${siteIndex})">
|
||
<span class="iconify" data-icon="material-symbols:delete-outline" data-width="16px" data-height="16px"></span>
|
||
</button>
|
||
<button onclick="event.stopPropagation(); openEditModal(${categoryIndex}, ${siteIndex}, '${escapeString(site.name)}', '${escapeString(site.url)}', '${escapeString(site.icon)}', '${escapeString(site.info || '')}')">
|
||
<span class="iconify" data-icon="raphael:edit" data-width="16px" data-height="16px"></span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
`).join('')}
|
||
</ul>
|
||
</section>
|
||
`).join('');
|
||
}
|
||
|
||
// 辅助函数:转义特殊字符
|
||
function escapeString(str) {
|
||
return str ? str
|
||
.replace(/\\/g, '\\\\')
|
||
.replace(/'/g, "\\'")
|
||
.replace(/"/g, '\\"')
|
||
.replace(/\n/g, '\\n')
|
||
.replace(/\r/g, '\\r') : '';
|
||
}
|