Loading... CF _worker搭建 WS01 Note,可设置私人日记或公开分享 变量设置: 1、帐号:USERNAME,默认:9527a 2、密码:PASSWORD,默认:9527abc 3、KV空间邦定:WS01_NOTE_KV ``` // WS01 Note - Cloudflare Workers + KV 日记本应用 // 作者: WS01 // 功能: 基于Cloudflare Workers和KV数据库的简单日记本 export default { async fetch(request, env, ctx) { const url = new URL(request.url); const path = url.pathname; // 处理CORS const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', }; if (request.method === 'OPTIONS') { return new Response(null, { status: 200, headers: corsHeaders }); } try { // 路由处理 if (path === '/' || path === '/login') { return new Response(getLoginPage(), { headers: { 'Content-Type': 'text/html; charset=utf-8', ...corsHeaders } }); } if (path === '/diary') { return new Response(getDiaryPage(), { headers: { 'Content-Type': 'text/html; charset=utf-8', ...corsHeaders } }); } // 查看具体日记页面 if (path.startsWith('/diary/') && path.split('/').length === 3) { const diaryId = path.split('/')[2]; return new Response(getDiaryDetailPage(diaryId), { headers: { 'Content-Type': 'text/html; charset=utf-8', ...corsHeaders } }); } // 分享日记目录页面路由 if (path === '/share') { return new Response(getShareIndexPage(), { headers: { 'Content-Type': 'text/html; charset=utf-8', ...corsHeaders } }); } // 分享页面路由 if (path.startsWith('/share/') && path.split('/').length === 3) { const shareId = path.split('/')[2]; return new Response(getSharePage(shareId), { headers: { 'Content-Type': 'text/html; charset=utf-8', ...corsHeaders } }); } if (path === '/api/auth') { return handleAuth(request, env, corsHeaders); } if (path === '/api/diary') { return handleDiaryAPI(request, env, corsHeaders); } // 添加分享相关API路由 if (path === '/api/diary/share' && (request.method === 'POST' || request.method === 'PUT')) { return handleShareDiary(request, env, corsHeaders); } // 获取分享日记API路由 if (path.startsWith('/api/share/') && path.split('/').length === 4) { const shareId = path.split('/')[3]; return handleGetSharedDiary(shareId, env, corsHeaders); } // 获取所有分享日记API路由 if (path === '/api/shares' && request.method === 'GET') { return handleGetAllShares(request, env, corsHeaders); } // 获取用户分享日记列表API路由 if (path === '/api/diary/shares' && request.method === 'GET') { return handleGetUserShares(request, env, corsHeaders); } // 删除分享日记API路由 if (path === '/api/diary/share' && request.method === 'DELETE') { return handleDeleteShare(request, env, corsHeaders); } // 添加备份相关API路由 if (path === '/api/diary/backup' && request.method === 'POST') { return handleCreateBackup(request, env, corsHeaders); } if (path === '/api/diary/backups' && request.method === 'GET') { return handleGetBackups(request, env, corsHeaders); } if (path === '/api/diary/restore' && request.method === 'POST') { return handleRestoreBackup(request, env, corsHeaders); } if (path === '/api/diary/backup' && request.method === 'DELETE') { return handleDeleteBackup(request, env, corsHeaders); } // 404处理 return new Response('页面未找到', { status: 404, headers: { 'Content-Type': 'text/plain; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('Worker错误:', error); return new Response('服务器内部错误', { status: 500, headers: { 'Content-Type': 'text/plain; charset=utf-8', ...corsHeaders } }); } } }; // 登录页面 function getLoginPage() { return ` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WS01 Note - 登录</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; } .login-container { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 10px 25px rgba(0,0,0,0.08); width: 100%; max-width: 360px; } .logo { text-align: center; margin-bottom: 1.5rem; } .logo h1 { color: #333; font-size: 1.6rem; font-weight: 400; } .logo p { color: #666; margin-top: 0.3rem; font-size: 0.9rem; } .form-group { margin-bottom: 1.2rem; } .form-group label { display: block; margin-bottom: 0.4rem; color: #333; font-weight: 500; font-size: 0.9rem; } .form-group input { width: 100%; padding: 0.6rem; border: 1px solid #e1e5e9; border-radius: 6px; font-size: 0.9rem; transition: border-color 0.3s; } .form-group input:focus { outline: none; border-color: #667eea; } .login-btn { width: 100%; padding: 0.6rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 6px; font-size: 0.9rem; font-weight: 500; cursor: pointer; transition: transform 0.2s; } .login-btn:hover { transform: translateY(-2px); } .error-message { color: #e74c3c; text-align: center; margin-top: 1rem; display: none; } </style> </head> <body> <div class="login-container"> <div class="logo"> <h1>WS01 Note</h1> <p>您的私人日记本</p> </div> <form id="loginForm"> <div class="form-group"> <label for="username">用户名</label> <input type="text" id="username" name="username" required> </div> <div class="form-group"> <label for="password">密码</label> <input type="password" id="password" name="password" required> </div> <button type="submit" class="login-btn">登录</button> </form> <div id="errorMessage" class="error-message"></div> </div> <script> document.getElementById('loginForm').addEventListener('submit', async (e) => { e.preventDefault(); const username = document.getElementById('username').value; const password = document.getElementById('password').value; const errorDiv = document.getElementById('errorMessage'); try { const response = await fetch('/api/auth', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username, password }) }); const result = await response.json(); if (result.success) { localStorage.setItem('ws01_token', result.token); window.location.href = '/diary'; } else { errorDiv.textContent = result.message || '登录失败'; errorDiv.style.display = 'block'; } } catch (error) { errorDiv.textContent = '网络错误,请重试'; errorDiv.style.display = 'block'; } }); </script> </body> </html>`; } // 日记页面 function getDiaryPage() { return ` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WS01 Note - 我的日记</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f8f9fa; min-height: 100vh; } .sites { width:1000px; background:#F2F2F2; border:1px solid rgba(0,0,0,.06); margin:10px auto; padding:0px; color: white; border-radius: 6px; } .sites01 { 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:#c5dff6; border-top:1px solid #ffffff; border-bottom:1px solid #ffffff; } .sites dl.alt2 { background:#dcecfa; 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; } .header { background: #D4D4D4; padding: 0.8rem 1.5rem; box-shadow: 0 1px 6px rgba(0,0,0,0.08); display: flex; justify-content: center; align-items: center; position: relative; } .logo { font-size: 1.5rem; font-weight: 400; color: #333; } .logout-btn { background: #e74c3c; color: white; border: none; width: 40px; height: 40px; border-radius: 50%; cursor: pointer; font-size: 1.2rem; position: absolute; right: 2.6rem; display: flex; align-items: center; justify-content: center; transition: transform 0.2s; } .logout-btn:hover { background: #c0392b; transform: scale(1.1); } .container { max-width: 960px; margin: 1.5rem auto; padding: 0 1.5rem; display: flex; gap: 1.5rem; } .main-content { flex: 1; min-width: 0; } .sidebar { width: 300px; flex-shrink: 0; } /* 搜索框样式 */ .search-section { margin-bottom: 1rem; } .search-container { position: relative; background: white; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); overflow: hidden; } .search-input { width: 100%; padding: 0.8rem 2.5rem 0.8rem 0.8rem; border: none; border-radius: 8px; font-size: 0.9rem; background: white; transition: all 0.3s ease; } .search-input:focus { outline: none; box-shadow: 0 0 0 2px #667eea; } .search-input::placeholder { color: #999; font-size: 0.85rem; } .clear-search-btn { position: absolute; right: 0.5rem; top: 50%; transform: translateY(-50%); background: none; border: none; color: #999; font-size: 1.2rem; cursor: pointer; padding: 0.2rem; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; } .clear-search-btn:hover { background: #f0f0f0; color: #666; } .clear-search-btn.hidden { display: none; } .diary-form { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); } .diary-form-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.2rem; } .diary-form-title { color: #333; margin: 0; font-size: 1.3rem; } .current-time { color: #666; font-size: 0.8rem; } /* 添加保存按钮图标样式 */ .save-btn-icon { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; width: 32px; height: 32px; border-radius: 50%; cursor: pointer; font-size: 1rem; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0,0,0,0.15); transition: transform 0.2s; } .save-btn-icon:hover { transform: scale(1.1); } /* 添加分享按钮样式 */ .share-btn-icon { background: linear-gradient(135deg, #28a745 0%, #20c997 100%); color: white; border: none; width: 32px; height: 32px; border-radius: 50%; cursor: pointer; font-size: 1rem; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0,0,0,0.15); transition: transform 0.2s; } .share-btn-icon:hover { transform: scale(1.1); } /* 添加分享按钮样式 */ .share01-btn-icon { background: linear-gradient(135deg, #28a745 0%, #20c997 100%); color: white; border: none; width: 40px; height: 40px; border-radius: 50%; cursor: pointer; font-size: 1.2rem; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0,0,0,0.15); transition: transform 0.2s; text-decoration: none; } .share01-btn-icon:hover { transform: scale(1.1); } .header-with-button { display: flex; justify-content: space-between; align-items: center; } .header-controls { display: flex; align-items: center; gap: 0.5rem; } .font-size-select { padding: 0.3rem 0.5rem; border: 1px solid #e1e5e9; border-radius: 4px; font-size: 0.8rem; background: white; cursor: pointer; transition: border-color 0.3s; } .font-size-select:focus { outline: none; border-color: #667eea; } .font-size-select:hover { border-color: #667eea; } .form-group { margin-bottom: 1.2rem; } .form-group label { display: block; margin-bottom: 0.4rem; color: #333; font-weight: 500; font-size: 0.9rem; } .form-group input, .form-group textarea { width: 100%; padding: 0.6rem; border: 1px solid #e1e5e9; border-radius: 6px; font-size: 0.9rem; font-family: inherit; transition: border-color 0.3s; } .form-group input:focus, .form-group textarea:focus { outline: none; border-color: #667eea; } .form-group textarea { width: 100%; padding: 0.6rem; border: 1px solid #e1e5e9; border-radius: 6px; font-size: 0.9rem; font-family: inherit; transition: border-color 0.3s; min-height: 720px; //写日记模块的高度 } /* 添加字符计数器样式 */ .char-count { font-size: 0.8rem; color: #666; text-align: right; margin-top: 0.25rem; } .char-count.warning { color: #ffc107; } .char-count.error { color: #dc3545; } .save-btn { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 0.75rem 2rem; border-radius: 8px; font-size: 1rem; font-weight: 500; cursor: pointer; transition: transform 0.2s; } .save-btn:hover { transform: translateY(-2px); } .diary-list { background: white; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); overflow: hidden; height: calc(100vh - 410px); display: flex; flex-direction: column; } .diary-list-header { padding: 1.2rem; margin: 0; color: #333; border-bottom: 1px solid #f0f0f0; background: #f8f9fa; font-size: 1rem; font-weight: 600; } .diary-list-content { flex: 1; overflow-y: auto; padding: 0; } .diary-date-group { margin-bottom: 0.8rem; } .date-header { background: #e9ecef; padding: 0.4rem 0.8rem; font-weight: 600; color: #495057; border-bottom: 1px solid #dee2e6; font-size: 0.8rem; position: sticky; top: 0; z-index: 1; } .diary-items { background: white; } .diary-item { padding: 0.6rem 0.8rem; border-bottom: 1px solid #f0f0f0; cursor: pointer; transition: background-color 0.2s; display: flex; flex-direction: column; gap: 0.2rem; } .diary-item:hover { background-color: #f8f9fa; } .diary-item:last-child { border-bottom: none; } .diary-title { color: #333; font-size: 0.85rem; font-weight: 500; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .diary-time { color: #666; font-size: 0.7rem; align-self: flex-end; } /* 修改: 按钮容器样式 */ .diary-actions { display: flex; gap: 0.4rem; margin-top: 0.3rem; justify-content: flex-end; } .edit-btn, .delete-btn { padding: 0.2rem 0.4rem; font-size: 0.7rem; border: none; border-radius: 3px; cursor: pointer; } .edit-btn { background-color: #007bff; color: white; } .delete-btn { background-color: #dc3545; color: white; } .edit-btn:hover { background-color: #0056b3; } .delete-btn:hover { background-color: #c82333; } .empty-state { text-align: center; padding: 3rem; color: #666; } .message { padding: 1rem; margin-bottom: 1rem; border-radius: 8px; display: none; } .message.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .message.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } /* 添加分享日记模块样式 */ .share-section { margin-top: 0.8rem; padding-top: 0.8rem; border-top: 1px solid #eee; } .share-controls { display: flex; gap: 0.4rem; margin-bottom: 0.8rem; } .share-list { flex: 1; overflow-y: auto; padding: 0; height: calc(100vh - 520px); } .share-date-group { margin-bottom: 0.8rem; } .share-items { background: white; } .share-item { padding: 0.6rem 0.8rem; border-bottom: 1px solid #f0f0f0; cursor: pointer; transition: background-color 0.2s; display: flex; flex-direction: column; gap: 0.2rem; } .share-item:hover { background-color: #f8f9fa; } .share-item:last-child { border-bottom: none; } .share-title { color: #333; font-size: 0.85rem; font-weight: 500; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .share-time { color: #666; font-size: 0.7rem; align-self: flex-end; } .share-actions { display: flex; gap: 0.4rem; margin-top: 0.3rem; justify-content: flex-end; } .edit-share-btn, .delete-share-btn { padding: 0.2rem 0.4rem; font-size: 0.7rem; border: none; border-radius: 3px; cursor: pointer; } .edit-share-btn { background-color: #007bff; color: white; } .delete-share-btn { background-color: #dc3545; color: white; } .edit-share-btn:hover { background-color: #0056b3; } .delete-share-btn:hover { background-color: #c82333; } /* 添加备份恢复按钮样式 */ .backup-section { margin-top: 0.8rem; padding-top: 0.8rem; border-top: 1px solid #eee; } .backup-controls { display: flex; gap: 0.4rem; margin-bottom: 0.8rem; } .backup-btn { padding: 0.4rem 0.8rem; border: none; border-radius: 4px; cursor: pointer; font-size: 0.8rem; } .backup-btn.primary { background: #28a745; color: white; } .backup-btn.secondary { background: #17a2b8; color: white; } .backup-list { max-height: 150px; overflow-y: auto; border: 1px solid #ddd; border-radius: 4px; padding: 0.4rem; } .backup-item { background: #ffffff; color: #969696; padding: 0.4rem; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; } .backup-item:last-child { border-bottom: none; } .restore-btn { background: #ffc107; color: black; border: none; padding: 0.2rem 0.4rem; border-radius: 3px; cursor: pointer; font-size: 0.7rem; } .delete-backup-btn { background: #dc3545; color: white; border: none; padding: 0.2rem 0.4rem; border-radius: 3px; cursor: pointer; font-size: 0.7rem; } /* 响应式设计 */ @media (max-width: 768px) { .sites { width: 95%; margin: 5px auto; } .container { flex-direction: column; gap: 0.8rem; margin: 1rem auto; padding: 0 1rem; } .sidebar { width: 100%; order: -1; } .diary-list { height: 300px; } .diary-form { padding: 1rem; } .form-group textarea { min-height: 200px; } /* 隐藏手机端的时间显示 */ .current-time { display: none; } } </style> </head> <body> <div class="sites"> <div class="header"> <div class="logo">WS01 Note</div> <a href="/share" class="share01-btn-icon" target="_blank" title="分享目录">📂</a> <button class="logout-btn" onclick="logout()" title="退出登录">🚪</button> </div> <div class="container"> <div id="message" class="message"></div> <div class="main-content"> <div class="diary-form"> <div class="diary-form-header"> <div class="header-with-button"> <h2 class="diary-form-title">写日记</h2> <div class="header-controls"> <select id="fontSizeSelect" class="font-size-select" title="选择字体大小"> <option value="12">12px</option> <option value="14">14px</option> <option value="16" selected>16px</option> <option value="18">18px</option> <option value="20">20px</option> <option value="22">22px</option> </select> <button type="submit" class="save-btn-icon" title="私人保存">💾</button> <button type="button" class="share-btn-icon" title="分享日记">🔗</button> </div> </div> <div class="current-time" id="currentTime"></div> </div> <form id="diaryForm"> <div class="form-group"> <!-- <label for="diaryTitle">标题</label> --> <input type="text" id="diaryTitle" name="title" placeholder="标题..." required> </div> <div class="form-group"> <!-- <label for="diaryContent">内容</label> --> <textarea id="diaryContent" name="content" placeholder="内容..." required></textarea> <div class="char-count" id="charCount">0 / 100000</div> </div> <!-- 删除原来的保存按钮 --> </form> </div> </div> <div class="sidebar"> <!-- 搜索框 --> <div class="search-section"> <div class="search-container"> <input type="text" id="searchInput" class="search-input" placeholder="搜索日记标题或内容..."> <button id="clearSearch" class="clear-search-btn" title="清除搜索">×</button> </div> </div> <div class="diary-list"> <div class="diary-list-header">我的日记</div> <div class="diary-list-content" id="diaryList"> <div class="empty-state">还没有日记,开始记录吧!</div> </div> </div> <!-- 分享日记模块 --> <div class="share-section"> <div class="diary-list-header">分享日记</div> <div class="share-controls"> <button class="backup-btn secondary" onclick="loadSharedDiaries()">刷新分享列表</button> </div> <div class="share-list" id="shareList"> <div class="empty-state">暂无分享日记</div> </div> </div> <!-- 移动备份和恢复功能到这里 --> <div class="backup-section"> <div class="diary-list-header">数据备份与恢复</div> <div class="backup-controls"> <button class="backup-btn primary" onclick="createBackup()">创建备份</button> <button class="backup-btn secondary" onclick="loadBackups()">刷新备份列表</button> </div> <div class="backup-list" id="backupList"> <div class="empty-state">暂无备份</div> </div> </div> </div> </div> <script> // 更新当前时间 function updateTime() { const now = new Date(); const timeString = now.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); document.getElementById('currentTime').textContent = timeString; } // HTML转义函数 function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // HTML反转义函数 function unescapeHtml(html) { const div = document.createElement('div'); div.innerHTML = html; return div.textContent || div.innerText || ''; } // 添加: 更新字符计数函数 function updateCharCount() { const content = document.getElementById('diaryContent'); const charCount = document.getElementById('charCount'); const currentLength = content.value.length; charCount.textContent = \`\${currentLength} / 100000\`; // 根据字符数量改变颜色 if (currentLength > 1800) { charCount.className = 'char-count error'; } else if (currentLength > 1500) { charCount.className = 'char-count warning'; } else { charCount.className = 'char-count'; } // 超过限制时截断内容 if (currentLength > 100000) { content.value = content.value.substring(0, 100000); charCount.textContent = '100000 / 100000'; } } // 添加: 字号选择功能 function changeFontSize() { const fontSizeSelect = document.getElementById('fontSizeSelect'); const contentTextarea = document.getElementById('diaryContent'); const selectedSize = fontSizeSelect.value; // 应用字体大小到内容区域 contentTextarea.style.fontSize = selectedSize + 'px'; // 保存用户选择到本地存储 localStorage.setItem('ws01_font_size', selectedSize); } // 添加: 加载保存的字体大小 function loadFontSize() { const savedSize = localStorage.getItem('ws01_font_size'); const fontSizeSelect = document.getElementById('fontSizeSelect'); const contentTextarea = document.getElementById('diaryContent'); if (savedSize) { fontSizeSelect.value = savedSize; contentTextarea.style.fontSize = savedSize + 'px'; } else { // 默认字体大小 contentTextarea.style.fontSize = '14px'; } } // 每秒更新时间 setInterval(updateTime, 1000); updateTime(); // 检查登录状态 function checkAuth() { const token = localStorage.getItem('ws01_token'); if (!token) { window.location.href = '/login'; return false; } return true; } // 显示消息 function showMessage(message, type = 'success') { const messageDiv = document.getElementById('message'); messageDiv.textContent = message; messageDiv.className = \`message \${type}\`; messageDiv.style.display = 'block'; setTimeout(() => { messageDiv.style.display = 'none'; }, 3000); } // 加载日记列表 async function loadDiaries() { try { const response = await fetch('/api/diary', { headers: { 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` } }); const result = await response.json(); if (result.success) { allDiaries = result.diaries; // 存储所有日记数据 // 检查是否有搜索条件,如果有则重新执行搜索 const searchTerm = document.getElementById('searchInput').value.trim(); if (searchTerm) { performSearch(); // 重新执行搜索 } else { filteredDiaries = [...allDiaries]; // 初始化过滤后的数据 displayDiaries(filteredDiaries); } } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('加载日记失败: ' + result.message, 'error'); } } } catch (error) { showMessage('网络错误,请重试', 'error'); } } // 显示日记列表 function displayDiaries(diaries) { const diaryList = document.getElementById('diaryList'); const searchTerm = document.getElementById('searchInput').value.trim(); if (diaries.length === 0) { if (searchTerm) { diaryList.innerHTML = '<div class="empty-state">没有找到匹配的日记</div>'; } else { diaryList.innerHTML = '<div class="empty-state">还没有日记,开始记录吧!</div>'; } return; } // 按日期分组日记 const groupedDiaries = {}; diaries.forEach(diary => { const date = new Date(diary.date).toLocaleDateString('zh-CN'); if (!groupedDiaries[date]) { groupedDiaries[date] = []; } groupedDiaries[date].push(diary); }); // 生成HTML let html = ''; const sortedDates = Object.keys(groupedDiaries).sort((a, b) => new Date(b) - new Date(a)); sortedDates.forEach(date => { html += \`<div class="diary-date-group"> <div class="date-header">\${date}</div> <div class="diary-items">\`; groupedDiaries[date].forEach(diary => { // 限制标题显示长度 const maxTitleLength = 15; const displayTitle = diary.title.length > maxTitleLength ? diary.title.substring(0, maxTitleLength) + '...' : diary.title; html += \`<div class="diary-item" onclick="viewDiary('\${diary.id}')"> <div style="display: flex; justify-content: space-between; align-items: center;"> <div class="diary-title">\${displayTitle}</div> <div class="diary-actions"> <button class="edit-btn" onclick="editDiary(event, '\${diary.id}')" title="编辑">✎</button> <button class="delete-btn" onclick="deleteDiary(event, '\${diary.id}')" title="删除">🗑</button> </div> </div> <!-- 删除时间显示 --> </div>\`; }); html += \`</div></div>\`; }); diaryList.innerHTML = html; } // 添加: 编辑日记功能 function editDiary(event, diaryId) { event.stopPropagation(); // 防止触发查看日记 // 查找要编辑的日记 fetch('/api/diary', { headers: { 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` } }) .then(response => response.json()) .then(result => { if (result.success) { const diary = result.diaries.find(d => d.id === diaryId); if (diary) { // 填充表单(反转义HTML字符) document.getElementById('diaryTitle').value = unescapeHtml(diary.title); document.getElementById('diaryContent').value = unescapeHtml(diary.content); // 保存当前编辑的日记ID到表单属性中 document.getElementById('diaryForm').setAttribute('data-edit-id', diaryId); // 更改按钮文字为"更新日记" document.querySelector('.save-btn').textContent = '更新日记'; // 滚动到表单顶部 document.querySelector('.diary-form').scrollIntoView({ behavior: 'smooth' }); } else { showMessage('找不到要编辑的日记', 'error'); } } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('加载失败: ' + result.message, 'error'); } } }) .catch(error => { showMessage('网络错误,请重试', 'error'); }); } // 添加: 删除日记功能 function deleteDiary(event, diaryId) { event.stopPropagation(); // 防止触发查看日记 if (!confirm('确定要删除这篇日记吗?')) { return; } fetch('/api/diary', { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` }, body: JSON.stringify({ id: diaryId }) }) .then(response => response.json()) .then(result => { if (result.success) { showMessage('日记删除成功!'); loadDiaries(); // 重新加载日记列表,这会更新allDiaries和filteredDiaries // 如果正在编辑被删除的日记,重置表单 const form = document.getElementById('diaryForm'); if (form.getAttribute('data-edit-id') === diaryId) { form.reset(); form.removeAttribute('data-edit-id'); document.querySelector('.save-btn').textContent = '保存日记'; } } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('删除失败: ' + result.message, 'error'); } } }) .catch(error => { showMessage('网络错误,请重试', 'error'); }); } // 保存日记 document.getElementById('diaryForm').addEventListener('submit', async (e) => { e.preventDefault(); if (!checkAuth()) return; const title = document.getElementById('diaryTitle').value; const content = document.getElementById('diaryContent').value; const editId = document.getElementById('diaryForm').getAttribute('data-edit-id'); const editShareId = document.getElementById('diaryForm').getAttribute('data-edit-share-id'); // 对HTML特殊字符进行转义 const escapedTitle = escapeHtml(title); const escapedContent = escapeHtml(content); try { const method = editId ? 'PUT' : 'POST'; const body = editId ? JSON.stringify({ id: editId, title: escapedTitle, content: escapedContent }) : JSON.stringify({ title: escapedTitle, content: escapedContent, editShareId: editShareId || null }); const response = await fetch('/api/diary', { method: method, headers: { 'Content-Type': 'application/json', 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` }, body: body }); const result = await response.json(); if (result.success) { if (editId) { showMessage('日记更新成功!'); // 重置表单状态 document.getElementById('diaryForm').removeAttribute('data-edit-id'); document.querySelector('.save-btn').textContent = '保存日记'; } else { showMessage('日记保存成功!'); document.getElementById('diaryForm').reset(); // 如果是从分享日记编辑而来,重置表单状态 if (editShareId) { document.getElementById('diaryForm').removeAttribute('data-edit-share-id'); } } // 刷新两个列表 loadDiaries(); loadSharedDiaries(); } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('保存失败: ' + result.message, 'error'); } } } catch (error) { showMessage('网络错误,请重试', 'error'); } }); // 添加: 为保存按钮图标添加点击事件监听器 document.querySelector('.save-btn-icon').addEventListener('click', function() { document.getElementById('diaryForm').dispatchEvent(new Event('submit')); }); // 添加: 为分享按钮图标添加点击事件监听器 document.querySelector('.share-btn-icon').addEventListener('click', function() { shareDiary(); }); // 分享日记功能 async function shareDiary() { if (!checkAuth()) return; const title = document.getElementById('diaryTitle').value; const content = document.getElementById('diaryContent').value; const editShareId = document.getElementById('diaryForm').getAttribute('data-edit-share-id'); const editDiaryId = document.getElementById('diaryForm').getAttribute('data-edit-id'); if (!title.trim() || !content.trim()) { showMessage('请先填写标题和内容', 'error'); return; } // 对HTML特殊字符进行转义 const escapedTitle = escapeHtml(title); const escapedContent = escapeHtml(content); try { const method = editShareId ? 'PUT' : 'POST'; const body = editShareId ? JSON.stringify({ shareId: editShareId, title: escapedTitle, content: escapedContent }) : JSON.stringify({ title: escapedTitle, content: escapedContent, editDiaryId: editDiaryId || null }); const response = await fetch('/api/diary/share', { method: method, headers: { 'Content-Type': 'application/json', 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` }, body: body }); const result = await response.json(); if (result.success) { if (editShareId) { // 更新分享日记 const shareUrl = \`\${window.location.origin}/share/\${editShareId}\`; showMessage(\`分享日记更新成功!链接:\${shareUrl}\`, 'success'); // 重置表单状态 document.getElementById('diaryForm').removeAttribute('data-edit-share-id'); } else { // 新建分享日记 const shareUrl = \`\${window.location.origin}/share/\${result.shareId}\`; showMessage(\`分享成功!链接:\${shareUrl}\`, 'success'); // 如果是从我的日记编辑而来,重置表单状态 if (editDiaryId) { document.getElementById('diaryForm').removeAttribute('data-edit-id'); } } // 可选:复制链接到剪贴板 if (navigator.clipboard) { const shareUrl = editShareId ? \`\${window.location.origin}/share/\${editShareId}\` : \`\${window.location.origin}/share/\${result.shareId}\`; navigator.clipboard.writeText(shareUrl).then(() => { console.log('分享链接已复制到剪贴板'); }); } // 刷新两个列表 loadDiaries(); loadSharedDiaries(); } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('分享失败: ' + result.message, 'error'); } } } catch (error) { showMessage('网络错误,请重试', 'error'); } } // 查看具体日记页面 function viewDiary(diaryId) { window.location.href = \`/diary/\${diaryId}\`; } // 退出登录 function logout() { localStorage.removeItem('ws01_token'); window.location.href = '/login'; } // 添加备份相关函数 async function createBackup() { try { const response = await fetch('/api/diary/backup', { method: 'POST', headers: { 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` } }); const result = await response.json(); if (result.success) { showMessage('备份创建成功!'); loadBackups(); } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('备份创建失败: ' + result.message, 'error'); } } } catch (error) { showMessage('网络错误,请重试', 'error'); } } async function loadBackups() { try { const response = await fetch('/api/diary/backups', { headers: { 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` } }); const result = await response.json(); if (result.success) { displayBackups(result.backups); } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('加载备份列表失败: ' + result.message, 'error'); } } } catch (error) { showMessage('网络错误,请重试', 'error'); } } function displayBackups(backups) { const backupList = document.getElementById('backupList'); if (backups.length === 0) { backupList.innerHTML = '<div class="empty-state">暂无备份</div>'; return; } let html = ''; backups.forEach(backup => { const date = new Date(backup.timestamp).toLocaleString('zh-CN'); html += \` <div class="backup-item"> <div> <div>备份 #\${backup.id}</div> <div style="font-size: 0.8rem; color: #666;">\${date}</div> <div style="font-size: 0.8rem; color: #666;">包含 \${backup.count} 条日记\${backup.shareCount > 0 ? ',' + backup.shareCount + ' 条分享日记' : ''}</div> </div> <div> <button class="restore-btn" onclick="restoreBackup('\${backup.id}')">恢复</button> <button class="delete-backup-btn" onclick="deleteBackup('\${backup.id}')">删除</button> </div> </div>\`; }); backupList.innerHTML = html; } async function restoreBackup(backupId) { if (!confirm('确定要恢复此备份吗?这将覆盖当前所有日记数据!')) { return; } try { const response = await fetch('/api/diary/restore', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` }, body: JSON.stringify({ backupId }) }); const result = await response.json(); if (result.success) { showMessage('数据恢复成功!'); loadDiaries(); // 重新加载日记列表 } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('数据恢复失败: ' + result.message, 'error'); } } } catch (error) { showMessage('网络错误,请重试', 'error'); } } async function deleteBackup(backupId) { if (!confirm('确定要删除此备份吗?')) { return; } try { const response = await fetch('/api/diary/backup', { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` }, body: JSON.stringify({ backupId }) }); const result = await response.json(); if (result.success) { showMessage('备份删除成功!'); loadBackups(); // 重新加载备份列表 } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('备份删除失败: ' + result.message, 'error'); } } } catch (error) { showMessage('网络错误,请重试', 'error'); } } // 搜索相关变量 let allDiaries = []; // 存储所有日记数据 let filteredDiaries = []; // 存储过滤后的日记数据 // 搜索功能 async function performSearch() { const searchTerm = document.getElementById('searchInput').value.toLowerCase().trim(); const clearBtn = document.getElementById('clearSearch'); if (searchTerm === '') { // 如果搜索框为空,显示所有日记 filteredDiaries = [...allDiaries]; clearBtn.classList.add('hidden'); displayDiaries(filteredDiaries); } else { // 搜索我的日记 const myDiaryResults = allDiaries.filter(diary => diary.title.toLowerCase().includes(searchTerm) || diary.content.toLowerCase().includes(searchTerm) ); // 搜索分享日记 try { const response = await fetch('/api/diary/shares', { headers: { 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` } }); if (response.ok) { const result = await response.json(); const shareResults = result.shares.filter(share => share.title.toLowerCase().includes(searchTerm) || share.content.toLowerCase().includes(searchTerm) ); // 合并搜索结果 const combinedResults = [ ...myDiaryResults.map(diary => ({ ...diary, type: 'private' })), ...shareResults.map(share => ({ ...share, type: 'shared' })) ]; // 按日期排序 combinedResults.sort((a, b) => new Date(b.date) - new Date(a.date)); filteredDiaries = combinedResults; } else { // 如果获取分享日记失败,只显示我的日记搜索结果 filteredDiaries = myDiaryResults.map(diary => ({ ...diary, type: 'private' })); } } catch (error) { // 如果网络错误,只显示我的日记搜索结果 filteredDiaries = myDiaryResults.map(diary => ({ ...diary, type: 'private' })); } clearBtn.classList.remove('hidden'); displaySearchResults(filteredDiaries); } } // 显示搜索结果(包含我的日记和分享日记) function displaySearchResults(results) { const diaryList = document.getElementById('diaryList'); const searchTerm = document.getElementById('searchInput').value.trim(); if (results.length === 0) { diaryList.innerHTML = '<div class="empty-state">没有找到匹配的日记</div>'; return; } // 按日期分组日记 const groupedDiaries = {}; results.forEach(diary => { const date = new Date(diary.date).toLocaleDateString('zh-CN'); if (!groupedDiaries[date]) { groupedDiaries[date] = []; } groupedDiaries[date].push(diary); }); // 生成HTML let html = ''; const sortedDates = Object.keys(groupedDiaries).sort((a, b) => new Date(b) - new Date(a)); sortedDates.forEach(date => { html += \`<div class="diary-date-group"> <div class="date-header">\${date}</div> <div class="diary-items">\`; groupedDiaries[date].forEach(diary => { // 限制标题显示长度 const maxTitleLength = 15; const displayTitle = diary.title.length > maxTitleLength ? diary.title.substring(0, maxTitleLength) + '...' : diary.title; // 根据类型显示不同的操作按钮 let actionButtons = ''; if (diary.type === 'private') { actionButtons = \` <div class="diary-actions"> <button class="edit-btn" onclick="editDiary(event, '\${diary.id}')" title="编辑">✎</button> <button class="delete-btn" onclick="deleteDiary(event, '\${diary.id}')" title="删除">🗑</button> </div>\`; } else if (diary.type === 'shared') { actionButtons = \` <div class="diary-actions"> <button class="edit-btn" onclick="editSharedDiary(event, '\${diary.id}')" title="编辑">✎</button> <button class="delete-btn" onclick="deleteSharedDiary(event, '\${diary.id}')" title="删除">🗑</button> </div>\`; } // 根据类型设置点击事件 const clickEvent = diary.type === 'private' ? \`onclick="viewDiary('\${diary.id}')"\` : \`onclick="viewSharedDiary('\${diary.shareUrl}')"\`; html += \`<div class="diary-item" \${clickEvent}> <div style="display: flex; justify-content: space-between; align-items: center;"> <div class="diary-title">\${displayTitle} \${diary.type === 'shared' ? '<span style="color: #28a745; font-size: 0.7rem;">[分享]</span>' : ''}</div> \${actionButtons} </div> </div>\`; }); html += \`</div></div>\`; }); diaryList.innerHTML = html; } // 清除搜索 function clearSearch() { document.getElementById('searchInput').value = ''; document.getElementById('clearSearch').classList.add('hidden'); filteredDiaries = [...allDiaries]; displayDiaries(filteredDiaries); } // 加载分享日记列表 async function loadSharedDiaries() { try { const response = await fetch('/api/diary/shares', { headers: { 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` } }); const result = await response.json(); if (result.success) { displaySharedDiaries(result.shares); } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('加载分享列表失败: ' + result.message, 'error'); } } } catch (error) { showMessage('网络错误,请重试', 'error'); } } // 显示分享日记列表 function displaySharedDiaries(shares) { const shareList = document.getElementById('shareList'); if (shares.length === 0) { shareList.innerHTML = '<div class="empty-state">暂无分享日记</div>'; return; } // 按日期分组分享日记 const groupedShares = {}; shares.forEach(share => { const date = new Date(share.date).toLocaleDateString('zh-CN'); if (!groupedShares[date]) { groupedShares[date] = []; } groupedShares[date].push(share); }); // 生成HTML let html = ''; const sortedDates = Object.keys(groupedShares).sort((a, b) => new Date(b) - new Date(a)); sortedDates.forEach(date => { html += \`<div class="share-date-group"> <div class="date-header">\${date}</div> <div class="share-items">\`; groupedShares[date].forEach(share => { // 限制标题显示长度 const maxTitleLength = 15; const displayTitle = share.title.length > maxTitleLength ? share.title.substring(0, maxTitleLength) + '...' : share.title; html += \`<div class="share-item" onclick="viewSharedDiary('\${share.shareUrl}')"> <div style="display: flex; justify-content: space-between; align-items: center;"> <div class="share-title">\${displayTitle}</div> <div class="share-actions"> <button class="edit-share-btn" onclick="editSharedDiary(event, '\${share.id}')" title="编辑">✎</button> <button class="delete-share-btn" onclick="deleteSharedDiary(event, '\${share.id}')" title="删除">🗑</button> </div> </div> </div>\`; }); html += \`</div></div>\`; }); shareList.innerHTML = html; } // 查看分享日记 function viewSharedDiary(shareUrl) { window.open(shareUrl, '_blank'); } // 编辑分享日记 async function editSharedDiary(event, shareId) { event.stopPropagation(); // 防止触发查看日记 try { // 获取分享日记内容 const response = await fetch(\`/api/share/\${shareId}\`); if (response.ok) { const shareData = await response.json(); // 填充表单(反转义HTML字符) document.getElementById('diaryTitle').value = unescapeHtml(shareData.title); document.getElementById('diaryContent').value = unescapeHtml(shareData.content); // 保存当前编辑的分享ID到表单属性中 document.getElementById('diaryForm').setAttribute('data-edit-share-id', shareId); // 滚动到表单顶部 document.querySelector('.diary-form').scrollIntoView({ behavior: 'smooth' }); showMessage('分享日记已加载到编辑区域,修改后点击分享按钮更新', 'success'); } else { showMessage('加载分享日记失败', 'error'); } } catch (error) { showMessage('网络错误,请重试', 'error'); } } // 删除分享日记 async function deleteSharedDiary(event, shareId) { event.stopPropagation(); // 防止触发查看日记 if (!confirm('确定要删除这个分享日记吗?')) { return; } try { const response = await fetch('/api/diary/share', { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` }, body: JSON.stringify({ shareId }) }); const result = await response.json(); if (result.success) { showMessage('分享日记删除成功!'); loadSharedDiaries(); // 重新加载分享列表 } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('删除失败: ' + result.message, 'error'); } } } catch (error) { showMessage('网络错误,请重试', 'error'); } } // 页面加载时检查认证并加载日记和备份 if (checkAuth()) { loadDiaries(); loadBackups(); // 加载备份列表 loadSharedDiaries(); // 加载分享日记列表 } // 添加内容输入事件监听器 document.addEventListener('DOMContentLoaded', function() { const contentTextarea = document.getElementById('diaryContent'); const fontSizeSelect = document.getElementById('fontSizeSelect'); const searchInput = document.getElementById('searchInput'); const clearSearchBtn = document.getElementById('clearSearch'); if (contentTextarea) { contentTextarea.addEventListener('input', updateCharCount); } if (fontSizeSelect) { fontSizeSelect.addEventListener('change', changeFontSize); } // 添加搜索功能事件监听器 if (searchInput) { searchInput.addEventListener('input', performSearch); } if (clearSearchBtn) { clearSearchBtn.addEventListener('click', clearSearch); } // 加载保存的字体大小 loadFontSize(); }); </script> </body> </html>`; } // 日记详情页面 function getDiaryDetailPage(diaryId) { return ` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WS01 Note - 日记详情</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f8f9fa; min-height: 100vh; } .sites { width:1000px; background:#F2F2F2; border:1px solid rgba(0,0,0,.06); margin:10px auto; padding:0px; color: white; border-radius: 6px; } .sites01 { 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:#c5dff6; border-top:1px solid #ffffff; border-bottom:1px solid #ffffff; } .sites dl.alt2 { background:#dcecfa; 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; } .header { background: #D4D4D4; padding: 0.8rem 1.5rem; box-shadow: 0 1px 6px rgba(0,0,0,0.08); display: flex; justify-content: center; align-items: center; position: relative; } .logo { font-size: 1.5rem; font-weight: 400; color: #333; } .back-btn { background: #1C86EE; color: white; border: none; padding: 0.4rem 0.8rem; border-radius: 4px; cursor: pointer; font-size: 0.8rem; position: absolute; right: 2.6rem; } .back-btn:hover { background: #1874CD; } .header-controls { display: flex; align-items: center; gap: 0.5rem; } .font-size-select { padding: 0.3rem 0.5rem; border: 1px solid #e1e5e9; border-radius: 4px; font-size: 0.8rem; background: white; cursor: pointer; transition: border-color 0.3s; } .font-size-select:focus { outline: none; border-color: #667eea; } .font-size-select:hover { border-color: #667eea; } .container { max-width: 960px; margin: 1.5rem auto; padding: 0 1.5rem; } .diary-detail { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); max-height: 900px; overflow-y: auto; } .diary-header { border-bottom: 1px solid #e9ecef; padding-bottom: 0.8rem; margin-bottom: 1.2rem; } .diary-title { font-size: 1.5rem; color: #333; margin-bottom: 0.4rem; font-weight: 600; word-wrap: break-word; } .diary-date { color: #666; font-size: 0.8rem; } .diary-content { color: #333; line-height: 1.6; white-space: pre-wrap; font-size: 0.9rem; word-wrap: break-word; overflow-wrap: break-word; word-break: break-all; border: 1px dashed #ccc; padding: 0.8rem; border-radius: 4px; } .loading { text-align: center; padding: 2rem; color: #666; } .error { text-align: center; padding: 2rem; color: #e74c3c; } /* 添加复制按钮样式 */ .copy-btn { background: #007bff; color: white; border: none; padding: 0.4rem 0.8rem; border-radius: 4px; cursor: pointer; font-size: 0.8rem; margin-left: 0.8rem; } .copy-btn:hover { background: #0056b3; } .title-container { display: flex; align-items: center; flex-wrap: wrap; } .notification { position: fixed; top: 15px; right: 15px; background: #28a745; color: white; padding: 0.8rem; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.12); display: none; z-index: 1000; font-size: 0.8rem; } </style> </head> <body> <div class="sites"> <div class="header"> <div class="logo">WS01 Note</div> <div class="header-controls"> <select id="fontSizeSelect" class="font-size-select" title="选择字体大小"> <option value="12">12px</option> <option value="14">14px</option> <option value="16" selected>16px</option> <option value="18">18px</option> <option value="20">20px</option> <option value="22">22px</option> </select> <a href="/diary" class="back-btn">← 返回</a> </div> </div> <div class="container"> <div id="diaryDetail" class="diary-detail"> <div class="loading">加载中...</div> </div> </div> <div id="notification" class="notification">内容已复制到剪贴板</div> <script> const diaryId = '${diaryId}'; // 检查登录状态 function checkAuth() { const token = localStorage.getItem('ws01_token'); if (!token) { window.location.href = '/login'; return false; } return true; } // 加载日记详情 async function loadDiaryDetail() { if (!checkAuth()) return; try { const response = await fetch('/api/diary', { headers: { 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` } }); const result = await response.json(); if (result.success) { const diary = result.diaries.find(d => d.id === diaryId); if (diary) { displayDiaryDetail(diary); } else { showError('日记不存在'); } } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showError('加载失败: ' + result.message); } } } catch (error) { showError('网络错误,请重试'); } } // 显示日记详情 function displayDiaryDetail(diary) { const diaryDetail = document.getElementById('diaryDetail'); const date = new Date(diary.date).toLocaleString('zh-CN'); // 限制标题显示长度 const maxTitleLength = 15; const displayTitle = diary.title.length > maxTitleLength ? diary.title.substring(0, maxTitleLength) + '...' : diary.title; diaryDetail.innerHTML = \` <div class="diary-header"> <div class="title-container"> <h1 class="diary-title">\${displayTitle}</h1> <button class="copy-btn" onclick="copyContent('\${diary.content.replace(/'/g, "\\'").replace(/\\n/g, '\\\\n')}')">复制内容</button> </div> <div class="diary-date">\${date}</div> </div> <div class="diary-content" id="diaryContent"></div> \`; // 使用textContent设置内容,避免HTML标签被解析 document.getElementById('diaryContent').textContent = diary.content; // 应用保存的字体大小 loadFontSize(); } // 添加: 字号选择功能 function changeFontSize() { const fontSizeSelect = document.getElementById('fontSizeSelect'); const contentDiv = document.getElementById('diaryContent'); const selectedSize = fontSizeSelect.value; if (contentDiv) { // 应用字体大小到内容区域 contentDiv.style.fontSize = selectedSize + 'px'; // 保存用户选择到本地存储 localStorage.setItem('ws01_detail_font_size', selectedSize); } } // 添加: 加载保存的字体大小 function loadFontSize() { const savedSize = localStorage.getItem('ws01_detail_font_size'); const fontSizeSelect = document.getElementById('fontSizeSelect'); const contentDiv = document.getElementById('diaryContent'); if (savedSize && fontSizeSelect && contentDiv) { fontSizeSelect.value = savedSize; contentDiv.style.fontSize = savedSize + 'px'; } else if (contentDiv) { // 默认字体大小16px contentDiv.style.fontSize = '16px'; } } // 显示错误 function showError(message) { const diaryDetail = document.getElementById('diaryDetail'); diaryDetail.innerHTML = \`<div class="error">\${message}</div>\`; } // 复制内容功能 function copyContent(content) { navigator.clipboard.writeText(content).then(() => { const notification = document.getElementById('notification'); notification.style.display = 'block'; setTimeout(() => { notification.style.display = 'none'; }, 100000); }).catch(err => { console.error('复制失败:', err); const notification = document.getElementById('notification'); notification.textContent = '复制失败'; notification.style.backgroundColor = '#dc3545'; notification.style.display = 'block'; setTimeout(() => { notification.style.display = 'none'; // 恢复默认文本和颜色 notification.textContent = '内容已复制到剪贴板'; notification.style.backgroundColor = '#28a745'; }, 100000); }); } // 复制分享内容功能 function copyShareContent() { if (window.shareContent) { copyContent(window.shareContent); } else { // 如果全局变量不存在,尝试从DOM元素获取 const contentElement = document.getElementById('shareContent'); if (contentElement) { copyContent(contentElement.textContent); } else { console.error('无法获取分享内容'); const notification = document.getElementById('notification'); notification.textContent = '复制失败:无法获取内容'; notification.style.backgroundColor = '#dc3545'; notification.style.display = 'block'; setTimeout(() => { notification.style.display = 'none'; }, 3000); } } } // 页面加载时加载日记详情 loadDiaryDetail(); // 添加字号选择事件监听器 document.addEventListener('DOMContentLoaded', function() { const fontSizeSelect = document.getElementById('fontSizeSelect'); if (fontSizeSelect) { fontSizeSelect.addEventListener('change', changeFontSize); } }); </script> </body> </html>`; } // 分享日记目录页面 function getShareIndexPage() { return ` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WS01 Note - 分享目录</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f8f9fa; min-height: 100vh; } .sites { width: 1000px; background: #F2F2F2; border: 1px solid rgba(0,0,0,.06); margin: 10px auto; padding: 0px; color: white; border-radius: 6px; } .header { background: #D4D4D4; padding: 0.8rem 1.5rem; box-shadow: 0 1px 6px rgba(0,0,0,0.08); display: flex; justify-content: center; align-items: center; position: relative; } .logo { font-size: 1.5rem; font-weight: 400; color: #333; } .back-btn { background: #1C86EE; color: white; border: none; padding: 0.4rem 0.8rem; border-radius: 4px; cursor: pointer; font-size: 0.8rem; position: absolute; right: 2.6rem; text-decoration: none; display: inline-block; } .back-btn:hover { background: #1874CD; } .container { max-width: 960px; margin: 1.5rem auto; padding: 0 1.5rem; } .share-index { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); } .share-index-header { border-bottom: 1px solid #e9ecef; padding-bottom: 0.8rem; margin-bottom: 1.2rem; } .share-index-title { font-size: 1.5rem; color: #333; margin-bottom: 0.4rem; font-weight: 600; } .share-index-subtitle { color: #666; font-size: 0.9rem; } .share-list { display: grid; gap: 1rem; } .share-date-group { margin-bottom: 1.5rem; } .date-header { background: #e9ecef; padding: 0.4rem 0.8rem; font-weight: 600; color: #495057; border-bottom: 1px solid #dee2e6; font-size: 0.9rem; position: sticky; top: 0; z-index: 1; border-radius: 4px 4px 0 0; } .share-items { background: white; border: 1px solid #e9ecef; border-top: none; border-radius: 0 0 4px 4px; } .share-item { padding: 1rem; border-bottom: 1px solid #f0f0f0; cursor: pointer; transition: background-color 0.2s; display: flex; justify-content: space-between; align-items: center; } .share-item:hover { background-color: #f8f9fa; } .share-item:last-child { border-bottom: none; } .share-item-info { flex: 1; min-width: 0; } .share-item-title { color: #333; font-size: 1rem; font-weight: 500; margin-bottom: 0.3rem; line-height: 1.4; } .share-item-preview { color: #666; font-size: 0.85rem; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-bottom: 0.3rem; } .share-item-date { color: #999; font-size: 0.75rem; } .share-item-actions { display: flex; gap: 0.5rem; margin-left: 1rem; } .view-btn { background: #007bff; color: white; border: none; padding: 0.4rem 0.8rem; border-radius: 4px; cursor: pointer; font-size: 0.8rem; text-decoration: none; display: inline-block; } .view-btn:hover { background: #0056b3; } .loading { text-align: center; padding: 2rem; color: #666; } .error { text-align: center; padding: 2rem; color: #e74c3c; } .empty-state { text-align: center; padding: 3rem; color: #666; } .empty-state-icon { font-size: 3rem; margin-bottom: 1rem; opacity: 0.5; } /* 分页控件样式 */ .pagination { display: flex; justify-content: center; align-items: center; margin-top: 2rem; padding: 1rem 0; border-top: 1px solid #e9ecef; } .pagination-info { margin-right: 1rem; color: #666; font-size: 0.9rem; } .pagination-controls { display: flex; gap: 0.5rem; align-items: center; } .pagination-btn { background: #007bff; color: white; border: none; padding: 0.5rem 0.8rem; border-radius: 4px; cursor: pointer; font-size: 0.9rem; transition: background-color 0.2s; } .pagination-btn:hover:not(:disabled) { background: #0056b3; } .pagination-btn:disabled { background: #6c757d; cursor: not-allowed; opacity: 0.6; } .pagination-current { background: #28a745; color: white; border: none; padding: 0.5rem 0.8rem; border-radius: 4px; font-size: 0.9rem; font-weight: 600; } .pagination-jump { display: flex; align-items: center; gap: 0.5rem; margin-left: 1rem; color: blue; } .pagination-jump input { width: 60px; padding: 0.4rem; border: 1px solid #ced4da; border-radius: 4px; text-align: center; font-size: 0.9rem; } .pagination-jump button { background: #6c757d; color: white; border: none; padding: 0.4rem 0.6rem; border-radius: 4px; cursor: pointer; font-size: 0.8rem; } .pagination-jump button:hover { background: #5a6268; } .footer { font-size: 14px; color: #292929; margin: 15px auto; text-align: center; } /* 响应式设计 */ @media (max-width: 768px) { .sites { width: 100%; margin: 0; border-radius: 0; } .container { margin: 0.5rem auto; padding: 0 0.5rem; } .share-index { padding: 0.5rem; margin: 0; } .share-item { flex-direction: column; align-items: flex-start; gap: 0.5rem; padding: 0.5rem; } .share-item-info { width: 100%; overflow: hidden; } .share-item-title { font-size: 0.9rem; word-break: break-word; overflow-wrap: break-word; } .share-item-preview { font-size: 0.8rem; -webkit-line-clamp: 3; } .share-item-date { font-size: 0.7rem; } .share-item-actions { margin-left: 0; align-self: flex-end; flex-shrink: 0; } .view-btn { padding: 0.3rem 0.6rem; font-size: 0.7rem; } .pagination { flex-direction: column; gap: 1rem; padding: 0.5rem 0; } .pagination-info { margin-right: 0; margin-bottom: 0.5rem; text-align: center; font-size: 0.8rem; } .pagination-controls { flex-wrap: wrap; justify-content: center; gap: 0.3rem; } .pagination-btn { padding: 0.4rem 0.6rem; font-size: 0.8rem; min-width: 40px; } .pagination-current { padding: 0.4rem 0.6rem; font-size: 0.8rem; min-width: 40px; } .pagination-jump { margin-left: 0; margin-top: 0.5rem; flex-wrap: wrap; justify-content: center; gap: 0.3rem; } .pagination-jump input { width: 50px; padding: 0.3rem; font-size: 0.8rem; } .pagination-jump button { padding: 0.3rem 0.5rem; font-size: 0.7rem; } .pagination-jump span { font-size: 0.8rem; } /* 确保所有元素不会溢出 */ * { max-width: 100%; box-sizing: border-box; } .share-item-title, .share-item-preview { word-break: break-word; overflow-wrap: break-word; hyphens: auto; } .share-date-group { margin-bottom: 1rem; } .date-header { font-size: 0.8rem; padding: 0.3rem 0.5rem; } } </style> </head> <body> <div class="sites"> <div class="header"> <div class="logo">WS01 Note - 分享目录</div> </div> <div class="container"> <div class="share-index"> <div class="share-index-header"> <h1 class="share-index-title">分享文章目录</h1> <p class="share-index-subtitle">这里展示所有公开分享的文章,点击标题可查看完整内容。复制的分享内容可能有改变。</p> </div> <div id="shareList" class="share-list"> <div class="loading">加载中...</div> </div> <div id="pagination" class="pagination" style="display: none;"> <div class="pagination-info" id="paginationInfo"></div> <div class="pagination-controls"> <button class="pagination-btn" id="prevBtn" onclick="changePage(currentPage - 1)">上一页</button> <div id="pageNumbers"></div> <button class="pagination-btn" id="nextBtn" onclick="changePage(currentPage + 1)">下一页</button> </div> <div class="pagination-jump"> <span>跳转到</span> <input type="number" id="jumpInput" min="1" placeholder="">页 <button onclick="jumpToPage()">跳转</button> </div> </div> </div> </div> <div class="footer"> <span id="timeDate">载入天数...</span> <script language="javascript"> var now = new Date(); function createtime(){ var grt= new Date("10/12/2025 00:00:00");/*---这里是网站的启用时间:月日年--*/ now.setTime(now.getTime()+250); days = (now - grt ) / 1000 / 60 / 60 / 24; dnum = Math.floor(days); document.getElementById("timeDate").innerHTML = "稳定运行"+dnum+"天"; } setInterval("createtime()",250); </script> <span <p> | 本页总访问量 <span id="busuanzi_site_pv"></span> 次 | <a href="/" target="_blank">登录</p></span> <script defer src="https://bsz.211119.xyz/js"></script> <script> // 全局变量 let currentPage = 1; let totalPages = 1; let totalCount = 0; const pageSize = 20; // 从URL获取当前页码 function getCurrentPageFromUrl() { const urlParams = new URLSearchParams(window.location.search); return parseInt(urlParams.get('page')) || 1; } // 更新URL function updateUrl(page) { const url = new URL(window.location); if (page > 1) { url.searchParams.set('page', page); } else { url.searchParams.delete('page'); } window.history.replaceState({}, '', url); } // 加载所有分享日记 async function loadAllShares(page = 1) { try { currentPage = page; const response = await fetch(\`/api/shares?page=\${page}&limit=\${pageSize}\`); if (response.ok) { const result = await response.json(); displayAllShares(result.shares); updatePagination(result.pagination); updateUrl(page); } else { showError('加载失败,请稍后重试'); } } catch (error) { showError('网络错误,请重试'); } } // 显示所有分享日记 function displayAllShares(shares) { const shareList = document.getElementById('shareList'); if (shares.length === 0) { shareList.innerHTML = \` <div class="empty-state"> <div class="empty-state-icon">📝</div> <div>暂无分享日记</div> </div>\`; return; } // 按日期分组分享日记 const groupedShares = {}; shares.forEach(share => { const date = new Date(share.date).toLocaleDateString('zh-CN'); if (!groupedShares[date]) { groupedShares[date] = []; } groupedShares[date].push(share); }); // 生成HTML let html = ''; const sortedDates = Object.keys(groupedShares).sort((a, b) => new Date(b) - new Date(a)); sortedDates.forEach(date => { html += \`<div class="share-date-group"> <div class="date-header">\${date}</div> <div class="share-items">\`; groupedShares[date].forEach(share => { // 生成内容预览(前100个字符) const preview = share.content.length > 100 ? share.content.substring(0, 100) + '...' : share.content; html += \`<div class="share-item" onclick="viewShare('\${share.shareUrl}')"> <div class="share-item-info"> <div class="share-item-title">\${share.title}</div> <div class="share-item-preview">\${preview}</div> <div class="share-item-date">\${new Date(share.date).toLocaleString('zh-CN')}</div> </div> <div class="share-item-actions"> <a href="\${share.shareUrl}" class="view-btn" onclick="event.stopPropagation()">查看</a> </div> </div>\`; }); html += \`</div></div>\`; }); shareList.innerHTML = html; } // 查看分享日记 function viewShare(shareUrl) { window.open(shareUrl, '_blank'); } // 显示错误 function showError(message) { const shareList = document.getElementById('shareList'); shareList.innerHTML = \`<div class="error">\${message}</div>\`; } // 更新分页控件 function updatePagination(pagination) { const paginationDiv = document.getElementById('pagination'); const paginationInfo = document.getElementById('paginationInfo'); const pageNumbers = document.getElementById('pageNumbers'); const prevBtn = document.getElementById('prevBtn'); const nextBtn = document.getElementById('nextBtn'); currentPage = pagination.currentPage; totalPages = pagination.totalPages; totalCount = pagination.totalCount; // 更新分页信息 const startItem = (currentPage - 1) * pageSize + 1; const endItem = Math.min(currentPage * pageSize, totalCount); paginationInfo.textContent = \`显示 \${startItem}-\${endItem} 条,共 \${totalCount} 条记录\`; // 更新按钮状态 prevBtn.disabled = !pagination.hasPrev; nextBtn.disabled = !pagination.hasNext; // 生成页码按钮 let pageHtml = ''; const maxVisiblePages = 5; let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2)); let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1); if (endPage - startPage + 1 < maxVisiblePages) { startPage = Math.max(1, endPage - maxVisiblePages + 1); } if (startPage > 1) { pageHtml += \`<button class="pagination-btn" onclick="changePage(1)">1</button>\`; if (startPage > 2) { pageHtml += \`<span style="padding: 0.5rem;">...</span>\`; } } for (let i = startPage; i <= endPage; i++) { if (i === currentPage) { pageHtml += \`<button class="pagination-current">\${i}</button>\`; } else { pageHtml += \`<button class="pagination-btn" onclick="changePage(\${i})">\${i}</button>\`; } } if (endPage < totalPages) { if (endPage < totalPages - 1) { pageHtml += \`<span style="padding: 0.5rem;">...</span>\`; } pageHtml += \`<button class="pagination-btn" onclick="changePage(\${totalPages})">\${totalPages}</button>\`; } pageNumbers.innerHTML = pageHtml; // 显示分页控件 if (totalPages > 1) { paginationDiv.style.display = 'flex'; } else { paginationDiv.style.display = 'none'; } } // 切换页面 function changePage(page) { if (page >= 1 && page <= totalPages && page !== currentPage) { loadAllShares(page); // 滚动到顶部 window.scrollTo({ top: 0, behavior: 'smooth' }); } } // 跳转到指定页面 function jumpToPage() { const jumpInput = document.getElementById('jumpInput'); const page = parseInt(jumpInput.value); if (page >= 1 && page <= totalPages) { changePage(page); jumpInput.value = ''; } else { alert(\`请输入 1 到 \${totalPages} 之间的页码\`); } } // 页面加载时加载所有分享日记 loadAllShares(getCurrentPageFromUrl()); </script> </body> </html>`; } // 分享页面 function getSharePage(shareId) { return ` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WS01 Note - 分享内容</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f8f9fa; min-height: 100vh; } .sites { width: 1000px; background: #F2F2F2; border: 1px solid rgba(0,0,0,.06); margin: 10px auto; padding: 0px; color: white; border-radius: 6px; } .header { background: #D4D4D4; padding: 0.8rem 1.5rem; box-shadow: 0 1px 6px rgba(0,0,0,0.08); display: flex; justify-content: center; align-items: center; position: relative; } .logo { font-size: 1.5rem; font-weight: 400; color: #333; } .back-btn { background: #1C86EE; color: white; border: none; padding: 0.4rem 0.8rem; border-radius: 4px; cursor: pointer; font-size: 0.8rem; position: absolute; right: 2.6rem; text-decoration: none; display: inline-block; } .back-btn:hover { background: #1874CD; } .container { max-width: 960px; margin: 1.5rem auto; padding: 0 1.5rem; } .diary-detail { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); max-height: 900px; overflow-y: auto; } .diary-header { border-bottom: 1px solid #e9ecef; padding-bottom: 0.8rem; margin-bottom: 1.2rem; } .diary-title { font-size: 1.5rem; color: #333; margin-bottom: 0.4rem; font-weight: 600; word-wrap: break-word; } .diary-date { color: #666; font-size: 0.8rem; } .diary-content { color: #333; line-height: 1.6; white-space: pre-wrap; font-size: 0.9rem; word-wrap: break-word; overflow-wrap: break-word; word-break: break-all; border: 1px dashed #ccc; padding: 0.8rem; border-radius: 4px; } .loading { text-align: center; padding: 2rem; color: #666; } .error { text-align: center; padding: 2rem; color: #e74c3c; } .share-notice { background: #e7f3ff; border: 1px solid #b3d9ff; border-radius: 4px; padding: 0.8rem; margin-bottom: 1rem; color: #0066cc; font-size: 0.9rem; } .notification { position: fixed; top: 15px; right: 15px; background: #28a745; color: white; padding: 0.8rem; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.12); display: none; z-index: 1000; font-size: 0.9rem; max-width: 300px; } .footer { font-size: 14px; color: #292929; margin: 15px auto; text-align: center; } /* 响应式设计 */ @media (max-width: 768px) { .sites { width: 95%; margin: 5px auto; } .container { margin: 1rem auto; padding: 0 1rem; } .diary-detail { padding: 1rem; } } </style> </head> <body> <div class="sites"> <div class="header"> <div class="logo">WS01 Note - 分享内容</div> <a href="/share" class="back-btn">← 返回分享目录</a> </div> <div class="container"> <div class="share-notice"> 📢 这是一篇分享的文章,任何人都可以查看 </div> <div id="diaryDetail" class="diary-detail"> <div class="loading">加载中...</div> </div> <!-- 通知元素 --> <div id="notification" class="notification">内容已复制到剪贴板</div> </div> <div class="footer"> <span id="timeDate">载入天数...</span> <script language="javascript"> var now = new Date(); function createtime(){ var grt= new Date("10/12/2025 00:00:00");/*---这里是网站的启用时间:月日年--*/ now.setTime(now.getTime()+250); days = (now - grt ) / 1000 / 60 / 60 / 24; dnum = Math.floor(days); document.getElementById("timeDate").innerHTML = "稳定运行"+dnum+"天"; } setInterval("createtime()",250); </script> <span <p> | 本页总访问量 <span id="busuanzi_site_pv"></span> 次 | <a href="/" target="_blank">登录</p></span> <script defer src="https://bsz.211119.xyz/js"></script> <script> const shareId = '${shareId}'; // 加载分享日记 async function loadSharedDiary() { try { const response = await fetch(\`/api/share/\${shareId}\`); if (response.ok) { const diary = await response.json(); displaySharedDiary(diary); } else if (response.status === 404) { showError('分享的日记不存在或已被删除'); } else { showError('加载失败,请稍后重试'); } } catch (error) { showError('网络错误,请重试'); } } // 显示分享日记 function displaySharedDiary(diary) { const diaryDetail = document.getElementById('diaryDetail'); const date = new Date(diary.date).toLocaleString('zh-CN'); diaryDetail.innerHTML = \` <div class="diary-header"> <div class="title-container"> <h1 class="diary-title">\${diary.title}</h1> <button class="copy-btn" onclick="copyShareContent()">复制内容</button> </div> <div class="diary-date">\${date}</div> </div> <div class="diary-content" id="shareContent"></div> \`; // 使用textContent设置内容,避免HTML标签被解析 document.getElementById('shareContent').textContent = diary.content; // 将原始内容存储到全局变量中,供复制功能使用 window.shareContent = diary.content; } // 显示错误 function showError(message) { const diaryDetail = document.getElementById('diaryDetail'); diaryDetail.innerHTML = \`<div class="error">\${message}</div>\`; } // 复制内容功能 function copyContent(content) { navigator.clipboard.writeText(content).then(() => { const notification = document.getElementById('notification'); notification.style.display = 'block'; setTimeout(() => { notification.style.display = 'none'; }, 3000); }).catch(err => { console.error('复制失败:', err); const notification = document.getElementById('notification'); notification.textContent = '复制失败'; notification.style.backgroundColor = '#dc3545'; notification.style.display = 'block'; setTimeout(() => { notification.style.display = 'none'; // 恢复默认文本和颜色 notification.textContent = '内容已复制到剪贴板'; notification.style.backgroundColor = '#28a745'; }, 3000); }); } // 复制分享内容功能 function copyShareContent() { if (window.shareContent) { copyContent(window.shareContent); } else { // 如果全局变量不存在,尝试从DOM元素获取 const contentElement = document.getElementById('shareContent'); if (contentElement) { copyContent(contentElement.textContent); } else { console.error('无法获取分享内容'); const notification = document.getElementById('notification'); notification.textContent = '复制失败:无法获取内容'; notification.style.backgroundColor = '#dc3545'; notification.style.display = 'block'; setTimeout(() => { notification.style.display = 'none'; }, 3000); } } } // 页面加载时加载分享日记 loadSharedDiary(); </script> </body> </html>`; } // 处理认证API async function handleAuth(request, env, corsHeaders) { if (request.method !== 'POST') { return new Response('方法不允许', { status: 405, headers: { 'Content-Type': 'text/plain; charset=utf-8', ...corsHeaders } }); } try { const { username, password } = await request.json(); // 验证用户名和密码(从环境变量获取) const validUsername = env.USERNAME || '9527a'; const validPassword = env.PASSWORD || '9527abc'; if (username === validUsername && password === validPassword) { // 生成简单的token(实际应用中应该使用更安全的方法) const token = btoa(username + ':' + Date.now()); return new Response(JSON.stringify({ success: true, token: token, message: '登录成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } else { return new Response(JSON.stringify({ success: false, message: '用户名或密码错误' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } catch (error) { return new Response(JSON.stringify({ success: false, message: '请求格式错误' }), { status: 400, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理日记API async function handleDiaryAPI(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } const token = authHeader.substring(7); try { if (request.method === 'GET') { // 获取日记列表 const diaries = await getDiaries(env); return new Response(JSON.stringify({ success: true, diaries: diaries }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } if (request.method === 'POST') { // 保存新日记 const { title, content, editShareId } = await request.json(); const diary = { id: Date.now().toString(), title: title, content: content, date: new Date().toISOString() }; await saveDiary(env, diary); // 如果是从分享日记编辑而来,需要从分享日记中删除 if (editShareId) { await env.WS01_NOTE_KV.delete(`shared_${editShareId}`); } return new Response(JSON.stringify({ success: true, message: '日记保存成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } // 添加: 处理更新日记 (PUT) if (request.method === 'PUT') { const { id, title, content } = await request.json(); // 获取现有日记 const diaries = await getDiaries(env); const diaryIndex = diaries.findIndex(d => d.id === id); if (diaryIndex === -1) { return new Response(JSON.stringify({ success: false, message: '日记不存在' }), { status: 404, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } // 更新日记内容 diaries[diaryIndex] = { ...diaries[diaryIndex], title, content, date: new Date().toISOString() // 更新时间 }; await env.WS01_NOTE_KV.put('diaries', JSON.stringify(diaries)); return new Response(JSON.stringify({ success: true, message: '日记更新成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } // 添加: 处理删除日记 (DELETE) if (request.method === 'DELETE') { const { id } = await request.json(); const diaries = await getDiaries(env); const filteredDiaries = diaries.filter(d => d.id !== id); if (filteredDiaries.length === diaries.length) { return new Response(JSON.stringify({ success: false, message: '日记不存在' }), { status: 404, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } await env.WS01_NOTE_KV.put('diaries', JSON.stringify(filteredDiaries)); return new Response(JSON.stringify({ success: true, message: '日记删除成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } return new Response('方法不允许', { status: 405, headers: { 'Content-Type': 'text/plain; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('日记API错误:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 获取日记列表 async function getDiaries(env) { try { const diariesJson = await env.WS01_NOTE_KV.get('diaries'); if (diariesJson) { const diaries = JSON.parse(diariesJson); return diaries.sort((a, b) => new Date(b.date) - new Date(a.date)); } return []; } catch (error) { console.error('获取日记失败:', error); return []; } } // 保存日记 async function saveDiary(env, diary) { try { const diaries = await getDiaries(env); diaries.unshift(diary); // 添加到开头 // 限制最多保存100篇日记 if (diaries.length > 100) { diaries.splice(100); } await env.WS01_NOTE_KV.put('diaries', JSON.stringify(diaries)); } catch (error) { console.error('保存日记失败:', error); throw error; } } // 处理创建备份 async function handleCreateBackup(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { // 获取当前所有日记 const diaries = await getDiaries(env); // 获取所有分享日记 const listResult = await env.WS01_NOTE_KV.list({ prefix: 'shared_' }); const shares = []; for (const key of listResult.keys) { try { const sharedDiaryJson = await env.WS01_NOTE_KV.get(key.name); if (sharedDiaryJson) { const sharedDiary = JSON.parse(sharedDiaryJson); shares.push(sharedDiary); } } catch (e) { console.error('读取分享日记失败:', e); } } // 创建备份数据 const backup = { id: Date.now().toString(), timestamp: new Date().toISOString(), data: diaries, shares: shares, count: diaries.length, shareCount: shares.length }; // 获取现有的备份列表 let backups = []; try { const backupsJson = await env.WS01_NOTE_KV.get('backups'); if (backupsJson) { backups = JSON.parse(backupsJson); } } catch (e) { console.error('读取备份列表失败:', e); } // 添加新备份到列表开头 backups.unshift(backup); // 限制最多5个备份 if (backups.length > 5) { backups = backups.slice(0, 5); } // 保存备份列表 await env.WS01_NOTE_KV.put('backups', JSON.stringify(backups)); return new Response(JSON.stringify({ success: true, message: '备份创建成功', backupId: backup.id }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('创建备份失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理获取备份列表 async function handleGetBackups(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { // 获取备份列表 let backups = []; try { const backupsJson = await env.WS01_NOTE_KV.get('backups'); if (backupsJson) { backups = JSON.parse(backupsJson); } } catch (e) { console.error('读取备份列表失败:', e); } // 只返回必要的信息 const backupInfo = backups.map(backup => ({ id: backup.id, timestamp: backup.timestamp, count: backup.count, shareCount: backup.shareCount || 0 })); return new Response(JSON.stringify({ success: true, backups: backupInfo }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('获取备份列表失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理恢复备份 async function handleRestoreBackup(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { const { backupId } = await request.json(); // 获取备份列表 let backups = []; try { const backupsJson = await env.WS01_NOTE_KV.get('backups'); if (backupsJson) { backups = JSON.parse(backupsJson); } } catch (e) { console.error('读取备份列表失败:', e); } // 查找指定的备份 const backup = backups.find(b => b.id === backupId); if (!backup) { return new Response(JSON.stringify({ success: false, message: '备份不存在' }), { status: 404, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } // 恢复个人日记数据 await env.WS01_NOTE_KV.put('diaries', JSON.stringify(backup.data)); // 恢复分享日记数据 if (backup.shares && backup.shares.length > 0) { // 先删除所有现有的分享日记 const existingShares = await env.WS01_NOTE_KV.list({ prefix: 'shared_' }); for (const key of existingShares.keys) { await env.WS01_NOTE_KV.delete(key.name); } // 恢复备份中的分享日记 for (const share of backup.shares) { await env.WS01_NOTE_KV.put(`shared_${share.id}`, JSON.stringify(share)); } } return new Response(JSON.stringify({ success: true, message: '数据恢复成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('恢复备份失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理分享日记 async function handleShareDiary(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { const { title, content, shareId, editDiaryId } = await request.json(); if (request.method === 'PUT' && shareId) { // 更新分享日记 const existingShareJson = await env.WS01_NOTE_KV.get(`shared_${shareId}`); if (!existingShareJson) { return new Response(JSON.stringify({ success: false, message: '分享日记不存在' }), { status: 404, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } const existingShare = JSON.parse(existingShareJson); const updatedShare = { ...existingShare, title: title, content: content, date: new Date().toISOString() // 更新修改时间 }; await env.WS01_NOTE_KV.put(`shared_${shareId}`, JSON.stringify(updatedShare)); return new Response(JSON.stringify({ success: true, message: '分享日记更新成功', shareId: shareId }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } else { // 创建新分享日记 const newShareId = Date.now().toString() + Math.random().toString(36).substr(2, 9); const sharedDiary = { id: newShareId, title: title, content: content, date: new Date().toISOString(), shared: true }; // 保存到KV存储 await env.WS01_NOTE_KV.put(`shared_${newShareId}`, JSON.stringify(sharedDiary)); // 如果是从我的日记编辑而来,需要从我的日记中删除 if (editDiaryId) { const diaries = await getDiaries(env); const filteredDiaries = diaries.filter(d => d.id !== editDiaryId); await env.WS01_NOTE_KV.put('diaries', JSON.stringify(filteredDiaries)); } return new Response(JSON.stringify({ success: true, message: '分享成功', shareId: newShareId }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } catch (error) { console.error('分享日记失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理获取分享日记 async function handleGetSharedDiary(shareId, env, corsHeaders) { try { // 从KV存储中获取分享日记 const sharedDiaryJson = await env.WS01_NOTE_KV.get(`shared_${shareId}`); if (!sharedDiaryJson) { return new Response(JSON.stringify({ success: false, message: '分享的日记不存在' }), { status: 404, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } const sharedDiary = JSON.parse(sharedDiaryJson); return new Response(JSON.stringify(sharedDiary), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('获取分享日记失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理获取所有分享日记(公开访问) async function handleGetAllShares(request, env, corsHeaders) { try { // 解析URL参数 const url = new URL(request.url); const page = parseInt(url.searchParams.get('page')) || 1; const limit = parseInt(url.searchParams.get('limit')) || 20; // 获取所有分享日记的键 const listResult = await env.WS01_NOTE_KV.list({ prefix: 'shared_' }); const shares = []; for (const key of listResult.keys) { try { const sharedDiaryJson = await env.WS01_NOTE_KV.get(key.name); if (sharedDiaryJson) { const sharedDiary = JSON.parse(sharedDiaryJson); shares.push({ id: sharedDiary.id, title: sharedDiary.title, content: sharedDiary.content, date: sharedDiary.date, shareUrl: `/share/${sharedDiary.id}` }); } } catch (e) { console.error('解析分享日记失败:', e); } } // 按日期排序(最新的在前) shares.sort((a, b) => new Date(b.date) - new Date(a.date)); // 计算分页 const totalCount = shares.length; const totalPages = Math.ceil(totalCount / limit); const startIndex = (page - 1) * limit; const endIndex = startIndex + limit; const paginatedShares = shares.slice(startIndex, endIndex); return new Response(JSON.stringify({ success: true, shares: paginatedShares, pagination: { currentPage: page, totalPages: totalPages, totalCount: totalCount, limit: limit, hasNext: page < totalPages, hasPrev: page > 1 } }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('获取所有分享日记失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理获取用户分享日记列表 async function handleGetUserShares(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { // 获取所有分享日记的键 const listResult = await env.WS01_NOTE_KV.list({ prefix: 'shared_' }); const shares = []; for (const key of listResult.keys) { try { const sharedDiaryJson = await env.WS01_NOTE_KV.get(key.name); if (sharedDiaryJson) { const sharedDiary = JSON.parse(sharedDiaryJson); shares.push({ id: sharedDiary.id, title: sharedDiary.title, content: sharedDiary.content, date: sharedDiary.date, shareUrl: `${request.url.split('/')[0]}//${request.headers.get('host')}/share/${sharedDiary.id}` }); } } catch (e) { console.error('解析分享日记失败:', e); } } // 按日期排序(最新的在前) shares.sort((a, b) => new Date(b.date) - new Date(a.date)); return new Response(JSON.stringify({ success: true, shares: shares }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('获取分享日记列表失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理删除分享日记 async function handleDeleteShare(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { const { shareId } = await request.json(); // 删除分享日记 await env.WS01_NOTE_KV.delete(`shared_${shareId}`); return new Response(JSON.stringify({ success: true, message: '分享日记删除成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('删除分享日记失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理删除备份 async function handleDeleteBackup(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { const { backupId } = await request.json(); // 获取备份列表 let backups = []; try { const backupsJson = await env.WS01_NOTE_KV.get('backups'); if (backupsJson) { backups = JSON.parse(backupsJson); } } catch (e) { console.error('读取备份列表失败:', e); } // 过滤掉要删除的备份 const filteredBackups = backups.filter(b => b.id !== backupId); if (filteredBackups.length === backups.length) { return new Response(JSON.stringify({ success: false, message: '备份不存在' }), { status: 404, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } // 保存更新后的备份列表 await env.WS01_NOTE_KV.put('backups', JSON.stringify(filteredBackups)); return new Response(JSON.stringify({ success: true, message: '备份删除成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('删除备份失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } ``` 最后修改:2025 年 10 月 19 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏