部署在CF的轻量化导航页面,可移动卡片式书签,方便管理
github项目
Card-Tab 书签卡片式管理,进入管理模式可以自由移动书签位置,添加和删除书签,支持自定义网站分类,支持切换黑暗色主题
一、cloudflare workes部署
新版本部署
2024.10.30 更新:
1)、增加了前端验证,并取消了在浏览器中保存日志。现在超过15分钟需重新登录,及时退出登录能让你的隐私更安全;
2)、进入设置之前会在自动备份书签,KV里将保存最近10次的备份;
3)、小幅更改了配色。
1、原workes,
- const HTML_CONTENT = `
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Card Tab</title>
- <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2280%22>⭐</text></svg>">
- <style>
- /* 全局样式 */
- body {
- font-family: Arial, sans-serif;
- margin: 0;
- padding: 0;
- background-color: #e8f4ea;
- transition: background-color 0.3s ease;
- }
-
- /* 固定元素样式 */
- .fixed-elements {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- background-color: #e8f4ea;
- z-index: 1000;
- padding: 10px;
- transition: background-color 0.3s ease;
- height: 130px;
- }
-
- .fixed-elements h3 {
- position: absolute;
- top: 10px;
- left: 20px;
- margin: 0;
- }
-
- /* 中心内容样式 */
- .center-content {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 100%;
- max-width: 600px;
- text-align: center;
- }
-
- /* 管理员控制面板样式 */
- .admin-controls {
- position: fixed;
- top: 10px;
- right: 10px;
- font-size: 60%;
- }
-
- /* 添加/删除控制按钮样式 */
- .add-remove-controls {
- display: none;
- flex-direction: column;
- position: fixed;
- right: 20px;
- top: 50%;
- transform: translateY(-50%);
- align-items: center;
- gap: 10px;
- }
-
- .round-btn {
- background-color: #007bff;
- color: white;
- border: none;
- border-radius: 50%;
- width: 40px;
- height: 40px;
- text-align: center;
- font-size: 24px;
- line-height: 40px;
- cursor: pointer;
- margin: 5px 0;
- }
-
- .add-btn { order: 1; }
- .remove-btn { order: 2; }
- .category-btn { order: 3; }
- .remove-category-btn { order: 4; }
-
- /* 主要内容区域样式 */
- .content {
- margin-top: 140px;
- padding: 20px;
- }
-
- /* 搜索栏样式 */
- .search-container {
- margin-top: 10px;
- }
-
- .search-bar {
- display: flex;
- justify-content: center;
- margin-bottom: 10px;
- }
-
- .search-bar input {
- width: 70%;
- padding: 5px;
- border: 1px solid #ccc;
- border-radius: 5px 0 0 5px;
- }
-
- .search-bar button {
- padding: 5px 10px;
- border: 1px solid #ccc;
- border-left: none;
- background-color: #f8f8;
- border-radius: 0 5px 5px 0;
- cursor: pointer;
- }
-
- /* 搜索引擎按钮样式 */
- .search-engines {
- display: flex;
- justify-content: center;
- gap: 10px;
- }
-
- .search-engine {
- padding: 5px 10px;
- border: 1px solid #ccc;
- background-color: #f0f0f0;
- border-radius: 5px;
- cursor: pointer;
- }
-
- /* 主题切换按钮样式 */
- #theme-toggle {
- position: fixed;
- bottom: 50px;
- right: 20px;
- background-color: #b8c9d9;
- color: white;
- border: none;
- border-radius: 50%;
- width: 40px;
- height: 40px;
- text-align: center;
- font-size: 24px;
- line-height: 40px;
- cursor: pointer;
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
- transition: background-color 0.3s ease;
- }
-
- #theme-toggle:hover {
- background-color: #007bff;
- }
-
- /* 对话框样式 */
- #dialog-overlay {
- display: none;
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.5);
- justify-content: center;
- align-items: center;
- }
-
- #dialog-box {
- background-color: white;
- padding: 20px;
- border-radius: 5px;
- width: 300px;
- }
-
- #dialog-box input, #dialog-box select {
- width: 100%;
- margin-bottom: 10px;
- padding: 5px;
- }
-
- /* 分类和卡片样式 */
- .section {
- margin-bottom: 20px;
- }
-
- .section-title-container {
- display: flex;
- align-items: center;
- margin-bottom: 10px;
- }
-
- .section-title {
- font-size: 18px;
- font-weight: bold;
- }
-
- .delete-category-btn {
- background-color: #ff9800;
- color: white;
- border: none;
- padding: 5px 10px;
- border-radius: 5px;
- cursor: pointer;
- }
-
- .card-container {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- }
-
- .card {
- background-color: #b8c9d9;
- border-radius: 5px;
- padding: 10px;
- width: 150px;
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
- cursor: pointer;
- transition: transform 0.2s;
- position: relative;
- user-select: none;
- }
-
- .card:hover {
- transform: translateY(-5px);
- }
-
- .card-top {
- display: flex;
- align-items: center;
- margin-bottom: 5px;
- }
-
- .card-icon {
- width: 16px;
- height: 16px;
- margin-right: 5px;
- }
-
- .card-title {
- font-size: 14px;
- font-weight: bold;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .card-url {
- font-size: 12px;
- color: #666;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .private-tag {
- background-color: #ff9800;
- color: white;
- font-size: 10px;
- padding: 2px 5px;
- border-radius: 3px;
- position: absolute;
- top: 5px;
- right: 5px;
- }
-
- .delete-btn {
- position: absolute;
- top: -10px;
- right: -10px;
- background-color: red;
- color: white;
- border: none;
- border-radius: 50%;
- width: 20px;
- height: 20px;
- text-align: center;
- font-size: 14px;
- line-height: 20px;
- cursor: pointer;
- display: none;
- }
-
- /* 版权信息样式 */
- #copyright {
- position: fixed;
- bottom: 0;
- left: 0;
- width: 100%;
- height: 40px;
- background-color: rgba(255, 255, 255, 0.8);
- display: flex;
- justify-content: center;
- align-items: center;
- font-size: 14px;
- z-index: 1000;
- box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
- }
-
- #copyright p {
- margin: 0;
- }
-
- #copyright a {
- color: #007bff;
- text-decoration: none;
- }
-
- #copyright a:hover {
- text-decoration: underline;
- }
-
- /* 响应式设计 */
- @media (max-width: 480px) {
- .fixed-elements {
- position: relative;
- padding: 5px;
- }
- .content {
- margin-top: 10px;
- }
-
- .admin-controls input,
- .admin-controls button {
- height: 30%;
- }
-
- .card-container {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 10px;
- }
- .card {
- width: 80%;
- max-width: 100%;
- padding: 5px;
- }
- .card-title {
- font-size: 12px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- max-width: 130px;
- }
- .card-url {
- font-size: 10px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- max-width: 130px;
- }
-
- .add-remove-controls {
- right: 2px;
- }
-
- .round-btn,
- #theme-toggle {
- right: 5px;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 30px;
- height: 30px;
- font-size: 24px;
- }
- }
- </style>
- </head>
-
- <body>
- <div class="fixed-elements">
- <h3>我的导航</h3>
- <div class="center-content">
- <!-- 一言模块 -->
- <p id="hitokoto">
- <a href="#" id="hitokoto_text"></a>
- </p>
- <script src="https://v1.hitokoto.cn/?encode=js&select=%23hitokoto" defer></script>
- <!-- 搜索栏 -->
- <div class="search-container">
- <div class="search-bar">
- <input type="text" id="search-input" placeholder="">
- <button id="search-button">🔍</button>
- </div>
- <div class="search-engines">
- <button class="search-engine" data-engine="baidu">百度</button>
- <button class="search-engine" data-engine="bing">必应</button>
- <button class="search-engine" data-engine="google">谷歌</button>
- </div>
- </div>
- </div>
- <!-- 管理员控制面板 -->
- <div class="admin-controls">
- <input type="password" id="admin-password" placeholder="输入密码">
- <button id="admin-mode-btn" onclick="toggleAdminMode()">设 置</button>
- <button id="secret-garden-btn" onclick="toggleSecretGarden()">登 录</button>
- </div>
- </div>
- <div class="content">
- <!-- 添加/删除控制按钮 -->
- <div class="add-remove-controls">
- <button class="round-btn add-btn" onclick="showAddDialog()">+</button>
- <button class="round-btn remove-btn" onclick="toggleRemoveMode()">-</button>
- <button class="round-btn category-btn" onclick="addCategory()">C+</button>
- <button class="round-btn remove-category-btn" onclick="toggleRemoveCategory()">C-</button>
-
- </div>
-
- <!-- 分类和卡片容器 -->
- <div id="sections-container"></div>
- <!-- 主题切换按钮 -->
- <button id="theme-toggle" onclick="toggleTheme()">◑</button>
- <!-- 添加链接对话框 -->
- <div id="dialog-overlay">
- <div id="dialog-box">
- <label for="name-input">名称</label>
- <input type="text" id="name-input">
- <label for="url-input">地址</label>
- <input type="text" id="url-input">
- <label for="category-select">选择分类</label>
- <select id="category-select"></select>
- <div class="private-link-container">
- <label for="private-checkbox">私密链接</label>
- <input type="checkbox" id="private-checkbox">
- </div>
- <button onclick="addLink()">确定</button>
- <button onclick="hideAddDialog()">取消</button>
- </div>
- </div>
- <!-- 版权信息 -->
- <div id="copyright" class="copyright">
- <!--请不要删除-->
- <p>项目地址:<a href="https://github.com/hmhm2022/Card-Tab" target="_blank">GitHub</a> 如果喜欢,烦请点个star!</p>
- </div>
- </div>
-
- <script>
- // 搜索引擎配置
- const searchEngines = {
- baidu: "https://www.baidu.com/s?wd=",
- bing: "https://www.bing.com/search?q=",
- google: "https://www.google.com/search?q="
- };
-
- let currentEngine = "baidu";
-
- // 日志记录函数
- function logAction(action, details) {
- const timestamp = new Date().toISOString();
- const logEntry = timestamp + ': ' + action + ' - ' + JSON.stringify(details);
- console.log(logEntry);
- }
-
- // 设置当前搜索引擎
- function setActiveEngine(engine) {
- currentEngine = engine;
- document.querySelectorAll('.search-engine').forEach(btn => {
- btn.style.backgroundColor = btn.dataset.engine === engine ? '#c0c0c0' : '#f0f0f0';
- });
- logAction('设置搜索引擎', { engine });
- }
-
- // 搜索引擎按钮点击事件
- document.querySelectorAll('.search-engine').forEach(button => {
- button.addEventListener('click', () => setActiveEngine(button.dataset.engine));
- });
-
- // 搜索按钮点击事件
- document.getElementById('search-button').addEventListener('click', () => {
- const query = document.getElementById('search-input').value;
- if (query) {
- logAction('执行搜索', { engine: currentEngine, query });
- window.open(searchEngines[currentEngine] + encodeURIComponent(query), '_blank');
- }
- });
-
- // 搜索输入框回车事件
- document.getElementById('search-input').addEventListener('keypress', (e) => {
- if (e.key === 'Enter') {
- document.getElementById('search-button').click();
- }
- });
-
- // 初始化搜索引擎
- setActiveEngine(currentEngine);
-
- // 全局变量
- let publicLinks = [];
- let privateLinks = [];
- let isAdmin = false;
- let isLoggedIn = false;
- let removeMode = false;
- let isRemoveCategoryMode = false;
- let isDarkTheme = false;
- let links = [];
- const categories = {};
-
- // 添加新分类
- async function addCategory() {
- if (!await validateToken()) {
- return;
- }
- const categoryName = prompt('请输入新分类名称:');
- if (categoryName && !categories[categoryName]) {
- categories[categoryName] = [];
- updateCategorySelect();
- renderCategories();
- saveLinks();
- logAction('添加分类', { categoryName, currentLinkCount: links.length });
- } else if (categories[categoryName]) {
- alert('该分类已存在');
- logAction('添加分类失败', { categoryName, reason: '分类已存在' });
- }
- }
-
- // 删除分类
- async function deleteCategory(category) {
- if (!await validateToken()) {
- return;
- }
- if (confirm('确定要删除 "' + category + '" 分类吗?这将删除该分类下的所有链接。')) {
- delete categories[category];
- links = links.filter(link => link.category !== category);
- publicLinks = publicLinks.filter(link => link.category !== category);
- privateLinks = privateLinks.filter(link => link.category !== category);
- updateCategorySelect();
- saveLinks();
- renderCategories();
- logAction('删除分类', { category });
- }
- }
-
- // 渲染分类(不重新加载链接)
- function renderCategories() {
- const container = document.getElementById('sections-container');
- container.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const section = document.createElement('div');
- section.className = 'section';
-
- const titleContainer = document.createElement('div');
- titleContainer.className = 'section-title-container';
-
- const title = document.createElement('div');
- title.className = 'section-title';
- title.textContent = category;
-
- titleContainer.appendChild(title);
-
- if (isAdmin) {
- const deleteBtn = document.createElement('button');
- deleteBtn.textContent = '删除分类';
- deleteBtn.className = 'delete-category-btn';
- deleteBtn.style.display = isRemoveCategoryMode ? 'inline-block' : 'none';
- deleteBtn.onclick = () => deleteCategory(category);
- titleContainer.appendChild(deleteBtn);
- }
-
- const cardContainer = document.createElement('div');
- cardContainer.className = 'card-container';
- cardContainer.id = category;
-
- section.appendChild(titleContainer);
- section.appendChild(cardContainer);
-
- container.appendChild(section);
-
- const categoryLinks = links.filter(link => link.category === category);
- categoryLinks.forEach(link => {
- createCard(link, cardContainer);
- });
- });
-
- logAction('渲染分类', { categoryCount: Object.keys(categories).length, linkCount: links.length });
- }
-
- // 读取链接数据
- async function loadLinks() {
- const headers = {
- 'Content-Type': 'application/json'
- };
-
- // 如果已登录,从 localStorage 获取 token 并添加到请求头
- if (isLoggedIn) {
- const token = localStorage.getItem('authToken');
- if (token) {
- headers['Authorization'] = token;
- }
- }
-
- try {
- const response = await fetch('/api/getLinks?userId=testUser', {
- headers: headers
- });
-
- if (!response.ok) {
- throw new Error("HTTP error! status: " + response.status);
- }
-
-
- const data = await response.json();
- console.log('Received data:', data);
-
- if (data.categories) {
- Object.assign(categories, data.categories);
- }
-
- publicLinks = data.links ? data.links.filter(link => !link.isPrivate) : [];
- privateLinks = data.links ? data.links.filter(link => link.isPrivate) : [];
- links = isLoggedIn ? [...publicLinks, ...privateLinks] : publicLinks;
-
- loadSections();
- updateCategorySelect();
- updateUIState();
- logAction('读取链接', {
- publicCount: publicLinks.length,
- privateCount: privateLinks.length,
- isLoggedIn: isLoggedIn,
- hasToken: !!localStorage.getItem('authToken')
- });
- } catch (error) {
- console.error('Error loading links:', error);
- alert('加载链接时出错,请刷新页面重试');
- }
- }
-
-
- // 更新UI状态
- function updateUIState() {
- const passwordInput = document.getElementById('admin-password');
- const adminBtn = document.getElementById('admin-mode-btn');
- const secretGardenBtn = document.getElementById('secret-garden-btn');
- const addRemoveControls = document.querySelector('.add-remove-controls');
-
- passwordInput.style.display = isLoggedIn ? 'none' : 'inline-block';
- secretGardenBtn.textContent = isLoggedIn ? "退出" : "登录";
- secretGardenBtn.style.display = 'inline-block';
-
- if (isAdmin) {
- adminBtn.textContent = "离开设置";
- adminBtn.style.display = 'inline-block';
- addRemoveControls.style.display = 'flex';
- } else if (isLoggedIn) {
- adminBtn.textContent = "设置";
- adminBtn.style.display = 'inline-block';
- addRemoveControls.style.display = 'none';
- } else {
- adminBtn.style.display = 'none';
- addRemoveControls.style.display = 'none';
- }
-
- logAction('更新UI状态', { isAdmin, isLoggedIn });
- }
-
- // 登录状态显示(加载所有链接)
- function showSecretGarden() {
- if (isLoggedIn) {
- links = [...publicLinks, ...privateLinks];
- loadSections();
- // 显示所有私密标签
- document.querySelectorAll('.private-tag').forEach(tag => {
- tag.style.display = 'block';
- });
- logAction('显示私密花园');
- }
- }
-
- // 加载分类和链接
- function loadSections() {
- const container = document.getElementById('sections-container');
- container.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const section = document.createElement('div');
- section.className = 'section';
-
- const titleContainer = document.createElement('div');
- titleContainer.className = 'section-title-container';
-
- const title = document.createElement('div');
- title.className = 'section-title';
- title.textContent = category;
-
- titleContainer.appendChild(title);
-
- if (isAdmin) {
- const deleteBtn = document.createElement('button');
- deleteBtn.textContent = '删除分类';
- deleteBtn.className = 'delete-category-btn';
- deleteBtn.style.display = 'none';
- deleteBtn.onclick = () => deleteCategory(category);
- titleContainer.appendChild(deleteBtn);
- }
-
- const cardContainer = document.createElement('div');
- cardContainer.className = 'card-container';
- cardContainer.id = category;
-
- section.appendChild(titleContainer);
- section.appendChild(cardContainer);
-
- let privateCount = 0;
- let linkCount = 0;
-
- links.forEach(link => {
- if (link.category === category) {
- if (link.isPrivate) privateCount++;
- linkCount++;
- createCard(link, cardContainer);
- }
- });
-
- if (privateCount < linkCount || isLoggedIn) {
- container.appendChild(section);
- }
- });
-
- logAction('加载分类和链接', { isAdmin: isAdmin, linkCount: links.length, categoryCount: Object.keys(categories).length });
- }
-
- // 创建卡片
- function createCard(link, container) {
- const card = document.createElement('div');
- card.className = 'card';
- card.setAttribute('draggable', isAdmin);
- card.dataset.isPrivate = link.isPrivate;
-
- const cardTop = document.createElement('div');
- cardTop.className = 'card-top';
-
- // 定义默认的 SVG 图标
- const defaultIconSVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
- '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>' +
- '<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>' +
- '</svg>';
-
- // 创建图标元素
- const icon = document.createElement('img');
- icon.className = 'card-icon';
- // icon.src = 'https://api.iowen.cn/favicon/' + extractDomain(link.url) + '.png';
- icon.src = 'https://www.faviconextractor.com/favicon/' + extractDomain(link.url);
- icon.alt = 'Website Icon';
-
- // 如果图片加载失败,使用默认的 SVG 图标
- icon.onerror = function() {
- const svgBlob = new Blob([defaultIconSVG], {type: 'image/svg+xml'});
- const svgUrl = URL.createObjectURL(svgBlob);
- this.src = svgUrl;
-
- this.onload = () => URL.revokeObjectURL(svgUrl);
- };
-
- function extractDomain(url) {
- let domain;
- try {
- domain = new URL(url).hostname;
- } catch (e) {
- domain = url;
- }
- return domain;
- }
-
- const title = document.createElement('div');
- title.className = 'card-title';
- title.textContent = link.name;
-
- cardTop.appendChild(icon);
- cardTop.appendChild(title);
-
- const url = document.createElement('div');
- url.className = 'card-url';
- url.textContent = link.url;
-
- card.appendChild(cardTop);
- card.appendChild(url);
-
- if (link.isPrivate) {
- const privateTag = document.createElement('div');
- privateTag.className = 'private-tag';
- privateTag.textContent = '私密';
- card.appendChild(privateTag);
- }
-
- const correctedUrl = link.url.startsWith('http://') || link.url.startsWith('https://') ? link.url : 'http://' + link.url;
-
- if (!isAdmin) {
- card.addEventListener('click', () => {
- window.open(correctedUrl, '_blank');
- logAction('打开链接', { name: link.name, url: correctedUrl });
- });
- }
-
- const deleteBtn = document.createElement('button');
- deleteBtn.textContent = '–';
- deleteBtn.className = 'delete-btn';
- deleteBtn.onclick = function (event) {
- event.stopPropagation();
- removeCard(card);
- };
- card.appendChild(deleteBtn);
-
- updateCardStyle(card);
-
- card.addEventListener('dragstart', dragStart);
- card.addEventListener('dragover', dragOver);
- card.addEventListener('dragend', dragEnd);
- card.addEventListener('drop', drop);
- card.addEventListener('touchstart', touchStart, { passive: false });
-
- if (isAdmin && removeMode) {
- deleteBtn.style.display = 'block';
- }
-
- if (isAdmin || (link.isPrivate && isLoggedIn) || !link.isPrivate) {
- container.appendChild(card);
- }
- // logAction('创建卡片', { name: link.name, isPrivate: link.isPrivate });
- }
-
- // 更新卡片样式
- function updateCardStyle(card) {
- if (isDarkTheme) {
- card.style.backgroundColor = '#1e1e1e';
- card.style.color = '#ffffff';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
- } else {
- card.style.backgroundColor = '#b8c9d9';
- card.style.color = '#333';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
- }
- }
-
- // 更新分类选择下拉框
- function updateCategorySelect() {
- const categorySelect = document.getElementById('category-select');
- categorySelect.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const option = document.createElement('option');
- option.value = category;
- option.textContent = category;
- categorySelect.appendChild(option);
- });
-
- logAction('更新分类选择', { categoryCount: Object.keys(categories).length });
- }
-
- // 保存链接数据
- async function saveLinks() {
- if (isAdmin && !(await validateToken())) {
- return;
- }
-
- let allLinks = [...publicLinks, ...privateLinks];
-
- try {
- await fetch('/api/saveOrder', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': localStorage.getItem('authToken')
- },
- body: JSON.stringify({
- userId: 'testUser',
- links: allLinks,
- categories: categories
- }),
- });
- logAction('保存链接', { linkCount: allLinks.length, categoryCount: Object.keys(categories).length });
- } catch (error) {
- logAction('保存链接失败', { error: error.message });
- alert('保存链接失败,请重试');
- }
- }
-
- // 添加卡片弹窗
- async function addLink() {
- if (!await validateToken()) {
- return;
- }
- const name = document.getElementById('name-input').value;
- const url = document.getElementById('url-input').value;
- const category = document.getElementById('category-select').value;
- const isPrivate = document.getElementById('private-checkbox').checked;
-
- if (name && url && category) {
- const newLink = { name, url, category, isPrivate };
-
- if (isPrivate) {
- privateLinks.push(newLink);
- } else {
- publicLinks.push(newLink);
- }
-
- links = isLoggedIn ? [...publicLinks, ...privateLinks] : publicLinks;
-
- if (isAdmin || (isPrivate && isLoggedIn) || !isPrivate) {
- const container = document.getElementById(category);
- if (container) {
- createCard(newLink, container);
- } else {
- categories[category] = [];
- renderCategories();
- }
- }
-
- saveLinks();
-
- document.getElementById('name-input').value = '';
- document.getElementById('url-input').value = '';
- document.getElementById('private-checkbox').checked = false;
- hideAddDialog();
-
- logAction('添加卡片', { name, url, category, isPrivate });
- }
- }
-
- // 删除卡片
- async function removeCard(card) {
- if (!await validateToken()) {
- return;
- }
- const name = card.querySelector('.card-title').textContent;
- const url = card.querySelector('.card-url').textContent;
- const isPrivate = card.dataset.isPrivate === 'true';
-
- links = links.filter(link => link.url !== url);
- if (isPrivate) {
- privateLinks = privateLinks.filter(link => link.url !== url);
- } else {
- publicLinks = publicLinks.filter(link => link.url !== url);
- }
-
- for (const key in categories) {
- categories[key] = categories[key].filter(link => link.url !== url);
- }
-
- card.remove();
-
- saveLinks();
-
- logAction('删除卡片', { name, url, isPrivate });
- }
-
- // 拖拽卡片
- let draggedCard = null;
- let touchStartX, touchStartY;
-
- // 触屏端拖拽卡片
- function touchStart(event) {
- if (!isAdmin) {
- return;
- }
- draggedCard = event.target.closest('.card');
- if (!draggedCard) return;
-
- event.preventDefault();
- const touch = event.touches[0];
- touchStartX = touch.clientX;
- touchStartY = touch.clientY;
-
- draggedCard.classList.add('dragging');
-
- document.addEventListener('touchmove', touchMove, { passive: false });
- document.addEventListener('touchend', touchEnd);
-
- }
-
- function touchMove(event) {
- if (!draggedCard) return;
- event.preventDefault();
-
- const touch = event.touches[0];
- const currentX = touch.clientX;
- const currentY = touch.clientY;
-
- const deltaX = currentX - touchStartX;
- const deltaY = currentY - touchStartY;
- draggedCard.style.transform = "translate(" + deltaX + "px, " + deltaY + "px)";
-
- const target = findCardUnderTouch(currentX, currentY);
- if (target && target !== draggedCard) {
- const container = target.parentElement;
- const targetRect = target.getBoundingClientRect();
-
- if (currentX < targetRect.left + targetRect.width / 2) {
- container.insertBefore(draggedCard, target);
- } else {
- container.insertBefore(draggedCard, target.nextSibling);
- }
- }
- }
-
- function touchEnd(event) {
- if (!draggedCard) return;
-
- const card = draggedCard;
- const targetCategory = card.closest('.card-container').id;
-
- validateToken().then(isValid => {
- if (isValid && card) {
- updateCardCategory(card, targetCategory);
- saveCardOrder().catch(error => {
- console.error('Save failed:', error);
- });
- }
- cleanupDragState();
- });
- }
-
- function findCardUnderTouch(x, y) {
- const cards = document.querySelectorAll('.card:not(.dragging)');
- return Array.from(cards).find(card => {
- const rect = card.getBoundingClientRect();
- return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
- });
- }
-
- // PC端拖拽卡片
- function dragStart(event) {
- if (!isAdmin) {
- event.preventDefault();
- return;
- }
- draggedCard = event.target.closest('.card');
- if (!draggedCard) return;
-
- draggedCard.classList.add('dragging');
- event.dataTransfer.effectAllowed = "move";
- logAction('开始拖拽卡片', { name: draggedCard.querySelector('.card-title').textContent });
- }
-
- function dragOver(event) {
- if (!isAdmin) {
- event.preventDefault();
- return;
- }
- event.preventDefault();
- const target = event.target.closest('.card');
- if (target && target !== draggedCard) {
- const container = target.parentElement;
- const mousePositionX = event.clientX;
- const targetRect = target.getBoundingClientRect();
-
- if (mousePositionX < targetRect.left + targetRect.width / 2) {
- container.insertBefore(draggedCard, target);
- } else {
- container.insertBefore(draggedCard, target.nextSibling);
- }
- }
- }
-
- // 清理拖拽状态函数
- function cleanupDragState() {
- if (draggedCard) {
- draggedCard.classList.remove('dragging');
- draggedCard.style.transform = '';
- draggedCard = null;
- }
-
- document.removeEventListener('touchmove', touchMove);
- document.removeEventListener('touchend', touchEnd);
-
- touchStartX = null;
- touchStartY = null;
- }
-
- // PC端拖拽结束
- function drop(event) {
- if (!isAdmin) {
- event.preventDefault();
- return;
- }
- event.preventDefault();
-
- const card = draggedCard;
- const targetCategory = event.target.closest('.card-container').id;
-
- validateToken().then(isValid => {
- if (isValid && card) {
- updateCardCategory(card, targetCategory);
- saveCardOrder().catch(error => {
- console.error('Save failed:', error);
- });
- }
- cleanupDragState();
- });
- }
-
- function dragEnd(event) {
- if (draggedCard) {
- draggedCard.classList.remove('dragging');
- logAction('拖拽卡片结束');
- }
- }
-
- // 更新卡片分类
- function updateCardCategory(card, newCategory) {
- const cardTitle = card.querySelector('.card-title').textContent;
- const cardUrl = card.querySelector('.card-url').textContent;
- const isPrivate = card.dataset.isPrivate === 'true';
-
- const linkIndex = links.findIndex(link => link.url === cardUrl);
- if (linkIndex !== -1) {
- links[linkIndex].category = newCategory;
- }
-
- const linkArray = isPrivate ? privateLinks : publicLinks;
- const arrayIndex = linkArray.findIndex(link => link.url === cardUrl);
- if (arrayIndex !== -1) {
- linkArray[arrayIndex].category = newCategory;
- }
-
- card.dataset.category = newCategory;
- }
-
- // 在页面加载完成后添加触摸事件监听器
- document.addEventListener('DOMContentLoaded', function() {
- const cardContainers = document.querySelectorAll('.card-container');
- cardContainers.forEach(container => {
- container.addEventListener('touchstart', touchStart, { passive: false });
- });
- });
-
- // 保存卡片顺序
- async function saveCardOrder() {
- if (!await validateToken()) {
- return;
- }
- const containers = document.querySelectorAll('.card-container');
- let newPublicLinks = [];
- let newPrivateLinks = [];
- let newCategories = {};
-
- containers.forEach(container => {
- const category = container.id;
- newCategories[category] = [];
-
- [...container.children].forEach(card => {
- const url = card.querySelector('.card-url').textContent;
- const name = card.querySelector('.card-title').textContent;
- const isPrivate = card.dataset.isPrivate === 'true';
- card.dataset.category = category;
- const link = { name, url, category, isPrivate };
- if (isPrivate) {
- newPrivateLinks.push(link);
- } else {
- newPublicLinks.push(link);
- }
- newCategories[category].push(link);
- });
- });
-
- publicLinks.length = 0;
- publicLinks.push(...newPublicLinks);
- privateLinks.length = 0;
- privateLinks.push(...newPrivateLinks);
- Object.keys(categories).forEach(key => delete categories[key]);
- Object.assign(categories, newCategories);
-
- logAction('保存卡片顺序', {
- publicCount: newPublicLinks.length,
- privateCount: newPrivateLinks.length,
- categoryCount: Object.keys(newCategories).length
- });
-
- try {
- const response = await fetch('/api/saveOrder', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': localStorage.getItem('authToken')
- },
- body: JSON.stringify({
- userId: 'testUser',
- links: [...newPublicLinks, ...newPrivateLinks],
- categories: newCategories
- }),
- });
- const result = await response.json();
- if (!result.success) {
- throw new Error('Failed to save order');
- }
- logAction('保存卡片顺序', { publicCount: newPublicLinks.length, privateCount: newPrivateLinks.length, categoryCount: Object.keys(newCategories).length });
- } catch (error) {
- logAction('保存顺序失败', { error: error.message });
- alert('保存顺序失败,请重试');
- }
- }
-
- // 设置状态重新加载卡片
- function reloadCardsAsAdmin() {
- document.querySelectorAll('.card-container').forEach(container => {
- container.innerHTML = '';
- });
- loadLinks().then(() => {
- if (isDarkTheme) {
- applyDarkTheme();
- }
- });
- logAction('重新加载卡片(管理员模式)');
- }
-
- // 密码输入框回车事件
- document.getElementById('admin-password').addEventListener('keypress', (e) => {
- if (e.key === 'Enter') {
- toggleSecretGarden();
- }
- });
-
- // 切换设置状态
- async function toggleAdminMode() {
- const adminBtn = document.getElementById('admin-mode-btn');
- const addRemoveControls = document.querySelector('.add-remove-controls');
-
- if (!isAdmin && isLoggedIn) {
- if (!await validateToken()) {
- return;
- }
-
- // 在进入设置模式之前进行备份
- try {
- const response = await fetch('/api/backupData', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': localStorage.getItem('authToken')
- },
- body: JSON.stringify({
- sourceUserId: 'testUser',
- backupUserId: 'backup'
- }),
- });
- const result = await response.json();
- if (result.success) {
- logAction('数据备份成功');
- } else {
- throw new Error('备份失败');
- }
- } catch (error) {
- logAction('数据备份失败', { error: error.message });
- if (!confirm('备份失败,是否仍要继续进入设置模式?')) {
- return;
- }
- }
-
- isAdmin = true;
- adminBtn.textContent = "退出设置";
- addRemoveControls.style.display = 'flex';
- alert('准备设置分类和书签');
- reloadCardsAsAdmin();
- logAction('进入设置');
- } else if (isAdmin) {
- isAdmin = false;
- removeMode = false;
- adminBtn.textContent = "设 置";
- addRemoveControls.style.display = 'none';
- alert('设置已保存');
- reloadCardsAsAdmin();
- logAction('离开设置');
- }
-
- updateUIState();
- }
-
- // 切换到登录状态
- function toggleSecretGarden() {
- const passwordInput = document.getElementById('admin-password');
- if (!isLoggedIn) {
- verifyPassword(passwordInput.value)
- .then(result => {
- if (result.valid) {
- isLoggedIn = true;
- localStorage.setItem('authToken', result.token);
- console.log('Token saved:', result.token);
- loadLinks();
- alert('登录成功!');
- logAction('登录成功');
- } else {
- alert('密码错误');
- logAction('登录失败', { reason: result.error || '密码错误' });
- }
- updateUIState();
- })
- .catch(error => {
- console.error('Login error:', error);
- alert('登录过程出错,请重试');
- });
- } else {
- isLoggedIn = false;
- isAdmin = false;
- localStorage.removeItem('authToken');
- links = publicLinks;
- loadSections();
- alert('退出登录!');
- updateUIState();
- passwordInput.value = '';
- logAction('退出登录');
- }
- }
-
- // 应用暗色主题
- function applyDarkTheme() {
- document.body.style.backgroundColor = '#121212';
- document.body.style.color = '#ffffff';
- const cards = document.querySelectorAll('.card');
- cards.forEach(card => {
- card.style.backgroundColor = '#1e1e1e';
- card.style.color = '#ffffff';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
- });
- logAction('应用暗色主题');
- }
-
- // 显示添加链接对话框
- function showAddDialog() {
- document.getElementById('dialog-overlay').style.display = 'flex';
- logAction('显示添加链接对话框');
- }
-
- // 隐藏添加链接对话框
- function hideAddDialog() {
- document.getElementById('dialog-overlay').style.display = 'none';
- logAction('隐藏添加链接对话框');
- }
-
- // 切换删除卡片模式
- function toggleRemoveMode() {
- removeMode = !removeMode;
- const deleteButtons = document.querySelectorAll('.delete-btn');
- deleteButtons.forEach(btn => {
- btn.style.display = removeMode ? 'block' : 'none';
- });
- logAction('切换删除卡片模式', { removeMode });
- }
-
- //切换删除分类模式
- function toggleRemoveCategory() {
- isRemoveCategoryMode = !isRemoveCategoryMode;
- const deleteButtons = document.querySelectorAll('.delete-category-btn');
- deleteButtons.forEach(btn => {
- btn.style.display = isRemoveCategoryMode ? 'inline-block' : 'none';
- });
- logAction('切换删除分类模式', { isRemoveCategoryMode });
- }
-
- // 切换主题
- function toggleTheme() {
- isDarkTheme = !isDarkTheme;
-
- document.body.style.backgroundColor = isDarkTheme ? '#121212' : '#e8f4ea';
- document.body.style.color = isDarkTheme ? '#ffffff' : '#333';
-
- const cards = document.querySelectorAll('.card');
- cards.forEach(card => {
- card.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#b8c9d9';
- card.style.color = isDarkTheme ? '#ffffff' : '#333';
- card.style.boxShadow = isDarkTheme
- ? '0 4px 8px rgba(0, 0, 0, 0.5)'
- : '0 4px 8px rgba(0, 0, 0, 0.1)';
- });
-
- const fixedElements = document.querySelectorAll('.fixed-elements');
- fixedElements.forEach(element => {
- element.style.backgroundColor = isDarkTheme ? '#121212' : '#e8f4ea';
- element.style.color = isDarkTheme ? '#ffffff' : '#333';
- });
-
- const dialogBox = document.getElementById('dialog-box');
- dialogBox.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#ffffff';
- dialogBox.style.color = isDarkTheme ? '#ffffff' : '#333';
-
- const inputs = document.querySelectorAll('input[type="text"], input[type="password"], select');
- inputs.forEach(input => {
- input.style.backgroundColor = isDarkTheme ? '#444' : '#fff';
- input.style.color = isDarkTheme ? '#fff' : '#333';
- input.style.borderColor = isDarkTheme ? '#555' : '#ccc';
- });
-
- logAction('切换主题', { isDarkTheme });
- }
-
- // 验证密码
- async function verifyPassword(inputPassword) {
- const response = await fetch('/api/verifyPassword', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ password: inputPassword }),
- });
- const result = await response.json();
- return result;
- }
-
- // 初始化加载
- document.addEventListener('DOMContentLoaded', async () => {
- await validateToken();
- loadLinks();
- });
-
-
- // 前端检查是否有 token
- async function validateToken() {
- const token = localStorage.getItem('authToken');
- if (!token) {
- isLoggedIn = false;
- updateUIState();
- return false;
- }
-
- try {
- const response = await fetch('/api/getLinks?userId=testUser', {
- headers: { 'Authorization': token }
- });
-
- if (response.status === 401) {
- await resetToLoginState('token已过期,请重新登录');
- return false;
- }
-
- isLoggedIn = true;
- updateUIState();
- return true;
- } catch (error) {
- console.error('Token validation error:', error);
- return false;
- }
- }
-
- // 重置状态
- async function resetToLoginState(message) {
- alert(message);
-
- cleanupDragState();
-
- localStorage.removeItem('authToken');
- isLoggedIn = false;
- isAdmin = false;
- removeMode = false;
- isRemoveCategoryMode = false;
-
- const passwordInput = document.getElementById('admin-password');
- if (passwordInput) {
- passwordInput.value = '';
- }
-
- updateUIState();
- links = publicLinks;
- loadSections();
-
- const addRemoveControls = document.querySelector('.add-remove-controls');
- if (addRemoveControls) {
- addRemoveControls.style.display = 'none';
- }
-
- document.querySelectorAll('.delete-btn').forEach(btn => {
- btn.style.display = 'none';
- });
-
- document.querySelectorAll('.delete-category-btn').forEach(btn => {
- btn.style.display = 'none';
- });
-
- const dialogOverlay = document.getElementById('dialog-overlay');
- if (dialogOverlay) {
- dialogOverlay.style.display = 'none';
- }
- }
-
- </script>
- </body>
-
- </html>
- `;
-
- // 服务端 token 验证
- async function validateServerToken(authToken, env) {
- if (!authToken) {
- return {
- isValid: false,
- status: 401,
- response: { error: 'Unauthorized', message: '未登录或登录已过期' }
- };
- }
-
- try {
- const [timestamp, hash] = authToken.split('.');
- const tokenTimestamp = parseInt(timestamp);
- const now = Date.now();
-
- const FIFTEEN_MINUTES = 15 * 60 * 1000;
- if (now - tokenTimestamp > FIFTEEN_MINUTES) {
- return {
- isValid: false,
- status: 401,
- response: {
- error: 'Token expired',
- tokenExpired: true,
- message: '登录已过期,请重新登录'
- }
- };
- }
-
- const tokenData = timestamp + "_" + env.ADMIN_PASSWORD;
- const encoder = new TextEncoder();
- const data = encoder.encode(tokenData);
- const hashBuffer = await crypto.subtle.digest('SHA-256', data);
- const expectedHash = btoa(String.fromCharCode(...new Uint8Array(hashBuffer)));
-
- if (hash !== expectedHash) {
- return {
- isValid: false,
- status: 401,
- response: {
- error: 'Invalid token',
- tokenInvalid: true,
- message: '登录状态无效,请重新登录'
- }
- };
- }
-
- return { isValid: true };
- } catch (error) {
- return {
- isValid: false,
- status: 401,
- response: {
- error: 'Invalid token',
- tokenInvalid: true,
- message: '登录验证失败,请重新登录'
- }
- };
- }
- }
-
- export default {
- async fetch(request, env) {
- const url = new URL(request.url);
-
- if (url.pathname === '/') {
- return new Response(HTML_CONTENT, {
- headers: { 'Content-Type': 'text/html' }
- });
- }
-
- if (url.pathname === '/api/getLinks') {
- const userId = url.searchParams.get('userId');
- const authToken = request.headers.get('Authorization');
- const data = await env.CARD_ORDER.get(userId);
-
- if (data) {
- const parsedData = JSON.parse(data);
-
- // 验证 token
- if (authToken) {
- const validation = await validateServerToken(authToken, env);
- if (!validation.isValid) {
- return new Response(JSON.stringify(validation.response), {
- status: validation.status,
- headers: { 'Content-Type': 'application/json' }
- });
- }
-
- // Token 有效,返回完整数据
- return new Response(JSON.stringify(parsedData), {
- status: 200,
- headers: { 'Content-Type': 'application/json' }
- });
- }
-
- // 未提供 token,只返回公开数据
- const filteredLinks = parsedData.links.filter(link => !link.isPrivate);
- const filteredCategories = {};
- Object.keys(parsedData.categories).forEach(category => {
- filteredCategories[category] = parsedData.categories[category].filter(link => !link.isPrivate);
- });
-
- return new Response(JSON.stringify({
- links: filteredLinks,
- categories: filteredCategories
- }), {
- status: 200,
- headers: { 'Content-Type': 'application/json' }
- });
- }
-
- return new Response(JSON.stringify({
- links: [],
- categories: {}
- }), {
- status: 200,
- headers: { 'Content-Type': 'application/json' }
- });
- }
-
- if (url.pathname === '/api/saveOrder' && request.method === 'POST') {
- const authToken = request.headers.get('Authorization');
- const validation = await validateServerToken(authToken, env);
-
- if (!validation.isValid) {
- return new Response(JSON.stringify(validation.response), {
- status: validation.status,
- headers: { 'Content-Type': 'application/json' }
- });
- }
-
- const { userId, links, categories } = await request.json();
- await env.CARD_ORDER.put(userId, JSON.stringify({ links, categories }));
- return new Response(JSON.stringify({
- success: true,
- message: '保存成功'
- }), {
- status: 200,
- headers: { 'Content-Type': 'application/json' }
- });
- }
-
- if (url.pathname === '/api/verifyPassword' && request.method === 'POST') {
- try {
- const { password } = await request.json();
- const isValid = password === env.ADMIN_PASSWORD;
-
- if (isValid) {
- // 生成包含时间戳的加密 token
- const timestamp = Date.now();
- const tokenData = timestamp + "_" + env.ADMIN_PASSWORD;
- const encoder = new TextEncoder();
- const data = encoder.encode(tokenData);
- const hashBuffer = await crypto.subtle.digest('SHA-256', data);
-
- // 使用指定格式:timestamp.hash
- const token = timestamp + "." + btoa(String.fromCharCode(...new Uint8Array(hashBuffer)));
-
- return new Response(JSON.stringify({
- valid: true,
- token: token
- }), {
- status: 200,
- headers: { 'Content-Type': 'application/json' }
- });
- }
-
- return new Response(JSON.stringify({
- valid: false,
- error: 'Invalid password'
- }), {
- status: 403,
- headers: { 'Content-Type': 'application/json' }
- });
- } catch (error) {
- return new Response(JSON.stringify({
- valid: false,
- error: error.message
- }), {
- status: 500,
- headers: { 'Content-Type': 'application/json' }
- });
- }
- }
-
- if (url.pathname === '/api/backupData' && request.method === 'POST') {
- const { sourceUserId } = await request.json();
- const result = await this.backupData(env, sourceUserId);
- return new Response(JSON.stringify(result), {
- status: result.success ? 200 : 404,
- headers: { 'Content-Type': 'application/json' }
- });
- }
-
- return new Response('Not Found', { status: 404 });
- },
-
- async backupData(env, sourceUserId) {
- const MAX_BACKUPS = 10;
- const sourceData = await env.CARD_ORDER.get(sourceUserId);
-
- if (sourceData) {
- try {
- const currentDate = new Date().toLocaleString('zh-CN', {
- timeZone: 'Asia/Shanghai',
- year: 'numeric',
- month: '2-digit',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit',
- second: '2-digit',
- hour12: false
- }).replace(/\//g, '-');
-
- const backupId = `backup_${currentDate}`;
-
- const backups = await env.CARD_ORDER.list({ prefix: 'backup_' });
- const backupKeys = backups.keys.map(key => key.name).sort((a, b) => {
- const timeA = new Date(a.split('_')[1].replace(/-/g, '/')).getTime();
- const timeB = new Date(b.split('_')[1].replace(/-/g, '/')).getTime();
- return timeB - timeA; // 降序排序,最新的在前
- });
-
- await env.CARD_ORDER.put(backupId, sourceData);
-
- const allBackups = [...backupKeys, backupId].sort((a, b) => {
- const timeA = new Date(a.split('_')[1].replace(/-/g, '/')).getTime();
- const timeB = new Date(b.split('_')[1].replace(/-/g, '/')).getTime();
- return timeB - timeA;
- });
-
- const backupsToDelete = allBackups.slice(MAX_BACKUPS);
-
- if (backupsToDelete.length > 0) {
- await Promise.all(
- backupsToDelete.map(key => env.CARD_ORDER.delete(key))
- );
- }
-
- return {
- success: true,
- backupId,
- remainingBackups: MAX_BACKUPS,
- deletedCount: backupsToDelete.length
- };
- } catch (error) {
- return {
- success: false,
- error: 'Backup operation failed',
- details: error.message
- };
- }
- }
- return { success: false, error: 'Source data not found' };
- }
- };
修改后workes, 效果
- const HTML_CONTENT = `
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>ws01主页</title>
- <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2280%22>💠</text></svg>">
- <style>
- /* 全局样式 */
- body {
- font-family: Arial, sans-serif;
- margin: 0;
- padding: 0;
- background-color: #d4d4d4;
- transition: background-color 0.3s ease;
- }
-
- ul {
- padding: 0;
- margin-block-start: 1em;
- margin-block-end: 1em;
- margin-inline-start: 0px;
- margin-inline-end: 0px;
- padding-inline-start: 40px;
- unicode-bidi: isolate;
- }
- li {
- display: list-item;
- text-align: -webkit-match-parent;
- unicode-bidi: isolate;
- margin: 0 8px;
- }
-
- .background {
- background-image: linear-gradient(#d4d4d4 1px, transparent 0), linear-gradient(90deg, #d4d4d4 1px, transparent 0);
- background-size: 32px 32px;
- background-color: #fffcf8;
- }
-
- .header {
- background-color: #fff;
- box-shadow: 0 0 5px rgba(0, 0, 0, .1);
- transition: background-color .5s;
- }
- .navbar {
- display: flex;
- width: 1280px;
- margin: auto;
- height: 40px;
- }
-
- .navbar .brand {
-
- display: flex;
- align-items: center;
- color: #555;
- }
-
- .brand .logo {
- max-width: 36px;
- }
-
- .brand .title {
- margin-left: 5px;
- font-family: helvetica neue, helvetica, arial, sans-serif;
- font-size: 24px;
- font-weight: 700;
- }
-
- .beta {
- color: #ccc;
- font-size: 11px;
- font-weight: 400;
- position: relative;
- top: -14px;
- }
-
- .category-list {
- list-style: none;
- display: flex;
- align-items: center;
- }
-
- .sites {
- width:1286px;
- background:;
- border:2px solid auto;
- margin:15px auto;
- padding:0px;
- text-align:center;
- }
- .sites1 {
- width:1286px;
- background:;
- border:2px solid auto;
- margin:15px auto;
- padding:0px;
- }
-
- .sites dl {
- height:36px;
- line-height:36px;
- display:block;
- margin:0;
- }
-
- .sites dl.alt {
- background:#d4d4d4;
- border-top:1px solid #ffffff;
- border-bottom:1px solid #ffffff;
- }
-
- .sites dl.alt2 {
- background:#d4d4d4;
- border-top:1px solid #ffffff;
- border-bottom:1px solid #ffffff;
- }
-
- .sites dt,.sites dd {
- text-align:center;
- display:block;
- float:left;
- }
-
- .sites dt {
- width:60px;
- }
-
- .sites dd {
- width:90px;
- margin:0;
- }
- .footer {
- width:580px;
- text-align:center;
- margin:5px auto;
- padding:2px;
- }
-
-
-
- /* 固定元素样式 */
- .fixed-elements {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- background-color: #d4d4d4;
- z-index: 1000;
- padding: 10px;
- transition: background-color 0.3s ease;
- height: 130px;
- }
-
- .fixed-elements h3 {
- position: absolute;
- top: 10px;
- left: 20px;
- margin: 0;
- }
-
- /* 中心内容样式 */
- .center-content {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 100%;
- max-width: 600px;
- text-align: center;
- }
-
- /* 管理员控制面板样式 */
- .admin-controls {
- position: fixed;
- top: 10px;
- right: 10px;
- font-size: 60%;
- }
-
- /* 添加/删除控制按钮样式 */
- .add-remove-controls {
- display: none;
- flex-direction: column;
- position: fixed;
- right: 20px;
- top: 50%;
- transform: translateY(-50%);
- align-items: center;
- gap: 10px;
- }
-
- .round-btn {
- background-color: #007bff;
- color: white;
- border: none;
- border-radius: 50%;
- width: 40px;
- height: 40px;
- text-align: center;
- font-size: 24px;
- line-height: 40px;
- cursor: pointer;
- margin: 5px 0;
- }
-
- .add-btn { order: 1; }
- .remove-btn { order: 2; }
- .category-btn { order: 3; }
- .remove-category-btn { order: 4; }
-
- /* 主要内容区域样式 */
- .content {
- margin-top: 140px;
- padding: 20px;
- }
-
- /* 搜索栏样式 */
- .search-container {
- margin-top: 10px;
- }
-
- .search-bar {
- display: flex;
- justify-content: center;
- margin-bottom: 10px;
- }
-
- .search-bar input {
- width: 50%;
- padding: 5px;
- border: 1px solid #ccc;
- border-radius: 5px 0 0 5px;
- }
-
- .search-bar button {
- padding: 5px 20px;
- border: 1px solid #ccc;
- border-left: none;
- background-color: rgba(160, 201, 229, 0.6);
- border-radius: 0 5px 5px 0;
- cursor: pointer;
- }
-
- /* 搜索引擎按钮样式 */
- .search-engines {
- display: flex;
- justify-content: center;
- gap: 10px;
- }
-
- .search-engine {
- padding: 5px 10px;
- border: 1px solid #ccc;
- background-color: #f0f0;
- border-radius: 5px;
- cursor: pointer;
- }
-
- /* 主题切换按钮样式 */
- #theme-toggle {
- position: fixed;
- bottom: 50px;
- right: 20px;
- background-color: #007bff;
- color: white;
- border: none;
- border-radius: 50%;
- width: 40px;
- height: 40px;
- text-align: center;
- font-size: 24px;
- line-height: 40px;
- cursor: pointer;
- }
-
- /* 显示日志按钮样式 */
- #view-logs-btn {
- position: fixed;
- top: 100px;
- right: 10px;
- z-index: 1000;
- }
-
- /* 对话框样式 */
- #dialog-overlay {
- display: none;
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.5);
- justify-content: center;
- align-items: center;
- }
-
- #dialog-box {
- background-color: white;
- padding: 20px;
- border-radius: 5px;
- width: 300px;
- }
-
- #dialog-box input, #dialog-box select {
- width: 100%;
- margin-bottom: 10px;
- padding: 5px;
- }
-
- /* 分类和卡片样式 */
- .section {
- margin-bottom: 20px;
- }
-
- .section-title-container {
- display: flex;
- align-items: center;
- margin-bottom: 10px;
- }
-
- .section-title {
- font-size: 18px;
- font-weight: bold;
- margin-left: 20px; /* 分类标签右移添加这一行 */
- color: green; /* 分类标签字体颜色添加这一行 */
- }
-
- .delete-category-btn {
- background-color: pink;
- color: white;
- border: none;
- padding: 5px 10px;
- border-radius: 5px;
- cursor: pointer;
- }
-
- .card-container {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- }
-
- .card {
- background-color: rgba(160, 201, 229, 0.6);
- border-radius: 5px;
- padding: 10px;
- width: 132px;
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
- cursor: pointer;
- transition: transform 0.2s;
- position: relative;
- }
-
- /* 中等屏幕 (平板等) */
- @media (max-width: 1024px) {
-
- }
- /* 小屏幕 (手机) */
- @media (max-width: 768px) {
-
- }
-
- .card:hover {
- transform: translateY(-5px);
- }
-
- .card-top {
- display: flex;
- align-items: center;
- margin-bottom: 5px;
- }
-
- .card-icon {
- width: 20px;
- height: 20px;
- margin-right: 5px;
- }
-
- .card-title {
- font-size: 18px;
- font-weight: bold;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .card-url {
- font-size: 14px;
- color: #666;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- text-align: left; /* 添加左对齐 */
- }
-
- /* 超小屏幕 (更小手机) */
- @media (max-width: 480px) {
- .card {
- padding: 6px; /* 进一步减小卡片内边距6 */
- width: 300px; /* 手机上显示4列,235时显示6列 */
- }
- .card-icon {
- width: 42px;
- height: 42px;
-
- }
- .card-title {
- font-size: 36px;
-
- }
- .card-url {
- font-size: 22px;
-
- }
- }
-
- .private-tag {
- background-color: #ff9800;
- color: white;
- font-size: 10px;
- padding: 2px 5px;
- border-radius: 3px;
- position: absolute;
- top: 5px;
- right: 5px;
- }
-
- .delete-btn {
- position: absolute;
- top: -10px;
- right: -10px;
- background-color: red;
- color: white;
- border: none;
- border-radius: 50%;
- width: 20px;
- height: 20px;
- text-align: center;
- font-size: 14px;
- line-height: 20px;
- cursor: pointer;
- display: none;
- }
-
- /* 版权信息样式 */
- #copyright {
- position: fixed;
- bottom: 0;
- left: 0;
- width: 100%;
- height: 40px;
- background-color: rgba(255, 255, 255, 0.8);
- display: flex;
- justify-content: center;
- align-items: center;
- font-size: 14px;
- z-index: 1000;
- box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
- }
-
- #copyright p {
- margin: 0;
- }
-
- #copyright a {
- color: #007bff;
- text-decoration: none;
- }
-
- #copyright a:hover {
- text-decoration: underline;
- }
-
- </style>
- </head>
- <body class="background">
-
- </div>
- <div class="sites"> <!-- 00 -->
- <header class="header">
- <nav class="navbar">
- <a href="https://www.199881.xyz/" class="brand">
- <img class="debug logo" src="https://cdn.glitch.global/efdace30-a873-49c7-aaa9-4fa31679ee0c/thumbnails%2F%E5%9B%BE%E6%A0%8701.jpg?1692046715299">
- <span class="debug title">WS01の主页</span>
- <span class="debug beta">beta</span>
- <!-- 一言模块 -->
- <p id="hitokoto">
- <a href="#" id="hitokoto_text"></a>
- </p>
- <script src="https://v1.hitokoto.cn/?encode=js&select=%23hitokoto" defer></script>
- </a>
- </header>
-
- <!-- 搜索栏 -->
- <div class="search-container">
- <div class="search-bar">
- <input type="text" id="search-input" placeholder="">
- <button id="search-button">🔍</button>
- </div>
- <div class="search-engines">
- <button class="search-engine" data-engine="baidu">百度</button>
- <button class="search-engine" data-engine="bing">必应</button>
- <button class="search-engine" data-engine="toutiao">头条</button>
- <button class="search-engine" data-engine="sm">神马</button>
- <button class="search-engine" data-engine="bilibili">哔哩</button>
- <button class="search-engine" data-engine="github">github</button>
- <button class="search-engine" data-engine="google">谷歌</button>
- <button class="search-engine" data-engine="yandex">yandex</button>
- </div>
- </div>
-
- <div class="head">
- <!-- 添加/删除控制按钮 -->
- <div class="add-remove-controls">
- <button class="round-btn add-btn" onclick="showAddDialog()">+</button>
- <button class="round-btn remove-btn" onclick="toggleRemoveMode()">-</button>
- <button class="round-btn category-btn" onclick="addCategory()">C+</button>
- <button class="round-btn remove-category-btn" onclick="toggleRemoveCategory()">C-</button>
-
- </div>
- <div class="sites1"> <!-- 00 -->
- <!-- 分类和卡片容器 -->
- <div id="sections-container"></div>
- <!-- 主题切换按钮 -->
- <button id="theme-toggle" onclick="toggleTheme()">◑</button>
- <!-- 显示日志按钮 用于调试-->
- <!--<button id="view-logs-btn" onclick="viewLogs()">显示日志</button>-->
- <!-- 添加链接对话框 -->
- <div id="dialog-overlay">
- <div id="dialog-box">
- <label for="name-input">名称</label>
- <input type="text" id="name-input">
- <label for="url-input">地址</label>
- <input type="text" id="url-input">
- <label for="category-select">选择分类</label>
- <select id="category-select"></select>
- <div class="private-link-container">
- <label for="private-checkbox">私密链接</label>
- <input type="checkbox" id="private-checkbox">
- </div>
- <button onclick="addLink()">确定</button>
- <button onclick="hideAddDialog()">取消</button>
- </div>
- </div>
-
- <br />
- <!-- body 页脚 -->
- <div class="footer">
- <!-- 管理员控制面板 -->
- <input type="password" id="admin-password" placeholder="输入密码">
- <button id="admin-mode-btn" onclick="toggleAdminMode()">设 置</button>
- <button id="secret-garden-btn" onclick="toggleSecretGarden()">登 录</button>
-
- <br />
- <br /> <br />
- <!-- 版权信息 -->
-
- <div id="copyright" class="copyright">
- <!--请不要删除-->
- <p><a href="https://github.com/hmhm2022/Card-Tab" target="_blank">GitHub</a> 项目
- <!-- 开站时间开始 -->
- <span id="timeDate">载入天数...</span><span id="times">载入时分秒...</span> <script language="javascript">
- var now = new Date();
- function createtime(){
- var grt= new Date("09/05/2024 00:00:00");/*---这里是网站的启用时间--*/
- now.setTime(now.getTime()+250);
- days = (now - grt ) / 1000 / 60 / 60 / 24;
- dnum = Math.floor(days);
- hours = (now - grt ) / 1000 / 60 / 60 - (24 * dnum);
- hnum = Math.floor(hours);
- if(String(hnum).length ==1 ){hnum = "0" + hnum;}
- minutes = (now - grt ) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum);
- mnum = Math.floor(minutes);
- if(String(mnum).length ==1 ){mnum = "0" + mnum;}
- seconds = (now - grt ) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum);
- snum = Math.round(seconds);
- if(String(snum).length ==1 ){snum = "0" + snum;}
- document.getElementById("timeDate").innerHTML = "稳定运行"+dnum+"天";
- document.getElementById("times").innerHTML = hnum + "小时" + mnum + "分" + snum + "秒";
- }
- setInterval("createtime()",250);
- </script>
- <!-- 开站时间结束 -->
- <!-- <script defer src="https://four-root-occupation.glitch.me/denglong.js"></script> 国庆快乐-->
-
- </p>
- </div>
- </div>
-
- <script>
- // 搜索引擎配置
- const searchEngines = {
- baidu: "https://www.baidu.com/s?wd=",
- bing: "https://www.bing.com/search?q=",
- sm: "https://m.sm.cn/s?q=",
- toutiao: "https://so.toutiao.com/search?dvpf=pc&source=trending_card&keyword=",
- bilibili: "https://search.bilibili.com/all?keyword=",
- github: "https://github.com/search?q=",
- google: "https://www.google.com/search?q=",
- yandex: "https://www.yandex.com/search/?text="
- };
-
- let currentEngine = "baidu";
-
- // 日志记录函数
- function logAction(action, details) {
- const timestamp = new Date().toISOString();
- const logEntry = timestamp + ': ' + action + ' - ' + JSON.stringify(details);
-
- let logs = JSON.parse(localStorage.getItem('cardTabLogs') || '[]');
- logs.push(logEntry);
-
- // 保留最新的1000条日志
- if (logs.length > 1000) {
- logs = logs.slice(-1000);
- }
-
- localStorage.setItem('cardTabLogs', JSON.stringify(logs));
- console.log(logEntry); // 同时在控制台输出日志
- }
-
- // 查看日志的函数
- function viewLogs() {
- const logs = JSON.parse(localStorage.getItem('cardTabLogs') || '[]');
- console.log('Card Tab Logs:');
- logs.forEach(log => console.log(log));
- alert('日志已在控制台输出,请按F12打开开发者工具查看。');
- }
-
- // 设置当前搜索引擎
- function setActiveEngine(engine) {
- currentEngine = engine;
- document.querySelectorAll('.search-engine').forEach(btn => {
- btn.style.backgroundColor = btn.dataset.engine === engine ? '#c0c0c0' : '#f0f0f0';
- });
- logAction('设置搜索引擎', { engine });
- }
-
- // 搜索引擎按钮点击事件
- document.querySelectorAll('.search-engine').forEach(button => {
- button.addEventListener('click', () => setActiveEngine(button.dataset.engine));
- });
-
- // 搜索按钮点击事件
- document.getElementById('search-button').addEventListener('click', () => {
- const query = document.getElementById('search-input').value;
- if (query) {
- logAction('执行搜索', { engine: currentEngine, query });
- window.open(searchEngines[currentEngine] + encodeURIComponent(query), '_blank');
- }
- });
-
- // 搜索输入框回车事件
- document.getElementById('search-input').addEventListener('keypress', (e) => {
- if (e.key === 'Enter') {
- document.getElementById('search-button').click();
- }
- });
-
- // 初始化搜索引擎
- setActiveEngine(currentEngine);
-
- // 全局变量
- let publicLinks = [];
- let privateLinks = [];
- let isAdmin = false;
- let isLoggedIn = false;
- let removeMode = false;
- let isRemoveCategoryMode = false;
- let isDarkTheme = false;
- let links = [];
- const categories = {};
-
- // 添加新分类
- async function addCategory() {
- if (!await validateToken()) {
- return;
- }
- const categoryName = prompt('请输入新分类名称:');
- if (categoryName && !categories[categoryName]) {
- categories[categoryName] = [];
- updateCategorySelect();
- renderCategories();
- saveLinks();
- logAction('添加分类', { categoryName, currentLinkCount: links.length });
- } else if (categories[categoryName]) {
- alert('该分类已存在');
- logAction('添加分类失败', { categoryName, reason: '分类已存在' });
- }
- }
-
- // 删除分类
- async function deleteCategory(category) {
- if (!await validateToken()) {
- return;
- }
- if (confirm('确定要删除 "' + category + '" 分类吗?这将删除该分类下的所有链接。')) {
- delete categories[category];
- links = links.filter(link => link.category !== category);
- publicLinks = publicLinks.filter(link => link.category !== category);
- privateLinks = privateLinks.filter(link => link.category !== category);
- updateCategorySelect();
- saveLinks();
- renderCategories();
- logAction('删除分类', { category });
- }
- }
-
- // 渲染分类(不重新加载链接)
- function renderCategories() {
- const container = document.getElementById('sections-container');
- container.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const section = document.createElement('div');
- section.className = 'section';
-
- const titleContainer = document.createElement('div');
- titleContainer.className = 'section-title-container';
-
- const title = document.createElement('div');
- title.className = 'section-title';
- title.textContent = category;
-
- titleContainer.appendChild(title);
-
- if (isAdmin) {
- const deleteBtn = document.createElement('button');
- deleteBtn.textContent = '删除分类';
- deleteBtn.className = 'delete-category-btn';
- deleteBtn.style.display = isRemoveCategoryMode ? 'inline-block' : 'none';
- deleteBtn.onclick = () => deleteCategory(category);
- titleContainer.appendChild(deleteBtn);
- }
-
- const cardContainer = document.createElement('div');
- cardContainer.className = 'card-container';
- cardContainer.id = category;
-
- section.appendChild(titleContainer);
- section.appendChild(cardContainer);
-
- container.appendChild(section);
-
- const categoryLinks = links.filter(link => link.category === category);
- categoryLinks.forEach(link => {
- createCard(link, cardContainer);
- });
- });
-
- logAction('渲染分类', { categoryCount: Object.keys(categories).length, linkCount: links.length });
- }
-
- // 读取链接数据
- async function loadLinks() {
- const headers = {
- 'Content-Type': 'application/json'
- };
-
- // 如果已登录,从 localStorage 获取 token 并添加到请求头
- if (isLoggedIn) {
- const token = localStorage.getItem('authToken');
- if (token) {
- headers['Authorization'] = token;
- }
- }
-
- try {
- const response = await fetch('/api/getLinks?userId=testUser', {
- headers: headers
- });
-
- if (!response.ok) {
- throw new Error("HTTP error! status: " + response.status);
- }
-
-
- const data = await response.json();
- console.log('Received data:', data);
-
- if (data.categories) {
- Object.assign(categories, data.categories);
- }
-
- publicLinks = data.links ? data.links.filter(link => !link.isPrivate) : [];
- privateLinks = data.links ? data.links.filter(link => link.isPrivate) : [];
- links = isLoggedIn ? [...publicLinks, ...privateLinks] : publicLinks;
-
- loadSections();
- updateCategorySelect();
- updateUIState();
- logAction('读取链接', {
- publicCount: publicLinks.length,
- privateCount: privateLinks.length,
- isLoggedIn: isLoggedIn,
- hasToken: !!localStorage.getItem('authToken')
- });
- } catch (error) {
- console.error('Error loading links:', error);
- alert('加载链接时出错,请刷新页面重试');
- }
- }
-
-
- // 更新UI状态
- function updateUIState() {
- const passwordInput = document.getElementById('admin-password');
- const adminBtn = document.getElementById('admin-mode-btn');
- const secretGardenBtn = document.getElementById('secret-garden-btn');
- const addRemoveControls = document.querySelector('.add-remove-controls');
-
- passwordInput.style.display = isLoggedIn ? 'none' : 'inline-block';
- secretGardenBtn.textContent = isLoggedIn ? "退出" : "登录";
- secretGardenBtn.style.display = 'inline-block';
-
- if (isAdmin) {
- adminBtn.textContent = "离开设置";
- adminBtn.style.display = 'inline-block';
- addRemoveControls.style.display = 'flex';
- } else if (isLoggedIn) {
- adminBtn.textContent = "设置";
- adminBtn.style.display = 'inline-block';
- addRemoveControls.style.display = 'none';
- } else {
- adminBtn.style.display = 'none';
- addRemoveControls.style.display = 'none';
- }
-
- logAction('更新UI状态', { isAdmin, isLoggedIn });
- }
-
- // 登录状态显示(加载所有链接)
- function showSecretGarden() {
- if (isLoggedIn) {
- links = [...publicLinks, ...privateLinks];
- loadSections();
- // 显示所有私密标签
- document.querySelectorAll('.private-tag').forEach(tag => {
- tag.style.display = 'block';
- });
- logAction('显示私密花园');
- }
- }
-
- // 加载分类和链接
- function loadSections() {
- const container = document.getElementById('sections-container');
- container.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const section = document.createElement('div');
- section.className = 'section';
-
- const titleContainer = document.createElement('div');
- titleContainer.className = 'section-title-container';
-
- const title = document.createElement('div');
- title.className = 'section-title';
- title.textContent = category;
-
- titleContainer.appendChild(title);
-
- if (isAdmin) {
- const deleteBtn = document.createElement('button');
- deleteBtn.textContent = '删除分类';
- deleteBtn.className = 'delete-category-btn';
- deleteBtn.style.display = 'none';
- deleteBtn.onclick = () => deleteCategory(category);
- titleContainer.appendChild(deleteBtn);
- }
-
- const cardContainer = document.createElement('div');
- cardContainer.className = 'card-container';
- cardContainer.id = category;
-
- section.appendChild(titleContainer);
- section.appendChild(cardContainer);
-
- let privateCount = 0;
- let linkCount = 0;
-
- links.forEach(link => {
- if (link.category === category) {
- if (link.isPrivate) privateCount++;
- linkCount++;
- createCard(link, cardContainer);
- }
- });
-
- if (privateCount < linkCount || isLoggedIn) {
- container.appendChild(section);
- }
- });
-
- logAction('加载分类和链接', { isAdmin: isAdmin, linkCount: links.length, categoryCount: Object.keys(categories).length });
- }
-
- // 创建卡片
- function createCard(link, container) {
- const card = document.createElement('div');
- card.className = 'card';
- card.setAttribute('draggable', isAdmin);
- card.dataset.isPrivate = link.isPrivate;
-
- const cardTop = document.createElement('div');
- cardTop.className = 'card-top';
-
- // 定义默认的 SVG 图标
- const defaultIconSVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
- '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>' +
- '<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>' +
- '</svg>';
-
- // 创建图标元素
- const icon = document.createElement('img');
- icon.className = 'card-icon';
- // icon.src = 'https://api.iowen.cn/favicon/' + extractDomain(link.url) + '.png';
- icon.src = 'https://www.faviconextractor.com/favicon/' + extractDomain(link.url);
- icon.alt = 'Website Icon';
-
- // 如果图片加载失败,使用默认的 SVG 图标
- icon.onerror = function() {
- const svgBlob = new Blob([defaultIconSVG], {type: 'image/svg+xml'});
- const svgUrl = URL.createObjectURL(svgBlob);
- this.src = svgUrl;
-
- this.onload = () => URL.revokeObjectURL(svgUrl);
- };
-
- function extractDomain(url) {
- let domain;
- try {
- domain = new URL(url).hostname;
- } catch (e) {
- domain = url;
- }
- return domain;
- }
-
- const title = document.createElement('div');
- title.className = 'card-title';
- title.textContent = link.name;
-
- cardTop.appendChild(icon);
- cardTop.appendChild(title);
-
- const url = document.createElement('div');
- url.className = 'card-url';
- url.textContent = link.url;
-
- card.appendChild(cardTop);
- card.appendChild(url);
-
- if (link.isPrivate) {
- const privateTag = document.createElement('div');
- privateTag.className = 'private-tag';
- privateTag.textContent = '私密';
- card.appendChild(privateTag);
- }
-
- const correctedUrl = link.url.startsWith('http://') || link.url.startsWith('https://') ? link.url : 'http://' + link.url;
-
- if (!isAdmin) {
- card.addEventListener('click', () => {
- window.open(correctedUrl, '_blank');
- logAction('打开链接', { name: link.name, url: correctedUrl });
- });
- }
-
- const deleteBtn = document.createElement('button');
- deleteBtn.textContent = '–';
- deleteBtn.className = 'delete-btn';
- deleteBtn.onclick = function (event) {
- event.stopPropagation();
- removeCard(card);
- };
- card.appendChild(deleteBtn);
-
- updateCardStyle(card);
-
- card.addEventListener('dragstart', dragStart);
- card.addEventListener('dragover', dragOver);
- card.addEventListener('dragend', dragEnd);
- card.addEventListener('drop', drop);
- card.addEventListener('touchstart', touchStart, { passive: false });
-
- if (isAdmin && removeMode) {
- deleteBtn.style.display = 'block';
- }
-
- if (isAdmin || (link.isPrivate && isLoggedIn) || !link.isPrivate) {
- container.appendChild(card);
- }
- // logAction('创建卡片', { name: link.name, isPrivate: link.isPrivate });
- }
-
- // 更新卡片样式
- function updateCardStyle(card) {
- if (isDarkTheme) {
- card.style.backgroundColor = '#1e1e1e';
- card.style.color = '#ffffff';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
- } else {
- card.style.backgroundColor = 'rgba(160, 201, 229, 0.6)';
- card.style.color = '#333';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
- }
- }
-
- // 更新分类选择下拉框
- function updateCategorySelect() {
- const categorySelect = document.getElementById('category-select');
- categorySelect.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const option = document.createElement('option');
- option.value = category;
- option.textContent = category;
- categorySelect.appendChild(option);
- });
-
- logAction('更新分类选择', { categoryCount: Object.keys(categories).length });
- }
-
- // 保存链接数据
- async function saveLinks() {
- if (isAdmin && !(await validateToken())) {
- return;
- }
-
- let allLinks = [...publicLinks, ...privateLinks];
-
- try {
- await fetch('/api/saveOrder', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': localStorage.getItem('authToken')
- },
- body: JSON.stringify({
- userId: 'testUser',
- links: allLinks,
- categories: categories
- }),
- });
- logAction('保存链接', { linkCount: allLinks.length, categoryCount: Object.keys(categories).length });
- } catch (error) {
- logAction('保存链接失败', { error: error.message });
- alert('保存链接失败,请重试');
- }
- }
-
- // 添加卡片弹窗
- async function addLink() {
- if (!await validateToken()) {
- return;
- }
- const name = document.getElementById('name-input').value;
- const url = document.getElementById('url-input').value;
- const category = document.getElementById('category-select').value;
- const isPrivate = document.getElementById('private-checkbox').checked;
-
- if (name && url && category) {
- const newLink = { name, url, category, isPrivate };
-
- if (isPrivate) {
- privateLinks.push(newLink);
- } else {
- publicLinks.push(newLink);
- }
-
- links = isLoggedIn ? [...publicLinks, ...privateLinks] : publicLinks;
-
- if (isAdmin || (isPrivate && isLoggedIn) || !isPrivate) {
- const container = document.getElementById(category);
- if (container) {
- createCard(newLink, container);
- } else {
- categories[category] = [];
- renderCategories();
- }
- }
-
- saveLinks();
-
- document.getElementById('name-input').value = '';
- document.getElementById('url-input').value = '';
- document.getElementById('private-checkbox').checked = false;
- hideAddDialog();
-
- logAction('添加卡片', { name, url, category, isPrivate });
- }
- }
-
- // 删除卡片
- async function removeCard(card) {
- if (!await validateToken()) {
- return;
- }
- const name = card.querySelector('.card-title').textContent;
- const url = card.querySelector('.card-url').textContent;
- const isPrivate = card.dataset.isPrivate === 'true';
-
- links = links.filter(link => link.url !== url);
- if (isPrivate) {
- privateLinks = privateLinks.filter(link => link.url !== url);
- } else {
- publicLinks = publicLinks.filter(link => link.url !== url);
- }
-
- for (const key in categories) {
- categories[key] = categories[key].filter(link => link.url !== url);
- }
-
- card.remove();
-
- saveLinks();
-
- logAction('删除卡片', { name, url, isPrivate });
- }
-
- // 拖拽卡片
- let draggedCard = null;
- let touchStartX, touchStartY;
-
- // 触屏端拖拽卡片
- function touchStart(event) {
- if (!isAdmin) {
- return;
- }
- draggedCard = event.target.closest('.card');
- if (!draggedCard) return;
-
- event.preventDefault();
- const touch = event.touches[0];
- touchStartX = touch.clientX;
- touchStartY = touch.clientY;
-
- draggedCard.classList.add('dragging');
-
- document.addEventListener('touchmove', touchMove, { passive: false });
- document.addEventListener('touchend', touchEnd);
-
- }
-
- function touchMove(event) {
- if (!draggedCard) return;
- event.preventDefault();
-
- const touch = event.touches[0];
- const currentX = touch.clientX;
- const currentY = touch.clientY;
-
- const deltaX = currentX - touchStartX;
- const deltaY = currentY - touchStartY;
- draggedCard.style.transform = "translate(" + deltaX + "px, " + deltaY + "px)";
-
- const target = findCardUnderTouch(currentX, currentY);
- if (target && target !== draggedCard) {
- const container = target.parentElement;
- const targetRect = target.getBoundingClientRect();
-
- if (currentX < targetRect.left + targetRect.width / 2) {
- container.insertBefore(draggedCard, target);
- } else {
- container.insertBefore(draggedCard, target.nextSibling);
- }
- }
- }
-
- function touchEnd(event) {
- if (!draggedCard) return;
-
- const card = draggedCard;
- const targetCategory = card.closest('.card-container').id;
-
- validateToken().then(isValid => {
- if (isValid && card) {
- updateCardCategory(card, targetCategory);
- saveCardOrder().catch(error => {
- console.error('Save failed:', error);
- });
- }
- cleanupDragState();
- });
- }
-
- function findCardUnderTouch(x, y) {
- const cards = document.querySelectorAll('.card:not(.dragging)');
- return Array.from(cards).find(card => {
- const rect = card.getBoundingClientRect();
- return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
- });
- }
-
- // PC端拖拽卡片
- function dragStart(event) {
- if (!isAdmin) {
- event.preventDefault();
- return;
- }
- draggedCard = event.target.closest('.card');
- if (!draggedCard) return;
-
- draggedCard.classList.add('dragging');
- event.dataTransfer.effectAllowed = "move";
- logAction('开始拖拽卡片', { name: draggedCard.querySelector('.card-title').textContent });
- }
-
- function dragOver(event) {
- if (!isAdmin) {
- event.preventDefault();
- return;
- }
- event.preventDefault();
- const target = event.target.closest('.card');
- if (target && target !== draggedCard) {
- const container = target.parentElement;
- const mousePositionX = event.clientX;
- const targetRect = target.getBoundingClientRect();
-
- if (mousePositionX < targetRect.left + targetRect.width / 2) {
- container.insertBefore(draggedCard, target);
- } else {
- container.insertBefore(draggedCard, target.nextSibling);
- }
- }
- }
-
- // 清理拖拽状态函数
- function cleanupDragState() {
- if (draggedCard) {
- draggedCard.classList.remove('dragging');
- draggedCard.style.transform = '';
- draggedCard = null;
- }
-
- document.removeEventListener('touchmove', touchMove);
- document.removeEventListener('touchend', touchEnd);
-
- touchStartX = null;
- touchStartY = null;
- }
-
- // PC端拖拽结束
- function drop(event) {
- if (!isAdmin) {
- event.preventDefault();
- return;
- }
- event.preventDefault();
-
- const card = draggedCard;
- const targetCategory = event.target.closest('.card-container').id;
-
- validateToken().then(isValid => {
- if (isValid && card) {
- updateCardCategory(card, targetCategory);
- saveCardOrder().catch(error => {
- console.error('Save failed:', error);
- });
- }
- cleanupDragState();
- });
- }
-
- function dragEnd(event) {
- if (draggedCard) {
- draggedCard.classList.remove('dragging');
- logAction('拖拽卡片结束');
- }
- }
-
- // 更新卡片分类
- function updateCardCategory(card, newCategory) {
- const cardTitle = card.querySelector('.card-title').textContent;
- const cardUrl = card.querySelector('.card-url').textContent;
- const isPrivate = card.dataset.isPrivate === 'true';
-
- const linkIndex = links.findIndex(link => link.url === cardUrl);
- if (linkIndex !== -1) {
- links[linkIndex].category = newCategory;
- }
-
- const linkArray = isPrivate ? privateLinks : publicLinks;
- const arrayIndex = linkArray.findIndex(link => link.url === cardUrl);
- if (arrayIndex !== -1) {
- linkArray[arrayIndex].category = newCategory;
- }
-
- card.dataset.category = newCategory;
- }
-
- // 在页面加载完成后添加触摸事件监听器
- document.addEventListener('DOMContentLoaded', function() {
- const cardContainers = document.querySelectorAll('.card-container');
- cardContainers.forEach(container => {
- container.addEventListener('touchstart', touchStart, { passive: false });
- });
- });
-
- // 保存卡片顺序
- async function saveCardOrder() {
- if (!await validateToken()) {
- return;
- }
- const containers = document.querySelectorAll('.card-container');
- let newPublicLinks = [];
- let newPrivateLinks = [];
- let newCategories = {};
-
- containers.forEach(container => {
- const category = container.id;
- newCategories[category] = [];
-
- [...container.children].forEach(card => {
- const url = card.querySelector('.card-url').textContent;
- const name = card.querySelector('.card-title').textContent;
- const isPrivate = card.dataset.isPrivate === 'true';
- card.dataset.category = category;
- const link = { name, url, category, isPrivate };
- if (isPrivate) {
- newPrivateLinks.push(link);
- } else {
- newPublicLinks.push(link);
- }
- newCategories[category].push(link);
- });
- });
-
- publicLinks.length = 0;
- publicLinks.push(...newPublicLinks);
- privateLinks.length = 0;
- privateLinks.push(...newPrivateLinks);
- Object.keys(categories).forEach(key => delete categories[key]);
- Object.assign(categories, newCategories);
-
- logAction('保存卡片顺序', {
- publicCount: newPublicLinks.length,
- privateCount: newPrivateLinks.length,
- categoryCount: Object.keys(newCategories).length
- });
-
- try {
- const response = await fetch('/api/saveOrder', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': localStorage.getItem('authToken')
- },
- body: JSON.stringify({
- userId: 'testUser',
- links: [...newPublicLinks, ...newPrivateLinks],
- categories: newCategories
- }),
- });
- const result = await response.json();
- if (!result.success) {
- throw new Error('Failed to save order');
- }
- logAction('保存卡片顺序', { publicCount: newPublicLinks.length, privateCount: newPrivateLinks.length, categoryCount: Object.keys(newCategories).length });
- } catch (error) {
- logAction('保存顺序失败', { error: error.message });
- alert('保存顺序失败,请重试');
- }
- }
-
- // 设置状态重新加载卡片
- function reloadCardsAsAdmin() {
- document.querySelectorAll('.card-container').forEach(container => {
- container.innerHTML = '';
- });
- loadLinks().then(() => {
- if (isDarkTheme) {
- applyDarkTheme();
- }
- });
- logAction('重新加载卡片(管理员模式)');
- }
-
- // 密码输入框回车事件
- document.getElementById('admin-password').addEventListener('keypress', (e) => {
- if (e.key === 'Enter') {
- toggleSecretGarden();
- }
- });
-
- // 切换设置状态
- async function toggleAdminMode() {
- const adminBtn = document.getElementById('admin-mode-btn');
- const addRemoveControls = document.querySelector('.add-remove-controls');
-
- if (!isAdmin && isLoggedIn) {
- if (!await validateToken()) {
- return;
- }
-
- // 在进入设置模式之前进行备份
- try {
- const response = await fetch('/api/backupData', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': localStorage.getItem('authToken')
- },
- body: JSON.stringify({
- sourceUserId: 'testUser',
- backupUserId: 'backup'
- }),
- });
- const result = await response.json();
- if (result.success) {
- logAction('数据备份成功');
- } else {
- throw new Error('备份失败');
- }
- } catch (error) {
- logAction('数据备份失败', { error: error.message });
- if (!confirm('备份失败,是否仍要继续进入设置模式?')) {
- return;
- }
- }
-
- isAdmin = true;
- adminBtn.textContent = "退出设置";
- addRemoveControls.style.display = 'flex';
- alert('准备设置分类和书签');
- reloadCardsAsAdmin();
- logAction('进入设置');
- } else if (isAdmin) {
- isAdmin = false;
- removeMode = false;
- adminBtn.textContent = "设 置";
- addRemoveControls.style.display = 'none';
- alert('设置已保存');
- reloadCardsAsAdmin();
- logAction('离开设置');
- }
-
- updateUIState();
- }
-
- // 切换到登录状态
- function toggleSecretGarden() {
- const passwordInput = document.getElementById('admin-password');
- if (!isLoggedIn) {
- verifyPassword(passwordInput.value)
- .then(result => {
- if (result.valid) {
- isLoggedIn = true;
- localStorage.setItem('authToken', result.token);
- console.log('Token saved:', result.token);
- loadLinks();
- alert('登录成功!');
- logAction('登录成功');
- } else {
- alert('密码错误');
- logAction('登录失败', { reason: result.error || '密码错误' });
- }
- updateUIState();
- })
- .catch(error => {
- console.error('Login error:', error);
- alert('登录过程出错,请重试');
- });
- } else {
- isLoggedIn = false;
- isAdmin = false;
- localStorage.removeItem('authToken');
- links = publicLinks;
- loadSections();
- alert('退出登录!');
- updateUIState();
- passwordInput.value = '';
- logAction('退出登录');
- }
- }
-
- // 应用暗色主题
- function applyDarkTheme() {
- document.body.style.backgroundColor = '#121212';
- document.body.style.color = '#ffffff';
- const cards = document.querySelectorAll('.card');
- cards.forEach(card => {
- card.style.backgroundColor = '#1e1e1e';
- card.style.color = '#ffffff';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
- });
- logAction('应用暗色主题');
- }
-
- // 显示添加链接对话框
- function showAddDialog() {
- document.getElementById('dialog-overlay').style.display = 'flex';
- logAction('显示添加链接对话框');
- }
-
- // 隐藏添加链接对话框
- function hideAddDialog() {
- document.getElementById('dialog-overlay').style.display = 'none';
- logAction('隐藏添加链接对话框');
- }
-
- // 切换删除卡片模式
- function toggleRemoveMode() {
- removeMode = !removeMode;
- const deleteButtons = document.querySelectorAll('.delete-btn');
- deleteButtons.forEach(btn => {
- btn.style.display = removeMode ? 'block' : 'none';
- });
- logAction('切换删除卡片模式', { removeMode });
- }
-
- //切换删除分类模式
- function toggleRemoveCategory() {
- isRemoveCategoryMode = !isRemoveCategoryMode;
- const deleteButtons = document.querySelectorAll('.delete-category-btn');
- deleteButtons.forEach(btn => {
- btn.style.display = isRemoveCategoryMode ? 'inline-block' : 'none';
- });
- logAction('切换删除分类模式', { isRemoveCategoryMode });
- }
-
- // 切换主题
- function toggleTheme() {
- isDarkTheme = !isDarkTheme;
-
- document.body.style.backgroundColor = isDarkTheme ? '#121212' : '#ffffff';
- document.body.style.color = isDarkTheme ? '#ffffff' : '#333';
-
- const cards = document.querySelectorAll('.card');
- cards.forEach(card => {
- card.style.backgroundColor = isDarkTheme ? '#1e1e1e' : 'rgba(160, 201, 229, 0.6)';
- card.style.color = isDarkTheme ? '#ffffff' : '#333';
- card.style.boxShadow = isDarkTheme
- ? '0 4px 8px rgba(0, 0, 0, 0.5)'
- : '0 4px 8px rgba(0, 0, 0, 0.1)';
- });
-
- const fixedElements = document.querySelectorAll('.fixed-elements');
- fixedElements.forEach(element => {
- element.style.backgroundColor = isDarkTheme ? '#121212' : '#ffffff';
- element.style.color = isDarkTheme ? '#ffffff' : '#333';
- });
-
- const dialogBox = document.getElementById('dialog-box');
- dialogBox.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#ffffff';
- dialogBox.style.color = isDarkTheme ? '#ffffff' : '#333';
-
- const inputs = document.querySelectorAll('input[type="text"], input[type="password"], select');
- inputs.forEach(input => {
- input.style.backgroundColor = isDarkTheme ? '#444' : '#fff';
- input.style.color = isDarkTheme ? '#fff' : '#333';
- input.style.borderColor = isDarkTheme ? '#555' : '#ccc';
- });
-
- logAction('切换主题', { isDarkTheme });
- }
-
- // 验证密码
- async function verifyPassword(inputPassword) {
- const response = await fetch('/api/verifyPassword', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ password: inputPassword }),
- });
- const result = await response.json();
- return result;
- }
-
- // 初始化加载
- document.addEventListener('DOMContentLoaded', async () => {
- await validateToken();
- loadLinks();
- });
-
-
- // 前端检查是否有 token
- async function validateToken() {
- const token = localStorage.getItem('authToken');
- if (!token) {
- isLoggedIn = false;
- updateUIState();
- return false;
- }
-
- try {
- const response = await fetch('/api/getLinks?userId=testUser', {
- headers: { 'Authorization': token }
- });
-
- if (response.status === 401) {
- await resetToLoginState('token已过期,请重新登录');
- return false;
- }
-
- isLoggedIn = true;
- updateUIState();
- return true;
- } catch (error) {
- console.error('Token validation error:', error);
- return false;
- }
- }
-
- // 重置状态
- async function resetToLoginState(message) {
- alert(message);
-
- cleanupDragState();
-
- localStorage.removeItem('authToken');
- isLoggedIn = false;
- isAdmin = false;
- removeMode = false;
- isRemoveCategoryMode = false;
-
- const passwordInput = document.getElementById('admin-password');
- if (passwordInput) {
- passwordInput.value = '';
- }
-
- updateUIState();
- links = publicLinks;
- loadSections();
-
- const addRemoveControls = document.querySelector('.add-remove-controls');
- if (addRemoveControls) {
- addRemoveControls.style.display = 'none';
- }
-
- document.querySelectorAll('.delete-btn').forEach(btn => {
- btn.style.display = 'none';
- });
-
- document.querySelectorAll('.delete-category-btn').forEach(btn => {
- btn.style.display = 'none';
- });
-
- const dialogOverlay = document.getElementById('dialog-overlay');
- if (dialogOverlay) {
- dialogOverlay.style.display = 'none';
- }
- }
-
- </script>
- </body>
-
- </html>
- `;
-
- // 服务端 token 验证
- async function validateServerToken(authToken, env) {
- if (!authToken) {
- return {
- isValid: false,
- status: 401,
- response: { error: 'Unauthorized', message: '未登录或登录已过期' }
- };
- }
-
- try {
- const [timestamp, hash] = authToken.split('.');
- const tokenTimestamp = parseInt(timestamp);
- const now = Date.now();
-
- const FIFTEEN_MINUTES = 15 * 60 * 1000;
- if (now - tokenTimestamp > FIFTEEN_MINUTES) {
- return {
- isValid: false,
- status: 401,
- response: {
- error: 'Token expired',
- tokenExpired: true,
- message: '登录已过期,请重新登录'
- }
- };
- }
-
- const tokenData = timestamp + "_" + env.ADMIN_PASSWORD;
- const encoder = new TextEncoder();
- const data = encoder.encode(tokenData);
- const hashBuffer = await crypto.subtle.digest('SHA-256', data);
- const expectedHash = btoa(String.fromCharCode(...new Uint8Array(hashBuffer)));
-
- if (hash !== expectedHash) {
- return {
- isValid: false,
- status: 401,
- response: {
- error: 'Invalid token',
- tokenInvalid: true,
- message: '登录状态无效,请重新登录'
- }
- };
- }
-
- return { isValid: true };
- } catch (error) {
- return {
- isValid: false,
- status: 401,
- response: {
- error: 'Invalid token',
- tokenInvalid: true,
- message: '登录验证失败,请重新登录'
- }
- };
- }
- }
-
- export default {
- async fetch(request, env) {
- const url = new URL(request.url);
-
- if (url.pathname === '/') {
- return new Response(HTML_CONTENT, {
- headers: { 'Content-Type': 'text/html' }
- });
- }
-
- if (url.pathname === '/api/getLinks') {
- const userId = url.searchParams.get('userId');
- const authToken = request.headers.get('Authorization');
- const data = await env.CARD_ORDER.get(userId);
-
- if (data) {
- const parsedData = JSON.parse(data);
-
- // 验证 token
- if (authToken) {
- const validation = await validateServerToken(authToken, env);
- if (!validation.isValid) {
- return new Response(JSON.stringify(validation.response), {
- status: validation.status,
- headers: { 'Content-Type': 'application/json' }
- });
- }
-
- // Token 有效,返回完整数据
- return new Response(JSON.stringify(parsedData), {
- status: 200,
- headers: { 'Content-Type': 'application/json' }
- });
- }
-
- // 未提供 token,只返回公开数据
- const filteredLinks = parsedData.links.filter(link => !link.isPrivate);
- const filteredCategories = {};
- Object.keys(parsedData.categories).forEach(category => {
- filteredCategories[category] = parsedData.categories[category].filter(link => !link.isPrivate);
- });
-
- return new Response(JSON.stringify({
- links: filteredLinks,
- categories: filteredCategories
- }), {
- status: 200,
- headers: { 'Content-Type': 'application/json' }
- });
- }
-
- return new Response(JSON.stringify({
- links: [],
- categories: {}
- }), {
- status: 200,
- headers: { 'Content-Type': 'application/json' }
- });
- }
-
- if (url.pathname === '/api/saveOrder' && request.method === 'POST') {
- const authToken = request.headers.get('Authorization');
- const validation = await validateServerToken(authToken, env);
-
- if (!validation.isValid) {
- return new Response(JSON.stringify(validation.response), {
- status: validation.status,
- headers: { 'Content-Type': 'application/json' }
- });
- }
-
- const { userId, links, categories } = await request.json();
- await env.CARD_ORDER.put(userId, JSON.stringify({ links, categories }));
- return new Response(JSON.stringify({
- success: true,
- message: '保存成功'
- }), {
- status: 200,
- headers: { 'Content-Type': 'application/json' }
- });
- }
-
- if (url.pathname === '/api/verifyPassword' && request.method === 'POST') {
- try {
- const { password } = await request.json();
- const isValid = password === env.ADMIN_PASSWORD;
-
- if (isValid) {
- // 生成包含时间戳的加密 token
- const timestamp = Date.now();
- const tokenData = timestamp + "_" + env.ADMIN_PASSWORD;
- const encoder = new TextEncoder();
- const data = encoder.encode(tokenData);
- const hashBuffer = await crypto.subtle.digest('SHA-256', data);
-
- // 使用指定格式:timestamp.hash
- const token = timestamp + "." + btoa(String.fromCharCode(...new Uint8Array(hashBuffer)));
-
- return new Response(JSON.stringify({
- valid: true,
- token: token
- }), {
- status: 200,
- headers: { 'Content-Type': 'application/json' }
- });
- }
-
- return new Response(JSON.stringify({
- valid: false,
- error: 'Invalid password'
- }), {
- status: 403,
- headers: { 'Content-Type': 'application/json' }
- });
- } catch (error) {
- return new Response(JSON.stringify({
- valid: false,
- error: error.message
- }), {
- status: 500,
- headers: { 'Content-Type': 'application/json' }
- });
- }
- }
-
- if (url.pathname === '/api/backupData' && request.method === 'POST') {
- const { sourceUserId } = await request.json();
- const result = await this.backupData(env, sourceUserId);
- return new Response(JSON.stringify(result), {
- status: result.success ? 200 : 404,
- headers: { 'Content-Type': 'application/json' }
- });
- }
-
- return new Response('Not Found', { status: 404 });
- },
-
- async backupData(env, sourceUserId) {
- const MAX_BACKUPS = 10;
- const sourceData = await env.CARD_ORDER.get(sourceUserId);
-
- if (sourceData) {
- try {
- const currentDate = new Date().toLocaleString('zh-CN', {
- timeZone: 'Asia/Shanghai',
- year: 'numeric',
- month: '2-digit',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit',
- second: '2-digit',
- hour12: false
- }).replace(/\//g, '-');
-
- const backupId = `backup_${currentDate}`;
-
- const backups = await env.CARD_ORDER.list({ prefix: 'backup_' });
- const backupKeys = backups.keys.map(key => key.name).sort((a, b) => {
- const timeA = new Date(a.split('_')[1].replace(/-/g, '/')).getTime();
- const timeB = new Date(b.split('_')[1].replace(/-/g, '/')).getTime();
- return timeB - timeA; // 降序排序,最新的在前
- });
-
- await env.CARD_ORDER.put(backupId, sourceData);
-
- const allBackups = [...backupKeys, backupId].sort((a, b) => {
- const timeA = new Date(a.split('_')[1].replace(/-/g, '/')).getTime();
- const timeB = new Date(b.split('_')[1].replace(/-/g, '/')).getTime();
- return timeB - timeA;
- });
-
- const backupsToDelete = allBackups.slice(MAX_BACKUPS);
-
- if (backupsToDelete.length > 0) {
- await Promise.all(
- backupsToDelete.map(key => env.CARD_ORDER.delete(key))
- );
- }
-
- return {
- success: true,
- backupId,
- remainingBackups: MAX_BACKUPS,
- deletedCount: backupsToDelete.length
- };
- } catch (error) {
- return {
- success: false,
- error: 'Backup operation failed',
- details: error.message
- };
- }
- }
- return { success: false, error: 'Source data not found' };
- }
- };
2024.10.26 修复:
原获取网站图标的接口https://favicon.zhusl.com/ico?url= 好像失效了,现更换接口https://www.faviconextractor.com/favicon/ 项目地址:【Github】 ;另一个备份接口为https://api.iowen.cn/favicon 项目地址:【Github】 感谢上述接口作者提供的服务
- const HTML_CONTENT = `
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Card Tab</title>
- <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2280%22>💠</text></svg>">
- <style>
- /* 全局样式 */
- body {
- font-family: Arial, sans-serif;
- margin: 0;
- padding: 0;
- background-color: #d4d4d4;
- transition: background-color 0.3s ease;
- }
-
- ul {
- padding: 0;
- margin-block-start: 1em;
- margin-block-end: 1em;
- margin-inline-start: 0px;
- margin-inline-end: 0px;
- padding-inline-start: 40px;
- unicode-bidi: isolate;
- }
- li {
- display: list-item;
- text-align: -webkit-match-parent;
- unicode-bidi: isolate;
- margin: 0 8px;
- }
-
- .background {
- background-image: linear-gradient(#d4d4d4 1px, transparent 0), linear-gradient(90deg, #d4d4d4 1px, transparent 0);
- background-size: 32px 32px;
- background-color: #fffcf8;
- }
-
- .header {
- background-color: #fff;
- box-shadow: 0 0 5px rgba(0, 0, 0, .1);
- transition: background-color .5s;
- }
- .navbar {
- display: flex;
- width: 1280px;
- margin: auto;
- height: 40px;
- }
-
- .navbar .brand {
-
- display: flex;
- align-items: center;
- color: #555;
- }
-
- .brand .logo {
- max-width: 36px;
- }
-
- .brand .title {
- margin-left: 5px;
- font-family: helvetica neue, helvetica, arial, sans-serif;
- font-size: 24px;
- font-weight: 700;
- }
-
- .beta {
- color: #ccc;
- font-size: 11px;
- font-weight: 400;
- position: relative;
- top: -14px;
- }
-
- .category-list {
- list-style: none;
- display: flex;
- align-items: center;
- }
-
- .sites {
- width:1286px;
- background:;
- border:2px solid auto;
- margin:15px auto;
- padding:0px;
- text-align:center;
- }
- .sites1 {
- width:1286px;
- background:;
- border:2px solid auto;
- margin:15px auto;
- padding:0px;
- }
-
- .sites dl {
- height:36px;
- line-height:36px;
- display:block;
- margin:0;
- }
-
- .sites dl.alt {
- background:#d4d4d4;
- border-top:1px solid #ffffff;
- border-bottom:1px solid #ffffff;
- }
-
- .sites dl.alt2 {
- background:#d4d4d4;
- border-top:1px solid #ffffff;
- border-bottom:1px solid #ffffff;
- }
-
- .sites dt,.sites dd {
- text-align:center;
- display:block;
- float:left;
- }
-
- .sites dt {
- width:60px;
- }
-
- .sites dd {
- width:90px;
- margin:0;
- }
- .footer {
- width:580px;
- text-align:center;
- margin:5px auto;
- padding:2px;
- }
-
-
- /* 固定元素样式 */
- .fixed-elements {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- background-color: #d4d4d4;
- z-index: 1000;
- padding: 10px;
- transition: background-color 0.3s ease;
- height: 130px;
- }
-
- .fixed-elements h3 {
- position: absolute;
- top: 10px;
- left: 20px;
- margin: 0;
- }
-
- /* 中心内容样式 */
- .center-content {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 100%;
- max-width: 600px;
- text-align: center;
- }
-
- /* 管理员控制面板样式 */
- .admin-controls {
- position: fixed;
- top: 10px;
- right: 10px;
- font-size: 60%;
- }
-
- /* 添加/删除控制按钮样式 */
- .add-remove-controls {
- display: none;
- flex-direction: column;
- position: fixed;
- right: 20px;
- top: 50%;
- transform: translateY(-50%);
- align-items: center;
- gap: 10px;
- }
-
- .round-btn {
- background-color: #007bff;
- color: white;
- border: none;
- border-radius: 50%;
- width: 40px;
- height: 40px;
- text-align: center;
- font-size: 24px;
- line-height: 40px;
- cursor: pointer;
- margin: 5px 0;
- }
-
- .add-btn { order: 1; }
- .remove-btn { order: 2; }
- .category-btn { order: 3; }
- .remove-category-btn { order: 4; }
-
- /* 主要内容区域样式 */
- .content {
- margin-top: 140px;
- padding: 20px;
- }
-
- /* 搜索栏样式 */
- .search-container {
- margin-top: 10px;
- }
-
- .search-bar {
- display: flex;
- justify-content: center;
- margin-bottom: 10px;
- }
-
- .search-bar input {
- width: 50%;
- padding: 5px;
- border: 1px solid #ccc;
- border-radius: 5px 0 0 5px;
- }
-
- .search-bar button {
- padding: 5px 10px;
- border: 1px solid #ccc;
- border-left: none;
- background-color: #a0c9e5;
- border-radius: 0 5px 5px 0;
- cursor: pointer;
- }
-
- /* 搜索引擎按钮样式 */
- .search-engines {
- display: flex;
- justify-content: center;
- gap: 10px;
- }
-
- .search-engine {
- padding: 5px 10px;
- border: 1px solid #ccc;
- background-color: #f0f0;
- border-radius: 5px;
- cursor: pointer;
- }
-
- /* 主题切换按钮样式 */
- #theme-toggle {
- position: fixed;
- bottom: 50px;
- right: 20px;
- background-color: #007bff;
- color: white;
- border: none;
- border-radius: 50%;
- width: 40px;
- height: 40px;
- text-align: center;
- font-size: 24px;
- line-height: 40px;
- cursor: pointer;
- }
-
- /* 显示日志按钮样式 */
- #view-logs-btn {
- position: fixed;
- top: 100px;
- right: 10px;
- z-index: 1000;
- }
-
- /* 对话框样式 */
- #dialog-overlay {
- display: none;
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.5);
- justify-content: center;
- align-items: center;
- }
-
- #dialog-box {
- background-color: white;
- padding: 20px;
- border-radius: 5px;
- width: 300px;
- }
-
- #dialog-box input, #dialog-box select {
- width: 100%;
- margin-bottom: 10px;
- padding: 5px;
- }
-
- /* 分类和卡片样式 */
- .section {
- margin-bottom: 20px;
- }
-
- .section-title-container {
- display: flex;
- align-items: center;
- margin-bottom: 10px;
- }
-
- .section-title {
- font-size: 18px;
- font-weight: bold;
- }
-
- .delete-category-btn {
- background-color: pink;
- color: white;
- border: none;
- padding: 5px 10px;
- border-radius: 5px;
- cursor: pointer;
- }
-
- .card-container {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- }
-
- .card {
- background-color: #a0c9e5;
- border-radius: 5px;
- padding: 10px;
- width: 132px;
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
- cursor: pointer;
- transition: transform 0.2s;
- position: relative;
- user-select: none;
- }
-
- /* 中等屏幕 (平板等) */
- @media (max-width: 1024px) {
-
- }
- /* 小屏幕 (手机) */
- @media (max-width: 768px) {
-
- }
-
- /* 超小屏幕 (更小手机) */
- @media (max-width: 480px) {
- .card {
- padding: 6px; /* 进一步减小卡片内边距6 */
- width: 235px;
- }
- }
-
-
- .card:hover {
- transform: translateY(-5px);
- }
-
- .card-top {
- display: flex;
- align-items: center;
- margin-bottom: 5px;
- }
-
- .card-icon {
- width: 20px;
- height: 20px;
- margin-right: 5px;
- }
-
- .card-title {
- font-size: 18px;
- font-weight: bold;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .card-url {
- font-size: 12px;
- color: #666;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .private-tag {
- background-color: #ff9800;
- color: white;
- font-size: 10px;
- padding: 2px 5px;
- border-radius: 3px;
- position: absolute;
- top: 5px;
- right: 5px;
- }
-
- .delete-btn {
- position: absolute;
- top: -10px;
- right: -10px;
- background-color: red;
- color: white;
- border: none;
- border-radius: 50%;
- width: 20px;
- height: 20px;
- text-align: center;
- font-size: 14px;
- line-height: 20px;
- cursor: pointer;
- display: none;
- }
-
- /* 版权信息样式 */
- #copyright {
- position: fixed;
- bottom: 0;
- left: 0;
- width: 100%;
- height: 40px;
- background-color: rgba(255, 255, 255, 0.8);
- display: flex;
- justify-content: center;
- align-items: center;
- font-size: 14px;
- z-index: 1000;
- box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
- }
-
- #copyright p {
- margin: 0;
- }
-
- #copyright a {
- color: #007bff;
- text-decoration: none;
- }
-
- #copyright a:hover {
- text-decoration: underline;
- }
-
- /* 响应式设计 */
- @media (max-width: 480px) {
- .fixed-elements {
- position: relative;
- padding: 5px;
- }
- .content {
- margin-top: 10px;
- }
-
- .admin-controls input,
- .admin-controls button {
- height: 30%;
- }
-
- .card-container {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 10px;
- }
- .card {
- width: 80%;
- max-width: 100%;
- padding: 5px;
- }
- .card-title {
- font-size: 12px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- max-width: 130px;
- }
- .card-url {
- font-size: 10px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- max-width: 130px;
- }
-
- .add-remove-controls {
- right: 2px;
- }
-
- .round-btn,
- #theme-toggle {
- right: 5px;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 30px;
- height: 30px;
- font-size: 24px;
- }
- }
- </style>
- </head>
- <body class="background">
-
- </div>
- <div class="sites"> <!-- 00 -->
- <header class="header">
- <nav class="navbar">
- <a href="https://www.199881.xyz/" class="brand">
- <img class="debug logo" src="https://cdn.glitch.global/efdace30-a873-49c7-aaa9-4fa31679ee0c/thumbnails%2F%E5%9B%BE%E6%A0%8701.jpg?1692046715299">
- <span class="debug title">WS01の主页</span>
- <span class="debug beta">beta</span>
- <!-- 一言模块 -->
- <p id="hitokoto">
- <a href="#" id="hitokoto_text"></a>
- </p>
- <script src="https://v1.hitokoto.cn/?encode=js&select=%23hitokoto" defer></script>
- </a>
- </header>
-
- <!-- 搜索栏 -->
- <div class="search-container">
- <div class="search-bar">
- <input type="text" id="search-input" placeholder="">
- <button id="search-button">🔍</button>
- </div>
- <div class="search-engines">
- <button class="search-engine" data-engine="baidu">百度</button>
- <button class="search-engine" data-engine="bing">必应</button>
- <button class="search-engine" data-engine="toutiao">头条</button>
- <button class="search-engine" data-engine="sm">神马</button>
- <button class="search-engine" data-engine="bilibili">哔哩</button>
- <button class="search-engine" data-engine="github">github</button>
- <button class="search-engine" data-engine="google">谷歌</button>
- <button class="search-engine" data-engine="yandex">yandex</button>
- </div>
- </div>
-
- <div class="head">
- <!-- 添加/删除控制按钮 -->
- <div class="add-remove-controls">
- <button class="round-btn add-btn" onclick="showAddDialog()">+</button>
- <button class="round-btn remove-btn" onclick="toggleRemoveMode()">-</button>
- <button class="round-btn category-btn" onclick="addCategory()">C+</button>
- <button class="round-btn remove-category-btn" onclick="toggleRemoveCategory()">C-</button>
-
- </div>
- <div class="sites1"> <!-- 00 -->
- <!-- 分类和卡片容器 -->
- <div id="sections-container"></div>
- <!-- 主题切换按钮 -->
- <button id="theme-toggle" onclick="toggleTheme()">◑</button>
- <!-- 显示日志按钮 用于调试-->
- <!--<button id="view-logs-btn" onclick="viewLogs()">显示日志</button>-->
- <!-- 添加链接对话框 -->
- <div id="dialog-overlay">
- <div id="dialog-box">
- <label for="name-input">名称</label>
- <input type="text" id="name-input">
- <label for="url-input">地址</label>
- <input type="text" id="url-input">
- <label for="category-select">选择分类</label>
- <select id="category-select"></select>
- <div class="private-link-container">
- <label for="private-checkbox">私密链接</label>
- <input type="checkbox" id="private-checkbox">
- </div>
- <button onclick="addLink()">确定</button>
- <button onclick="hideAddDialog()">取消</button>
- </div>
- </div>
-
- <br />
- <!-- body 页脚 -->
- <div class="footer">
- <!-- 管理员控制面板 -->
- <input type="password" id="admin-password" placeholder="输入密码">
- <button id="admin-mode-btn" onclick="toggleAdminMode()">设 置</button>
- <button id="secret-garden-btn" onclick="toggleSecretGarden()">登 录</button>
-
- <br />
- <br /> <br />
- <!-- 版权信息 -->
-
- <div id="copyright" class="copyright">
- <!--请不要删除-->
- <p><a href="https://github.com/hmhm2022/Card-Tab" target="_blank">GitHub</a> 项目
- <!-- 开站时间开始 -->
- <span id="timeDate">载入天数...</span><span id="times">载入时分秒...</span> <script language="javascript">
- var now = new Date();
- function createtime(){
- var grt= new Date("09/05/2024 00:00:00");/*---这里是网站的启用时间--*/
- now.setTime(now.getTime()+250);
- days = (now - grt ) / 1000 / 60 / 60 / 24;
- dnum = Math.floor(days);
- hours = (now - grt ) / 1000 / 60 / 60 - (24 * dnum);
- hnum = Math.floor(hours);
- if(String(hnum).length ==1 ){hnum = "0" + hnum;}
- minutes = (now - grt ) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum);
- mnum = Math.floor(minutes);
- if(String(mnum).length ==1 ){mnum = "0" + mnum;}
- seconds = (now - grt ) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum);
- snum = Math.round(seconds);
- if(String(snum).length ==1 ){snum = "0" + snum;}
- document.getElementById("timeDate").innerHTML = "稳定运行"+dnum+"天";
- document.getElementById("times").innerHTML = hnum + "小时" + mnum + "分" + snum + "秒";
- }
- setInterval("createtime()",250);
- </script>
- <!-- 开站时间结束 -->
- <!-- <script defer src="https://four-root-occupation.glitch.me/denglong.js"></script> 国庆快乐-->
-
- </p>
- </div>
- </div>
-
- <script>
- // 搜索引擎配置
- const searchEngines = {
- baidu: "https://www.baidu.com/s?wd=",
- bing: "https://www.bing.com/search?q=",
- sm: "https://m.sm.cn/s?q=",
- toutiao: "https://so.toutiao.com/search?dvpf=pc&source=trending_card&keyword=",
- bilibili: "https://search.bilibili.com/all?keyword=",
- github: "https://github.com/search?q=",
- google: "https://www.google.com/search?q=",
- yandex: "https://www.yandex.com/search/?text="
- };
-
- let currentEngine = "baidu";
-
- // 日志记录函数
- function logAction(action, details) {
- const timestamp = new Date().toISOString();
- const logEntry = timestamp + ': ' + action + ' - ' + JSON.stringify(details);
-
- let logs = JSON.parse(localStorage.getItem('cardTabLogs') || '[]');
- logs.push(logEntry);
-
- // 保留最新的1000条日志
- if (logs.length > 1000) {
- logs = logs.slice(-1000);
- }
-
- localStorage.setItem('cardTabLogs', JSON.stringify(logs));
- console.log(logEntry); // 同时在控制台输出日志
- }
-
- // 查看日志的函数
- function viewLogs() {
- const logs = JSON.parse(localStorage.getItem('cardTabLogs') || '[]');
- console.log('Card Tab Logs:');
- logs.forEach(log => console.log(log));
- alert('日志已在控制台输出,请按F12打开开发者工具查看。');
- }
-
- // 设置当前搜索引擎
- function setActiveEngine(engine) {
- currentEngine = engine;
- document.querySelectorAll('.search-engine').forEach(btn => {
- btn.style.backgroundColor = btn.dataset.engine === engine ? '#c0c0c0' : '#f0f0f0';
- });
- logAction('设置搜索引擎', { engine });
- }
-
- // 搜索引擎按钮点击事件
- document.querySelectorAll('.search-engine').forEach(button => {
- button.addEventListener('click', () => setActiveEngine(button.dataset.engine));
- });
-
- // 搜索按钮点击事件
- document.getElementById('search-button').addEventListener('click', () => {
- const query = document.getElementById('search-input').value;
- if (query) {
- logAction('执行搜索', { engine: currentEngine, query });
- window.open(searchEngines[currentEngine] + encodeURIComponent(query), '_blank');
- }
- });
-
- // 搜索输入框回车事件
- document.getElementById('search-input').addEventListener('keypress', (e) => {
- if (e.key === 'Enter') {
- document.getElementById('search-button').click();
- }
- });
-
- // 初始化搜索引擎
- setActiveEngine(currentEngine);
-
- // 全局变量
- let publicLinks = [];
- let privateLinks = [];
- let isAdmin = false;
- let isLoggedIn = false;
- let removeMode = false;
- let isRemoveCategoryMode = false;
- let isDarkTheme = false;
- let links = [];
- const categories = {};
-
- // 添加新分类
- function addCategory() {
- const categoryName = prompt('请输入新分类名称:');
- if (categoryName && !categories[categoryName]) {
- categories[categoryName] = [];
- updateCategorySelect();
- renderCategories();
- saveLinks();
- logAction('添加分类', { categoryName, currentLinkCount: links.length });
- } else if (categories[categoryName]) {
- alert('该分类已存在');
- logAction('添加分类失败', { categoryName, reason: '分类已存在' });
- }
- }
-
- // 删除分类
- function deleteCategory(category) {
- if (confirm('确定要删除 "' + category + '" 分类吗?这将删除该分类下的所有链接。')) {
- delete categories[category];
- links = links.filter(link => link.category !== category);
- publicLinks = publicLinks.filter(link => link.category !== category);
- privateLinks = privateLinks.filter(link => link.category !== category);
- updateCategorySelect();
- saveLinks();
- renderCategories();
- logAction('删除分类', { category });
- }
- }
-
- // 渲染分类(不重新加载链接)
- function renderCategories() {
- const container = document.getElementById('sections-container');
- container.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const section = document.createElement('div');
- section.className = 'section';
-
- const titleContainer = document.createElement('div');
- titleContainer.className = 'section-title-container';
-
- const title = document.createElement('div');
- title.className = 'section-title';
- title.textContent = category;
-
- titleContainer.appendChild(title);
-
- if (isAdmin) {
- const deleteBtn = document.createElement('button');
- deleteBtn.textContent = '删除分类';
- deleteBtn.className = 'delete-category-btn';
- deleteBtn.style.display = isRemoveCategoryMode ? 'inline-block' : 'none';
- deleteBtn.onclick = () => deleteCategory(category);
- titleContainer.appendChild(deleteBtn);
- }
-
- const cardContainer = document.createElement('div');
- cardContainer.className = 'card-container';
- cardContainer.id = category;
-
- section.appendChild(titleContainer);
- section.appendChild(cardContainer);
-
- container.appendChild(section);
-
- const categoryLinks = links.filter(link => link.category === category);
- categoryLinks.forEach(link => {
- createCard(link, cardContainer);
- });
- });
-
- logAction('渲染分类', { categoryCount: Object.keys(categories).length, linkCount: links.length });
- }
-
- // 读取链接数据
- async function loadLinks() {
- const response = await fetch('/api/getLinks?userId=testUser');
- const data = await response.json();
- if (data.categories) {
- Object.assign(categories, data.categories);
- }
-
- publicLinks = data.links ? data.links.filter(link => !link.isPrivate) : [];
- privateLinks = data.links ? data.links.filter(link => link.isPrivate) : [];
- links = isLoggedIn ? [...publicLinks, ...privateLinks] : publicLinks;
-
- loadSections();
- updateCategorySelect();
- updateUIState();
- logAction('读取链接', { publicCount: publicLinks.length, privateCount: privateLinks.length });
- }
-
-
- // 更新UI状态
- function updateUIState() {
- const passwordInput = document.getElementById('admin-password');
- const adminBtn = document.getElementById('admin-mode-btn');
- const secretGardenBtn = document.getElementById('secret-garden-btn');
- const addRemoveControls = document.querySelector('.add-remove-controls');
-
- passwordInput.style.display = isLoggedIn ? 'none' : 'inline-block';
- secretGardenBtn.textContent = isLoggedIn ? "退出" : "登录";
- secretGardenBtn.style.display = 'inline-block';
-
- if (isAdmin) {
- adminBtn.textContent = "离开设置";
- adminBtn.style.display = 'inline-block';
- addRemoveControls.style.display = 'flex';
- } else if (isLoggedIn) {
- adminBtn.textContent = "设置";
- adminBtn.style.display = 'inline-block';
- addRemoveControls.style.display = 'none';
- } else {
- adminBtn.style.display = 'none';
- addRemoveControls.style.display = 'none';
- }
-
- logAction('更新UI状态', { isAdmin, isLoggedIn });
- }
-
- // 登录状态显示(加载所有链接)
- function showSecretGarden() {
- if (isLoggedIn) {
- links = [...publicLinks, ...privateLinks];
- loadSections();
- // 显示所有私密标签
- document.querySelectorAll('.private-tag').forEach(tag => {
- tag.style.display = 'block';
- });
- logAction('显示私密花园');
- }
- }
-
- // 加载分类和链接
- function loadSections() {
- const container = document.getElementById('sections-container');
- container.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const section = document.createElement('div');
- section.className = 'section';
-
- const titleContainer = document.createElement('div');
- titleContainer.className = 'section-title-container';
-
- const title = document.createElement('div');
- title.className = 'section-title';
- title.textContent = category;
-
- titleContainer.appendChild(title);
-
- if (isAdmin) {
- const deleteBtn = document.createElement('button');
- deleteBtn.textContent = '删除分类';
- deleteBtn.className = 'delete-category-btn';
- deleteBtn.style.display = 'none';
- deleteBtn.onclick = () => deleteCategory(category);
- titleContainer.appendChild(deleteBtn);
- }
-
- const cardContainer = document.createElement('div');
- cardContainer.className = 'card-container';
- cardContainer.id = category;
-
- section.appendChild(titleContainer);
- section.appendChild(cardContainer);
-
- let privateCount = 0;
- let linkCount = 0;
-
- links.forEach(link => {
- if (link.category === category) {
- if (link.isPrivate) privateCount++;
- linkCount++;
- createCard(link, cardContainer);
- }
- });
-
- if (privateCount < linkCount || isLoggedIn) {
- container.appendChild(section);
- }
- });
-
- logAction('加载分类和链接', { isAdmin: isAdmin, linkCount: links.length, categoryCount: Object.keys(categories).length });
- }
-
- // 创建卡片
- function createCard(link, container) {
- const card = document.createElement('div');
- card.className = 'card';
- card.setAttribute('draggable', isAdmin);
- card.dataset.isPrivate = link.isPrivate;
-
- const cardTop = document.createElement('div');
- cardTop.className = 'card-top';
-
- // const icon = document.createElement('img');
- // icon.className = 'card-icon';
- // icon.src = 'https://favicon.zhusl.com/ico?url=' + link.url;
- // icon.alt = 'Website Icon';
- // 定义默认的 SVG 图标
- const defaultIconSVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
- '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>' +
- '<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>' +
- '</svg>';
-
- // 创建图标元素
- const icon = document.createElement('img');
- icon.className = 'card-icon';
- // icon.src = 'https://api.iowen.cn/favicon/' + extractDomain(link.url) + '.png';
- icon.src = 'https://www.faviconextractor.com/favicon/' + extractDomain(link.url);
- icon.alt = 'Website Icon';
-
- // 错误处理:如果图片加载失败,使用默认的 SVG 图标
- icon.onerror = function() {
- const svgBlob = new Blob([defaultIconSVG], {type: 'image/svg+xml'});
- const svgUrl = URL.createObjectURL(svgBlob);
- this.src = svgUrl;
-
- // 清理:当图片不再需要时,撤销对象 URL
- this.onload = () => URL.revokeObjectURL(svgUrl);
- };
-
- // 辅助函数:从 URL 中提取域名
- function extractDomain(url) {
- let domain;
- try {
- domain = new URL(url).hostname;
- } catch (e) {
- // 如果 URL 无效,返回原始输入
- domain = url;
- }
- return domain;
- }
-
- const title = document.createElement('div');
- title.className = 'card-title';
- title.textContent = link.name;
-
- cardTop.appendChild(icon);
- cardTop.appendChild(title);
-
- const url = document.createElement('div');
- url.className = 'card-url';
- url.textContent = link.url;
-
- card.appendChild(cardTop);
- card.appendChild(url);
-
- if (link.isPrivate) {
- const privateTag = document.createElement('div');
- privateTag.className = 'private-tag';
- privateTag.textContent = '私密';
- card.appendChild(privateTag);
- }
-
- const correctedUrl = link.url.startsWith('http://') || link.url.startsWith('https://') ? link.url : 'http://' + link.url;
-
- if (!isAdmin) {
- card.addEventListener('click', () => {
- window.open(correctedUrl, '_blank');
- logAction('打开链接', { name: link.name, url: correctedUrl });
- });
- }
-
- const deleteBtn = document.createElement('button');
- deleteBtn.textContent = '–';
- deleteBtn.className = 'delete-btn';
- deleteBtn.onclick = function (event) {
- event.stopPropagation();
- removeCard(card);
- };
- card.appendChild(deleteBtn);
-
- updateCardStyle(card);
-
- card.addEventListener('dragstart', dragStart);
- card.addEventListener('dragover', dragOver);
- card.addEventListener('dragend', dragEnd);
- card.addEventListener('drop', drop);
- card.addEventListener('touchstart', touchStart, { passive: false });
-
- if (isAdmin && removeMode) {
- deleteBtn.style.display = 'block';
- }
-
- if (isAdmin || (link.isPrivate && isLoggedIn) || !link.isPrivate) {
- container.appendChild(card);
- }
- // logAction('创建卡片', { name: link.name, isPrivate: link.isPrivate });
- }
-
- // 更新卡片样式
- function updateCardStyle(card) {
- if (isDarkTheme) {
- card.style.backgroundColor = '#1e1e1e';
- card.style.color = '#ffffff';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
- } else {
- card.style.backgroundColor = '#a0c9e5';
- card.style.color = '#333';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
- }
- }
-
- // 更新分类选择下拉框
- function updateCategorySelect() {
- const categorySelect = document.getElementById('category-select');
- categorySelect.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const option = document.createElement('option');
- option.value = category;
- option.textContent = category;
- categorySelect.appendChild(option);
- });
-
- logAction('更新分类选择', { categoryCount: Object.keys(categories).length });
- }
-
- // 保存链接数据
- async function saveLinks() {
- let allLinks = [...publicLinks, ...privateLinks];
-
- try {
- await fetch('/api/saveOrder', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- userId: 'testUser',
- links: allLinks,
- categories: categories
- }),
- });
- logAction('保存链接', { linkCount: allLinks.length, categoryCount: Object.keys(categories).length });
- } catch (error) {
- console.error('Error saving links:', error);
- logAction('保存链接失败', { error: error.message });
- alert('保存链接失败,请重试');
- }
- }
-
- // 添加卡片弹窗
- function addLink() {
- const name = document.getElementById('name-input').value;
- const url = document.getElementById('url-input').value;
- const category = document.getElementById('category-select').value;
- const isPrivate = document.getElementById('private-checkbox').checked;
-
- if (name && url && category) {
- const newLink = { name, url, category, isPrivate };
-
- if (isPrivate) {
- privateLinks.push(newLink);
- } else {
- publicLinks.push(newLink);
- }
-
- links = isLoggedIn ? [...publicLinks, ...privateLinks] : publicLinks;
-
- if (isAdmin || (isPrivate && isLoggedIn) || !isPrivate) {
- const container = document.getElementById(category);
- if (container) {
- createCard(newLink, container);
- } else {
- categories[category] = [];
- renderCategories();
- }
- }
-
- saveLinks();
-
- document.getElementById('name-input').value = '';
- document.getElementById('url-input').value = '';
- document.getElementById('private-checkbox').checked = false;
- hideAddDialog();
-
- logAction('添加卡片', { name, url, category, isPrivate });
- }
- }
-
- // 删除卡片
- function removeCard(card) {
- const name = card.querySelector('.card-title').textContent;
- const url = card.querySelector('.card-url').textContent;
- const isPrivate = card.dataset.isPrivate === 'true';
-
- links = links.filter(link => link.url !== url);
- if (isPrivate) {
- privateLinks = privateLinks.filter(link => link.url !== url);
- } else {
- publicLinks = publicLinks.filter(link => link.url !== url);
- }
-
- for (const key in categories) {
- categories[key] = categories[key].filter(link => link.url !== url);
- }
-
- card.remove();
-
- saveLinks();
-
- logAction('删除卡片', { name, url, isPrivate });
- }
-
- // 拖拽卡片
- let draggedCard = null;
- let touchStartX, touchStartY;
-
- // 触屏端拖拽卡片
- function touchStart(event) {
- if (!isAdmin) return;
- draggedCard = event.target.closest('.card');
- if (!draggedCard) return;
-
- event.preventDefault();
- const touch = event.touches[0];
- touchStartX = touch.clientX;
- touchStartY = touch.clientY;
-
- draggedCard.classList.add('dragging');
-
- document.addEventListener('touchmove', touchMove, { passive: false });
- document.addEventListener('touchend', touchEnd);
-
- }
-
- function touchMove(event) {
- if (!draggedCard) return;
- event.preventDefault();
-
- const touch = event.touches[0];
- const currentX = touch.clientX;
- const currentY = touch.clientY;
-
- const deltaX = currentX - touchStartX;
- const deltaY = currentY - touchStartY;
- draggedCard.style.transform = "translate(" + deltaX + "px, " + deltaY + "px)";
-
- const target = findCardUnderTouch(currentX, currentY);
- if (target && target !== draggedCard) {
- const container = target.parentElement;
- const targetRect = target.getBoundingClientRect();
-
- if (currentX < targetRect.left + targetRect.width / 2) {
- container.insertBefore(draggedCard, target);
- } else {
- container.insertBefore(draggedCard, target.nextSibling);
- }
- }
- }
-
- function touchEnd(event) {
- if (!draggedCard) return;
-
- document.removeEventListener('touchmove', touchMove);
- document.removeEventListener('touchend', touchEnd);
-
- draggedCard.classList.remove('dragging');
- draggedCard.style.transform = '';
-
- const targetCategory = draggedCard.closest('.card-container').id;
- updateCardCategory(draggedCard, targetCategory);
- saveCardOrder();
-
- draggedCard = null;
- }
-
- function findCardUnderTouch(x, y) {
- const cards = document.querySelectorAll('.card:not(.dragging)');
- return Array.from(cards).find(card => {
- const rect = card.getBoundingClientRect();
- return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
- });
- }
-
- // PC端拖拽卡片
- function dragStart(event) {
- if (!isAdmin) return;
- draggedCard = event.target.closest('.card');
- if (!draggedCard) return;
-
- draggedCard.classList.add('dragging');
- event.dataTransfer.effectAllowed = "move";
- logAction('开始拖拽卡片', { name: draggedCard.querySelector('.card-title').textContent });
- }
-
- function dragOver(event) {
- if (!isAdmin || !draggedCard) return;
- event.preventDefault();
- const target = event.target.closest('.card');
- if (target && target !== draggedCard) {
- const container = target.parentElement;
- const mousePositionX = event.clientX;
- const targetRect = target.getBoundingClientRect();
-
- if (mousePositionX < targetRect.left + targetRect.width / 2) {
- container.insertBefore(draggedCard, target);
- } else {
- container.insertBefore(draggedCard, target.nextSibling);
- }
- }
- }
-
- function drop(event) {
- if (!isAdmin || !draggedCard) return;
- event.preventDefault();
- draggedCard.classList.remove('dragging');
- const targetCategory = event.target.closest('.card-container').id;
- updateCardCategory(draggedCard, targetCategory);
- logAction('放下卡片', { name: draggedCard.querySelector('.card-title').textContent, category: targetCategory });
- draggedCard = null;
- saveCardOrder();
- }
-
- function dragEnd(event) {
- if (draggedCard) {
- draggedCard.classList.remove('dragging');
- logAction('拖拽卡片结束');
- draggedCard = null;
- }
- }
-
- // 更新卡片分类
- function updateCardCategory(card, newCategory) {
- const cardTitle = card.querySelector('.card-title').textContent;
- const cardUrl = card.querySelector('.card-url').textContent;
- const isPrivate = card.dataset.isPrivate === 'true';
-
- const linkIndex = links.findIndex(link => link.url === cardUrl);
- if (linkIndex !== -1) {
- links[linkIndex].category = newCategory;
- }
-
- const linkArray = isPrivate ? privateLinks : publicLinks;
- const arrayIndex = linkArray.findIndex(link => link.url === cardUrl);
- if (arrayIndex !== -1) {
- linkArray[arrayIndex].category = newCategory;
- }
-
- card.dataset.category = newCategory;
- }
-
- // 在页面加载完成后添加触摸事件监听器
- document.addEventListener('DOMContentLoaded', function() {
- const cardContainers = document.querySelectorAll('.card-container');
- cardContainers.forEach(container => {
- container.addEventListener('touchstart', touchStart, { passive: false });
- });
- });
-
- // 保存卡片顺序
- async function saveCardOrder() {
- if (!isAdmin) return;
- const containers = document.querySelectorAll('.card-container');
- let newPublicLinks = [];
- let newPrivateLinks = [];
- let newCategories = {};
-
- containers.forEach(container => {
- const category = container.id;
- newCategories[category] = [];
-
- [...container.children].forEach(card => {
- const url = card.querySelector('.card-url').textContent;
- const name = card.querySelector('.card-title').textContent;
- const isPrivate = card.dataset.isPrivate === 'true';
- card.dataset.category = category;
- const link = { name, url, category, isPrivate };
- if (isPrivate) {
- newPrivateLinks.push(link);
- } else {
- newPublicLinks.push(link);
- }
- newCategories[category].push(link);
- });
- });
-
- publicLinks.length = 0;
- publicLinks.push(...newPublicLinks);
- privateLinks.length = 0;
- privateLinks.push(...newPrivateLinks);
- Object.keys(categories).forEach(key => delete categories[key]);
- Object.assign(categories, newCategories);
-
- logAction('保存卡片顺序', {
- publicCount: newPublicLinks.length,
- privateCount: newPrivateLinks.length,
- categoryCount: Object.keys(newCategories).length
- });
-
- try {
- const response = await fetch('/api/saveOrder', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- userId: 'testUser',
- links: [...newPublicLinks, ...newPrivateLinks],
- categories: newCategories
- }),
- });
- const result = await response.json();
- if (!result.success) {
- throw new Error('Failed to save order');
- }
- logAction('保存卡片顺序', { publicCount: newPublicLinks.length, privateCount: newPrivateLinks.length, categoryCount: Object.keys(newCategories).length });
- } catch (error) {
- console.error('Error saving order:', error);
- logAction('保存顺序失败', { error: error.message });
- alert('保存顺序失败,请重试');
- }
- }
-
- // 设置状态重新加载卡片
- function reloadCardsAsAdmin() {
- document.querySelectorAll('.card-container').forEach(container => {
- container.innerHTML = '';
- });
- loadLinks().then(() => {
- if (isDarkTheme) {
- applyDarkTheme();
- }
- });
- logAction('重新加载卡片(管理员模式)');
- }
-
- // 密码输入框回车事件
- document.getElementById('admin-password').addEventListener('keypress', (e) => {
- if (e.key === 'Enter') {
- toggleSecretGarden();
- }
- });
-
- // 切换设置状态
- function toggleAdminMode() {
- const adminBtn = document.getElementById('admin-mode-btn');
- const addRemoveControls = document.querySelector('.add-remove-controls');
-
- if (!isAdmin && isLoggedIn) {
- isAdmin = true;
- adminBtn.textContent = "退出设置";
- addRemoveControls.style.display = 'flex';
- alert('准备设置分类和书签');
- reloadCardsAsAdmin();
- logAction('进入设置');
- } else if (isAdmin) {
- isAdmin = false;
- removeMode = false;
- adminBtn.textContent = "设 置";
- addRemoveControls.style.display = 'none';
- alert('设置已保存');
- reloadCardsAsAdmin();
- logAction('离开设置');
- }
-
- updateUIState();
- }
-
- // 切换到登录状态
- function toggleSecretGarden() {
- const passwordInput = document.getElementById('admin-password');
- if (!isLoggedIn) {
- verifyPassword(passwordInput.value).then(isValid => {
- if (isValid) {
- isLoggedIn = true;
- links = [...publicLinks, ...privateLinks];
- loadSections();
- alert('登录成功!');
- logAction('登录成功');
- } else {
- alert('密码错误');
- logAction('登录失败', { reason: '密码错误' });
- }
- updateUIState();
- });
- } else {
- isLoggedIn = false;
- isAdmin = false;
- links = publicLinks;
- loadSections();
- alert('退出登录!');
- updateUIState();
- passwordInput.value = '';
- logAction('退出登录');
- }
- }
-
- // 应用暗色主题
- function applyDarkTheme() {
- document.body.style.backgroundColor = '#121212';
- document.body.style.color = '#ffffff';
- const cards = document.querySelectorAll('.card');
- cards.forEach(card => {
- card.style.backgroundColor = '#1e1e1e';
- card.style.color = '#ffffff';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
- });
- logAction('应用暗色主题');
- }
-
- // 显示添加链接对话框
- function showAddDialog() {
- document.getElementById('dialog-overlay').style.display = 'flex';
- logAction('显示添加链接对话框');
- }
-
- // 隐藏添加链接对话框
- function hideAddDialog() {
- document.getElementById('dialog-overlay').style.display = 'none';
- logAction('隐藏添加链接对话框');
- }
-
- // 切换删除卡片模式
- function toggleRemoveMode() {
- removeMode = !removeMode;
- const deleteButtons = document.querySelectorAll('.delete-btn');
- deleteButtons.forEach(btn => {
- btn.style.display = removeMode ? 'block' : 'none';
- });
- logAction('切换删除卡片模式', { removeMode });
- }
-
- //切换删除分类模式
- function toggleRemoveCategory() {
- isRemoveCategoryMode = !isRemoveCategoryMode;
- const deleteButtons = document.querySelectorAll('.delete-category-btn');
- deleteButtons.forEach(btn => {
- btn.style.display = isRemoveCategoryMode ? 'inline-block' : 'none';
- });
- logAction('切换删除分类模式', { isRemoveCategoryMode });
- }
-
- // 切换主题
- function toggleTheme() {
- isDarkTheme = !isDarkTheme;
-
- document.body.style.backgroundColor = isDarkTheme ? '#121212' : '#ffffff';
- document.body.style.color = isDarkTheme ? '#ffffff' : '#333';
-
- const cards = document.querySelectorAll('.card');
- cards.forEach(card => {
- card.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#a0c9e5';
- card.style.color = isDarkTheme ? '#ffffff' : '#333';
- card.style.boxShadow = isDarkTheme
- ? '0 4px 8px rgba(0, 0, 0, 0.5)'
- : '0 4px 8px rgba(0, 0, 0, 0.1)';
- });
-
- const fixedElements = document.querySelectorAll('.fixed-elements');
- fixedElements.forEach(element => {
- element.style.backgroundColor = isDarkTheme ? '#121212' : '#ffffff';
- element.style.color = isDarkTheme ? '#ffffff' : '#333';
- });
-
- const dialogBox = document.getElementById('dialog-box');
- dialogBox.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#ffffff';
- dialogBox.style.color = isDarkTheme ? '#ffffff' : '#333';
-
- const inputs = document.querySelectorAll('input[type="text"], input[type="password"], select');
- inputs.forEach(input => {
- input.style.backgroundColor = isDarkTheme ? '#444' : '#fff';
- input.style.color = isDarkTheme ? '#fff' : '#333';
- input.style.borderColor = isDarkTheme ? '#555' : '#ccc';
- });
-
- logAction('切换主题', { isDarkTheme });
- }
-
- // 验证密码
- async function verifyPassword(inputPassword) {
- const response = await fetch('/api/verifyPassword', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ password: inputPassword }),
- });
- const result = await response.json();
- return result.valid;
- }
-
- // 初始化加载链接
- loadLinks();
-
- </script>
- </body>
-
- </html>
- `;
-
- export default {
- async fetch(request, env) {
- const url = new URL(request.url);
-
- if (url.pathname === '/') {
- return new Response(HTML_CONTENT, {
- headers: { 'Content-Type': 'text/html' }
- });
- }
-
- if (url.pathname === '/api/getLinks') {
- const userId = url.searchParams.get('userId');
- const links = await env.CARD_ORDER.get(userId);
- return new Response(links || JSON.stringify([]), { status: 200 });
- }
-
- if (url.pathname === '/api/saveOrder' && request.method === 'POST') {
- const { userId, links, categories } = await request.json();
- await env.CARD_ORDER.put(userId, JSON.stringify({ links, categories })); //保存链接和分类
- return new Response(JSON.stringify({ success: true }), { status: 200 });
- }
-
- if (url.pathname === '/api/verifyPassword' && request.method === 'POST') {
- const { password } = await request.json();
- const isValid = password === env.ADMIN_PASSWORD; // 从环境变量中获取密码
- return new Response(JSON.stringify({ valid: isValid }), {
- status: isValid ? 200 : 403,
- headers: { 'Content-Type': 'application/json' },
- });
- }
-
- return new Response('Not Found', { status: 404 });
- }
- };
2024.09.09 更新:
1)、增加私密书签,登录后可见
2)、增加网站分类管理,现在你无需编辑代码,通过页面即可进行网站分类的添加和删除操作
3)、增加搜索框和一言接口
1、原workes, 效果
- const HTML_CONTENT = `
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Card Tab</title>
- <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2280%22>⭐</text></svg>">
- <style>
- /* 全局样式 */
- body {
- font-family: Arial, sans-serif;
- margin: 0;
- padding: 0;
- background-color: #d8eac4;
- transition: background-color 0.3s ease;
- }
-
- /* 固定元素样式 */
- .fixed-elements {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- background-color: #d8eac4;
- z-index: 1000;
- padding: 10px;
- transition: background-color 0.3s ease;
- height: 130px;
- }
-
- .fixed-elements h3 {
- position: absolute;
- top: 10px;
- left: 20px;
- margin: 0;
- }
-
- /* 中心内容样式 */
- .center-content {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 100%;
- max-width: 600px;
- text-align: center;
- }
-
- /* 管理员控制面板样式 */
- .admin-controls {
- position: fixed;
- top: 10px;
- right: 10px;
- font-size: 60%;
- }
-
- /* 添加/删除控制按钮样式 */
- .add-remove-controls {
- display: none;
- flex-direction: column;
- position: fixed;
- right: 20px;
- top: 50%;
- transform: translateY(-50%);
- align-items: center;
- gap: 10px;
- }
-
- .round-btn {
- background-color: #007bff;
- color: white;
- border: none;
- border-radius: 50%;
- width: 40px;
- height: 40px;
- text-align: center;
- font-size: 24px;
- line-height: 40px;
- cursor: pointer;
- margin: 5px 0;
- }
-
- .add-btn { order: 1; }
- .remove-btn { order: 2; }
- .category-btn { order: 3; }
- .remove-category-btn { order: 4; }
-
- /* 主要内容区域样式 */
- .content {
- margin-top: 140px;
- padding: 20px;
- }
-
- /* 搜索栏样式 */
- .search-container {
- margin-top: 10px;
- }
-
- .search-bar {
- display: flex;
- justify-content: center;
- margin-bottom: 10px;
- }
-
- .search-bar input {
- width: 70%;
- padding: 5px;
- border: 1px solid #ccc;
- border-radius: 5px 0 0 5px;
- }
-
- .search-bar button {
- padding: 5px 10px;
- border: 1px solid #ccc;
- border-left: none;
- background-color: #f8f8;
- border-radius: 0 5px 5px 0;
- cursor: pointer;
- }
-
- /* 搜索引擎按钮样式 */
- .search-engines {
- display: flex;
- justify-content: center;
- gap: 10px;
- }
-
- .search-engine {
- padding: 5px 10px;
- border: 1px solid #ccc;
- background-color: #f0f0;
- border-radius: 5px;
- cursor: pointer;
- }
-
- /* 主题切换按钮样式 */
- #theme-toggle {
- position: fixed;
- bottom: 50px;
- right: 20px;
- background-color: #007bff;
- color: white;
- border: none;
- border-radius: 50%;
- width: 40px;
- height: 40px;
- text-align: center;
- font-size: 24px;
- line-height: 40px;
- cursor: pointer;
- }
-
- /* 显示日志按钮样式 */
- #view-logs-btn {
- position: fixed;
- top: 100px;
- right: 10px;
- z-index: 1000;
- }
-
- /* 对话框样式 */
- #dialog-overlay {
- display: none;
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.5);
- justify-content: center;
- align-items: center;
- }
-
- #dialog-box {
- background-color: white;
- padding: 20px;
- border-radius: 5px;
- width: 300px;
- }
-
- #dialog-box input, #dialog-box select {
- width: 100%;
- margin-bottom: 10px;
- padding: 5px;
- }
-
- /* 分类和卡片样式 */
- .section {
- margin-bottom: 20px;
- }
-
- .section-title-container {
- display: flex;
- align-items: center;
- margin-bottom: 10px;
- }
-
- .section-title {
- font-size: 18px;
- font-weight: bold;
- }
-
- .delete-category-btn {
- background-color: pink;
- color: white;
- border: none;
- padding: 5px 10px;
- border-radius: 5px;
- cursor: pointer;
- }
-
- .card-container {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- }
-
- .card {
- background-color: #a0c9e5;
- border-radius: 5px;
- padding: 10px;
- width: 150px;
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
- cursor: pointer;
- transition: transform 0.2s;
- position: relative;
- user-select: none;
- }
-
- .card:hover {
- transform: translateY(-5px);
- }
-
- .card-top {
- display: flex;
- align-items: center;
- margin-bottom: 5px;
- }
-
- .card-icon {
- width: 16px;
- height: 16px;
- margin-right: 5px;
- }
-
- .card-title {
- font-size: 14px;
- font-weight: bold;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .card-url {
- font-size: 12px;
- color: #666;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .private-tag {
- background-color: #ff9800;
- color: white;
- font-size: 10px;
- padding: 2px 5px;
- border-radius: 3px;
- position: absolute;
- top: 5px;
- right: 5px;
- }
-
- .delete-btn {
- position: absolute;
- top: -10px;
- right: -10px;
- background-color: red;
- color: white;
- border: none;
- border-radius: 50%;
- width: 20px;
- height: 20px;
- text-align: center;
- font-size: 14px;
- line-height: 20px;
- cursor: pointer;
- display: none;
- }
-
- /* 版权信息样式 */
- #copyright {
- position: fixed;
- bottom: 0;
- left: 0;
- width: 100%;
- height: 40px;
- background-color: rgba(255, 255, 255, 0.8);
- display: flex;
- justify-content: center;
- align-items: center;
- font-size: 14px;
- z-index: 1000;
- box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
- }
-
- #copyright p {
- margin: 0;
- }
-
- #copyright a {
- color: #007bff;
- text-decoration: none;
- }
-
- #copyright a:hover {
- text-decoration: underline;
- }
-
- /* 响应式设计 */
- @media (max-width: 480px) {
- .fixed-elements {
- position: relative;
- padding: 5px;
- }
- .content {
- margin-top: 10px;
- }
-
- .admin-controls input,
- .admin-controls button {
- height: 30%;
- }
-
- .card-container {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 10px;
- }
- .card {
- width: 80%;
- max-width: 100%;
- padding: 5px;
- }
- .card-title {
- font-size: 12px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- max-width: 130px;
- }
- .card-url {
- font-size: 10px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- max-width: 130px;
- }
-
- .add-remove-controls {
- right: 2px;
- }
-
- .round-btn,
- #theme-toggle {
- right: 5px;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 30px;
- height: 30px;
- font-size: 24px;
- }
- }
- </style>
- </head>
-
- <body>
- <div class="fixed-elements">
- <h3>我的导航</h3>
- <div class="center-content">
- <!-- 一言模块 -->
- <p id="hitokoto">
- <a href="#" id="hitokoto_text"></a>
- </p>
- <script src="https://v1.hitokoto.cn/?encode=js&select=%23hitokoto" defer></script>
- <!-- 搜索栏 -->
- <div class="search-container">
- <div class="search-bar">
- <input type="text" id="search-input" placeholder="">
- <button id="search-button">🔍</button>
- </div>
- <div class="search-engines">
- <button class="search-engine" data-engine="baidu">百度</button>
- <button class="search-engine" data-engine="bing">必应</button>
- <button class="search-engine" data-engine="google">谷歌</button>
- </div>
- </div>
- </div>
- <!-- 管理员控制面板 -->
- <div class="admin-controls">
- <input type="password" id="admin-password" placeholder="输入密码">
- <button id="admin-mode-btn" onclick="toggleAdminMode()">设 置</button>
- <button id="secret-garden-btn" onclick="toggleSecretGarden()">登 录</button>
- </div>
- </div>
- <div class="content">
- <!-- 添加/删除控制按钮 -->
- <div class="add-remove-controls">
- <button class="round-btn add-btn" onclick="showAddDialog()">+</button>
- <button class="round-btn remove-btn" onclick="toggleRemoveMode()">-</button>
- <button class="round-btn category-btn" onclick="addCategory()">C+</button>
- <button class="round-btn remove-category-btn" onclick="toggleRemoveCategory()">C-</button>
-
- </div>
-
- <!-- 分类和卡片容器 -->
- <div id="sections-container"></div>
- <!-- 主题切换按钮 -->
- <button id="theme-toggle" onclick="toggleTheme()">◑</button>
- <!-- 显示日志按钮 用于调试-->
- <!--<button id="view-logs-btn" onclick="viewLogs()">显示日志</button>-->
- <!-- 添加链接对话框 -->
- <div id="dialog-overlay">
- <div id="dialog-box">
- <label for="name-input">名称</label>
- <input type="text" id="name-input">
- <label for="url-input">地址</label>
- <input type="text" id="url-input">
- <label for="category-select">选择分类</label>
- <select id="category-select"></select>
- <div class="private-link-container">
- <label for="private-checkbox">私密链接</label>
- <input type="checkbox" id="private-checkbox">
- </div>
- <button onclick="addLink()">确定</button>
- <button onclick="hideAddDialog()">取消</button>
- </div>
- </div>
- <!-- 版权信息 -->
- <div id="copyright" class="copyright">
- <!--请不要删除-->
- <p>项目地址:<a href="https://github.com/hmhm2022/Card-Tab" target="_blank">GitHub</a> 如果喜欢,烦请点个star!</p>
- </div>
- </div>
-
- <script>
- // 搜索引擎配置
- const searchEngines = {
- baidu: "https://www.baidu.com/s?wd=",
- bing: "https://www.bing.com/search?q=",
- google: "https://www.google.com/search?q="
- };
-
- let currentEngine = "baidu";
-
- // 日志记录函数
- function logAction(action, details) {
- const timestamp = new Date().toISOString();
- const logEntry = timestamp + ': ' + action + ' - ' + JSON.stringify(details);
-
- let logs = JSON.parse(localStorage.getItem('cardTabLogs') || '[]');
- logs.push(logEntry);
-
- // 保留最新的1000条日志
- if (logs.length > 1000) {
- logs = logs.slice(-1000);
- }
-
- localStorage.setItem('cardTabLogs', JSON.stringify(logs));
- console.log(logEntry); // 同时在控制台输出日志
- }
-
- // 查看日志的函数
- function viewLogs() {
- const logs = JSON.parse(localStorage.getItem('cardTabLogs') || '[]');
- console.log('Card Tab Logs:');
- logs.forEach(log => console.log(log));
- alert('日志已在控制台输出,请按F12打开开发者工具查看。');
- }
-
- // 设置当前搜索引擎
- function setActiveEngine(engine) {
- currentEngine = engine;
- document.querySelectorAll('.search-engine').forEach(btn => {
- btn.style.backgroundColor = btn.dataset.engine === engine ? '#c0c0c0' : '#f0f0f0';
- });
- logAction('设置搜索引擎', { engine });
- }
-
- // 搜索引擎按钮点击事件
- document.querySelectorAll('.search-engine').forEach(button => {
- button.addEventListener('click', () => setActiveEngine(button.dataset.engine));
- });
-
- // 搜索按钮点击事件
- document.getElementById('search-button').addEventListener('click', () => {
- const query = document.getElementById('search-input').value;
- if (query) {
- logAction('执行搜索', { engine: currentEngine, query });
- window.open(searchEngines[currentEngine] + encodeURIComponent(query), '_blank');
- }
- });
-
- // 搜索输入框回车事件
- document.getElementById('search-input').addEventListener('keypress', (e) => {
- if (e.key === 'Enter') {
- document.getElementById('search-button').click();
- }
- });
-
- // 初始化搜索引擎
- setActiveEngine(currentEngine);
-
- // 全局变量
- let publicLinks = [];
- let privateLinks = [];
- let isAdmin = false;
- let isLoggedIn = false;
- let removeMode = false;
- let isRemoveCategoryMode = false;
- let isDarkTheme = false;
- let links = [];
- const categories = {};
-
- // 添加新分类
- function addCategory() {
- const categoryName = prompt('请输入新分类名称:');
- if (categoryName && !categories[categoryName]) {
- categories[categoryName] = [];
- updateCategorySelect();
- renderCategories();
- saveLinks();
- logAction('添加分类', { categoryName, currentLinkCount: links.length });
- } else if (categories[categoryName]) {
- alert('该分类已存在');
- logAction('添加分类失败', { categoryName, reason: '分类已存在' });
- }
- }
-
- // 删除分类
- function deleteCategory(category) {
- if (confirm('确定要删除 "' + category + '" 分类吗?这将删除该分类下的所有链接。')) {
- delete categories[category];
- links = links.filter(link => link.category !== category);
- publicLinks = publicLinks.filter(link => link.category !== category);
- privateLinks = privateLinks.filter(link => link.category !== category);
- updateCategorySelect();
- saveLinks();
- renderCategories();
- logAction('删除分类', { category });
- }
- }
-
- // 渲染分类(不重新加载链接)
- function renderCategories() {
- const container = document.getElementById('sections-container');
- container.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const section = document.createElement('div');
- section.className = 'section';
-
- const titleContainer = document.createElement('div');
- titleContainer.className = 'section-title-container';
-
- const title = document.createElement('div');
- title.className = 'section-title';
- title.textContent = category;
-
- titleContainer.appendChild(title);
-
- if (isAdmin) {
- const deleteBtn = document.createElement('button');
- deleteBtn.textContent = '删除分类';
- deleteBtn.className = 'delete-category-btn';
- deleteBtn.style.display = isRemoveCategoryMode ? 'inline-block' : 'none';
- deleteBtn.onclick = () => deleteCategory(category);
- titleContainer.appendChild(deleteBtn);
- }
-
- const cardContainer = document.createElement('div');
- cardContainer.className = 'card-container';
- cardContainer.id = category;
-
- section.appendChild(titleContainer);
- section.appendChild(cardContainer);
-
- container.appendChild(section);
-
- const categoryLinks = links.filter(link => link.category === category);
- categoryLinks.forEach(link => {
- createCard(link, cardContainer);
- });
- });
-
- logAction('渲染分类', { categoryCount: Object.keys(categories).length, linkCount: links.length });
- }
-
- // 读取链接数据
- async function loadLinks() {
- const response = await fetch('/api/getLinks?userId=testUser');
- const data = await response.json();
- if (data.categories) {
- Object.assign(categories, data.categories);
- }
-
- publicLinks = data.links ? data.links.filter(link => !link.isPrivate) : [];
- privateLinks = data.links ? data.links.filter(link => link.isPrivate) : [];
- links = isLoggedIn ? [...publicLinks, ...privateLinks] : publicLinks;
-
- loadSections();
- updateCategorySelect();
- updateUIState();
- logAction('读取链接', { publicCount: publicLinks.length, privateCount: privateLinks.length });
- }
-
-
- // 更新UI状态
- function updateUIState() {
- const passwordInput = document.getElementById('admin-password');
- const adminBtn = document.getElementById('admin-mode-btn');
- const secretGardenBtn = document.getElementById('secret-garden-btn');
- const addRemoveControls = document.querySelector('.add-remove-controls');
-
- passwordInput.style.display = isLoggedIn ? 'none' : 'inline-block';
- secretGardenBtn.textContent = isLoggedIn ? "退出" : "登录";
- secretGardenBtn.style.display = 'inline-block';
-
- if (isAdmin) {
- adminBtn.textContent = "离开设置";
- adminBtn.style.display = 'inline-block';
- addRemoveControls.style.display = 'flex';
- } else if (isLoggedIn) {
- adminBtn.textContent = "设置";
- adminBtn.style.display = 'inline-block';
- addRemoveControls.style.display = 'none';
- } else {
- adminBtn.style.display = 'none';
- addRemoveControls.style.display = 'none';
- }
-
- logAction('更新UI状态', { isAdmin, isLoggedIn });
- }
-
- // 登录状态显示(加载所有链接)
- function showSecretGarden() {
- if (isLoggedIn) {
- links = [...publicLinks, ...privateLinks];
- loadSections();
- // 显示所有私密标签
- document.querySelectorAll('.private-tag').forEach(tag => {
- tag.style.display = 'block';
- });
- logAction('显示私密花园');
- }
- }
-
- // 加载分类和链接
- function loadSections() {
- const container = document.getElementById('sections-container');
- container.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const section = document.createElement('div');
- section.className = 'section';
-
- const titleContainer = document.createElement('div');
- titleContainer.className = 'section-title-container';
-
- const title = document.createElement('div');
- title.className = 'section-title';
- title.textContent = category;
-
- titleContainer.appendChild(title);
-
- if (isAdmin) {
- const deleteBtn = document.createElement('button');
- deleteBtn.textContent = '删除分类';
- deleteBtn.className = 'delete-category-btn';
- deleteBtn.style.display = 'none';
- deleteBtn.onclick = () => deleteCategory(category);
- titleContainer.appendChild(deleteBtn);
- }
-
- const cardContainer = document.createElement('div');
- cardContainer.className = 'card-container';
- cardContainer.id = category;
-
- section.appendChild(titleContainer);
- section.appendChild(cardContainer);
-
- let privateCount = 0;
- let linkCount = 0;
-
- links.forEach(link => {
- if (link.category === category) {
- if (link.isPrivate) privateCount++;
- linkCount++;
- createCard(link, cardContainer);
- }
- });
-
- if (privateCount < linkCount || isLoggedIn) {
- container.appendChild(section);
- }
- });
-
- logAction('加载分类和链接', { isAdmin: isAdmin, linkCount: links.length, categoryCount: Object.keys(categories).length });
- }
-
- // 创建卡片
- function createCard(link, container) {
- const card = document.createElement('div');
- card.className = 'card';
- card.setAttribute('draggable', isAdmin);
- card.dataset.isPrivate = link.isPrivate;
-
- const cardTop = document.createElement('div');
- cardTop.className = 'card-top';
-
- const icon = document.createElement('img');
- icon.className = 'card-icon';
- icon.src = 'https://favicon.zhusl.com/ico?url=' + link.url;
- icon.alt = 'Website Icon';
-
- const title = document.createElement('div');
- title.className = 'card-title';
- title.textContent = link.name;
-
- cardTop.appendChild(icon);
- cardTop.appendChild(title);
-
- const url = document.createElement('div');
- url.className = 'card-url';
- url.textContent = link.url;
-
- card.appendChild(cardTop);
- card.appendChild(url);
-
- if (link.isPrivate) {
- const privateTag = document.createElement('div');
- privateTag.className = 'private-tag';
- privateTag.textContent = '私密';
- card.appendChild(privateTag);
- }
-
- const correctedUrl = link.url.startsWith('http://') || link.url.startsWith('https://') ? link.url : 'http://' + link.url;
-
- if (!isAdmin) {
- card.addEventListener('click', () => {
- window.open(correctedUrl, '_blank');
- logAction('打开链接', { name: link.name, url: correctedUrl });
- });
- }
-
- const deleteBtn = document.createElement('button');
- deleteBtn.textContent = '–';
- deleteBtn.className = 'delete-btn';
- deleteBtn.onclick = function (event) {
- event.stopPropagation();
- removeCard(card);
- };
- card.appendChild(deleteBtn);
-
- updateCardStyle(card);
-
- card.addEventListener('dragstart', dragStart);
- card.addEventListener('dragover', dragOver);
- card.addEventListener('dragend', dragEnd);
- card.addEventListener('drop', drop);
- card.addEventListener('touchstart', touchStart, { passive: false });
-
- if (isAdmin && removeMode) {
- deleteBtn.style.display = 'block';
- }
-
- if (isAdmin || (link.isPrivate && isLoggedIn) || !link.isPrivate) {
- container.appendChild(card);
- }
- // logAction('创建卡片', { name: link.name, isPrivate: link.isPrivate });
- }
-
- // 更新卡片样式
- function updateCardStyle(card) {
- if (isDarkTheme) {
- card.style.backgroundColor = '#1e1e1e';
- card.style.color = '#ffffff';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
- } else {
- card.style.backgroundColor = '#a0c9e5';
- card.style.color = '#333';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
- }
- }
-
- // 更新分类选择下拉框
- function updateCategorySelect() {
- const categorySelect = document.getElementById('category-select');
- categorySelect.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const option = document.createElement('option');
- option.value = category;
- option.textContent = category;
- categorySelect.appendChild(option);
- });
-
- logAction('更新分类选择', { categoryCount: Object.keys(categories).length });
- }
-
- // 保存链接数据
- async function saveLinks() {
- let allLinks = [...publicLinks, ...privateLinks];
-
- try {
- await fetch('/api/saveOrder', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- userId: 'testUser',
- links: allLinks,
- categories: categories
- }),
- });
- logAction('保存链接', { linkCount: allLinks.length, categoryCount: Object.keys(categories).length });
- } catch (error) {
- console.error('Error saving links:', error);
- logAction('保存链接失败', { error: error.message });
- alert('保存链接失败,请重试');
- }
- }
-
- // 添加卡片弹窗
- function addLink() {
- const name = document.getElementById('name-input').value;
- const url = document.getElementById('url-input').value;
- const category = document.getElementById('category-select').value;
- const isPrivate = document.getElementById('private-checkbox').checked;
-
- if (name && url && category) {
- const newLink = { name, url, category, isPrivate };
-
- if (isPrivate) {
- privateLinks.push(newLink);
- } else {
- publicLinks.push(newLink);
- }
-
- links = isLoggedIn ? [...publicLinks, ...privateLinks] : publicLinks;
-
- if (isAdmin || (isPrivate && isLoggedIn) || !isPrivate) {
- const container = document.getElementById(category);
- if (container) {
- createCard(newLink, container);
- } else {
- categories[category] = [];
- renderCategories();
- }
- }
-
- saveLinks();
-
- document.getElementById('name-input').value = '';
- document.getElementById('url-input').value = '';
- document.getElementById('private-checkbox').checked = false;
- hideAddDialog();
-
- logAction('添加卡片', { name, url, category, isPrivate });
- }
- }
-
- // 删除卡片
- function removeCard(card) {
- const name = card.querySelector('.card-title').textContent;
- const url = card.querySelector('.card-url').textContent;
- const isPrivate = card.dataset.isPrivate === 'true';
-
- links = links.filter(link => link.url !== url);
- if (isPrivate) {
- privateLinks = privateLinks.filter(link => link.url !== url);
- } else {
- publicLinks = publicLinks.filter(link => link.url !== url);
- }
-
- for (const key in categories) {
- categories[key] = categories[key].filter(link => link.url !== url);
- }
-
- card.remove();
-
- saveLinks();
-
- logAction('删除卡片', { name, url, isPrivate });
- }
-
- // 拖拽卡片
- let draggedCard = null;
- let touchStartX, touchStartY;
-
- // 触屏端拖拽卡片
- function touchStart(event) {
- if (!isAdmin) return;
- draggedCard = event.target.closest('.card');
- if (!draggedCard) return;
-
- event.preventDefault();
- const touch = event.touches[0];
- touchStartX = touch.clientX;
- touchStartY = touch.clientY;
-
- draggedCard.classList.add('dragging');
-
- document.addEventListener('touchmove', touchMove, { passive: false });
- document.addEventListener('touchend', touchEnd);
-
- }
-
- function touchMove(event) {
- if (!draggedCard) return;
- event.preventDefault();
-
- const touch = event.touches[0];
- const currentX = touch.clientX;
- const currentY = touch.clientY;
-
- const deltaX = currentX - touchStartX;
- const deltaY = currentY - touchStartY;
- draggedCard.style.transform = "translate(" + deltaX + "px, " + deltaY + "px)";
-
- const target = findCardUnderTouch(currentX, currentY);
- if (target && target !== draggedCard) {
- const container = target.parentElement;
- const targetRect = target.getBoundingClientRect();
-
- if (currentX < targetRect.left + targetRect.width / 2) {
- container.insertBefore(draggedCard, target);
- } else {
- container.insertBefore(draggedCard, target.nextSibling);
- }
- }
- }
-
- function touchEnd(event) {
- if (!draggedCard) return;
-
- document.removeEventListener('touchmove', touchMove);
- document.removeEventListener('touchend', touchEnd);
-
- draggedCard.classList.remove('dragging');
- draggedCard.style.transform = '';
-
- const targetCategory = draggedCard.closest('.card-container').id;
- updateCardCategory(draggedCard, targetCategory);
- saveCardOrder();
-
- draggedCard = null;
- }
-
- function findCardUnderTouch(x, y) {
- const cards = document.querySelectorAll('.card:not(.dragging)');
- return Array.from(cards).find(card => {
- const rect = card.getBoundingClientRect();
- return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
- });
- }
-
- // PC端拖拽卡片
- function dragStart(event) {
- if (!isAdmin) return;
- draggedCard = event.target.closest('.card');
- if (!draggedCard) return;
-
- draggedCard.classList.add('dragging');
- event.dataTransfer.effectAllowed = "move";
- logAction('开始拖拽卡片', { name: draggedCard.querySelector('.card-title').textContent });
- }
-
- function dragOver(event) {
- if (!isAdmin || !draggedCard) return;
- event.preventDefault();
- const target = event.target.closest('.card');
- if (target && target !== draggedCard) {
- const container = target.parentElement;
- const mousePositionX = event.clientX;
- const targetRect = target.getBoundingClientRect();
-
- if (mousePositionX < targetRect.left + targetRect.width / 2) {
- container.insertBefore(draggedCard, target);
- } else {
- container.insertBefore(draggedCard, target.nextSibling);
- }
- }
- }
-
- function drop(event) {
- if (!isAdmin || !draggedCard) return;
- event.preventDefault();
- draggedCard.classList.remove('dragging');
- const targetCategory = event.target.closest('.card-container').id;
- updateCardCategory(draggedCard, targetCategory);
- logAction('放下卡片', { name: draggedCard.querySelector('.card-title').textContent, category: targetCategory });
- draggedCard = null;
- saveCardOrder();
- }
-
- function dragEnd(event) {
- if (draggedCard) {
- draggedCard.classList.remove('dragging');
- logAction('拖拽卡片结束');
- draggedCard = null;
- }
- }
-
- // 更新卡片分类
- function updateCardCategory(card, newCategory) {
- const cardTitle = card.querySelector('.card-title').textContent;
- const cardUrl = card.querySelector('.card-url').textContent;
- const isPrivate = card.dataset.isPrivate === 'true';
-
- const linkIndex = links.findIndex(link => link.url === cardUrl);
- if (linkIndex !== -1) {
- links[linkIndex].category = newCategory;
- }
-
- const linkArray = isPrivate ? privateLinks : publicLinks;
- const arrayIndex = linkArray.findIndex(link => link.url === cardUrl);
- if (arrayIndex !== -1) {
- linkArray[arrayIndex].category = newCategory;
- }
-
- card.dataset.category = newCategory;
- }
-
- // 在页面加载完成后添加触摸事件监听器
- document.addEventListener('DOMContentLoaded', function() {
- const cardContainers = document.querySelectorAll('.card-container');
- cardContainers.forEach(container => {
- container.addEventListener('touchstart', touchStart, { passive: false });
- });
- });
-
- // 保存卡片顺序
- async function saveCardOrder() {
- if (!isAdmin) return;
- const containers = document.querySelectorAll('.card-container');
- let newPublicLinks = [];
- let newPrivateLinks = [];
- let newCategories = {};
-
- containers.forEach(container => {
- const category = container.id;
- newCategories[category] = [];
-
- [...container.children].forEach(card => {
- const url = card.querySelector('.card-url').textContent;
- const name = card.querySelector('.card-title').textContent;
- const isPrivate = card.dataset.isPrivate === 'true';
- card.dataset.category = category;
- const link = { name, url, category, isPrivate };
- if (isPrivate) {
- newPrivateLinks.push(link);
- } else {
- newPublicLinks.push(link);
- }
- newCategories[category].push(link);
- });
- });
-
- publicLinks.length = 0;
- publicLinks.push(...newPublicLinks);
- privateLinks.length = 0;
- privateLinks.push(...newPrivateLinks);
- Object.keys(categories).forEach(key => delete categories[key]);
- Object.assign(categories, newCategories);
-
- logAction('保存卡片顺序', {
- publicCount: newPublicLinks.length,
- privateCount: newPrivateLinks.length,
- categoryCount: Object.keys(newCategories).length
- });
-
- try {
- const response = await fetch('/api/saveOrder', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- userId: 'testUser',
- links: [...newPublicLinks, ...newPrivateLinks],
- categories: newCategories
- }),
- });
- const result = await response.json();
- if (!result.success) {
- throw new Error('Failed to save order');
- }
- logAction('保存卡片顺序', { publicCount: newPublicLinks.length, privateCount: newPrivateLinks.length, categoryCount: Object.keys(newCategories).length });
- } catch (error) {
- console.error('Error saving order:', error);
- logAction('保存顺序失败', { error: error.message });
- alert('保存顺序失败,请重试');
- }
- }
-
- // 设置状态重新加载卡片
- function reloadCardsAsAdmin() {
- document.querySelectorAll('.card-container').forEach(container => {
- container.innerHTML = '';
- });
- loadLinks().then(() => {
- if (isDarkTheme) {
- applyDarkTheme();
- }
- });
- logAction('重新加载卡片(管理员模式)');
- }
-
- // 密码输入框回车事件
- document.getElementById('admin-password').addEventListener('keypress', (e) => {
- if (e.key === 'Enter') {
- toggleSecretGarden();
- }
- });
-
- // 切换设置状态
- function toggleAdminMode() {
- const adminBtn = document.getElementById('admin-mode-btn');
- const addRemoveControls = document.querySelector('.add-remove-controls');
-
- if (!isAdmin && isLoggedIn) {
- isAdmin = true;
- adminBtn.textContent = "退出设置";
- addRemoveControls.style.display = 'flex';
- alert('准备设置分类和书签');
- reloadCardsAsAdmin();
- logAction('进入设置');
- } else if (isAdmin) {
- isAdmin = false;
- removeMode = false;
- adminBtn.textContent = "设 置";
- addRemoveControls.style.display = 'none';
- alert('设置已保存');
- reloadCardsAsAdmin();
- logAction('离开设置');
- }
-
- updateUIState();
- }
-
- // 切换到登录状态
- function toggleSecretGarden() {
- const passwordInput = document.getElementById('admin-password');
- if (!isLoggedIn) {
- verifyPassword(passwordInput.value).then(isValid => {
- if (isValid) {
- isLoggedIn = true;
- links = [...publicLinks, ...privateLinks];
- loadSections();
- alert('登录成功!');
- logAction('登录成功');
- } else {
- alert('密码错误');
- logAction('登录失败', { reason: '密码错误' });
- }
- updateUIState();
- });
- } else {
- isLoggedIn = false;
- isAdmin = false;
- links = publicLinks;
- loadSections();
- alert('退出登录!');
- updateUIState();
- passwordInput.value = '';
- logAction('退出登录');
- }
- }
-
- // 应用暗色主题
- function applyDarkTheme() {
- document.body.style.backgroundColor = '#121212';
- document.body.style.color = '#ffffff';
- const cards = document.querySelectorAll('.card');
- cards.forEach(card => {
- card.style.backgroundColor = '#1e1e1e';
- card.style.color = '#ffffff';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
- });
- logAction('应用暗色主题');
- }
-
- // 显示添加链接对话框
- function showAddDialog() {
- document.getElementById('dialog-overlay').style.display = 'flex';
- logAction('显示添加链接对话框');
- }
-
- // 隐藏添加链接对话框
- function hideAddDialog() {
- document.getElementById('dialog-overlay').style.display = 'none';
- logAction('隐藏添加链接对话框');
- }
-
- // 切换删除卡片模式
- function toggleRemoveMode() {
- removeMode = !removeMode;
- const deleteButtons = document.querySelectorAll('.delete-btn');
- deleteButtons.forEach(btn => {
- btn.style.display = removeMode ? 'block' : 'none';
- });
- logAction('切换删除卡片模式', { removeMode });
- }
-
- //切换删除分类模式
- function toggleRemoveCategory() {
- isRemoveCategoryMode = !isRemoveCategoryMode;
- const deleteButtons = document.querySelectorAll('.delete-category-btn');
- deleteButtons.forEach(btn => {
- btn.style.display = isRemoveCategoryMode ? 'inline-block' : 'none';
- });
- logAction('切换删除分类模式', { isRemoveCategoryMode });
- }
-
- // 切换主题
- function toggleTheme() {
- isDarkTheme = !isDarkTheme;
-
- document.body.style.backgroundColor = isDarkTheme ? '#121212' : '#d8eac4';
- document.body.style.color = isDarkTheme ? '#ffffff' : '#333';
-
- const cards = document.querySelectorAll('.card');
- cards.forEach(card => {
- card.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#a0c9e5';
- card.style.color = isDarkTheme ? '#ffffff' : '#333';
- card.style.boxShadow = isDarkTheme
- ? '0 4px 8px rgba(0, 0, 0, 0.5)'
- : '0 4px 8px rgba(0, 0, 0, 0.1)';
- });
-
- const fixedElements = document.querySelectorAll('.fixed-elements');
- fixedElements.forEach(element => {
- element.style.backgroundColor = isDarkTheme ? '#121212' : '#d8eac4';
- element.style.color = isDarkTheme ? '#ffffff' : '#333';
- });
-
- const dialogBox = document.getElementById('dialog-box');
- dialogBox.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#ffffff';
- dialogBox.style.color = isDarkTheme ? '#ffffff' : '#333';
-
- const inputs = document.querySelectorAll('input[type="text"], input[type="password"], select');
- inputs.forEach(input => {
- input.style.backgroundColor = isDarkTheme ? '#444' : '#fff';
- input.style.color = isDarkTheme ? '#fff' : '#333';
- input.style.borderColor = isDarkTheme ? '#555' : '#ccc';
- });
-
- logAction('切换主题', { isDarkTheme });
- }
-
- // 验证密码
- async function verifyPassword(inputPassword) {
- const response = await fetch('/api/verifyPassword', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ password: inputPassword }),
- });
- const result = await response.json();
- return result.valid;
- }
-
- // 初始化加载链接
- loadLinks();
-
- </script>
- </body>
-
- </html>
- `;
-
- export default {
- async fetch(request, env) {
- const url = new URL(request.url);
-
- if (url.pathname === '/') {
- return new Response(HTML_CONTENT, {
- headers: { 'Content-Type': 'text/html' }
- });
- }
-
- if (url.pathname === '/api/getLinks') {
- const userId = url.searchParams.get('userId');
- const links = await env.CARD_ORDER.get(userId);
- return new Response(links || JSON.stringify([]), { status: 200 });
- }
-
- if (url.pathname === '/api/saveOrder' && request.method === 'POST') {
- const { userId, links, categories } = await request.json();
- await env.CARD_ORDER.put(userId, JSON.stringify({ links, categories })); //保存链接和分类
- return new Response(JSON.stringify({ success: true }), { status: 200 });
- }
-
- if (url.pathname === '/api/verifyPassword' && request.method === 'POST') {
- const { password } = await request.json();
- const isValid = password === env.ADMIN_PASSWORD; // 从环境变量中获取密码
- return new Response(JSON.stringify({ valid: isValid }), {
- status: isValid ? 200 : 403,
- headers: { 'Content-Type': 'application/json' },
- });
- }
-
- return new Response('Not Found', { status: 404 });
- }
- };
修改后workes, 效果
- const HTML_CONTENT = `
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>ws01主页</title>
- <link rel="icon" href="https://cdn.glitch.global/efdace30-a873-49c7-aaa9-4fa31679ee0c/thumbnails%2F%E5%9B%BE%E6%A0%8701.jpg?1692046715299">
- <style>
- /* 全局样式 */
- body {
- font-family: Arial, sans-serif;
- margin: 0;
- padding: 0;
- background-color: #d4d4d4;
- transition: background-color 0.3s ease;
- }
-
- ul {
- padding: 0;
- margin-block-start: 1em;
- margin-block-end: 1em;
- margin-inline-start: 0px;
- margin-inline-end: 0px;
- padding-inline-start: 40px;
- unicode-bidi: isolate;
- }
- li {
- display: list-item;
- text-align: -webkit-match-parent;
- unicode-bidi: isolate;
- margin: 0 8px;
- }
-
- .background {
- background-image: linear-gradient(#d4d4d4 1px, transparent 0), linear-gradient(90deg, #d4d4d4 1px, transparent 0);
- background-size: 32px 32px;
- background-color: #fffcf8;
- }
-
- .header {
- background-color: #fff;
- box-shadow: 0 0 5px rgba(0, 0, 0, .1);
- transition: background-color .5s;
- }
- .navbar {
- display: flex;
- width: 1280px;
- margin: auto;
- height: 40px;
- }
-
- .navbar .brand {
-
- display: flex;
- align-items: center;
- color: #555;
- }
-
- .brand .logo {
- max-width: 36px;
- }
-
- .brand .title {
- margin-left: 5px;
- font-family: helvetica neue, helvetica, arial, sans-serif;
- font-size: 24px;
- font-weight: 700;
- }
-
- .beta {
- color: #ccc;
- font-size: 11px;
- font-weight: 400;
- position: relative;
- top: -14px;
- }
-
- .category-list {
- list-style: none;
- display: flex;
- align-items: center;
- }
-
- .sites {
- width:1286px;
- background:;
- border:2px solid auto;
- margin:15px auto;
- padding:0px;
- text-align:center;
- }
- .sites1 {
- width:1286px;
- background:;
- border:2px solid auto;
- margin:15px auto;
- padding:0px;
- }
-
- .sites dl {
- height:36px;
- line-height:36px;
- display:block;
- margin:0;
- }
-
- .sites dl.alt {
- background:#d4d4d4;
- border-top:1px solid #ffffff;
- border-bottom:1px solid #ffffff;
- }
-
- .sites dl.alt2 {
- background:#d4d4d4;
- border-top:1px solid #ffffff;
- border-bottom:1px solid #ffffff;
- }
-
- .sites dt,.sites dd {
- text-align:center;
- display:block;
- float:left;
- }
-
- .sites dt {
- width:60px;
- }
-
- .sites dd {
- width:90px;
- margin:0;
- }
- .footer {
- width:580px;
- text-align:center;
- margin:5px auto;
- padding:2px;
- }
-
-
-
- /* 固定元素样式 */
- .fixed-elements {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- background-color: #d4d4d4;
- z-index: 1000;
- padding: 10px;
- transition: background-color 0.3s ease;
- height: 130px;
- }
-
- .fixed-elements h3 {
- position: absolute;
- top: 10px;
- left: 20px;
- margin: 0;
- }
-
- /* 中心内容样式 */
- .center-content {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 100%;
- max-width: 600px;
- text-align: center;
- }
-
- /* 管理员控制面板样式 */
- .admin-controls {
- position: fixed;
- top: 10px;
- right: 10px;
- font-size: 60%;
- }
-
- /* 添加/删除控制按钮样式 */
- .add-remove-controls {
- display: none;
- flex-direction: column;
- position: fixed;
- right: 20px;
- top: 50%;
- transform: translateY(-50%);
- align-items: center;
- gap: 10px;
- }
-
- .round-btn {
- background-color: #007bff;
- color: white;
- border: none;
- border-radius: 50%;
- width: 40px;
- height: 40px;
- text-align: center;
- font-size: 24px;
- line-height: 40px;
- cursor: pointer;
- margin: 5px 0;
- }
-
- .add-btn { order: 1; }
- .remove-btn { order: 2; }
- .category-btn { order: 3; }
- .remove-category-btn { order: 4; }
-
- /* 主要内容区域样式 */
- .content {
- margin-top: 140px;
- padding: 20px;
- }
-
- /* 搜索栏样式 */
- .search-container {
- margin-top: 10px;
- }
-
- .search-bar {
- display: flex;
- justify-content: center;
- margin-bottom: 10px;
- }
-
- .search-bar input {
- width: 50%;
- padding: 5px;
- border: 1px solid #ccc;
- border-radius: 5px 0 0 5px;
- }
-
- .search-bar button {
- padding: 5px 20px;
- border: 1px solid #ccc;
- border-left: none;
- background-color: #a0c9e5;
- border-radius: 0 5px 5px 0;
- cursor: pointer;
- }
-
- /* 搜索引擎按钮样式 */
- .search-engines {
- display: flex;
- justify-content: center;
- gap: 10px;
- }
-
- .search-engine {
- padding: 5px 10px;
- border: 1px solid #ccc;
- background-color: #f0f0;
- border-radius: 5px;
- cursor: pointer;
- }
-
- /* 主题切换按钮样式 */
- #theme-toggle {
- position: fixed;
- bottom: 50px;
- right: 20px;
- background-color: #007bff;
- color: white;
- border: none;
- border-radius: 50%;
- width: 40px;
- height: 40px;
- text-align: center;
- font-size: 24px;
- line-height: 40px;
- cursor: pointer;
- }
-
- /* 显示日志按钮样式 */
- #view-logs-btn {
- position: fixed;
- top: 100px;
- right: 10px;
- z-index: 1000;
- }
-
- /* 对话框样式 */
- #dialog-overlay {
- display: none;
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.5);
- justify-content: center;
- align-items: center;
- }
-
- #dialog-box {
- background-color: white;
- padding: 20px;
- border-radius: 5px;
- width: 300px;
- }
-
- #dialog-box input, #dialog-box select {
- width: 100%;
- margin-bottom: 10px;
- padding: 5px;
- }
-
- /* 分类和卡片样式 */
- .section {
- margin-bottom: 20px;
- }
-
- .section-title-container {
- display: flex;
- align-items: center;
- margin-bottom: 10px;
- }
-
- .section-title {
- font-size: 18px;
- font-weight: bold;
- margin-left: 20px; /* 分类标签右移添加这一行 */
- color: green; /* 分类标签字体颜色添加这一行 */
- }
-
- .delete-category-btn {
- background-color: pink;
- color: white;
- border: none;
- padding: 5px 10px;
- border-radius: 5px;
- cursor: pointer;
- }
-
- .card-container {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- }
-
- .card {
- background-color: #a0c9e5;
- border-radius: 5px;
- padding: 10px;
- width: 132px;
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
- cursor: pointer;
- transition: transform 0.2s;
- position: relative;
- }
-
- /* 中等屏幕 (平板等) */
- @media (max-width: 1024px) {
-
- }
- /* 小屏幕 (手机) */
- @media (max-width: 768px) {
-
- }
-
- /* 超小屏幕 (更小手机) */
- @media (max-width: 480px) {
- .card {
- padding: 6px; /* 进一步减小卡片内边距6 */
- width: 235px;
- }
- }
-
- .card:hover {
- transform: translateY(-5px);
- }
-
- .card-top {
- display: flex;
- align-items: center;
- margin-bottom: 5px;
- }
-
- .card-icon {
- width: 20px;
- height: 20px;
- margin-right: 5px;
- }
-
- .card-title {
- font-size: 18px;
- font-weight: bold;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .card-url {
- font-size: 12px;
- color: #666;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .private-tag {
- background-color: #ff9800;
- color: white;
- font-size: 10px;
- padding: 2px 5px;
- border-radius: 3px;
- position: absolute;
- top: 5px;
- right: 5px;
- }
-
- .delete-btn {
- position: absolute;
- top: -10px;
- right: -10px;
- background-color: red;
- color: white;
- border: none;
- border-radius: 50%;
- width: 20px;
- height: 20px;
- text-align: center;
- font-size: 14px;
- line-height: 20px;
- cursor: pointer;
- display: none;
- }
-
- /* 版权信息样式 */
- #copyright {
- position: fixed;
- bottom: 0;
- left: 0;
- width: 100%;
- height: 40px;
- background-color: rgba(255, 255, 255, 0.8);
- display: flex;
- justify-content: center;
- align-items: center;
- font-size: 14px;
- z-index: 1000;
- box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
- }
-
- #copyright p {
- margin: 0;
- }
-
- #copyright a {
- color: #007bff;
- text-decoration: none;
- }
-
- #copyright a:hover {
- text-decoration: underline;
- }
-
- </style>
- </head>
- <body class="background">
-
- </div>
- <div class="sites"> <!-- 00 -->
- <header class="header">
- <nav class="navbar">
- <a href="https://www.199881.xyz/" class="brand">
- <img class="debug logo" src="https://cdn.glitch.global/efdace30-a873-49c7-aaa9-4fa31679ee0c/thumbnails%2F%E5%9B%BE%E6%A0%8701.jpg?1692046715299">
- <span class="debug title">WS01の主页</span>
- <span class="debug beta">beta</span>
- <!-- 一言模块 -->
- <p id="hitokoto">
- <a href="#" id="hitokoto_text"></a>
- </p>
- <script src="https://v1.hitokoto.cn/?encode=js&select=%23hitokoto" defer></script>
- </a>
- </header>
-
- <!-- 搜索栏 -->
- <div class="search-container">
- <div class="search-bar">
- <input type="text" id="search-input" placeholder="">
- <button id="search-button">🔍</button>
- </div>
- <div class="search-engines">
- <button class="search-engine" data-engine="baidu">百度</button>
- <button class="search-engine" data-engine="bing">必应</button>
- <button class="search-engine" data-engine="toutiao">头条</button>
- <button class="search-engine" data-engine="sm">神马</button>
- <button class="search-engine" data-engine="bilibili">哔哩</button>
- <button class="search-engine" data-engine="github">github</button>
- <button class="search-engine" data-engine="google">谷歌</button>
- <button class="search-engine" data-engine="yandex">yandex</button>
- </div>
- </div>
-
- <div class="head">
- <!-- 添加/删除控制按钮 -->
- <div class="add-remove-controls">
- <button class="round-btn add-btn" onclick="showAddDialog()">+</button>
- <button class="round-btn remove-btn" onclick="toggleRemoveMode()">-</button>
- <button class="round-btn category-btn" onclick="addCategory()">C+</button>
- <button class="round-btn remove-category-btn" onclick="toggleRemoveCategory()">C-</button>
-
- </div>
- <div class="sites1"> <!-- 00 -->
- <!-- 分类和卡片容器 -->
- <div id="sections-container"></div>
- <!-- 主题切换按钮 -->
- <button id="theme-toggle" onclick="toggleTheme()">◑</button>
- <!-- 显示日志按钮 用于调试-->
- <!--<button id="view-logs-btn" onclick="viewLogs()">显示日志</button>-->
- <!-- 添加链接对话框 -->
- <div id="dialog-overlay">
- <div id="dialog-box">
- <label for="name-input">名称</label>
- <input type="text" id="name-input">
- <label for="url-input">地址</label>
- <input type="text" id="url-input">
- <label for="category-select">选择分类</label>
- <select id="category-select"></select>
- <div class="private-link-container">
- <label for="private-checkbox">私密链接</label>
- <input type="checkbox" id="private-checkbox">
- </div>
- <button onclick="addLink()">确定</button>
- <button onclick="hideAddDialog()">取消</button>
- </div>
- </div>
-
- <br />
- <!-- body 页脚 -->
- <div class="footer">
- <!-- 管理员控制面板 -->
- <input type="password" id="admin-password" placeholder="输入密码">
- <button id="admin-mode-btn" onclick="toggleAdminMode()">设 置</button>
- <button id="secret-garden-btn" onclick="toggleSecretGarden()">登 录</button>
-
- <br />
- <br /> <br />
- <!-- 版权信息 -->
-
- <div id="copyright" class="copyright">
- <!--请不要删除-->
- <p><a href="https://github.com/hmhm2022/Card-Tab" target="_blank">GitHub</a> 项目
- <!-- 开站时间开始 -->
- <span id="timeDate">载入天数...</span><span id="times">载入时分秒...</span> <script language="javascript">
- var now = new Date();
- function createtime(){
- var grt= new Date("09/05/2024 00:00:00");/*---这里是网站的启用时间--*/
- now.setTime(now.getTime()+250);
- days = (now - grt ) / 1000 / 60 / 60 / 24;
- dnum = Math.floor(days);
- hours = (now - grt ) / 1000 / 60 / 60 - (24 * dnum);
- hnum = Math.floor(hours);
- if(String(hnum).length ==1 ){hnum = "0" + hnum;}
- minutes = (now - grt ) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum);
- mnum = Math.floor(minutes);
- if(String(mnum).length ==1 ){mnum = "0" + mnum;}
- seconds = (now - grt ) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum);
- snum = Math.round(seconds);
- if(String(snum).length ==1 ){snum = "0" + snum;}
- document.getElementById("timeDate").innerHTML = "稳定运行"+dnum+"天";
- document.getElementById("times").innerHTML = hnum + "小时" + mnum + "分" + snum + "秒";
- }
- setInterval("createtime()",250);
- </script>
- <!-- 开站时间结束 -->
-
-
- </p>
- </div>
- </div>
-
- <script>
- // 搜索引擎配置
- const searchEngines = {
- baidu: "https://www.baidu.com/s?wd=",
- bing: "https://www.bing.com/search?q=",
- sm: "https://m.sm.cn/s?q=",
- toutiao: "https://so.toutiao.com/search?dvpf=pc&source=trending_card&keyword=",
- bilibili: "https://search.bilibili.com/all?keyword=",
- github: "https://github.com/search?q=",
- google: "https://www.google.com/search?q=",
- yandex: "https://www.yandex.com/search/?text="
- };
-
- let currentEngine = "baidu";
-
- // 日志记录函数
- function logAction(action, details) {
- const timestamp = new Date().toISOString();
- const logEntry = timestamp + ': ' + action + ' - ' + JSON.stringify(details);
-
- let logs = JSON.parse(localStorage.getItem('cardTabLogs') || '[]');
- logs.push(logEntry);
-
- // 保留最新的1000条日志
- if (logs.length > 1000) {
- logs = logs.slice(-1000);
- }
-
- localStorage.setItem('cardTabLogs', JSON.stringify(logs));
- console.log(logEntry); // 同时在控制台输出日志
- }
-
- // 查看日志的函数
- function viewLogs() {
- const logs = JSON.parse(localStorage.getItem('cardTabLogs') || '[]');
- console.log('Card Tab Logs:');
- logs.forEach(log => console.log(log));
- alert('日志已在控制台输出,请按F12打开开发者工具查看。');
- }
-
- // 设置当前搜索引擎
- function setActiveEngine(engine) {
- currentEngine = engine;
- document.querySelectorAll('.search-engine').forEach(btn => {
- btn.style.backgroundColor = btn.dataset.engine === engine ? '#c0c0c0' : '#f0f0f0';
- });
- logAction('设置搜索引擎', { engine });
- }
-
- // 搜索引擎按钮点击事件
- document.querySelectorAll('.search-engine').forEach(button => {
- button.addEventListener('click', () => setActiveEngine(button.dataset.engine));
- });
-
- // 搜索按钮点击事件
- document.getElementById('search-button').addEventListener('click', () => {
- const query = document.getElementById('search-input').value;
- if (query) {
- logAction('执行搜索', { engine: currentEngine, query });
- window.open(searchEngines[currentEngine] + encodeURIComponent(query), '_blank');
- }
- });
-
- // 搜索输入框回车事件
- document.getElementById('search-input').addEventListener('keypress', (e) => {
- if (e.key === 'Enter') {
- document.getElementById('search-button').click();
- }
- });
-
- // 初始化搜索引擎
- setActiveEngine(currentEngine);
-
- // 全局变量
- let publicLinks = [];
- let privateLinks = [];
- let isAdmin = false;
- let isLoggedIn = false;
- let removeMode = false;
- let isRemoveCategoryMode = false;
- let isDarkTheme = false;
- let links = [];
- const categories = {};
-
- // 添加新分类
- function addCategory() {
- const categoryName = prompt('请输入新分类名称:');
- if (categoryName && !categories[categoryName]) {
- categories[categoryName] = [];
- updateCategorySelect();
- renderCategories();
- saveLinks();
- logAction('添加分类', { categoryName, currentLinkCount: links.length });
- } else if (categories[categoryName]) {
- alert('该分类已存在');
- logAction('添加分类失败', { categoryName, reason: '分类已存在' });
- }
- }
-
- // 删除分类
- function deleteCategory(category) {
- if (confirm('确定要删除 "' + category + '" 分类吗?这将删除该分类下的所有链接。')) {
- delete categories[category];
- links = links.filter(link => link.category !== category);
- publicLinks = publicLinks.filter(link => link.category !== category);
- privateLinks = privateLinks.filter(link => link.category !== category);
- updateCategorySelect();
- saveLinks();
- renderCategories();
- logAction('删除分类', { category });
- }
- }
-
- // 渲染分类(不重新加载链接)
- function renderCategories() {
- const container = document.getElementById('sections-container');
- container.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const section = document.createElement('div');
- section.className = 'section';
-
- const titleContainer = document.createElement('div');
- titleContainer.className = 'section-title-container';
-
- const title = document.createElement('div');
- title.className = 'section-title';
- title.textContent = category;
-
- titleContainer.appendChild(title);
-
- if (isAdmin) {
- const deleteBtn = document.createElement('button');
- deleteBtn.textContent = '删除分类';
- deleteBtn.className = 'delete-category-btn';
- deleteBtn.style.display = isRemoveCategoryMode ? 'inline-block' : 'none';
- deleteBtn.onclick = () => deleteCategory(category);
- titleContainer.appendChild(deleteBtn);
- }
-
- const cardContainer = document.createElement('div');
- cardContainer.className = 'card-container';
- cardContainer.id = category;
-
- section.appendChild(titleContainer);
- section.appendChild(cardContainer);
-
- container.appendChild(section);
-
- // 只渲染属于当前分类的链接
- const categoryLinks = links.filter(link => link.category === category);
- categoryLinks.forEach(link => {
- createCard(link, cardContainer);
- });
- });
-
- logAction('渲染分类', { categoryCount: Object.keys(categories).length, linkCount: links.length });
- }
-
- // 读取链接数据
- async function loadLinks() {
- const response = await fetch('/api/getLinks?userId=testUser');
- const data = await response.json();
- if (data.categories) {
- Object.assign(categories, data.categories);
- }
-
- publicLinks = data.links ? data.links.filter(link => !link.isPrivate) : [];
- privateLinks = data.links ? data.links.filter(link => link.isPrivate) : [];
- links = isLoggedIn ? [...publicLinks, ...privateLinks] : publicLinks;
-
- loadSections();
- updateCategorySelect();
- updateUIState();
- logAction('读取链接', { publicCount: publicLinks.length, privateCount: privateLinks.length });
- }
-
-
- // 更新UI状态
- function updateUIState() {
- const passwordInput = document.getElementById('admin-password');
- const adminBtn = document.getElementById('admin-mode-btn');
- const secretGardenBtn = document.getElementById('secret-garden-btn');
- const addRemoveControls = document.querySelector('.add-remove-controls');
-
- passwordInput.style.display = isLoggedIn ? 'none' : 'inline-block';
- secretGardenBtn.textContent = isLoggedIn ? "退出" : "登录";
- secretGardenBtn.style.display = 'inline-block';
-
- if (isAdmin) {
- adminBtn.textContent = "离开设置";
- adminBtn.style.display = 'inline-block';
- addRemoveControls.style.display = 'flex';
- } else if (isLoggedIn) {
- adminBtn.textContent = "设置";
- adminBtn.style.display = 'inline-block';
- addRemoveControls.style.display = 'none';
- } else {
- adminBtn.style.display = 'none';
- addRemoveControls.style.display = 'none';
- }
-
- logAction('更新UI状态', { isAdmin, isLoggedIn });
- }
-
- // 登录状态显示(加载所有链接)
- function showSecretGarden() {
- if (isLoggedIn) {
- links = [...publicLinks, ...privateLinks];
- loadSections();
- // 显示所有私密标签
- document.querySelectorAll('.private-tag').forEach(tag => {
- tag.style.display = 'block';
- });
- logAction('显示私密花园');
- }
- }
-
- // 加载分类和链接
- function loadSections() {
- const container = document.getElementById('sections-container');
- container.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const section = document.createElement('div');
- section.className = 'section';
-
- const titleContainer = document.createElement('div');
- titleContainer.className = 'section-title-container';
-
- const title = document.createElement('div');
- title.className = 'section-title';
- title.textContent = category;
-
- titleContainer.appendChild(title);
-
- if (isAdmin) {
- const deleteBtn = document.createElement('button');
- deleteBtn.textContent = '删除分类';
- deleteBtn.className = 'delete-category-btn';
- deleteBtn.style.display = 'none';
- deleteBtn.onclick = () => deleteCategory(category);
- titleContainer.appendChild(deleteBtn);
- }
-
- const cardContainer = document.createElement('div');
- cardContainer.className = 'card-container';
- cardContainer.id = category;
-
- section.appendChild(titleContainer);
- section.appendChild(cardContainer);
-
- let privateCount = 0;
- let linkCount = 0;
-
- links.forEach(link => {
- if (link.category === category) {
- if (link.isPrivate) privateCount++;
- linkCount++;
- createCard(link, cardContainer);
- }
- });
-
- if (privateCount < linkCount || isLoggedIn) {
- container.appendChild(section);
- }
- });
-
- logAction('加载分类和链接', { isAdmin: isAdmin, linkCount: links.length, categoryCount: Object.keys(categories).length });
- }
-
- // 创建卡片
- function createCard(link, container) {
- const card = document.createElement('div');
- card.className = 'card';
- card.setAttribute('draggable', isAdmin);
- card.dataset.isPrivate = link.isPrivate;
-
- const cardTop = document.createElement('div');
- cardTop.className = 'card-top';
-
- const icon = document.createElement('img');
- icon.className = 'card-icon';
- icon.src = 'https://favicon.zhusl.com/ico?url=' + link.url;
- icon.alt = 'Website Icon';
-
- const title = document.createElement('div');
- title.className = 'card-title';
- title.textContent = link.name;
-
- cardTop.appendChild(icon);
- cardTop.appendChild(title);
-
- const url = document.createElement('div');
- url.className = 'card-url';
- url.textContent = link.url;
-
- card.appendChild(cardTop);
- card.appendChild(url);
-
- if (link.isPrivate) {
- const privateTag = document.createElement('div');
- privateTag.className = 'private-tag';
- privateTag.textContent = '私密';
- card.appendChild(privateTag);
- }
-
- const correctedUrl = link.url.startsWith('http://') || link.url.startsWith('https://') ? link.url : 'http://' + link.url;
-
- if (!isAdmin) {
- card.addEventListener('click', () => {
- window.open(correctedUrl, '_blank');
- logAction('打开链接', { name: link.name, url: correctedUrl });
- });
- }
-
- const deleteBtn = document.createElement('button');
- deleteBtn.textContent = '–';
- deleteBtn.className = 'delete-btn';
- deleteBtn.onclick = function (event) {
- event.stopPropagation();
- removeCard(card);
- };
- card.appendChild(deleteBtn);
-
- updateCardStyle(card);
-
- card.addEventListener('dragstart', dragStart);
- card.addEventListener('dragover', dragOver);
- card.addEventListener('dragend', dragEnd);
- card.addEventListener('drop', drop);
-
- if (isAdmin && removeMode) {
- deleteBtn.style.display = 'block';
- }
-
- if (isAdmin || (link.isPrivate && isLoggedIn) || !link.isPrivate) {
- container.appendChild(card);
- }
- // logAction('创建卡片', { name: link.name, isPrivate: link.isPrivate });
- }
-
- // 更新卡片样式
- function updateCardStyle(card) {
- if (isDarkTheme) {
- card.style.backgroundColor = '#1e1e1e';
- card.style.color = '#ffffff';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
- } else {
- card.style.backgroundColor = '#a0c9e5';
- card.style.color = '#333';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
- }
- }
-
- // 更新分类选择下拉框
- function updateCategorySelect() {
- const categorySelect = document.getElementById('category-select');
- categorySelect.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const option = document.createElement('option');
- option.value = category;
- option.textContent = category;
- categorySelect.appendChild(option);
- });
-
- logAction('更新分类选择', { categoryCount: Object.keys(categories).length });
- }
-
- // 保存链接数据
- async function saveLinks() {
- let allLinks = [...publicLinks, ...privateLinks];
-
- try {
- await fetch('/api/saveOrder', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- userId: 'testUser',
- links: allLinks,
- categories: categories
- }),
- });
- logAction('保存链接', { linkCount: allLinks.length, categoryCount: Object.keys(categories).length });
- } catch (error) {
- console.error('Error saving links:', error);
- logAction('保存链接失败', { error: error.message });
- alert('保存链接失败,请重试');
- }
- }
-
- // 添加卡片弹窗
- function addLink() {
- const name = document.getElementById('name-input').value;
- const url = document.getElementById('url-input').value;
- const category = document.getElementById('category-select').value;
- const isPrivate = document.getElementById('private-checkbox').checked;
-
- if (name && url && category) {
- const newLink = { name, url, category, isPrivate };
-
- if (isPrivate) {
- privateLinks.push(newLink);
- } else {
- publicLinks.push(newLink);
- }
-
- links = isLoggedIn ? [...publicLinks, ...privateLinks] : publicLinks;
-
- if (isAdmin || (isPrivate && isLoggedIn) || !isPrivate) {
- const container = document.getElementById(category);
- if (container) {
- createCard(newLink, container);
- } else {
- categories[category] = [];
- renderCategories();
- }
- }
-
- saveLinks();
-
- document.getElementById('name-input').value = '';
- document.getElementById('url-input').value = '';
- document.getElementById('private-checkbox').checked = false;
- hideAddDialog();
-
- logAction('添加卡片', { name, url, category, isPrivate });
- }
- }
-
- // 删除卡片
- function removeCard(card) {
- const name = card.querySelector('.card-title').textContent;
- const url = card.querySelector('.card-url').textContent;
- const isPrivate = card.dataset.isPrivate === 'true';
-
- links = links.filter(link => link.url !== url);
- if (isPrivate) {
- privateLinks = privateLinks.filter(link => link.url !== url);
- } else {
- publicLinks = publicLinks.filter(link => link.url !== url);
- }
-
- for (const key in categories) {
- categories[key] = categories[key].filter(link => link.url !== url);
- }
-
- card.remove();
-
- saveLinks();
-
- logAction('删除卡片', { name, url, isPrivate });
- }
-
- // 拖拽卡片
- let draggedCard = null;
-
- function dragStart(event) {
- if (!isAdmin) return;
- draggedCard = event.target;
- draggedCard.classList.add('dragging');
- event.dataTransfer.effectAllowed = "move";
- logAction('开始拖拽卡片', { name: draggedCard.querySelector('.card-title').textContent });
- }
-
- function dragOver(event) {
- if (!isAdmin) return;
- event.preventDefault();
- const target = event.target.closest('.card');
- if (target && target !== draggedCard) {
- const container = target.parentElement;
- const mousePositionX = event.clientX;
- const targetRect = target.getBoundingClientRect();
-
- if (mousePositionX < targetRect.left + targetRect.width /2) {
- container.insertBefore(draggedCard, target);
- } else {
- container.insertBefore(draggedCard, target.nextSibling);
- }
- }
- //logAction('拖拽卡片中', { over: target ? target.querySelector('.card-title').textContent : 'unknown' });
- }
-
- function drop(event) {
- if (!isAdmin) return;
- event.preventDefault();
- draggedCard.classList.remove('dragging');
- const targetCategory = event.target.closest('.card-container').id;
- // 更新卡片的分类信息
- const cardTitle = draggedCard.querySelector('.card-title').textContent;
- const cardUrl = draggedCard.querySelector('.card-url').textContent;
- const isPrivate = draggedCard.dataset.isPrivate === 'true';
- // 在 links 数组中更新卡片信息
- const linkIndex = links.findIndex(link => link.url === cardUrl);
- if (linkIndex !== -1) {
- links[linkIndex].category = targetCategory;
- }
-
- // 在 publicLinks 或 privateLinks 中更新卡片信息
- const linkArray = isPrivate ? privateLinks : publicLinks;
- const arrayIndex = linkArray.findIndex(link => link.url === cardUrl);
- if (arrayIndex !== -1) {
- linkArraycategory = targetCategory;
- }
-
- draggedCard.dataset.category = targetCategory;
- logAction('放下卡片', { name: cardTitle, category: targetCategory });
- draggedCard = null;
- saveCardOrder();
- }
-
- function dragEnd(event) {
- if (draggedCard) {
- draggedCard.classList.remove('dragging');
- logAction('拖拽卡片结束');
- }
- }
-
- // 保存卡片顺序
- async function saveCardOrder() {
- if (!isAdmin) return;
- const containers = document.querySelectorAll('.card-container');
- let newPublicLinks = [];
- let newPrivateLinks = [];
- let newCategories = {};
-
- containers.forEach(container => {
- const category = container.id;
- newCategories[category] = [];
-
- [...container.children].forEach(card => {
- const url = card.querySelector('.card-url').textContent;
- const name = card.querySelector('.card-title').textContent;
- const isPrivate = card.dataset.isPrivate === 'true';
- card.dataset.category = category;
- const link = { name, url, category, isPrivate };
- if (isPrivate) {
- newPrivateLinks.push(link);
- } else {
- newPublicLinks.push(link);
- }
- newCategories[category].push(link);
- });
- });
-
- publicLinks.length = 0;
- publicLinks.push(...newPublicLinks);
- privateLinks.length = 0;
- privateLinks.push(...newPrivateLinks);
- Object.keys(categories).forEach(key => delete categories[key]);
- Object.assign(categories, newCategories);
-
- logAction('保存卡片顺序', {
- publicCount: newPublicLinks.length,
- privateCount: newPrivateLinks.length,
- categoryCount: Object.keys(newCategories).length
- });
-
- try {
- const response = await fetch('/api/saveOrder', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- userId: 'testUser',
- links: [...newPublicLinks, ...newPrivateLinks],
- categories: newCategories
- }),
- });
- const result = await response.json();
- if (!result.success) {
- throw new Error('Failed to save order');
- }
- logAction('保存卡片顺序', { publicCount: newPublicLinks.length, privateCount: newPrivateLinks.length, categoryCount: Object.keys(newCategories).length });
- } catch (error) {
- console.error('Error saving order:', error);
- logAction('保存顺序失败', { error: error.message });
- alert('保存顺序失败,请重试');
- }
- }
-
- // 设置状态重新加载卡片
- function reloadCardsAsAdmin() {
- document.querySelectorAll('.card-container').forEach(container => {
- container.innerHTML = '';
- });
- loadLinks().then(() => {
- if (isDarkTheme) {
- applyDarkTheme();
- }
- });
- logAction('重新加载卡片(管理员模式)');
- }
-
- // 密码输入框回车事件
- document.getElementById('admin-password').addEventListener('keypress', (e) => {
- if (e.key === 'Enter') {
- toggleSecretGarden();
- }
- });
-
- // 切换设置状态
- function toggleAdminMode() {
- const adminBtn = document.getElementById('admin-mode-btn');
- const addRemoveControls = document.querySelector('.add-remove-controls');
-
- if (!isAdmin && isLoggedIn) {
- isAdmin = true;
- adminBtn.textContent = "退出设置";
- addRemoveControls.style.display = 'flex';
- alert('准备设置分类和书签');
- reloadCardsAsAdmin();
- logAction('进入设置');
- } else if (isAdmin) {
- isAdmin = false;
- removeMode = false;
- adminBtn.textContent = "设 置";
- addRemoveControls.style.display = 'none';
- alert('设置已保存');
- reloadCardsAsAdmin();
- logAction('离开');
- }
-
- updateUIState();
- }
-
- // 切换到登录状态
- function toggleSecretGarden() {
- const passwordInput = document.getElementById('admin-password');
- if (!isLoggedIn) {
- verifyPassword(passwordInput.value).then(isValid => {
- if (isValid) {
- isLoggedIn = true;
- links = [...publicLinks, ...privateLinks];
- loadSections();
- alert('登录成功!');
- logAction('登录成功');
- } else {
- alert('密码错误');
- logAction('登录失败', { reason: '密码错误' });
- }
- updateUIState();
- });
- } else {
- isLoggedIn = false;
- isAdmin = false;
- links = publicLinks;
- loadSections();
- alert('退出登录!');
- updateUIState();
- passwordInput.value = '';
- logAction('退出登录');
- }
- }
-
- // 应用暗色主题
- function applyDarkTheme() {
- document.body.style.backgroundColor = '#121212';
- document.body.style.color = '#ffffff';
- const cards = document.querySelectorAll('.card');
- cards.forEach(card => {
- card.style.backgroundColor = '#1e1e1e';
- card.style.color = '#ffffff';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
- });
- logAction('应用暗色主题');
- }
-
- // 显示添加链接对话框
- function showAddDialog() {
- document.getElementById('dialog-overlay').style.display = 'flex';
- logAction('显示添加链接对话框');
- }
-
- // 隐藏添加链接对话框
- function hideAddDialog() {
- document.getElementById('dialog-overlay').style.display = 'none';
- logAction('隐藏添加链接对话框');
- }
-
- // 切换删除卡片模式
- function toggleRemoveMode() {
- removeMode = !removeMode;
- const deleteButtons = document.querySelectorAll('.delete-btn');
- deleteButtons.forEach(btn => {
- btn.style.display = removeMode ? 'block' : 'none';
- });
- logAction('切换删除卡片模式', { removeMode });
- }
-
- //切换删除分类模式
- function toggleRemoveCategory() {
- isRemoveCategoryMode = !isRemoveCategoryMode;
- const deleteButtons = document.querySelectorAll('.delete-category-btn');
- deleteButtons.forEach(btn => {
- btn.style.display = isRemoveCategoryMode ? 'inline-block' : 'none';
- });
- logAction('切换删除分类模式', { isRemoveCategoryMode });
- }
-
- // 切换主题
- function toggleTheme() {
- isDarkTheme = !isDarkTheme;
-
- document.body.style.backgroundColor = isDarkTheme ? '#121212' : '#ffffff';
- document.body.style.color = isDarkTheme ? '#ffffff' : '#333';
-
- const cards = document.querySelectorAll('.card');
- cards.forEach(card => {
- card.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#a0c9e5';
- card.style.color = isDarkTheme ? '#ffffff' : '#333';
- card.style.boxShadow = isDarkTheme
- ? '0 4px 8px rgba(0, 0, 0, 0.5)'
- : '0 4px 8px rgba(0, 0, 0, 0.1)';
- });
-
- const fixedElements = document.querySelectorAll('.fixed-elements');
- fixedElements.forEach(element => {
- element.style.backgroundColor = isDarkTheme ? '#121212' : '#ffffff';
- element.style.color = isDarkTheme ? '#ffffff' : '#333';
- });
-
- const dialogBox = document.getElementById('dialog-box');
- dialogBox.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#ffffff';
- dialogBox.style.color = isDarkTheme ? '#ffffff' : '#333';
-
- const inputs = document.querySelectorAll('input[type="text"], input[type="password"], select');
- inputs.forEach(input => {
- input.style.backgroundColor = isDarkTheme ? '#444' : '#fff';
- input.style.color = isDarkTheme ? '#fff' : '#333';
- input.style.borderColor = isDarkTheme ? '#555' : '#ccc';
- });
-
- logAction('切换主题', { isDarkTheme });
- }
-
- // 验证密码
- async function verifyPassword(inputPassword) {
- const response = await fetch('/api/verifyPassword', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ password: inputPassword }),
- });
- const result = await response.json();
- return result.valid;
- }
-
- // 初始化加载链接
- loadLinks();
-
- </script>
- </body>
-
- </html>
- `;
-
- export default {
- async fetch(request, env) {
- const url = new URL(request.url);
-
- if (url.pathname === '/') {
- return new Response(HTML_CONTENT, {
- headers: { 'Content-Type': 'text/html' }
- });
- }
-
- if (url.pathname === '/api/getLinks') {
- const userId = url.searchParams.get('userId');
- const links = await env.CARD_ORDER.get(userId);
- return new Response(links || JSON.stringify([]), { status: 200 });
- }
-
- if (url.pathname === '/api/saveOrder' && request.method === 'POST') {
- const { userId, links, categories } = await request.json();
- await env.CARD_ORDER.put(userId, JSON.stringify({ links, categories })); //保存链接和分类
- return new Response(JSON.stringify({ success: true }), { status: 200 });
- }
-
- if (url.pathname === '/api/verifyPassword' && request.method === 'POST') {
- const { password } = await request.json();
- const isValid = password === env.ADMIN_PASSWORD; // 从环境变量中获取密码
- return new Response(JSON.stringify({ valid: isValid }), {
- status: isValid ? 200 : 403,
- headers: { 'Content-Type': 'application/json' },
- });
- }
-
- return new Response('Not Found', { status: 404 });
- }
- };
旧版本
1、原workes, 效果
- const HTML_CONTENT = `<!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Card Tab</title>
- <style>
- body {
- font-family: Arial, sans-serif;
- // background-color: #f4f4f4;
- background-color: #d8eac4;
- margin: 0;
- padding: 20px;
- display: flex;
- flex-direction: column;
- align-items: center;
- transition: background-color 0.3s ease;
- }
- .card-container {
- display: grid;
- grid-template-columns: repeat(6, 1fr);
- gap: 10px;
- }
- .card {
- display: flex;
- flex-direction: column;
- position: relative;
- background-color: #a0c9e5;
- padding: 10px;
- border-radius: 10px;
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
- cursor: grab;
- transition: transform 0.2s ease, box-shadow 0.2s ease;
- width: 200px;
- height: auto;
- }
-
- .card-top {
- display: flex;
- align-items: center;
- margin-bottom: 5px;
- }
-
- .card-icon {
- width: 24px;
- height: 24px;
- margin-right: 10px;
- }
-
- .card-title {
- font-size: 16px;
- font-weight: bold;
- }
-
- .card-url {
- color: #555;
- font-size: 12px;
- word-break: break-all;
- }
-
- .card.dragging {
- opacity: 0.8;
- transform: scale(1.05);
- cursor: grabbing;
- }
- .card:hover {
- transform: translateY(-5px);
- box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
- }
- .delete-btn {
- position: absolute;
- top: -10px;
- right: -10px;
- background-color: red;
- color: white;
- border: none;
- border-radius: 50%;
- width: 20px;
- height: 20px;
- text-align: center;
- font-size: 14px;
- line-height: 20px;
- cursor: pointer;
- display: none;
- }
- .admin-controls {
- position: fixed;
- top: 10px;
- right: 10px;
- font-size: 60%;
- }
- .admin-controls input {
- padding: 5px;
- font-size: 60%;
- }
- .admin-controls button {
- padding: 5px 10px;
- font-size: 60%;
- margin-left: 10px;
- }
- .add-remove-controls {
- display: none;
- margin-top: 10px;
- }
- .round-btn {
- background-color: #007bff;
- color: white;
- border: none;
- border-radius: 50%;
- width: 40px;
- height: 40px;
- text-align: center;
- font-size: 24px;
- line-height: 40px;
- cursor: pointer;
- margin: 0 10px;
- }
- #theme-toggle {
- position: fixed;
- bottom: 10px;
- left: 10px;
- background-color: #007bff;
- color: white;
- border: none;
- border-radius: 50%;
- width: 40px;
- height: 40px;
- text-align: center;
- font-size: 24px;
- line-height: 40px;
- cursor: pointer;
- }
- #dialog-overlay {
- display: none;
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.5);
- justify-content: center;
- align-items: center;
- }
- #dialog-box {
- background: white;
- padding: 20px;
- border-radius: 10px;
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
- }
- #dialog-box label {
- display: block;
- margin-bottom: 5px;
- }
- #dialog-box input, #dialog-box select {
- width: 100%;
- padding: 5px;
- margin-bottom: 10px;
- }
- #dialog-box button {
- padding: 5px 10px;
- margin-right: 10px;
- }
-
- .section {
- margin-bottom: 20px;
- }
-
- .section-title {
- font-size: 24px;
- font-weight: bold;
- color: #333;
- margin-bottom: 10px;
- }
- </style>
- </head>
- <body>
- <h1>我的导航</h1>
-
- <div class="admin-controls">
- <input type="password" id="admin-password" placeholder="输入密码">
- <button id="admin-mode-btn" onclick="toggleAdminMode()">进入管理模式</button>
- </div>
-
- <div class="add-remove-controls">
- <button class="round-btn" onclick="showAddDialog()">+</button>
- <button class="round-btn" onclick="toggleRemoveMode()">-</button>
- </div>
-
- <div id="sections-container">
- <!-- 分类将在这里动态生成 -->
- </div>
-
- <button id="theme-toggle" onclick="toggleTheme()">◑</button>
-
- <div id="dialog-overlay">
- <div id="dialog-box">
- <label for="name-input">名称</label>
- <input type="text" id="name-input">
- <label for="url-input">地址</label>
- <input type="text" id="url-input">
- <label for="category-select">选择分类</label>
- <select id="category-select">
- <!-- 分类选项将在这里动态生成 -->
- </select>
- <button onclick="addLink()">确定</button>
- <button onclick="hideAddDialog()">取消</button>
- </div>
- </div>
- <div class="copyright">
- <!-- 请不要删除 -->
- <p> 项目地址: <a href="https://github.com/hmhm2022/Card-Tab" target="_blank">GitHub</a> 烦请点个star!
- </div>
-
- <script>
- let isAdmin = false;
- let removeMode = false;
- let isDarkTheme = false;
- let links = [];
- const categories = {
- "常用网站": [], // **编辑自己的网站分类**
- "工具导航": [],
- "游戏娱乐": [],
- "影音视听": [],
- "技术论坛": []
- };
-
- async function loadLinks() {
- const response = await fetch('/api/getLinks?userId=testUser');
- links = await response.json();
-
- Object.keys(categories).forEach(key => {
- categories[key] = [];
- });
-
- links.forEach(link => {
- if (categories[link.category]) {
- categories[link.category].push(link);
- }
- });
-
- loadSections();
- updateCategorySelect();
- // applyTheme();
- }
-
- function loadSections() {
- const container = document.getElementById('sections-container');
- container.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const section = document.createElement('div');
- section.className = 'section';
-
- const title = document.createElement('div');
- title.className = 'section-title';
- title.textContent = category;
-
- const cardContainer = document.createElement('div');
- cardContainer.className = 'card-container';
- cardContainer.id = category;
-
- section.appendChild(title);
- section.appendChild(cardContainer);
-
- categories[category].forEach(link => {
- createCard(link, cardContainer);
- });
-
- container.appendChild(section);
- });
- }
-
- function createCard(link, container) {
- const card = document.createElement('div');
- card.className = 'card';
- card.setAttribute('draggable', isAdmin);
-
- const cardTop = document.createElement('div');
- cardTop.className = 'card-top';
-
- const icon = document.createElement('img');
- icon.className = 'card-icon';
- // icon.src = 'https://www.google.com/s2/favicons?domain=' + link.url;
- icon.src = 'https://favicon.zhusl.com/ico?url=' + link.url;
- icon.alt = 'Website Icon';
-
- const title = document.createElement('div');
- title.className = 'card-title';
- title.textContent = link.name;
-
- cardTop.appendChild(icon);
- cardTop.appendChild(title);
-
- const url = document.createElement('div');
- url.className = 'card-url';
- url.textContent = link.url;
-
- card.appendChild(cardTop);
- card.appendChild(url);
-
- // URL 检查和修正
- function correctUrl(url) {
- if (url.startsWith('http://') || url.startsWith('https://')) {
- return url;
- } else {
- return 'http://' + url;
- }
- }
-
- let correctedUrl = correctUrl(link.url);
-
- if (!isAdmin) {
- card.addEventListener('click', () => {
- window.open(correctedUrl, '_blank');
- });
- }
-
- const deleteBtn = document.createElement('button');
- deleteBtn.textContent = '–';
- deleteBtn.className = 'delete-btn';
- deleteBtn.onclick = function (event) {
- event.stopPropagation();
- removeCard(card);
- };
- card.appendChild(deleteBtn);
-
- if (isDarkTheme) {
- card.style.backgroundColor = '#1e1e1e';
- card.style.color = '#ffffff';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
- } else {
- card.style.backgroundColor = '#a0c9e5';
- card.style.color = '#333';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
- }
-
- card.addEventListener('dragstart', dragStart);
- card.addEventListener('dragover', dragOver);
- card.addEventListener('dragend', dragEnd);
- card.addEventListener('drop', drop);
-
- if (isAdmin && removeMode) {
- deleteBtn.style.display = 'block';
- }
-
- container.appendChild(card);
- }
-
- function updateCategorySelect() {
- const categorySelect = document.getElementById('category-select');
- categorySelect.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const option = document.createElement('option');
- option.value = category;
- option.textContent = category;
- categorySelect.appendChild(option);
- });
- }
-
- async function saveLinks() {
- let links = [];
- for (const category in categories) {
- links = links.concat(categories[category]);
- }
-
- await fetch('/api/saveOrder', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ userId: 'testUser', links }),
- });
- }
-
- function addLink() {
- const name = document.getElementById('name-input').value;
- const url = document.getElementById('url-input').value;
- const category = document.getElementById('category-select').value;
-
- if (name && url && category) {
- const newLink = { name, url, category };
-
- if (!categories[category]) {
- categories[category] = [];
- }
- categories[category].push(newLink);
-
- const container = document.getElementById(category);
- createCard(newLink, container);
-
- saveLinks();
-
- document.getElementById('name-input').value = '';
- document.getElementById('url-input').value = '';
- hideAddDialog();
- }
- }
-
- function removeCard(card) {
- const url = card.querySelector('.card-url').textContent;
- let category;
- for (const key in categories) {
- const index = categories[key].findIndex(link => link.url === url);
- if (index !== -1) {
- categories[key].splice(index, 1);
- category = key;
- break;
- }
- }
- card.remove();
-
- saveLinks();
- }
-
- let draggedCard = null;
-
- function dragStart(event) {
- if (!isAdmin) return;
- draggedCard = event.target;
- draggedCard.classList.add('dragging');
- event.dataTransfer.effectAllowed = "move";
- }
-
- function dragOver(event) {
- if (!isAdmin) return;
- event.preventDefault();
- const target = event.target.closest('.card');
- if (target && target !== draggedCard) {
- const container = target.parentElement;
- const mousePositionX = event.clientX;
- const targetRect = target.getBoundingClientRect();
-
- if (mousePositionX < targetRect.left + targetRect.width / 2) {
- container.insertBefore(draggedCard, target);
- } else {
- container.insertBefore(draggedCard, target.nextSibling);
- }
- }
- }
-
- function drop(event) {
- if (!isAdmin) return;
- event.preventDefault();
- draggedCard.classList.remove('dragging');
- draggedCard = null;
- saveCardOrder();
- }
-
- // function dragEnd(event) {
- // draggedCard.classList.remove('dragging');
- function dragEnd(event) {
- if (draggedCard) {
- draggedCard.classList.remove('dragging');
- }
- }
-
- async function saveCardOrder() {
- if (!isAdmin) return;
- const containers = document.querySelectorAll('.card-container');
- let newLinks = [];
-
- containers.forEach(container => {
- const category = container.id;
- categories[category] = [];
- [...container.children].forEach(card => {
- const url = card.querySelector('.card-url').textContent;
- const name = card.querySelector('.card-title').textContent;
- const link = { name, url, category };
- categories[category].push(link);
- newLinks.push(link);
- });
- });
-
- links = newLinks;
-
- await fetch('/api/saveOrder', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ userId: 'testUser', links: newLinks }),
- });
- }
-
-
- function toggleAdminMode() {
- const passwordInput = document.getElementById('admin-password');
- const adminBtn = document.getElementById('admin-mode-btn');
- const addRemoveControls = document.querySelector('.add-remove-controls');
-
- if (!isAdmin) {
- verifyPassword(passwordInput.value)
- .then(isValid => {
- if (isValid) {
- isAdmin = true;
- adminBtn.textContent = "退出管理模式";
- alert('已进入管理模式');
- addRemoveControls.style.display = 'block';
- reloadCardsAsAdmin();
- } else {
- alert('密码错误');
- }
- });
- } else {
- isAdmin = false;
- removeMode = false;
- adminBtn.textContent = "进入管理模式";
- alert('已退出管理模式');
- addRemoveControls.style.display = 'none';
- const deleteButtons = document.querySelectorAll('.delete-btn');
- deleteButtons.forEach(btn => btn.style.display = 'none');
- reloadCardsAsAdmin();
- }
-
- passwordInput.value = '';
- }
-
- function reloadCardsAsAdmin() {
- document.querySelectorAll('.card-container').forEach(container => {
- container.innerHTML = '';
- });
- loadLinks().then(() => {
- if (isDarkTheme) {
- applyDarkTheme();
- }
- });
- }
-
- function applyDarkTheme() {
- document.body.style.backgroundColor = '#121212';
- document.body.style.color = '#ffffff';
- const cards = document.querySelectorAll('.card');
- cards.forEach(card => {
- card.style.backgroundColor = '#1e1e1e';
- card.style.color = '#ffffff';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
- });
- }
-
- function showAddDialog() {
- document.getElementById('dialog-overlay').style.display = 'flex';
- }
-
- function hideAddDialog() {
- document.getElementById('dialog-overlay').style.display = 'none';
- }
-
-
- function toggleRemoveMode() {
- removeMode = !removeMode;
- const deleteButtons = document.querySelectorAll('.delete-btn');
- deleteButtons.forEach(btn => {
- btn.style.display = removeMode ? 'block' : 'none';
- });
- }
-
- function toggleTheme() {
- isDarkTheme = !isDarkTheme;
- // 设置暗色主题和亮色主题的背景色
- document.body.style.backgroundColor = isDarkTheme ? '#121212' : '#d8eac4';
- // 设置暗色主题和亮色主题的文本颜色
- document.body.style.color = isDarkTheme ? '#ffffff' : '#333';
-
- const cards = document.querySelectorAll('.card');
- cards.forEach(card => {
- // 卡片背景和文本颜色设置
- card.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#a0c9e5';
- card.style.color = isDarkTheme ? '#ffffff' : '#333';
- // 卡片阴影的设置,增强暗色主题的阴影
- card.style.boxShadow = isDarkTheme
- ? '0 4px 8px rgba(0, 0, 0, 0.5)'
- : '0 4px 8px rgba(0, 0, 0, 0.1)';
- });
-
- const dialogBox = document.getElementById('dialog-box');
- // 对话框背景和文本颜色设置
- dialogBox.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#ffffff';
- dialogBox.style.color = isDarkTheme ? '#ffffff' : '#333';
-
- const inputs = dialogBox.querySelectorAll('input, select');
- inputs.forEach(input => {
- // 输入框背景和文本颜色设置
- input.style.backgroundColor = isDarkTheme ? '#333333' : '#ffffff';
- input.style.color = isDarkTheme ? '#ffffff' : '#333';
- });
- }
-
-
- async function verifyPassword(inputPassword) {
- const response = await fetch('/api/verifyPassword', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ password: inputPassword }),
- });
- const result = await response.json();
- return result.valid;
- }
-
- loadLinks();
- </script>
- </body>
- </html>
- `;
-
- export default {
- async fetch(request, env) {
- const url = new URL(request.url);
-
- if (url.pathname === '/') {
- return new Response(HTML_CONTENT, {
- headers: { 'Content-Type': 'text/html' }
- });
- }
-
- if (url.pathname === '/api/getLinks') {
- const userId = url.searchParams.get('userId');
- const links = await env.CARD_ORDER.get(userId);
- return new Response(links || JSON.stringify([]), { status: 200 });
- }
-
- if (url.pathname === '/api/saveOrder' && request.method === 'POST') {
- const { userId, links } = await request.json();
- await env.CARD_ORDER.put(userId, JSON.stringify(links));
- return new Response(JSON.stringify({ success: true }), { status: 200 });
- }
-
- if (url.pathname === '/api/verifyPassword' && request.method === 'POST') {
- const { password } = await request.json();
- const isValid = password === env.ADMIN_PASSWORD; // 从环境变量中获取密码
- return new Response(JSON.stringify({ valid: isValid }), {
- status: isValid ? 200 : 403,
- headers: { 'Content-Type': 'application/json' },
- });
- }
-
- return new Response('Not Found', { status: 404 });
- }
- };
2、修改后的workes, 效果
- const HTML_CONTENT = `<!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>WS01の主页</title>
- <style>
- body {
- font-family: Arial, sans-serif;
- // background-color: #f4f4f4;
- background-color: #d4d4d4;
- margin: 0;
- padding: 20px;
- display: flex;
- flex-direction: column;
- align-items: center;
- transition: background-color 0.3s ease;
- }
- ul {
- padding: 0;
- margin-block-start: 1em;
- margin-block-end: 1em;
- margin-inline-start: 0px;
- margin-inline-end: 0px;
- padding-inline-start: 40px;
- unicode-bidi: isolate;
- }
- li {
- display: list-item;
- text-align: -webkit-match-parent;
- unicode-bidi: isolate;
- margin: 0 8px;
- }
-
- .background {
- background-image: linear-gradient(#d4d4d4 1px, transparent 0), linear-gradient(90deg, #d4d4d4 1px, transparent 0);
- background-size: 32px 32px;
- background-color: #fffcf8;
- }
-
-
- .header {
- background-color: #fff;
- box-shadow: 0 0 5px rgba(0, 0, 0, .1);
- transition: background-color .5s;
- }
- .navbar {
- display: flex;
- width: 1280px;
- margin: auto;
- height: 40px;
- }
-
- .navbar .brand {
-
- display: flex;
- align-items: center;
- color: #555;
- }
-
- .brand .logo {
- max-width: 36px;
- }
-
- .brand .title {
- margin-left: 5px;
- font-family: helvetica neue, helvetica, arial, sans-serif;
- font-size: 24px;
- font-weight: 700;
- }
-
- .beta {
- color: #ccc;
- font-size: 11px;
- font-weight: 400;
- position: relative;
- top: -14px;
- }
-
- .category-list {
- list-style: none;
- display: flex;
- align-items: center;
- }
-
-
-
- .sites {
- width:1280px;
- background:;
- border:2px solid auto;
- margin:15px auto;
- padding:0px;
- text-align:center;
- }
- .sites1 {
- width:1280px;
- background:;
- border:2px solid auto;
- margin:15px auto;
- padding:0px;
- }
-
- .sites dl {
- height:36px;
- line-height:36px;
- display:block;
- margin:0;
- }
-
- .sites dl.alt {
- background:#d4d4d4;
- border-top:1px solid #ffffff;
- border-bottom:1px solid #ffffff;
- }
-
- .sites dl.alt2 {
- background:#d4d4d4;
- border-top:1px solid #ffffff;
- border-bottom:1px solid #ffffff;
- }
-
- .sites dt,.sites dd {
- text-align:center;
- display:block;
- float:left;
- }
-
- .sites dt {
- width:60px;
- }
-
- .sites dd {
- width:90px;
- margin:0;
- }
- .footer {
- width:580px;
- text-align:center;
- margin:5px auto;
- padding:2px;
- }
-
-
- .card-container {
- display: grid;
- grid-template-columns: repeat(8, 1fr);
- gap: 6px;
- width: 100%; /* 宽度设为100%,以适应不同设备 */
- }
- .card {
- display: flex;
- flex-direction: column;
- position: relative;
- background-color: #d4d4d4;
- padding: 10px;
- border-radius: 10px;
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
- cursor: grab;
- transition: transform 0.2s ease, box-shadow 0.2s ease;
- word-break: break-word; /* 防止超出边界 */
- }
- /* 中等屏幕 (平板等) */
- @media (max-width: 1024px) {
- .card-container {
- grid-template-columns: repeat(8, 1fr); /* 中等屏幕显示4列 */
- }
- }
-
- /* 小屏幕 (手机) */
- @media (max-width: 768px) {
- .card-container {
- grid-template-columns: repeat(6, 1fr); /* 小屏幕显示2列 */
- }
-
- .card {
- padding: 8px; /* 减小卡片内边距 */
- font-size: 0.9rem; /* 调整字体大小0.9 */
- }
-
- .admin-controls {
- top: 5px;
- right: 5px;
- }
-
- .round-btn, #theme-toggle {
- width: 30px;
- height: 30px;
- font-size: 18px;
- line-height: 30px;
- }
- }
-
- /* 超小屏幕 (更小手机) */
- @media (max-width: 480px) {
- .card-container {
- grid-template-columns: repeat(5, 1fr); /* 超小屏幕显示1列 */
- }
-
- .card {
- padding: 6px; /* 进一步减小卡片内边距5 */
- font-size: 0.8rem; /* 再次缩小字体0.8 */
- }
-
- .round-btn, #theme-toggle {
- width: 25px;
- height: 25px;
- font-size: 16px;
- line-height: 25px;
- }
- }
-
-
- .card-top {
- display: flex;
- align-items: center;
- margin-bottom: 5px;
- }
-
- .card-icon {
- width: 20px;
- height: 20px;
- margin-right: 8px;
- }
-
- .card-title {
- font-size: 16px;
- font-weight: bold;
- }
-
- .card-url {
- color: #555;
- font-size: 12px;
- word-break: break-all;
- }
-
- .card.dragging {
- opacity: 0.8;
- transform: scale(1.05);
- cursor: grabbing;
- }
- .card:hover {
- transform: translateY(-5px);
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
- }
- .delete-btn {
- position: absolute;
- top: -10px;
- right: -10px;
- background-color: red;
- color: white;
- border: none;
- border-radius: 50%;
- width: 20px;
- height: 20px;
- text-align: center;
- font-size: 14px;
- line-height: 20px;
- cursor: pointer;
- display: none;
- }
- .admin-controls {
- position: fixed;
- top: 10px;
- right: 10px;
- font-size: 60%;
- }
- .admin-controls input {
- padding: 5px;
- font-size: 60%;
- }
- .admin-controls button {
- padding: 5px 10px;
- font-size: 60%;
- margin-left: 10px;
- }
- .add-remove-controls {
- display: none;
- margin-top: 10px;
- }
- .round-btn {
- background-color: #007bff;
- color: white;
- border: none;
- border-radius: 50%;
- width: 40px;
- height: 40px;
- text-align: center;
- font-size: 24px;
- line-height: 40px;
- cursor: pointer;
- margin: 0 10px;
- }
- #theme-toggle {
- position: fixed;
- bottom: 10px;
- left: 10px;
- background-color: #007bff;
- color: white;
- border: none;
- border-radius: 50%;
- width: 40px;
- height: 40px;
- text-align: center;
- font-size: 24px;
- line-height: 40px;
- cursor: pointer;
- }
- #dialog-overlay {
- display: none;
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.5);
- justify-content: center;
- align-items: center;
- }
- #dialog-box {
- background: white;
- padding: 20px;
- border-radius: 10px;
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
- }
- #dialog-box label {
- display: block;
- margin-bottom: 5px;
- }
- #dialog-box input, #dialog-box select {
- width: 100%;
- padding: 5px;
- margin-bottom: 10px;
- }
- #dialog-box button {
- padding: 5px 10px;
- margin-right: 10px;
- }
-
- .section {
- margin-bottom: 20px;
- }
-
- .section-title {
- font-size: 24px;
- font-weight: bold;
- color: #333;
- margin-bottom: 10px;
- }
- </style>
-
- </head>
- <body class="background">
-
- </div>
- <div class="sites"> <!-- 00 -->
- <header class="header">
- <nav class="navbar">
- <a href="https://ddo.us.kg/" class="brand">
- <img class="debug logo" src="https://cdn.glitch.global/efdace30-a873-49c7-aaa9-4fa31679ee0c/thumbnails%2F%E5%9B%BE%E6%A0%8701.jpg?1692046715299">
- <span class="debug title">WS01の主页</span>
- <span class="debug beta">beta</span>
- </a>
-
- </header>
-
-
- </div>
- </div>
- <div class="add-remove-controls">
- <button class="round-btn" onclick="showAddDialog()">+</button>
- <button class="round-btn" onclick="toggleRemoveMode()">-</button>
- </div>
- <div class="sites1"> <!-- 00 -->
- <div id="sections-container">
- <!-- 分类将在这里动态生成 -->
- </div>
-
- <button id="theme-toggle" onclick="toggleTheme()">◑</button>
-
- <div id="dialog-overlay">
- <div id="dialog-box">
- <label for="name-input">名称</label>
- <input type="text" id="name-input">
- <label for="url-input">地址</label>
- <input type="text" id="url-input">
- <label for="category-select">选择分类</label>
- <select id="category-select">
- <!-- 分类选项将在这里动态生成 -->
- </select>
- <button onclick="addLink()">确定</button>
- <button onclick="hideAddDialog()">取消</button>
- </div>
- </div>
- <div class="copyright">
- <!-- 请不要删除 -->
- <br />
- <!-- body 页脚 -->
- <div class="footer">
-
- <input type="password" id="admin-password" placeholder="输入密码">
- <button id="admin-mode-btn" onclick="toggleAdminMode()">进入管理模式</button>
- <p> <a href="https://github.com/hmhm2022/Card-Tab" target="_blank">GitHub</a> 项目
-
- <!-- 开站时间开始 -->
- <span id="timeDate">载入天数...</span><span id="times">载入时分秒...</span> <script language="javascript">
- var now = new Date();
- function createtime(){
- var grt= new Date("09/05/2024 00:00:00");/*---这里是网站的启用时间--*/
- now.setTime(now.getTime()+250);
- days = (now - grt ) / 1000 / 60 / 60 / 24;
- dnum = Math.floor(days);
- hours = (now - grt ) / 1000 / 60 / 60 - (24 * dnum);
- hnum = Math.floor(hours);
- if(String(hnum).length ==1 ){hnum = "0" + hnum;}
- minutes = (now - grt ) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum);
- mnum = Math.floor(minutes);
- if(String(mnum).length ==1 ){mnum = "0" + mnum;}
- seconds = (now - grt ) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum);
- snum = Math.round(seconds);
- if(String(snum).length ==1 ){snum = "0" + snum;}
- document.getElementById("timeDate").innerHTML = "稳定运行"+dnum+"天";
- document.getElementById("times").innerHTML = hnum + "小时" + mnum + "分" + snum + "秒";
- }
- setInterval("createtime()",250);
- </script>
- <!-- 开站时间结束 -->
- </div>
-
- <script>
- let isAdmin = false;
- let removeMode = false;
- let isDarkTheme = false;
- let links = [];
- const categories = {
- "常·用": [], // **编辑自己的网站分类**
- "工·具": [],
- "影·音": [],
- "N·B·A": [],
- "论·坛": [],
- "主·页": [],
- "玩·具": [],
- "v·p·s": [],
- "下·载": [],
- "商·城": [],
- "搜·译": [],
- "学·习": [],
- "其·它": []
- };
-
- async function loadLinks() {
- const response = await fetch('/api/getLinks?userId=testUser');
- links = await response.json();
-
- Object.keys(categories).forEach(key => {
- categories[key] = [];
- });
-
- links.forEach(link => {
- if (categories[link.category]) {
- categories[link.category].push(link);
- }
- });
-
- loadSections();
- updateCategorySelect();
- // applyTheme();
- }
-
- function loadSections() {
- const container = document.getElementById('sections-container');
- container.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const section = document.createElement('div');
- section.className = 'section';
-
- const title = document.createElement('div');
- title.className = 'section-title';
- title.textContent = category;
-
- const cardContainer = document.createElement('div');
- cardContainer.className = 'card-container';
- cardContainer.id = category;
-
- section.appendChild(title);
- section.appendChild(cardContainer);
-
- categories[category].forEach(link => {
- createCard(link, cardContainer);
- });
-
- container.appendChild(section);
- });
- }
-
- function createCard(link, container) {
- const card = document.createElement('div');
- card.className = 'card';
- card.setAttribute('draggable', isAdmin);
-
- const cardTop = document.createElement('div');
- cardTop.className = 'card-top';
-
- const icon = document.createElement('img');
- icon.className = 'card-icon';
- // icon.src = 'https://www.google.com/s2/favicons?domain=' + link.url;
- icon.src = 'https://favicon.zhusl.com/ico?url=' + link.url;
- icon.alt = 'Website Icon';
-
- const title = document.createElement('div');
- title.className = 'card-title';
- title.textContent = link.name;
-
- cardTop.appendChild(icon);
- cardTop.appendChild(title);
-
- const url = document.createElement('div');
- url.className = 'card-url';
- url.textContent = link.url;
-
- card.appendChild(cardTop);
- card.appendChild(url);
-
- // URL 检查和修正
- function correctUrl(url) {
- if (url.startsWith('http://') || url.startsWith('https://')) {
- return url;
- } else {
- return 'http://' + url;
- }
- }
-
- let correctedUrl = correctUrl(link.url);
-
- if (!isAdmin) {
- card.addEventListener('click', () => {
- window.open(correctedUrl, '_blank');
- });
- }
-
- const deleteBtn = document.createElement('button');
- deleteBtn.textContent = '–';
- deleteBtn.className = 'delete-btn';
- deleteBtn.onclick = function (event) {
- event.stopPropagation();
- removeCard(card);
- };
- card.appendChild(deleteBtn);
-
- if (isDarkTheme) {
- card.style.backgroundColor = '#1e1e1e';
- card.style.color = '#ffffff';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
- } else {
- card.style.backgroundColor = '#d4d4d4';
- card.style.color = '#333';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
- }
-
- card.addEventListener('dragstart', dragStart);
- card.addEventListener('dragover', dragOver);
- card.addEventListener('dragend', dragEnd);
- card.addEventListener('drop', drop);
-
- if (isAdmin && removeMode) {
- deleteBtn.style.display = 'block';
- }
-
- container.appendChild(card);
- }
-
- function updateCategorySelect() {
- const categorySelect = document.getElementById('category-select');
- categorySelect.innerHTML = '';
-
- Object.keys(categories).forEach(category => {
- const option = document.createElement('option');
- option.value = category;
- option.textContent = category;
- categorySelect.appendChild(option);
- });
- }
-
- async function saveLinks() {
- let links = [];
- for (const category in categories) {
- links = links.concat(categories[category]);
- }
-
- await fetch('/api/saveOrder', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ userId: 'testUser', links }),
- });
- }
-
- function addLink() {
- const name = document.getElementById('name-input').value;
- const url = document.getElementById('url-input').value;
- const category = document.getElementById('category-select').value;
-
- if (name && url && category) {
- const newLink = { name, url, category };
-
- if (!categories[category]) {
- categories[category] = [];
- }
- categories[category].push(newLink);
-
- const container = document.getElementById(category);
- createCard(newLink, container);
-
- saveLinks();
-
- document.getElementById('name-input').value = '';
- document.getElementById('url-input').value = '';
- hideAddDialog();
- }
- }
-
- function removeCard(card) {
- const url = card.querySelector('.card-url').textContent;
- let category;
- for (const key in categories) {
- const index = categories[key].findIndex(link => link.url === url);
- if (index !== -1) {
- categories[key].splice(index, 1);
- category = key;
- break;
- }
- }
- card.remove();
-
- saveLinks();
- }
-
- let draggedCard = null;
-
- function dragStart(event) {
- if (!isAdmin) return;
- draggedCard = event.target;
- draggedCard.classList.add('dragging');
- event.dataTransfer.effectAllowed = "move";
- }
-
- function dragOver(event) {
- if (!isAdmin) return;
- event.preventDefault();
- const target = event.target.closest('.card');
- if (target && target !== draggedCard) {
- const container = target.parentElement;
- const mousePositionX = event.clientX;
- const targetRect = target.getBoundingClientRect();
-
- if (mousePositionX < targetRect.left + targetRect.width / 2) {
- container.insertBefore(draggedCard, target);
- } else {
- container.insertBefore(draggedCard, target.nextSibling);
- }
- }
- }
-
- function drop(event) {
- if (!isAdmin) return;
- event.preventDefault();
- draggedCard.classList.remove('dragging');
- draggedCard = null;
- saveCardOrder();
- }
-
- // function dragEnd(event) {
- // draggedCard.classList.remove('dragging');
- function dragEnd(event) {
- if (draggedCard) {
- draggedCard.classList.remove('dragging');
- }
- }
-
- async function saveCardOrder() {
- if (!isAdmin) return;
- const containers = document.querySelectorAll('.card-container');
- let newLinks = [];
-
- containers.forEach(container => {
- const category = container.id;
- categories[category] = [];
- [...container.children].forEach(card => {
- const url = card.querySelector('.card-url').textContent;
- const name = card.querySelector('.card-title').textContent;
- const link = { name, url, category };
- categories[category].push(link);
- newLinks.push(link);
- });
- });
-
- links = newLinks;
-
- await fetch('/api/saveOrder', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ userId: 'testUser', links: newLinks }),
- });
- }
-
-
- function toggleAdminMode() {
- const passwordInput = document.getElementById('admin-password');
- const adminBtn = document.getElementById('admin-mode-btn');
- const addRemoveControls = document.querySelector('.add-remove-controls');
-
- if (!isAdmin) {
- verifyPassword(passwordInput.value)
- .then(isValid => {
- if (isValid) {
- isAdmin = true;
- adminBtn.textContent = "退出管理模式";
- alert('已进入管理模式');
- addRemoveControls.style.display = 'block';
- reloadCardsAsAdmin();
- } else {
- alert('密码错误');
- }
- });
- } else {
- isAdmin = false;
- removeMode = false;
- adminBtn.textContent = "进入管理模式";
- alert('已退出管理模式');
- addRemoveControls.style.display = 'none';
- const deleteButtons = document.querySelectorAll('.delete-btn');
- deleteButtons.forEach(btn => btn.style.display = 'none');
- reloadCardsAsAdmin();
- }
-
- passwordInput.value = '';
- }
-
- function reloadCardsAsAdmin() {
- document.querySelectorAll('.card-container').forEach(container => {
- container.innerHTML = '';
- });
- loadLinks().then(() => {
- if (isDarkTheme) {
- applyDarkTheme();
- }
- });
- }
-
- function applyDarkTheme() {
- document.body.style.backgroundColor = '#121212';
- document.body.style.color = '#ffffff';
- const cards = document.querySelectorAll('.card');
- cards.forEach(card => {
- card.style.backgroundColor = '#1e1e1e';
- card.style.color = '#ffffff';
- card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
- });
- }
-
- function showAddDialog() {
- document.getElementById('dialog-overlay').style.display = 'flex';
- }
-
- function hideAddDialog() {
- document.getElementById('dialog-overlay').style.display = 'none';
- }
-
-
- function toggleRemoveMode() {
- removeMode = !removeMode;
- const deleteButtons = document.querySelectorAll('.delete-btn');
- deleteButtons.forEach(btn => {
- btn.style.display = removeMode ? 'block' : 'none';
- });
- }
-
- function toggleTheme() {
- isDarkTheme = !isDarkTheme;
- // 设置暗色主题和亮色主题的背景色
- document.body.style.backgroundColor = isDarkTheme ? '#696969' : '#FFFFFF';
- // 设置暗色主题和亮色主题的文本颜色
- document.body.style.color = isDarkTheme ? '#ffffff' : '#333';
-
- const cards = document.querySelectorAll('.card');
- cards.forEach(card => {
- // 卡片背景和文本颜色设置
- card.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#d4d4d4';
- card.style.color = isDarkTheme ? '#ffffff' : '#333';
- // 卡片阴影的设置,增强暗色主题的阴影
- card.style.boxShadow = isDarkTheme
- ? '0 4px 8px rgba(0, 0, 0, 0.5)'
- : '0 4px 8px rgba(0, 0, 0, 0.1)';
- });
-
- const dialogBox = document.getElementById('dialog-box');
- // 对话框背景和文本颜色设置
- dialogBox.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#ffffff';
- dialogBox.style.color = isDarkTheme ? '#ffffff' : '#333';
-
- const inputs = dialogBox.querySelectorAll('input, select');
- inputs.forEach(input => {
- // 输入框背景和文本颜色设置
- input.style.backgroundColor = isDarkTheme ? '#333333' : '#ffffff';
- input.style.color = isDarkTheme ? '#ffffff' : '#333';
- });
- }
-
-
- async function verifyPassword(inputPassword) {
- const response = await fetch('/api/verifyPassword', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ password: inputPassword }),
- });
- const result = await response.json();
- return result.valid;
- }
-
- loadLinks();
- </script>
- </body>
- </html>
- `;
-
- export default {
- async fetch(request, env) {
- const url = new URL(request.url);
-
- if (url.pathname === '/') {
- return new Response(HTML_CONTENT, {
- headers: { 'Content-Type': 'text/html' }
- });
- }
-
- if (url.pathname === '/api/getLinks') {
- const userId = url.searchParams.get('userId');
- const links = await env.CARD_ORDER.get(userId);
- return new Response(links || JSON.stringify([]), { status: 200 });
- }
-
- if (url.pathname === '/api/saveOrder' && request.method === 'POST') {
- const { userId, links } = await request.json();
- await env.CARD_ORDER.put(userId, JSON.stringify(links));
- return new Response(JSON.stringify({ success: true }), { status: 200 });
- }
-
- if (url.pathname === '/api/verifyPassword' && request.method === 'POST') {
- const { password } = await request.json();
- const isValid = password === env.ADMIN_PASSWORD; // 从环境变量中获取密码
- return new Response(JSON.stringify({ valid: isValid }), {
- status: isValid ? 200 : 403,
- headers: { 'Content-Type': 'application/json' },
- });
- }
-
- return new Response('Not Found', { status: 404 });
- }
- };
二、添加变量
1、先建立一个kv空间,名字为: CARD_ORDER ,再在 变量-KV 命名空间绑定 刚才建立的kv空间。
2、变量-环境变量 中添加 ADMIN_PASSWORD ,值是你的后台管理员密码
3、有域名的可邦定域名,完成。
评论 (0)