index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>简单导航 </title>
<meta name="keywords" content="简单导航">
<meta name="description" content=""/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="/dd3.png">
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?786a6208323fbecfb41d71bfda8daca6";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
<style>
/* ================ reset 样式 start ================ */
* { margin: 0; padding: 0; font-family: "微软雅黑", "Microsoft YaHei", sans-serif; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; }
body, ul, li, h1, h2, h3, h4, h5, h6, p, form, dl, dt, dd { margin: 0px; padding: 0px; font-size: 14px; font-weight: normal; }
img { border-style: none; }
li { list-style: none; }
a { text-decoration: none; }
html, body { background: #fff; height: 100%; }
input[type="button"], input[type="submit"], input[type="reset"] { -webkit-appearance: none; outline: 0; }
textarea { -webkit-appearance: none; }
/* ================ reset 样式 end ================ */
.inner-center { width: 1000px; margin: 0 auto; }
.main { padding-top: 5%; min-height: 100%; }
.content-inside{ padding-bottom: 100px; }
.footer { height: 60px; text-align: center; margin-top: -60px; }
/* 搜索框 start */
.search-section { margin-top: 14px; margin-bottom: 15px; position: relative; display: flex; height: 44px; width: 100%; }
.search-left { display: flex; width: calc(100% - 109.09px); height: 100%; border: 1px solid rgba(217, 217, 217, 0.96); border-right: none; position: relative; border-radius: 2px 0 0 2px; }
.search-engine-selector { width: 105px; height: 100%; display: flex; align-items: center; justify-content: center; cursor: pointer; padding: 0 10px; border-right: 1px solid rgba(217, 217, 217, 0.96); font-size: 14px; color: #000; position: relative; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-radius: 2px 0 0 2px; }
.search-engine-selector:hover { background-color: #f0f0f0; }
.search-methods { display: none; position: absolute; left: -1px; top: 43px; width: 106px; border: 1px solid rgba(217, 217, 217, 0.96); background-color: #fff; z-index: 100; border-radius: 2px; }
.search-methods li { padding-left: 38px; overflow: hidden; height: 34px; line-height: 34px; color: #000; cursor: pointer; font-size: 14px; background-image: none !important; }
.search-methods .search-item:hover { background-color: #f0f0f0; }
.input-wrap { position: relative; flex: 1; display: flex; height: 100%; cursor: default; }
.input-wrap .search-input { height: 100%; width: 100%; outline: 0; border: 0; font-size: 16px; padding-left: 15px; padding-right: 30px; }
.input-wrap .search-input:focus { outline: none; box-shadow: none; }
.input-wrap .clear-keyword { display: none; position: absolute; top: 50%; right: 10px; transform: translateY(-50%); cursor: pointer; color: #d2d2d2; font-size: 20px; line-height: 1; }
.search-submit { width: 111px; height: 44px; line-height: 44px; background-color: #f0f0f0; color: #000; font-size: 14px; font-weight: 500; text-align: center; font-family: Roboto, "Microsoft YaHei", sans-serif; border: 1px solid rgba(217, 217, 217, 0.96); cursor: pointer; transition: background-color 0.3s; border-radius: 0 2px 2px 0; }
.search-submit:hover { background-color: #d0e9f8; }
/* 搜索框 end */
/* 导航内容 start */
.nav-content { overflow: hidden; padding-top: 20px; }
.jj-list { width: 33.33%; float: left; margin-bottom: 30px; padding-right: 16px; }
.jj-list:nth-of-type(3n) { padding-right: 0; }
.jj-list-tit { font-size: 14px; line-height: 25px; color: rgba(49, 70, 89, 0.75); font-weight: bold; margin-bottom: 10px; text-align: center; border-radius: 2px; }
.jj-list-con { overflow: hidden; margin: 0 auto; }
.jj-list-con li { padding: 1px; width: 33.33%; float: left; }
.jj-list-link { display: block; background: rgba(230, 247, 255, 0.96); color:rgba(49, 70, 89, 1); font-size: 13px; text-align: center; line-height: 44px; transition: all 0.2s; border-radius: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; position: relative; }
.jj-list-link .link-text { display: inline-block; }
.jj-list-link:hover { background: #1890FF; color: #fff; }
/* Mobile Responsiveness */
@media (max-width: 1024px) { .search-section, .search-left { width: 100%; } .search-left { width: calc(100% - 109.09px); } }
@media (max-width: 768px) {
.inner-center { width: 95%; }
.main { padding-top: 3%; }
.search-section { flex-direction: column; height: auto; }
.search-left { width: 100%; margin-bottom: 10px; height: 44px; border-radius: 2px; }
.search-engine-selector { width: 90px; font-size: 13px; border-radius: 2px 0 0 2px; }
.search-methods { width: 90px; }
.input-wrap .search-input { font-size: 14px; }
.search-submit { width: 100%; font-size: 16px; height: 44px; line-height: 44px; border-radius: 2px; }
.jj-list { width: 100%; padding-right: 0; margin-bottom: 20px; }
.jj-list:nth-of-type(3n) { padding-right: 0; }
.jj-list-con li { width: 50%; }
.jj-list-link { line-height: 40px; font-size: 13px; }
}
@media (max-width: 480px) {
.jj-list-con li { width: 100%; }
.search-engine-selector { padding: 0 5px; width: 80px; }
.search-methods { width: 80px; }
.input-wrap .search-input { padding-left: 10px; }
}
/* Styles for Edit Mode & Modals */
.edit-mode-controls { margin-bottom: 20px; padding: 10px; background-color: #f0f0f0; border: 1px solid #ccc; border-radius: 2px; text-align: center; }
.edit-mode-controls button { padding: 8px 15px; margin: 0 5px; background-color: #007bff; color: white; border: none; border-radius: 2px; cursor: pointer; }
.edit-mode-controls button:hover { background-color: #0056b3; }
.jj-list-link.editable, .jj-list-tit.editable { padding: 5px; line-height: normal; height: auto; }
.jj-list-link.editable input[type="text"], .jj-list-tit.editable input[type="text"] { box-sizing: border-box; padding: 5px; margin: 3px 0; font-size: 13px; border: 1px solid #ccc; border-radius: 2px; display: block; }
.jj-list-link.editable input[type="text"] { width: calc(100% - 10px); }
.jj-list-link.editable label { font-size: 12px; display: block; text-align: left; margin-top: 3px; color: #333; }
.jj-list-tit.editable { display: flex; justify-content: space-between; align-items: center; padding: 5px; }
.jj-list-tit.editable input[type="text"] { flex-grow: 1; text-align: center; font-weight: bold; font-size: 16px; margin-right: 10px;}
.action-button { font-size: 12px; padding: 4px 8px; margin-left: 8px; background-color: #dc3545; color: white; border: none; border-radius: 2px; cursor: pointer; line-height: 1.2; }
.action-button:hover { background-color: #c82333; }
.action-button.add-link-btn { background-color: #28a745; display: block; margin: 10px auto 5px auto; }
.action-button.add-link-btn:hover { background-color: #218838; }
#statusMessage { text-align: center; padding: 8px; margin-bottom:15px; font-weight: bold; border-radius: 2px; }
#statusMessage.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
#statusMessage.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
/* Custom Modal Styles */
#customModalOverlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.6); z-index: 1000; display: none; justify-content: center; align-items: center; }
#customModal { background-color: #fff; padding: 25px; border-radius: 4px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); width: 90%; max-width: 450px; z-index: 1001; }
#customModalTitle { margin-top: 0; margin-bottom: 20px; font-size: 20px; color: #333; text-align: center; }
#customModalContent input[type="text"], #customModalContent textarea { width: calc(100% - 16px); padding: 10px; margin-bottom: 12px; border: 1px solid #ddd; border-radius: 2px; font-size: 14px; }
#customModalContent label { display: block; margin-bottom: 5px; font-weight: bold; font-size: 14px; color: #555; }
#customModalActions { text-align: right; margin-top: 25px; }
#customModalActions button { padding: 10px 18px; margin-left: 10px; border: none; border-radius: 2px; cursor: pointer; font-size: 14px; font-weight: 500; }
#customModalSave { background-color: #007bff; color: white; }
#customModalSave:hover { background-color: #0056b3; }
#customModalCancel { background-color: #6c757d; color: white; }
#customModalCancel:hover { background-color: #545b62; }
</style>
</head>
<body>
<div class="inner-center main">
<div class="content-inside">
<div id="search_form" class="search-section" data-search-engine="google">
<div class="search-left">
<div id="search_engine_display" class="search-engine-selector">
<span id="current_search_engine_name">谷歌</span>
</div>
<div class="input-wrap" id="searchInputWrap">
<input id="search_keyword" class="search-input" name="keyword" maxlength="100" autocomplete="off" type="text" placeholder="">
<div class="clear-keyword" id="clear_keyword" title="清空">x</div>
</div>
<ul id="search_methods" class="search-methods">
<li data-type="google" class="search-item google">谷歌</li>
<li data-type="baidu" class="search-item baidu">百度</li>
</ul>
</div>
<input class="search-submit" value="搜索" id="search_submit" type="button">
</div>
<div id="statusMessage"></div>
<div class="edit-mode-controls" id="editModeControlsContainer" style="display:none;">
<button id="exitEditModeBtn">退出编辑模式</button>
<button id="saveAllChangesBtn">保存所有更改</button>
<button id="addCategoryBtn">添加新分类</button>
</div>
<div class="nav-content" id="navContent">
</div>
</div>
</div>
<div class="footer"></div>
<div id="customModalOverlay">
<div id="customModal">
<h3 id="customModalTitle">Modal Title</h3>
<div id="customModalContent">
</div>
<div id="customModalActions">
<button id="customModalSave">保存</button>
<button id="customModalCancel">取消</button>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// --- Search Functionality ---
const searchEngineDisplay = document.getElementById('search_engine_display');
const currentSearchEngineName = document.getElementById('current_search_engine_name');
const searchMethodsList = document.getElementById('search_methods');
const searchForm = document.getElementById('search_form');
const searchKeywordInput = document.getElementById('search_keyword');
const searchSubmitButton = document.getElementById('search_submit');
const clearKeywordButton = document.getElementById('clear_keyword');
if (searchEngineDisplay) {
searchEngineDisplay.addEventListener('click', function(event) {
event.stopPropagation();
if (searchMethodsList) {
searchMethodsList.style.display = searchMethodsList.style.display === 'block' ? 'none' : 'block';
}
});
}
document.addEventListener('click', function(event) {
if (searchMethodsList && searchMethodsList.style.display === 'block' &&
searchEngineDisplay && !searchEngineDisplay.contains(event.target) &&
!searchMethodsList.contains(event.target)) {
searchMethodsList.style.display = 'none';
}
});
if (searchMethodsList) {
searchMethodsList.addEventListener('click', function(event) {
event.stopPropagation();
if (event.target.tagName === 'LI') {
const selectedType = event.target.getAttribute('data-type');
const selectedName = event.target.textContent;
if (currentSearchEngineName) currentSearchEngineName.textContent = selectedName;
if (searchForm) searchForm.setAttribute('data-search-engine', selectedType);
searchMethodsList.style.display = 'none';
// Placeholder does not change
}
});
}
if (searchSubmitButton) {
searchSubmitButton.addEventListener('click', function() {
if (!searchKeywordInput || !searchForm) return;
const keyword = searchKeywordInput.value.trim();
if (!keyword) {
searchKeywordInput.focus();
return;
}
const engine = searchForm.getAttribute('data-search-engine');
let url = '';
if (engine === 'baidu') {
url = `https://www.baidu.com/s?wd=${encodeURIComponent(keyword)}`;
} else if (engine === 'google') {
url = `https://www.google.com/search?q=${encodeURIComponent(keyword)}`;
}
if (url) window.open(url, '_blank');
});
}
if (searchKeywordInput) {
searchKeywordInput.addEventListener('keypress', function(event) {
if (event.key === 'Enter' && searchSubmitButton) searchSubmitButton.click();
});
searchKeywordInput.addEventListener('input', function() {
if (clearKeywordButton) {
clearKeywordButton.style.display = searchKeywordInput.value.length > 0 ? 'block' : 'none';
}
});
}
if (clearKeywordButton) {
clearKeywordButton.addEventListener('click', function() {
if (searchKeywordInput) {
searchKeywordInput.value = '';
searchKeywordInput.focus();
}
clearKeywordButton.style.display = 'none';
});
}
// --- End of Search Functionality ---
// --- Navigation Editing Functionality ---
const navContentContainer = document.getElementById('navContent');
const editModeControlsContainer = document.getElementById('editModeControlsContainer');
const exitEditModeBtn = document.getElementById('exitEditModeBtn');
const saveAllChangesBtn = document.getElementById('saveAllChangesBtn');
const addCategoryBtn = document.getElementById('addCategoryBtn');
const statusMessageEl = document.getElementById('statusMessage');
const searchInputWrap = document.getElementById('searchInputWrap');
// Modal elements
const customModalOverlay = document.getElementById('customModalOverlay');
const customModalTitleEl = document.getElementById('customModalTitle');
const customModalContentEl = document.getElementById('customModalContent');
const customModalSaveBtn = document.getElementById('customModalSave');
const customModalCancelBtn = document.getElementById('customModalCancel');
let currentModalSaveHandler = null;
let navigationData = [];
let isEditMode = false;
// 6-click edit mode trigger
let clickCount = 0;
let clickTimer = null;
const CLICK_DELAY_MS = 400;
const REQUIRED_CLICKS = 6;
if (searchInputWrap) {
searchInputWrap.addEventListener('click', () => {
clickCount++;
if (clickTimer) clearTimeout(clickTimer);
clickTimer = setTimeout(() => {
clickCount = 0;
}, CLICK_DELAY_MS * (REQUIRED_CLICKS -1) );
if (clickCount === REQUIRED_CLICKS) {
clearTimeout(clickTimer);
clickCount = 0;
if (!isEditMode) {
isEditMode = true;
if(editModeControlsContainer) editModeControlsContainer.style.display = 'block';
renderNavigation();
showStatusMessage('已进入编辑模式。', 'success', 2000);
}
}
});
}
function showStatusMessage(message, type = 'success', duration = 3000) {
if (!statusMessageEl) return;
statusMessageEl.textContent = message;
statusMessageEl.className = type;
setTimeout(() => {
statusMessageEl.textContent = '';
statusMessageEl.className = '';
}, duration);
}
function renderNavigation() {
if (!navContentContainer) return;
navContentContainer.innerHTML = '';
if (navigationData.length === 0 && !isEditMode) {
navContentContainer.innerHTML = '<p style="text-align:center; color: #777; margin-top: 20px;">导航数据为空。尝试进入编辑模式添加内容,或检查 navigation_data.json 文件。</p>';
return;
}
if (navigationData.length === 0 && isEditMode) {
navContentContainer.innerHTML = '<p style="text-align:center; color: #777; margin-top: 20px;">导航数据为空。点击 "添加新分类" 开始。</p>';
}
navigationData.forEach((category, catIndex) => {
const catDiv = document.createElement('div');
catDiv.className = 'jj-list';
const titleDiv = document.createElement('div');
titleDiv.className = 'jj-list-tit';
if (isEditMode) {
titleDiv.classList.add('editable');
const titleInput = document.createElement('input');
titleInput.type = 'text';
titleInput.value = category.categoryTitle;
titleInput.onchange = (e) => {
navigationData[catIndex].categoryTitle = e.target.value.trim();
};
titleDiv.appendChild(titleInput);
const removeCategoryBtn = document.createElement('button');
removeCategoryBtn.textContent = '删除分类';
removeCategoryBtn.className = 'action-button';
removeCategoryBtn.onclick = () => removeCategory(catIndex);
titleDiv.appendChild(removeCategoryBtn);
} else {
titleDiv.textContent = category.categoryTitle;
}
catDiv.appendChild(titleDiv);
const ul = document.createElement('ul');
ul.className = 'jj-list-con';
category.links.forEach((link, linkIndex) => {
const li = document.createElement('li');
const a = document.createElement('a');
a.className = 'jj-list-link';
if (isEditMode) {
a.classList.add('editable');
const nameLabel = document.createElement('label');
nameLabel.textContent = '名称:';
const textInput = document.createElement('input');
textInput.type = 'text';
textInput.value = link.text;
textInput.placeholder = 'Link Name';
textInput.onchange = (e) => {
navigationData[catIndex].links[linkIndex].text = e.target.value.trim();
};
const urlLabel = document.createElement('label');
urlLabel.textContent = 'URL:';
const urlInput = document.createElement('input');
urlInput.type = 'text';
urlInput.value = link.url;
urlInput.placeholder = 'Link URL';
urlInput.onchange = (e) => {
navigationData[catIndex].links[linkIndex].url = e.target.value.trim();
};
const removeBtn = document.createElement('button');
removeBtn.textContent = 'X';
removeBtn.title = '删除此链接';
removeBtn.className = 'action-button';
removeBtn.style.cssText = 'float: right; margin-top: 0; padding: 2px 5px;';
removeBtn.onclick = (event) => {
event.preventDefault();
event.stopPropagation();
removeLink(catIndex, linkIndex);
};
a.appendChild(removeBtn);
a.appendChild(nameLabel);
a.appendChild(textInput);
a.appendChild(urlLabel);
a.appendChild(urlInput);
} else {
a.href = link.url || '#';
a.target = '_blank';
const textSpan = document.createElement('span');
textSpan.className = 'link-text';
textSpan.textContent = link.text;
a.appendChild(textSpan);
}
li.appendChild(a);
ul.appendChild(li);
});
catDiv.appendChild(ul);
if(isEditMode){
const addLinkBtn = document.createElement('button');
addLinkBtn.textContent = '为此分类添加链接';
addLinkBtn.className = 'action-button add-link-btn';
addLinkBtn.onclick = () => showAddLinkModal(catIndex);
catDiv.appendChild(addLinkBtn);
}
navContentContainer.appendChild(catDiv);
});
}
// --- Modal Functions ---
function showModal(title, contentHTML, saveHandler) {
if (!customModalOverlay || !customModalTitleEl || !customModalContentEl) return;
customModalTitleEl.textContent = title;
customModalContentEl.innerHTML = contentHTML;
customModalOverlay.style.display = 'flex';
currentModalSaveHandler = saveHandler;
const firstInput = customModalContentEl.querySelector('input[type="text"], textarea');
if (firstInput) {
firstInput.focus();
}
}
function hideModal() {
if (!customModalOverlay || !customModalContentEl) return;
customModalOverlay.style.display = 'none';
customModalContentEl.innerHTML = '';
currentModalSaveHandler = null;
}
if (customModalSaveBtn) {
customModalSaveBtn.onclick = () => {
if (currentModalSaveHandler) {
currentModalSaveHandler();
}
};
}
if (customModalCancelBtn) customModalCancelBtn.onclick = hideModal;
// --- End Modal Functions ---
function showAddCategoryModal() {
const contentHTML = `
<label for="newCategoryTitleInput">分类名称:</label>
<input type="text" id="newCategoryTitleInput" placeholder="输入新分类的名称">
`;
showModal("添加新分类", contentHTML, () => {
const titleInput = document.getElementById('newCategoryTitleInput');
if (!titleInput) return;
const newCategoryTitle = titleInput.value.trim();
if (newCategoryTitle) {
navigationData.push({ categoryTitle: newCategoryTitle, links: [] });
renderNavigation();
hideModal();
showStatusMessage(`分类 "${newCategoryTitle}" 已添加 (未保存)`, 'success');
} else {
titleInput.style.border = "1px solid red";
setTimeout(()=> { if(titleInput) titleInput.style.border = ""; }, 2000);
titleInput.focus();
}
});
}
function showAddLinkModal(catIndex) {
if (!navigationData[catIndex]) return;
const contentHTML = `
<label for="newLinkTextInput">链接名称:</label>
<input type="text" id="newLinkTextInput" placeholder="例如:谷歌">
<label for="newLinkUrlInput">链接 URL:</label>
<input type="text" id="newLinkUrlInput" placeholder="例如:https://www.google.com">
`;
showModal(`为 "${navigationData[catIndex].categoryTitle}" 添加链接`, contentHTML, () => {
const textInput = document.getElementById('newLinkTextInput');
const urlInput = document.getElementById('newLinkUrlInput');
if (!textInput || !urlInput) return;
const newLinkText = textInput.value.trim();
const newLinkUrl = urlInput.value.trim();
let isValid = true;
if (!newLinkText) {
textInput.style.border = "1px solid red"; isValid = false;
} else {
textInput.style.border = "";
}
if (!newLinkUrl) {
urlInput.style.border = "1px solid red"; isValid = false;
} else {
if (!newLinkUrl.toLowerCase().startsWith('http://') && !newLinkUrl.toLowerCase().startsWith('https://') && !newLinkUrl.toLowerCase().startsWith('ftp://')) {
urlInput.style.border = "1px solid red"; isValid = false;
} else {
urlInput.style.border = "";
}
}
if (isValid) {
navigationData[catIndex].links.push({ text: newLinkText, url: newLinkUrl });
renderNavigation();
hideModal();
showStatusMessage(`链接 "${newLinkText}" 已添加到 "${navigationData[catIndex].categoryTitle}" (未保存)`, 'success');
} else {
if (!newLinkText && textInput) textInput.focus();
else if (urlInput) urlInput.focus();
setTimeout(()=> {
if(textInput) textInput.style.border = "";
if(urlInput) urlInput.style.border = "";
}, 2000);
}
});
}
function removeLink(catIndex, linkIndex) {
// No confirm dialog
const removedLinkName = navigationData[catIndex].links[linkIndex].text;
navigationData[catIndex].links.splice(linkIndex, 1);
renderNavigation();
showStatusMessage(`链接 "${removedLinkName}" 已删除 (未保存)`, 'success');
}
function removeCategory(catIndex) {
// No confirm dialog
const removedCategoryName = navigationData[catIndex].categoryTitle;
navigationData.splice(catIndex, 1);
renderNavigation();
showStatusMessage(`分类 "${removedCategoryName}" 已删除 (未保存)`, 'success');
}
// --- Data Loading and Saving ---
const DATA_SOURCE_URL = 'navigation_data.json';
const SAVE_ENDPOINT_URL = 'save_navigation.php';
async function loadNavigationData() {
try {
const response = await fetch(DATA_SOURCE_URL + '?t=' + new Date().getTime());
if (!response.ok) {
if (response.status === 404) {
console.warn(`'${DATA_SOURCE_URL}' not found. Using fallback data. Create this file to use your own data.`);
navigationData = getExampleFallbackData();
showStatusMessage(`导航文件 '${DATA_SOURCE_URL}' 未找到, 已加载示例数据。`, 'error', 5000);
} else {
throw new Error(`HTTP error! status: ${response.status} while fetching ${DATA_SOURCE_URL}`);
}
} else {
const data = await response.json();
if (data && Array.isArray(data)) {
navigationData = data;
// No success message for loading data as per user request
if (data.length === 0) {
console.log(`从 '${DATA_SOURCE_URL}' 加载到空导航数据。`);
}
} else {
console.warn(`Data from '${DATA_SOURCE_URL}' is not a valid array. Using fallback data.`);
navigationData = getExampleFallbackData();
showStatusMessage(`'${DATA_SOURCE_URL}' 中的数据格式无效, 已加载示例数据。`, 'error', 5000);
}
}
} catch (error) {
console.error("无法从服务器或文件加载导航:", error);
showStatusMessage(`加载导航失败: ${error.message}. 已加载示例数据。`, 'error', 5000);
navigationData = getExampleFallbackData();
} finally {
renderNavigation();
}
}
function getExampleFallbackData() {
return [
{
"categoryTitle": "常用项目 (示例)",
"links": [
{ "text": "博客", "url": "https://wowood.cn/" },
{ "text": "笔记", "url": "https://baicai.online/" }
]
},
{
"categoryTitle": "实用工具 (示例)",
"links": [
{ "text": "谷歌翻译", "url": "https://translate.google.com/" }
]
}
];
}
async function saveNavigationToServer() {
if (!isEditMode) {
showStatusMessage('不在编辑模式,无需保存。', 'error');
return;
}
for (const category of navigationData) {
if (!category.categoryTitle.trim()) {
showStatusMessage('错误:分类名称不能为空!请修正后再保存。', 'error', 5000);
return;
}
for (const link of category.links) {
if (!link.text.trim() || !link.url.trim()) {
showStatusMessage(`错误:分类 "${category.categoryTitle}" 中的链接名称和URL均不能为空!`, 'error', 7000);
return;
}
if (!link.url.toLowerCase().startsWith('http://') && !link.url.toLowerCase().startsWith('https://') && !link.url.toLowerCase().startsWith('ftp://')) {
showStatusMessage(`错误:链接 "${link.text}" 的URL格式无效 (需以 http://, https:// 或 ftp:// 开头)!`, 'error', 7000);
return;
}
}
}
showStatusMessage('正在保存到服务器...', 'success', 10000);
try {
const response = await fetch(SAVE_ENDPOINT_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(navigationData),
});
const contentType = response.headers.get("content-type");
let result;
if (contentType && contentType.indexOf("application/json") !== -1) {
result = await response.json();
} else {
const textResponse = await response.text();
if (!response.ok) {
throw new Error(`服务器错误: ${response.status}. 响应: ${textResponse}`);
}
result = { status: 'success', message: textResponse || '保存成功 (非JSON响应)' };
}
if (response.ok && result.status === 'success') {
showStatusMessage(result.message || '导航已成功保存到服务器!', 'success');
} else {
throw new Error(result.message || `保存操作未成功 (服务器状态: ${response.status})`);
}
} catch (error) {
console.error("保存导航到服务器失败:", error);
showStatusMessage(`保存失败: ${error.message}`, 'error', 7000);
}
}
if(exitEditModeBtn) {
exitEditModeBtn.addEventListener('click', () => {
if (isEditMode) {
// No confirm dialog
isEditMode = false;
if(editModeControlsContainer) editModeControlsContainer.style.display = 'none';
loadNavigationData(); // Reload original data to discard local changes
showStatusMessage('已退出编辑模式。未保存的更改已丢弃。', 'success', 2000);
}
});
}
if(saveAllChangesBtn) saveAllChangesBtn.addEventListener('click', saveNavigationToServer);
if(addCategoryBtn) addCategoryBtn.addEventListener('click', showAddCategoryModal);
// --- Initial Load ---
loadNavigationData();
console.log("页面脚本已加载。导航数据将从 'navigation_data.json' 或回退到示例数据。");
});
</script>
</body>
</html>
save_navigation.php
<?php
header('Content-Type: application/json'); // Send JSON response back
$dataFile = 'navigation_data.json';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Get the raw POST data (which should be a JSON string)
$jsonData = file_get_contents('php://input');
if ($jsonData) {
$data = json_decode($jsonData, true); // Decode JSON into a PHP array
// Basic validation: check if json_decode was successful
if (json_last_error() === JSON_ERROR_NONE) {
// Overwrite the file with the new data
// JSON_PRETTY_PRINT makes the file human-readable
// JSON_UNESCAPED_UNICODE ensures Chinese characters are saved correctly
if (file_put_contents($dataFile, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) {
echo json_encode(['status' => 'success', 'message' => '导航已保存!']);
} else {
// http_response_code(500); // Internal Server Error
echo json_encode(['status' => 'error', 'message' => '无法写入文件. 请检查权限.']);
}
} else {
// http_response_code(400); // Bad Request
echo json_encode(['status' => 'error', 'message' => '无效的JSON数据.']);
}
} else {
// http_response_code(400); // Bad Request
echo json_encode(['status' => 'error', 'message' => '没有收到数据.']);
}
} else {
// http_response_code(405); // Method Not Allowed
echo json_encode(['status' => 'error', 'message' => '仅允许POST请求.']);
}
?>
load_navigation.php
<?php
header('Content-Type: application/json'); // Important: tell the browser it's JSON
$dataFile = 'navigation_data.json';
if (file_exists($dataFile)) {
$contents = file_get_contents($dataFile);
// Optionally validate if $contents is valid JSON, though if you control the save script, it should be.
echo $contents;
} else {
// If the file doesn't exist, you could return an empty array
// or a default structure if you have one.
// For simplicity, we'll return an empty JSON array,
// the frontend will then use the static HTML as a base.
echo json_encode([]);
}
?>
navigation_data.json
[
{
"categoryTitle": "常用项目",
"links": [
{
"text": "博客",
"url": "https:\/\/wowood.cn\/"
},
{
"text": "笔记",
"url": "https:\/\/baicai.online\/"
},
{
"text": "网盘",
"url": "https:\/\/download.wowood.cn\/"
},
{
"text": "看板",
"url": "https:\/\/plan.baicai.online\/"
},
{
"text": "宝塔",
"url": "http:\/\/111.229.144.188:8888\/"
},
{
"text": "BI",
"url": "https:\/\/work.jiushuyun.com\/decision\/shared\/e1f34feb"
},
{
"text": "订阅",
"url": "https:\/\/amz.baicai.online\/"
},
{
"text": "领星",
"url": "https:\/\/ruotai.lingxing.com\/"
},
{
"text": "翻译",
"url": "https:\/\/translate.google.com\/"
}
]
},
{
"categoryTitle": "跨境相关",
"links": [
{
"text": "北美",
"url": "https:\/\/www.amazon.com\/"
},
{
"text": "加拿大",
"url": "https:\/\/www.amazon.ca\/"
},
{
"text": "V2EX",
"url": "https:\/\/www.v2ex.com\/"
},
{
"text": "微博",
"url": "https:\/\/weibo.com\/"
},
{
"text": "简书",
"url": "https:\/\/www.jianshu.com\/"
},
{
"text": "豆瓣",
"url": "https:\/\/www.douban.com\/"
},
{
"text": "产品经理",
"url": "http:\/\/www.woshipm.com\/"
},
{
"text": "产品壹佰",
"url": "http:\/\/www.chanpin100.com\/"
},
{
"text": "只言片语",
"url": "http:\/\/pm.budong.me\/"
}
]
},
{
"categoryTitle": "实用工具",
"links": [
{
"text": "腾讯课堂",
"url": "https:\/\/ke.qq.com\/"
},
{
"text": "网易云课堂",
"url": "https:\/\/study.163.com\/"
},
{
"text": "慕课网",
"url": "https:\/\/www.imooc.com\/"
},
{
"text": "哔哩哔哩",
"url": "https:\/\/www.bilibili.com\/"
},
{
"text": "爱奇艺",
"url": "https:\/\/www.iqiyi.com\/"
},
{
"text": "腾讯视频",
"url": "https:\/\/v.qq.com\/"
},
{
"text": "优酷",
"url": "https:\/\/www.youku.com\/"
},
{
"text": "1024",
"url": "http:\/\/cpc.people.com.cn\/"
},
{
"text": "人人影视",
"url": "http:\/\/www.zimuzu.tv\/"
}
]
},
{
"categoryTitle": "优质博客",
"links": [
{
"text": "36氪",
"url": "https:\/\/www.36kr.com\/"
},
{
"text": "虎嗅",
"url": "https:\/\/www.huxiu.com\/"
},
{
"text": "今日头条",
"url": "https:\/\/www.toutiao.com\/"
},
{
"text": "网易",
"url": "https:\/\/www.163.com\/"
},
{
"text": "少数派",
"url": "https:\/\/sspai.com\/"
},
{
"text": "谷歌镜像",
"url": "http:\/\/ac.scmor.com\/"
},
{
"text": "小仙女博客",
"url": "http:\/\/crud.budong.me\/"
},
{
"text": "Coding",
"url": "https:\/\/coding.net\/"
},
{
"text": "酷软清单",
"url": "https:\/\/www.coolist.net\/"
}
]
},
{
"categoryTitle": "其他收藏",
"links": [
{
"text": "百度地图",
"url": "https:\/\/map.baidu.com\/"
},
{
"text": "谷歌地图",
"url": "http:\/\/www.google.cn\/maps"
},
{
"text": "高德地图",
"url": "https:\/\/www.amap.com\/"
},
{
"text": "SM图床",
"url": "https:\/\/sm.ms\/"
},
{
"text": "程序员工具",
"url": "https:\/\/tool.lu\/"
},
{
"text": "前端工具",
"url": "http:\/\/www.alloyteam.com\/nav\/"
},
{
"text": "网盘搜索",
"url": "http:\/\/www.99baiduyun.com\/"
},
{
"text": "二维码",
"url": "https:\/\/cli.im\/"
},
{
"text": "条形码",
"url": "http:\/\/www.qinms.com\/webapp\/barcode\/index.aspx"
}
]
},
{
"categoryTitle": "个人兴趣",
"links": [
{
"text": "电影天堂",
"url": "http:\/\/www.dytt8.net\/"
},
{
"text": "图片素材",
"url": "https:\/\/zhuanlan.zhihu.com\/p\/36456284"
},
{
"text": "效率工具",
"url": "https:\/\/www.portablesoft.org\/"
},
{
"text": "淘宝",
"url": "https:\/\/www.taobao.com\/"
},
{
"text": "京东",
"url": "https:\/\/www.jd.com\/"
},
{
"text": "苏宁",
"url": "https:\/\/www.suning.com\/"
},
{
"text": "什么值得买",
"url": "https:\/\/www.smzdm.com\/"
},
{
"text": "Zealer",
"url": "http:\/\/www.zealer.com\/"
},
{
"text": "慢慢买",
"url": "http:\/\/www.manmanbuy.com\/"
}
]
}
]