首页
友链
关于
推荐
wszx博客
Search
1
精美的桌面壁纸2
1 阅读
2
RN服务器添加 IPV6 地址
1 阅读
3
简洁而功能强大的音乐播放器
1 阅读
4
水:生命之源
1 阅读
5
Gmail隐藏技能:一个账号如何变出99个邮箱身分,垃圾注册永远不再困扰你
1 阅读
默认
日常
学习
技术
登录
Search
标签搜索
cloudflare
壁纸
CF
白嫖
安装
脚本
图片
Linux
docker
域名
桌面壁纸
手机壁纸
NAT
LXC
哪吒
高清壁纸
优选
github
vps
代码
ws01
累计撰写
64
篇文章
累计收到
5
条评论
首页
栏目
默认
日常
学习
技术
页面
友链
关于
推荐
wszx博客
搜索到
9
篇与
的结果
2024-09-08
部署在CF的轻量化导航页面,可移动卡片式书签,方便管理
部署在CF的轻量化导航页面,可移动卡片式书签,方便管理 github项目 Card-Tab 书签卡片式管理,进入管理模式可以自由移动书签位置,添加和删除书签,支持自定义网站分类,支持切换黑暗色主题一、cloudflare workes部署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、有域名的可邦定域名,完成。
2024年09月08日
1 阅读
0 评论
0 点赞
2024-07-24
一次可生成n加节点,YTB视频4K秒开!2024最新永久免费,订阅节点订阅搭建保姆教程
一次可生成n加节点,YTB视频4K秒开!2024最新永久免费,订阅节点订阅搭建保姆教程 本文转自: 科技共享 原项目地址 代码 创建邦定KV空间,命名:settings支持Windows、MacOS、Linux 优选IP使用,操作简单,速度快。创建好后, 第一次打开设置登录密码 第一次打开设置登录密码 第一次打开设置登录密码 其它根据需要再设置
2024年07月24日
1 阅读
0 评论
0 点赞
2024-07-19
永久免费节点搭建!通过Cloudflare Worker部署免费的VLESS节点,4K高速,解锁Netflix、ChatGPT
永久免费节点搭建!通过Cloudflare Worker部署免费的VLESS节点,4K高速,解锁Netflix、ChatGPT本文转载自: 科技小露 本文是通过 zizifn 大佬的一个开源项目 edgetunnel ,使得我们可以免费的在 Cloudflare 上面通过部署 Worker ,来创建一个免费 VLESS 节点!一、准备工作注册 Cloudflare 账号并登录二、CloudFlare 部署免费节点原作者 GitHub 源项目地址 Worker 部署 VLESS1、来到 Cloudflare 首页,点击 Workers 和 Pages ,创建 Work ,自定义名称,然后部署!而后,编辑代码,清除原先代码,填入如下代码: Workers代码 2、在线生成一个 UUID ,用于替换下面代码中第七行的 UUID。(或是用 V2rayN 生成一个)3、因为原项目里面的代码“ proxy IP ”为空,关于 proxy IP ,是用于转发CF的一些流量,所以,若是存在套了CF的一些网站无法打开,请更换其中的其他网址,也就是第九行中的部分网址!ProxyIP 可以替换成域名或优选IPproxyip.us.fxxk.dedyn.io更多 proxyIP 列表:CM 维护proxyip.us.fxxk.dedyn.io IP落地区域: 美国 维护频率: 12小时/次 proxyip.sg.fxxk.dedyn.io IP落地区域: 新加坡 维护频率: 12小时/次 proxyip.jp.fxxk.dedyn.io IP落地区域: 日本 维护频率: 12小时/次 proxyip.hk.fxxk.dedyn.io IP落地区域: 香港 维护频率: 12小时/次 proxyip.aliyun.fxxk.dedyn.io IP落地区域: 阿里云 维护频率: 4小时/次 proxyip.oracle.fxxk.dedyn.io IP落地区域: 甲骨文 维护频率: 4小时/次 proxyip.digitalocean.fxxk.dedyn.io IP落地区域: 数码海 维护频率: 4小时/次白嫖哥维护workers.cloudflare.cyouMingyu 维护my-telegram-is-herocore.onecf.eu.org sg.ipdb.rr.nu nl.ipdb.rr.nu hk.ipdb.rr.nu jp.ipdb.rr.nu us.ipdb.rr.nu小一维护hk.cf.zhetengsha.eu.org sg.cf.zhetengsha.eu.org us.cf.zhetengsha.eu.org jp.cf.zhetengsha.eu.org代码修改完毕以后,点击右边的部署 – 保存并部署,然后点击左边的箭头,返回! 4、访问创建的vlessjd,在网站后加上“/UUID” ,即可看到节点信息,当然,里面包含了 订阅链接! 至此,节点部署完毕,复制 VLESS 的订阅链接,粘贴到 V2rayN 里面。其它使用和优选等自己找教程
2024年07月19日
0 阅读
0 评论
0 点赞
2024-07-18
使用cloudflare搭建永久免费Trojan节点,免费创建自定义域名,多个节点,无需手动优选,免费无门槛搭建
使用cloudflare搭建永久免费Trojan节点,免费创建自定义域名,多个节点,无需手动优选,免费无门槛搭建本文转载自: 小白科技 之前小白分享过很多期使用cloudflare自建VLESS节点,但是cloudflare的规则跟新之后导致很多VLESS节点都无法使用,但是Trojan的节点是可以使用的,所以本期视频我们通过cloudflare的Workers搭建Trojan节点。首先说明,本期视频使用的所有代码都是CM大佬发布在Github上的免费开源项目,在这里感谢大佬。源项目地址:https://github.com/cmliu/epeius?tab=readme-ov-file一、项目部署:cloudflare官网地址:https://dash.cloudflare.com/sign-up项目代码地址:https://github.com/cmliu/epeius/blob/main/_worker.js项目部署完毕后添加变量1、PASSWORD,密码:个人喜好可取任意数字2、SUB,trojan.fxxk.dedyn.io二、自定义域名:自己解决,设置完这两步就可以正常使用。{dotted startColor="#ff6c6c" endColor="#1989fa"/}以下是进价 1、在变量中加入 ADD,本地优选域名/优选IP(支持多元素之间,或 换行 作间隔,如:168.1221.us.kg:443#优选 268.1221.us.kg:443#优选 ip.sb japan.com malaysia.com russia.com skk.moe www.visa.com www.visa.com.sg www.visa.com.hk www.visa.com.tw www.visakorea.com www.gov.se www.digitalocean.com www.whoer.net www.whatismyip.com www.hugedomains.com www.udacity.com www.4chan.org www.okcupid.com www.glassdoor.com www.udemy.com alejandracaiccedo.com log.bpminecraft.com www.boba88slot.com gur.gov.ua www.zsu.gov.ua www.iakeys.com www.d-555.com fbi.gov bestcf.onecf.eu.org#Mingyu大佬 cfip.xxxxxxxx.tk#OTC大佬 xn--b6gac.eu.org#优选域名2、在变量中加入 ADDAPI,如https://raw.githubusercontent.com/cmliu/WorkerVless2sub/main/addressesapi.txt https://wenben-738.pages.dev/ip.txt?token=9527 https://raw.githubusercontent.com/cmliu/WorkerVless2sub/main/addressesapi.txt3、在变量中加入 PROXYIP,如workers.cloudflare.cyou my-telegram-is-herocore.onecf.eu.org proxyip.fxxk.dedyn.io4、在变量中加入SUBAPI,clash、singbox等 订阅转换后端,如subapi.fxxk.dedyn.io5、在变量中加入ADDCSV,如https://raw.githubusercontent.com/cmliu/WorkerVless2sub/main/addressescsv.csv三、该项目部署的节点可通过节点PATH(路径)的方式,使用指定的PROXYIP或SOCKS5!!!1、指定 PROXYIP 案例 (仅限于域名开头为'proxyip.'的域名),可把jp改为其它国/proxyip=proxyip.jp.fxxk.dedyn.io /?proxyip=proxyip.jp.fxxk.dedyn.io /proxyip.jp.fxxk.dedyn.io2、指定 SOCKS5 案例/socks5=user:password@127.0.0.1:1080 /?socks5=user:password@127.0.0.1:1080 /socks://dXNlcjpwYXNzd29yZA==@127.0.0.1:1080 /socks5://user:password@127.0.0.1:1080等等
2024年07月18日
1 阅读
0 评论
0 点赞
2024-05-31
注册GCP150刀相关【有手就行】
注册GCP150刀相关【有手就行】地址: GCP 一路点下去就行,如果出现要验卡步骤就算了【或换个干净IP和环境再试】,换个号和窗口继续,手快有,手慢就要封车了。一、GCP 改成root密码登录方法1、先选择从浏览器打开ssh连接服务器2、切换到root账号,输入代码:sudo -i3、设置root密码,输入代码:passwd然后会要求输入新密码,然后再重复一次密码,输入密码的时候不会显示出来,所以直接输入密码,然后回车,再然后重复输入密码回车开启SSH权限1、CentOS和Debian通用,输入以下两条命令sed -i 's/PermitRootLogin no/PermitRootLogin yes/g' /etc/ssh/sshd_config sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config2、Ubuntu系统,输入以下两条命令sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/g' /etc/ssh/sshd_config sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config3、重启服务器,输入命令:reboot二、gcp亚洲速度较快各区台湾【tw】asia-east1 10.140.0.0/20香港【hk】asia-east2 10.170.0.0/20东京【tokyo】asia-northeast1 10.146.0.0/20大板【osaka】asia-northeast2 10.174.0.0/20首尔【seoul】asia-northeast3 10.178.0.0/20孟买asia-south1 10.160.0.0/20新德里asia-south2 10.190.0.0/20新加坡【sg】asia-southeast1 10.148.0.0/20加达asia-southeast2 10.184.0.0/20三、防火墙用:v4:0.0.0.0/0v6:::/0四、300刀创建免费200G注意事项1、地点必须是us-west(俗称美西)和另2种(不推荐)2、机型必须是e2-micro(2C 1G)3、磁盘选:标准和30G4、网络要换到:标准,us-west免费200G五、ip不好或被强时可更换ip从左上方选择 “VPC网络” 进入“ip地址”,操作方法看图
2024年05月31日
1 阅读
0 评论
0 点赞
1
2
您是第
27039
位访客