首页
文章导航
导航
壁纸
更多
留言板
直播
友链
统计
关于
推荐
wszx博客
Search
1
免费二级域名,包括可托管到cf的二级域名
8 阅读
2
网络收集的优选域名列表
8 阅读
3
MoonTV 完整部署教程|免费搭建影视聚合平台!支持 Cloudflare Pages + 自动更新 + 多资源接口
8 阅读
4
serv00注册等系列教程,服务器清理,ssh连接不上问题轻松解决,非挂代理
5 阅读
5
部署一个独享视频网站,海量资源,高清不限速
4 阅读
默认
日常
学习
技术
登录
Search
标签搜索
cloudflare
白嫖
CF
docker
安装
脚本
壁纸
图片
Linux
Caddy
代码
哪吒
节点
域名
github
搭建
桌面壁纸
手机壁纸
NAT
LXC
ws01
累计撰写
124
篇文章
累计收到
45
条评论
首页
栏目
默认
日常
学习
技术
页面
文章导航
导航
壁纸
留言板
直播
友链
统计
关于
推荐
wszx博客
搜索到
92
篇与
的结果
2025-10-19
基于 Cloudflare Workers AI 的在线文生图/图生图/重绘服务,开箱即用
基于 Cloudflare Workers AI 的在线文生图/图生图/重绘服务,开箱即用项目地址:https://github.com/zhumengkang/cf-ai-image 功能总览 多模型:SDXL、FLUX、DreamShaper、Lightning、SD1.5 图生图、SD1.5 局部重绘一次生成 1–8 张,画廊预览 + 悬浮操作(放大/复制/单张下载)批量下载 ZIP、复制参数、显示每张尺寸与大小真实 it/s 指标(服务端推理耗时),带进度条与 60s 超时提示登录认证(Cookie),支持密码保护、明暗主题、自适应移动端一键部署 1、Cloudflare 控制台 → Workers & Pages → 创建 Worker → 部署。2、绑定 Workers AI:设置 → 绑定 → 添加绑定 → 类型选 “Workers AI”,变量名填 AI → 保存。3、复制代码:将 src/worker.js 与 src/index.html 内容分别放入同名文件,保存并部署。【worker.js文件的第75行是密码,默认密码是admin123】4、可选:设置自定义域(设置 → 域和路由)。配置与自定义 模型清单:编辑 src/worker.js 中 AVAILABLE_MODELS 可增删/改描述、是否需要图片/遮罩。随机提示词:在 RANDOM_PROMPTS 维护。密码:PASSWORDS=['admin123'](留空即无密码),前端含登录遮罩与 Cookie 认证。生成数量:默认开放 1–8,可在前端下拉与后端上限同步调整。常见问题 3001 Unknown internal error:通常为尺寸/步数过大或图片直链不规范。将宽高调到 512–768、步数 < 20;确保 image_url/mask_url 响应头为 image/* 且 ≤10MB。3030 missing mask_image:使用 inpainting 时必须提供 mask_url(已在前端/后端分别做必填校验)。it/s 为什么波动:以服务端推理耗时为准(X-Server-Seconds),网络/解码不会影响该指标。完成后访问 https://..workers.dev/ 即可使用。
2025年10月19日
2 阅读
0 评论
0 点赞
2025-10-19
CF _worker搭建 WS01 Note-20251019
CF _worker搭建 WS01 Note,可设置私人日记或公开分享变量设置:1、帐号:USERNAME,默认:9527a2、密码:PASSWORD,默认:9527abc3、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日
1 阅读
0 评论
0 点赞
2025-09-03
bpb面板搭建【有时效性,可能1001用不了】
bpb面板搭建 参考 不一样的强哥 创建项目和设置时,不要有敏感词,如vless、trojan等 最新修复BPB Panel失效!混淆代码后的节点高速稳定!CF免费Workers快速部署高速上网,4K起飞!BPB项目:https://github.com/bia-pain-bache/BPB-Worker-PanelProxyIP在线:https://www.nslookup.io/domains/bpb.yousef.isegaro.com/dns-records/大佬分享的部分Proxy_IP域名:bpb.yousef.isegaro.com,ts.hpc.tw,cdn.xn--b6gac.eu.org、cdn-all.xn--b6gac.eu.org、bestproxy.onecf.eu.org、proxyip.cmliussss.net优选IP在线:https://www.wetest.vip/page/cloudflare/address_v4.html https://ipdb.030101.xyz/bestcfv4/ https://stock.hostmonit.com/CloudFlareYes 一、cf项目中设置:1、自定义域2、变量和机密中, UUID 、 TR_PASS 、 PROXY_IP,其中,UUID是设置VLESS节点的 UUID,TR_PASS是设置Trojan节点的密码,PROXY_IP可以先不设置,在BPB面板中设置3、绑定kv,KV 命名空间:kv4、项目中的 最新版本worker.js 代码,更新到版本v3.6.1【2025.10.19】二、面板中设置:1、登录密码【最新几个版本好像无用,第一次打开面板时设置】,设置变量名是 TR_PASS 2、优选域名设置【可以自己找的 优选域名1 , 优选域名2,以下是找好的】:Clean IPs / Domainskk168.wszx.ip-ddns.com cloudflare.182682.xyz freeyx.cloudflare88.eu.org cmcc.090227.xyz www.visa.com.hk bestcf.top cdn.2020111.xyz www.visa.com www.visa.com.sg www.visa.com.tw www.visa.co.jp www.visakorea.com time.is icook.hk icook.tw canva.com envato.com hostinger.com ahrefs.com unpkg.com cf.877774.xyz ct.877774.xyz cmcc.877774.xyz cu.877774.xyz asia.877774.xyz eur.877774.xyz na.877774.xyz www.gco.gov.qa www.gov.se www.gov.ua www.ipget.net www.hugedomains.com shopify.com ip.sb japan.com malaysia.com russia.com singapore.com skk.moe cdn-b100.xn--b6gac.eu.org netlify-cname.xingpingcn.top vercel.001315.xyz vercel-cname.xingpingcn.top cnamefuckxxs.yuchen.icu cf-cname.xingpingcn.top cfcdn.v6.rocks aliyun.2096.us.kg cf.090227.xyz time.cloudflare.com checkout.shopify.com www.digitalocean.com www.csgo.com www.shopify.com www.whoer.net www.whatismyip.com www.udacity.com www.4chan.org www.okcupid.com www.glassdoor.com www.udemy.com www.baipiao.eu.org cdn.anycast.eu.org cdn-all.xn--b6gac.eu.org cdn-b100.xn--b6gac.eu.org xn--b6gac.eu.org edgetunnel.anycast.eu.org alejandracaiccedo.com nc.gocada.co log.bpminecraft.com www.boba88slot.com gur.gov.ua www.zsu.gov.ua www.iakeys.com edtunnel-dgp.pages.dev www.d-555.com fbi.gov *.cloudflare.182682.xyz linux.do 3、没 IPv6 的,登录后关闭 IPv6 4、设置节点类型:rotocols,一般 VLESS 和 Trojan 两种都可以选择5、端口选择:一般 443 就可以了6、proxy IPs规则 Bypass rules ,Bypass LAN 、 Bypass Chin 和 Block Ads 三项打勾7、 Proxy IPs / Domains 中设置: bpb.yousef.isegaro.com 8、 NAT64 Prefixes[2602:fc59:b0:64::] [2a02:898:146:64::] [2602:fc59:11:64::]以上设置完后 “ 确定 ”三、连接 在 Subscriptions - Configs 中
2025年09月03日
2 阅读
0 评论
0 点赞
2025-08-26
★★★docker 安装最小化带数据库多网站 示例
docker 安装最小化带数据库多网站 示例,安装前看看目录结构,如需要修改,先研究一下。Debian 11、12系统适用,与其它方式安装不冲突一、目录结构/home/html/docker/web1/├── docker-compose.yml├── Caddyfile├── public/(网站文件存放目录public)├── public1/(网站文件存放目录public1)├── public2/(网站文件存放目录public2)├── public3/(网站文件存放目录public3)├── public4/(网站文件存放目录public4)等├── php/│ └── Dockerfile└── data/ (一个数据库,安装后数据库文件会自动生成)二、安装前准备:1、相关升级apt update && apt upgrade -y && apt install -y curl wget unzip zip2、安装好 docker composecurl -fsSL https://get.docker.com | sh && ln -s /usr/libexec/docker/cli-plugins/docker-compose /usr/local/bin三、创建目录结构和三个需要文件1、创建目录并给予相应775权限sudo mkdir -p /home/html/docker/web1/php/sudo mkdir -p /home/html/docker/web1/public && sudo chown -R www-data:www-data /home/html/docker/web1/public && sudo chmod -R 775 /home/html/docker/web1/public2、添加 web1 775权限sudo chown -R www-data:www-data /home/html/docker/web1 && sudo chmod -R 775 /home/html/docker/web1 3、下载三个需要文件下载docker-compose.yml文件【一定要修改数据库中的两个密码,安装多站点时要相应修改配置 】cd /home/html/docker/web1/ wget https://raw.githubusercontent.com/wszx123/gongjuxiang/refs/heads/main/docker/typecho/docker-compose.yml下载Caddyfile文件【下载后记事本打开,修改好解析的域名,安装多站点时也要添加相应域名配置 】cd /home/html/docker/web1/ wget https://raw.githubusercontent.com/wszx123/gongjuxiang/refs/heads/main/docker/typecho/Caddyfile下载Dockerfile文件cd /home/html/docker/web1/php/ wget https://raw.githubusercontent.com/wszx123/gongjuxiang/refs/heads/main/docker/typecho/Dockerfile4、下载官方 Typecho 安装包或上传后解压【已测试正常】cd /home/html/docker/web1/public/ wget https://github.com/typecho/typecho/releases/download/v1.2.1/typecho.zip unzip typecho.zip rm typecho.zip四、以上步骤完成后启动【1检查域名配置文件 Caddyfile 是否配置好,2检查 docker-compose.yml 中的数据库密码等是否修改,如果是多站点时,是否已添加多站点的配置】cd /home/html/docker/web1/ docker-compose up -d五、打开网站安装,安装时如会出现 uploads 权限问题,给予权限,其它小问题按提示解决或刷新网页重新安装数据库地址: db 或用默认数据库名: web1 用户名: web1 密码: web1pass123 【或修改后的密码】⚡ 如果你要再装第二个、第三个或多网站,只需要在 /home/html/docker/web1/ 下新建文件夹public1、public2、public3等,然后在 docker-compose.yml 里添加好相应配置,域名配置 Caddyfile 文件也要添加好域名以此类推六、🚀 迁移步骤(最简洁)旧 VPS 打包,打包整个项目目录,包括数据库。cd /home/html/docker/ zip -q -r /home/backup$(date +%Y%m%d%H%M).zip web1或cd /home/html/docker/ tar -czvf typecho1_backup$(date +%Y%m%d%H%M).tar.gz web1把备份传输到新 VPS或手动下载后再上传到新vps上新 VPS 解压 & 启动进入目录并启动:cd /home/html/docker/web1/ docker-compose up -d✅ 总结1、这种方式安装,数据库数据 已经在 data/ 挂载目录里,所以无需单独 mysqldump 导出导入。2、整个 web1 项目文件夹就是完整环境,直接打包迁移即可。3、在新 VPS 上只需要 docker-compose up -d,就能恢复完整博客,方便迁移和备份,适合折腾。4、可以快捷的一个vps中添加更多的网站
2025年08月26日
0 阅读
0 评论
0 点赞
2025-08-25
vps上创建文件夹时的命令汇总
⚠️ 注意:777 权限非常不安全(任何人都能读写执行),一般只在测试阶段用。更推荐的做法是只给 www-data 用户和组写权限,不给“其它”写:拥有者 (www-data) → rwx属组 (www-data) → rwx其它用户 → r-x777 权限非常不安全(任何人都能读写执行),一般只在测试阶段用。更推荐的做法是只给 www-data 用户和组写权限,不给“其它”写:一、775(推荐,安全一些)sudo mkdir -p /home/html/docker/web1/public1 && sudo chown -R www-data:www-data /home/html/docker/web1/public1 && sudo chmod -R 775 /home/html/docker/web1/public1这样:拥有者 (www-data) → rwx属组 (www-data) → rwx其它用户 → r-x二、想要 777(所有人可写,测试用)sudo mkdir -p /home/html/docker/web1/public1 && sudo chown -R www-data:www-data /home/html/docker/web1/public1 && sudo chmod -R 777 /home/html/docker/web1/public1三、脚本,执行时只需要带上路径就能自动完成创建、设置属主和权限。脚本内容(保存为 mkwebdir.sh)#!/bin/bash # 用法: sudo ./mkwebdir.sh /home/html/docker/web1/public1 [mode] # mode 可选: 775 (默认) 或 777 # 传入的目录路径 DIR=$1 # 权限模式(默认 775) MODE=${2:-775} if [ -z "$DIR" ]; then echo "❌ 请提供目录路径,例如:" echo " sudo $0 /home/html/docker/web1/public1 775" exit 1 fi # 创建目录并设置权限 mkdir -p "$DIR" && \ chown -R www-data:www-data "$DIR" && \ chmod -R "$MODE" "$DIR" echo "✅ 已创建目录 $DIR 并设置属主 www-data:www-data 和权限 $MODE"使用方法把脚本保存到 VPS,例如:nano mkwebdir.sh把上面的代码粘贴进去,保存退出。赋予可执行权限:chmod +x mkwebdir.sh使用示例:sudo ./mkwebdir.sh /home/html/docker/web1/public1默认会用 775 权限。如果要用 777:sudo ./mkwebdir.sh /home/html/docker/web1/public1 777
2025年08月25日
1 阅读
0 评论
0 点赞
1
2
...
19
您是第
75286
位访客