巨二兔 发表于 2024-9-4 14:56:35

Cloudflare Workers 记事本

想记录一些安装命令文字啥的,找不到一个一劳永逸的开源項目,还是 Cloudflare 大厂靠谱需要登陆才能编辑,可以修改管理密码,黑夜模式.....演示站(代码也在这里面了): https://loc.lol
部署说明:
1、创建 Cloudflare Workers (编辑代码将下面的代码复制~粘贴~部署)
2、创建 R2 存储桶(随意名称举例;AAA123)
3、在 Cloudflare Workers 设置~变量~R2 存储桶绑定~添加变量名称必须 JSBR2 选择R2存储桶 AAA123 部署即可。
注:修改了帖子里 树莓派 提出的问题手机浏览时,最底下的三两行代码会被状态栏遮住。
注:增加了帖子里 playbear 提出的问题 能不能输入密码后才能查看内容。
1、代码如下:(无需密码可查看内容)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
const { pathname, searchParams } = new URL(request.url);

// Check if password protection is enabled
let ADMIN_PASSWORD = await JSBR2.get('admin_password');
ADMIN_PASSWORD = ADMIN_PASSWORD ? await ADMIN_PASSWORD.text() : null;

const isPasswordProtected = ADMIN_PASSWORD !== undefined && ADMIN_PASSWORD !== null;

console.log('isPasswordProtected:', isPasswordProtected);

if (request.method === 'GET' && pathname === '/') {
    return new Response(await getHTML(isPasswordProtected), {
      headers: { 'content-type': 'text/html;charset=UTF-8' },
    });
}

if (request.method === 'GET' && pathname.startsWith('/notes/')) {
    const key = pathname.replace('/notes/', '');
    try {
      const object = await JSBR2.get(key);
      if (!object) {
      return new Response('Note not found', {
          headers: { 'content-type': 'text/plain;charset=UTF-8' },
      });
      }
      const value = await object.text();
      return new Response(value, {
      headers: { 'content-type': 'text/plain;charset=UTF-8' },
      });
    } catch (err) {
      console.error('Error retrieving note:', err);
      return new Response('Error retrieving note', {
      status: 500,
      headers: { 'content-type': 'text/plain;charset=UTF-8' },
      });
    }
}

if (request.method === 'PUT' && pathname.startsWith('/notes/')) {
    const key = pathname.replace('/notes/', '');
    const password = searchParams.get('password');
    if (isPasswordProtected && password !== ADMIN_PASSWORD) {
      return new Response('Unauthorized', {
      status: 401,
      headers: { 'content-type': 'text/plain;charset=UTF-8' },
      });
    }
    try {
      const value = await request.text();
      await JSBR2.put(key, value);
      const timestamp = new Date().toISOString();
      await JSBR2.put(`${key}_timestamp`, timestamp);
      return new Response('Note saved', {
      headers: { 'content-type': 'text/plain;charset=UTF-8' },
      });
    } catch (err) {
      console.error('Error saving note:', err);
      return new Response('Error saving note', {
      status: 500,
      headers: { 'content-type': 'text/plain;charset=UTF-8' },
      });
    }
}

if (request.method === 'POST' && pathname === '/set-password') {
    const password = await request.text();
    await JSBR2.put('admin_password', password);
    return new Response('Password set', {
      headers: { 'content-type': 'text/plain;charset=UTF-8' },
    });
}

if (request.method === 'POST' && pathname === '/change-password') {
    const { oldPassword, newPassword } = await request.json();
    if (isPasswordProtected && oldPassword !== ADMIN_PASSWORD) {
      return new Response('Unauthorized', {
      status: 401,
      headers: { 'content-type': 'text/plain;charset=UTF-8' },
      });
    }
    await JSBR2.put('admin_password', newPassword);
    return new Response('Password changed', {
      headers: { 'content-type': 'text/plain;charset=UTF-8' },
    });
}

return new Response('Not found', {
    status: 404,
    headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}

async function getHTML(isPasswordProtected) {
return `
    <!DOCTYPE html>
    <html>
    <head>
      <title>记事本</title>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <style>
      body, html {
          margin: 0;
          padding: 0;
          height: 100%;
          width: 100%;
          display: flex;
          flex-direction: column;
          font-family: Arial, sans-serif;
      }
      textarea {
          flex: 1;
          width: 100%;
          box-sizing: border-box;
          padding: 10px;
          font-size: 16px;
          resize: none;
      }
      .status {
          background-color: rgba(0, 0, 0, 0.5);
          color: white;
          padding: 10px;
          border-radius: 5px;
          font-size: 14px;
          display: flex;
          flex-direction: column;
          align-items: flex-start;
      }
      .status-row {
          display: flex;
          justify-content: space-between;
          width: 100%;
      }
      .status span, .status button {
          margin: 5px 10px;
      }
      .password-setup,
      .change-password {
          display: none;
          flex-direction: row;
          align-items: center;
          margin-top: 10px;
      }
      .password-setup input,
      .change-password input {
          margin-right: 10px;
          margin-bottom: 5px;
      }
      .change-password input {
          margin-right: 5px;
      }
      @media (max-width: 600px) {
          .status {
            position: static;
            width: 100%;
            padding: 5px;
            font-size: 12px;
          }
          .status-row {
            flex-direction: row;
            flex-wrap: wrap;
            justify-content: space-between;
          }
          .status span, .status button {
            margin: 2px 5px;
          }
          .password-setup,
          .change-password {
            flex-direction: column;
            align-items: flex-start;
          }
      }
      @media (min-width: 601px) {
          .status {
            position: fixed;
            bottom: 10px;
            right: 10px;
          }
      }
      .dark-mode {
          background-color: black;
          color: white;
      }
      .dark-mode textarea {
          background-color: black;
          color: white;
      }
      .dark-mode #moon-icon path {
          fill: white;
      }
      #moon-icon path {
          fill: black;
      }
      </style>
    </head>
    <body>
      <textarea id="note" placeholder="开始输入..."></textarea>
      <div class="status" id="status">
      <div class="status-row">
          <span>当前已使用:<span id="used-space">0.00 KB</span></span>
          <span>总剩余空间:<span id="remaining-space">9.99 GB</span></span>
      </div>
      <div class="status-row">
          <span id="last-write-time-container">最/后写入时间:<span id="last-write-time">N/A</span></span>
          <button id="change-password-button" onclick="showChangePassword()" style="display: none; margin-right: 20px;">修改密码</button>
      </div>
      <div class="password-setup" id="password-setup">
          <input type="password" id="admin-password" placeholder="设置管理员密码" />
          <button onclick="setPassword()">确认</button>
      </div>
      <div class="change-password" id="change-password">
          <input type="password" id="old-password" placeholder="旧密码" />
          <input type="password" id="new-password" placeholder="新密码" />
          <button onclick="changePassword()">确定</button>
      </div>
      </div>
      <svg id="moon-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" style="position: fixed; top: 10px; right: 10px; cursor: pointer;">
      <path d="M12 2a10 10 0 0 0 0 20 10 10 0 0 0 0-20zm0 18a8 8 0 1 1 0-16 8 8 0 0 1 0 16z"/>
      </svg>
      <script>
      const TOTAL_STORAGE = 10 * 1024 * 1024 * 1024; // 10 GB in bytes

      function updateStorageStatus() {
          const note = document.getElementById('note').value;
          const usedBytes = new TextEncoder().encode(note).length;
          const remainingBytes = TOTAL_STORAGE - usedBytes;

          document.getElementById('used-space').textContent = formatBytes(usedBytes);
          document.getElementById('remaining-space').textContent = formatBytes(remainingBytes);
      }

      function formatBytes(bytes) {
          const units = ['B', 'KB', 'MB', 'GB'];
          let unitIndex = 0;
          let value = bytes;

          while (value >= 1024 && unitIndex < units.length - 1) {
            value /= 1024;
            unitIndex++;
          }

          return value.toFixed(2) + ' ' + units;
      }

      async function saveNote() {
          const note = document.getElementById('note').value;
          let password = localStorage.getItem('adminPassword');
          if (${isPasswordProtected} && !password) {
            password = prompt('请输入管理员密码:');
            if (password) {
            localStorage.setItem('adminPassword', password);
            document.getElementById('change-password-button').style.display = 'inline-block';
            } else {
            alert('需要密码才能编辑笔记。');
            return;
            }
          }
          const response = await fetch(\`/notes/my-note?password=\${password || ''}\`, {
            method: 'PUT',
            body: note,
            headers: {
            'Content-Type': 'text/plain;charset=UTF-8'
            }
          });
          if (response.status === 401) {
            alert('密码错误。请刷新页面并输入正确的密码。');
            localStorage.removeItem('adminPassword');
            document.getElementById('change-password-button').style.display = 'none';
            return;
          }
          const timestamp = new Date().toISOString();
          document.getElementById('last-write-time').textContent = new Date(timestamp).toLocaleString();
          document.getElementById('last-write-time-container').style.display = 'inline'; // 显示最/后写入时间
      }

      async function loadNote() {
          const response = await fetch('/notes/my-note');
          const note = await response.text();
          document.getElementById('note').value = note;
          updateStorageStatus();
          updateLastWriteTime(); // Initial load of last write time
      }

      async function updateLastWriteTime() {
          const timestampResponse = await fetch('/notes/my-note_timestamp');
          if (timestampResponse.ok) {
            const timestamp = await timestampResponse.text();
            const localTimestamp = new Date(timestamp).toLocaleString();
            document.getElementById('last-write-time').textContent = localTimestamp;
            document.getElementById('last-write-time-container').style.display = 'inline'; // 显示最/后写入时间
          } else {
            document.getElementById('last-write-time-container').style.display = 'none'; // 隐藏最/后写入时间
          }
      }

      function debounce(func, wait) {
          let timeout;
          return function() {
            const context = this, args = arguments;
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(context, args), wait);
          };
      }

      const debouncedSaveNote = debounce(saveNote, 200);

      document.getElementById('note').addEventListener('input', () => {
          debouncedSaveNote();
          updateStorageStatus();
      });

      window.addEventListener('load', () => {
          loadNote();
          setInterval(updateLastWriteTime, 1000); // 每秒更新一次最/后写入时间

          const password = localStorage.getItem('adminPassword');
          if (password) {
            document.getElementById('change-password-button').style.display = 'inline-block';
          }
      });

      async function setPassword() {
          const password = document.getElementById('admin-password').value;
          if (password) {
            const response = await fetch('/set-password', {
            method: 'POST',
            body: password,
            headers: {
                'Content-Type': 'text/plain;charset=UTF-8'
            }
            });
            if (response.ok) {
            alert('管理员密码设置成功');
            document.getElementById('password-setup').style.display = 'none';
            } else {
            alert('设置管理员密码失败');
            }
          } else {
            alert('请输入密码');
          }
      }

      function showChangePassword() {
          const password = localStorage.getItem('adminPassword');
          if (!password) {
            alert('您尚未登陆,请登陆后再修改密码');
            return;
          }
          document.getElementById('change-password').style.display = 'flex';
          document.getElementById('change-password-button').style.display = 'none';
      }

      async function changePassword() {
          const oldPassword = document.getElementById('old-password').value;
          const newPassword = document.getElementById('new-password').value;
          if (!oldPassword || !newPassword) {
            alert('请输入旧密码和新密码');
            return;
          }
          const response = await fetch('/change-password', {
            method: 'POST',
            body: JSON.stringify({ oldPassword, newPassword }),
            headers: {
            'Content-Type': 'application/json;charset=UTF-8'
            }
          });
          if (response.status === 401) {
            alert('旧密码不正确');
            return;
          }
          if (response.ok) {
            alert('密码修改成功');
            document.getElementById('change-password').style.display = 'none';
            localStorage.setItem('adminPassword', newPassword);
          } else {
            alert('密码修改失败');
          }
      }

      document.getElementById('moon-icon').addEventListener('click', () => {
          document.body.classList.toggle('dark-mode');
      });
      </script>
    </body>
    </html>
`;
}






















2、代码如下:(需要密码可查看内容)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~












addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
const { pathname, searchParams } = new URL(request.url);

// Check if password protection is enabled
let ADMIN_PASSWORD = await JSBR2.get('admin_password');
ADMIN_PASSWORD = ADMIN_PASSWORD ? await ADMIN_PASSWORD.text() : null;

const isPasswordProtected = ADMIN_PASSWORD !== undefined && ADMIN_PASSWORD !== null;

console.log('isPasswordProtected:', isPasswordProtected);

if (request.method === 'GET' && pathname === '/') {
    return new Response(await getHTML(isPasswordProtected), {
      headers: { 'content-type': 'text/html;charset=UTF-8' },
    });
}

if (request.method === 'GET' && pathname.startsWith('/notes/')) {
    const key = pathname.replace('/notes/', '');
    const password = searchParams.get('password');

    if (isPasswordProtected && password !== ADMIN_PASSWORD) {
      return new Response('Unauthorized', {
      status: 401,
      headers: { 'content-type': 'text/plain;charset=UTF-8' },
      });
    }

    try {
      const object = await JSBR2.get(key);
      if (!object) {
      return new Response('Note not found', {
          headers: { 'content-type': 'text/plain;charset=UTF-8' },
      });
      }
      const value = await object.text();
      return new Response(value, {
      headers: { 'content-type': 'text/plain;charset=UTF-8' },
      });
    } catch (err) {
      console.error('Error retrieving note:', err);
      return new Response('Error retrieving note', {
      status: 500,
      headers: { 'content-type': 'text/plain;charset=UTF-8' },
      });
    }
}

if (request.method === 'PUT' && pathname.startsWith('/notes/')) {
    const key = pathname.replace('/notes/', '');
    const password = searchParams.get('password');
    if (isPasswordProtected && password !== ADMIN_PASSWORD) {
      return new Response('Unauthorized', {
      status: 401,
      headers: { 'content-type': 'text/plain;charset=UTF-8' },
      });
    }
    try {
      const value = await request.text();
      await JSBR2.put(key, value);
      const timestamp = new Date().toISOString();
      await JSBR2.put(`${key}_timestamp`, timestamp);
      return new Response('Note saved', {
      headers: { 'content-type': 'text/plain;charset=UTF-8' },
      });
    } catch (err) {
      console.error('Error saving note:', err);
      return new Response('Error saving note', {
      status: 500,
      headers: { 'content-type': 'text/plain;charset=UTF-8' },
      });
    }
}

if (request.method === 'POST' && pathname === '/set-password') {
    const password = await request.text();
    await JSBR2.put('admin_password', password);
    return new Response('Password set', {
      headers: { 'content-type': 'text/plain;charset=UTF-8' },
    });
}

if (request.method === 'POST' && pathname === '/change-password') {
    const { oldPassword, newPassword } = await request.json();
    if (isPasswordProtected && oldPassword !== ADMIN_PASSWORD) {
      return new Response('Unauthorized', {
      status: 401,
      headers: { 'content-type': 'text/plain;charset=UTF-8' },
      });
    }
    await JSBR2.put('admin_password', newPassword);
    return new Response('Password changed', {
      headers: { 'content-type': 'text/plain;charset=UTF-8' },
    });
}

return new Response('Not found', {
    status: 404,
    headers: { 'content-type': 'text/plain;charset=UTF-8' },
});
}

async function getHTML(isPasswordProtected) {
return `
    <!DOCTYPE html>
    <html>
    <head>
      <title>记事本</title>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <style>
      body, html {
          margin: 0;
          padding: 0;
          height: 100%;
          width: 100%;
          display: flex;
          flex-direction: column;
          font-family: Arial, sans-serif;
      }
      textarea {
          flex: 1;
          width: 100%;
          box-sizing: border-box;
          padding: 10px;
          font-size: 16px;
          resize: none;
      }
      .status {
          background-color: rgba(0, 0, 0, 0.5);
          color: white;
          padding: 10px;
          border-radius: 5px;
          font-size: 14px;
          display: flex;
          flex-direction: column;
          align-items: flex-start;
      }
      .status-row {
          display: flex;
          justify-content: space-between;
          width: 100%;
      }
      .status span, .status button {
          margin: 5px 10px;
      }
      .password-setup,
      .change-password {
          display: none;
          flex-direction: row;
          align-items: center;
          margin-top: 10px;
      }
      .password-setup input,
      .change-password input {
          margin-right: 10px;
          margin-bottom: 5px;
      }
      .change-password input {
          margin-right: 5px;
      }
      @media (max-width: 600px) {
          .status {
            position: static;
            width: 100%;
            padding: 5px;
            font-size: 12px;
          }
          .status-row {
            flex-direction: row;
            flex-wrap: wrap;
            justify-content: space-between;
          }
          .status span, .status button {
            margin: 2px 5px;
          }
          .password-setup,
          .change-password {
            flex-direction: column;
            align-items: flex-start;
          }
      }
      @media (min-width: 601px) {
          .status {
            position: fixed;
            bottom: 10px;
            right: 10px;
          }
      }
      .dark-mode {
          background-color: black;
          color: white;
      }
      .dark-mode textarea {
          background-color: black;
          color: white;
      }
      .dark-mode #moon-icon path {
          fill: white;
      }
      #moon-icon path {
          fill: black;
      }
      </style>
    </head>
    <body>
      <textarea id="note" placeholder="请输入密码以查看内容" disabled></textarea>
      <div class="status" id="status">
      <div class="status-row">
          <span>当前已使用:<span id="used-space">0.00 KB</span></span>
          <span>总剩余空间:<span id="remaining-space">9.99 GB</span></span>
      </div>
      <div class="status-row">
          <span id="last-write-time-container">最/后写入时间:<span id="last-write-time">N/A</span></span>
          <button id="change-password-button" onclick="showChangePassword()" style="display: none; margin-right: 20px;">修改密码</button>
      </div>
      <div class="password-setup" id="password-setup">
          <input type="password" id="admin-password" placeholder="设置管理员密码" />
          <button onclick="setPassword()">确认</button>
      </div>
      <div class="change-password" id="change-password">
          <input type="password" id="old-password" placeholder="旧密码" />
          <input type="password" id="new-password" placeholder="新密码" />
          <button onclick="changePassword()">确定</button>
      </div>
      </div>
      <svg id="moon-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" style="position: fixed; top: 10px; right: 10px; cursor: pointer;">
      <path d="M12 2a10 10 0 0 0 0 20 10 10 0 0 0 0-20zm0 18a8 8 0 1 1 0-16 8 8 0 0 1 0 16z"/>
      </svg>
      <script>
      const TOTAL_STORAGE = 10 * 1024 * 1024 * 1024; // 10 GB in bytes

      function updateStorageStatus() {
          const note = document.getElementById('note').value;
          const usedBytes = new TextEncoder().encode(note).length;
          const remainingBytes = TOTAL_STORAGE - usedBytes;

          document.getElementById('used-space').textContent = formatBytes(usedBytes);
          document.getElementById('remaining-space').textContent = formatBytes(remainingBytes);
      }

      function formatBytes(bytes) {
          const units = ['B', 'KB', 'MB', 'GB'];
          let unitIndex = 0;
          let value = bytes;

          while (value >= 1024 && unitIndex < units.length - 1) {
            value /= 1024;
            unitIndex++;
          }

          return value.toFixed(2) + ' ' + units;
      }

      async function saveNote() {
          const note = document.getElementById('note').value;
          let password = localStorage.getItem('adminPassword');
          if (${isPasswordProtected} && !password) {
            password = prompt('请输入管理员密码:');
            if (password) {
            localStorage.setItem('adminPassword', password);
            document.getElementById('change-password-button').style.display = 'inline-block';
            } else {
            alert('需要密码才能编辑笔记。');
            return;
            }
          }
          const response = await fetch(\`/notes/my-note?password=\${password || ''}\`, {
            method: 'PUT',
            body: note,
            headers: {
            'Content-Type': 'text/plain;charset=UTF-8'
            }
          });
          if (response.status === 401) {
            alert('密码错误。请刷新页面并输入正确的密码。');
            localStorage.removeItem('adminPassword');
            document.getElementById('change-password-button').style.display = 'none';
            return;
          }
          const timestamp = new Date().toISOString();
          document.getElementById('last-write-time').textContent = new Date(timestamp).toLocaleString();
          document.getElementById('last-write-time-container').style.display = 'inline'; // 显示最/后写入时间
      }

      async function loadNote() {
          let password = localStorage.getItem('adminPassword');
          if (${isPasswordProtected} && !password) {
            password = prompt('请输入管理员密码:');
            if (password) {
            localStorage.setItem('adminPassword', password);
            } else {
            alert('需要密码才能查看笔记内容。');
            return;
            }
          }
          const response = await fetch(\`/notes/my-note?password=\${password || ''}\`);
          if (response.status === 401) {
            alert('密码错误。请刷新页面并输入正确的密码。');
            localStorage.removeItem('adminPassword');
            return;
          }
          const note = await response.text();
          document.getElementById('note').value = note;
          document.getElementById('note').disabled = false; // 启用输入框
          updateStorageStatus();
          updateLastWriteTime(); // Initial load of last write time
      }

      async function updateLastWriteTime() {
          const timestampResponse = await fetch('/notes/my-note_timestamp');
          if (timestampResponse.ok) {
            const timestamp = await timestampResponse.text();
            const localTimestamp = new Date(timestamp).toLocaleString();
            document.getElementById('last-write-time').textContent = localTimestamp;
            document.getElementById('last-write-time-container').style.display = 'inline'; // 显示最/后写入时间
          } else {
            document.getElementById('last-write-time-container').style.display = 'none'; // 隐藏最/后写入时间
          }
      }

      function debounce(func, wait) {
          let timeout;
          return function() {
            const context = this, args = arguments;
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(context, args), wait);
          };
      }

      const debouncedSaveNote = debounce(saveNote, 200);

      document.getElementById('note').addEventListener('input', () => {
          debouncedSaveNote();
          updateStorageStatus();
      });

      window.addEventListener('load', () => {
          loadNote();
          setInterval(updateLastWriteTime, 1000); // 每秒更新一次最/后写入时间

          const password = localStorage.getItem('adminPassword');
          if (password) {
            document.getElementById('change-password-button').style.display = 'inline-block';
          }
      });

      async function setPassword() {
          const password = document.getElementById('admin-password').value;
          if (password) {
            const response = await fetch('/set-password', {
            method: 'POST',
            body: password,
            headers: {
                'Content-Type': 'text/plain;charset=UTF-8'
            }
            });
            if (response.ok) {
            alert('管理员密码设置成功');
            document.getElementById('password-setup').style.display = 'none';
            } else {
            alert('设置管理员密码失败');
            }
          } else {
            alert('请输入密码');
          }
      }

      function showChangePassword() {
          const password = localStorage.getItem('adminPassword');
          if (!password) {
            alert('您尚未登陆,请登陆后再修改密码');
            return;
          }
          document.getElementById('change-password').style.display = 'flex';
          document.getElementById('change-password-button').style.display = 'none';
      }

      async function changePassword() {
          const oldPassword = document.getElementById('old-password').value;
          const newPassword = document.getElementById('new-password').value;
          if (!oldPassword || !newPassword) {
            alert('请输入旧密码和新密码');
            return;
          }
          const response = await fetch('/change-password', {
            method: 'POST',
            body: JSON.stringify({ oldPassword, newPassword }),
            headers: {
            'Content-Type': 'application/json;charset=UTF-8'
            }
          });
          if (response.status === 401) {
            alert('旧密码不正确');
            return;
          }
          if (response.ok) {
            alert('密码修改成功');
            document.getElementById('change-password').style.display = 'none';
            localStorage.setItem('adminPassword', newPassword);
          } else {
            alert('密码修改失败');
          }
      }

      document.getElementById('moon-icon').addEventListener('click', () => {
          document.body.classList.toggle('dark-mode');
      });
      </script>
    </body>
    </html>
`;
}



页: [1]
查看完整版本: Cloudflare Workers 记事本