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