完整静态工具网站(尝试)

一,目标

构建一个模块化、可扩展、支持 PWA 的前端工具小站,支持以下工具:

  1. JSON 格式化
  2. Base64 编解码
  3. 时间戳转换(Unix Timestamp ↔ 日期)
  4. 字段命名转换(驼峰 ↔ 下划线 ↔ 横线)

二,项目结构与核心框架(PWA + 模块化基础)


一、最终项目结构(模块化设计)

bash 复制代码
tools-site/
│
├── index.html                     # 主页(工具列表)
├── manifest.json                  # PWA 配置
├── sw.js                          # Service Worker
│
├── css/
│   └── style.css                  # 全局样式
│
├── js/
│   ├── main.js                    # 主逻辑(注册 SW)
│   └── tools/                     # 工具模块目录
│       ├── json-formatter.js      # JSON 格式化
│       ├── base64-encoder.js      # Base64 编解码
│       ├── timestamp-converter.js # 时间戳转换
│       └── case-converter.js      # 命名转换(驼峰/下划线等)
│
└── assets/
    ├── favicon.svg
    ├── icon-72x72.png
    ├── icon-96x96.png
    ├── icon-128x128.png
    ├── icon-192x192.png
    └── icon-512x512.png 

所有工具独立成 JS 文件,便于维护和扩展。


二、核心文件(PWA + 页面结构)

1. index.html(主页面,工具入口)
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
  <meta name="description" content="轻量级在线工具集合:JSON格式化、Base64编解码、时间戳转换、字段命名转换等" />
  <title>工具小站</title>

  <!-- PWA 支持 -->
  <link rel="manifest" href="manifest.json">
  <meta name="theme-color" content="#2c3e50">
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="default">
  <meta name="apple-mobile-web-app-title" content="工具小站">
  <link rel="apple-touch-icon" href="assets/icon-192x192.png">
  <link rel="icon" type="image/svg+xml" href="assets/favicon.svg">
  <link rel="icon" type="image/png" href="assets/icon-192x192.png">

  <!-- 样式 -->
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <div class="container">
    <header>
      <h1>工具小站</h1>
      <p>轻量级在线工具集合 | 支持离线使用</p>
    </header>

    <main>
      <div class="tool-grid">
        <a href="#" class="tool-card" onclick="showTool('json'); return false;">
          <h3>JSON 格式化</h3>
          <p>美化或压缩 JSON 字符串</p>
        </a>

        <a href="#" class="tool-card" onclick="showTool('base64'); return false;">
          <h3>Base64 编解码</h3>
          <p>文本与 Base64 互转</p>
        </a>

        <a href="#" class="tool-card" onclick="showTool('timestamp'); return false;">
          <h3>时间戳转换</h3>
          <p>Unix 时间戳 ↔ 日期时间</p>
        </a>

        <a href="#" class="tool-card" onclick="showTool('case'); return false;">
          <h3>命名转换</h3>
          <p>驼峰、下划线、横线互转</p>
        </a>
      </div>

      <!-- 工具容器 -->
      <div id="tool-container" class="tool-container"></div>
    </main>

    <footer>
      <p>© 2025 工具小站 | 支持离线使用</p>
    </footer>
  </div>

  <!-- 工具模块 -->
  <script src="js/tools/json-formatter.js"></script>
  <script src="js/tools/base64-encoder.js"></script>
  <script src="js/tools/timestamp-converter.js"></script>
  <script src="js/tools/case-converter.js"></script>

  <!-- 主逻辑 -->
  <script src="js/main.js"></script>
</body>
</html>

2. css/style.css(全局样式)
css 复制代码
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
  background-color: #f5f7fa;
  color: #333;
  line-height: 1.6;
}

.container {
  max-width: 900px;
  margin: 0 auto;
  padding: 20px;
}

header {
  text-align: center;
  margin-bottom: 40px;
}

header h1 {
  font-size: 2.5rem;
  color: #2c3e50;
}

header p {
  color: #7f8c8d;
  font-size: 1.1rem;
}

/* 工具卡片网格 */
.tool-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 20px;
  margin-bottom: 40px;
}

.tool-card {
  background: white;
  padding: 20px;
  border-radius: 12px;
  box-shadow: 0 4px 6px rgba(0,0,0,0.1);
  text-decoration: none;
  color: #2c3e50;
  transition: transform 0.2s;
}

.tool-card:hover {
  transform: translateY(-4px);
}

.tool-card h3 {
  margin-bottom: 8px;
  font-size: 1.2rem;
}

.tool-card p {
  color: #7f8c8d;
  font-size: 0.9rem;
}

/* 工具容器 */
.tool-container {
  display: none;
  background: white;
  padding: 20px;
  border-radius: 12px;
  box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}

.tool-container.active {
  display: block;
}

.tool-container h2 {
  color: #2c3e50;
  margin-bottom: 15px;
  border-bottom: 1px solid #eee;
  padding-bottom: 10px;
}

textarea {
  width: 100%;
  height: 120px;
  padding: 12px;
  border: 1px solid #ddd;
  border-radius: 8px;
  font-family: monospace;
  font-size: 14px;
  resize: vertical;
}

button {
  background-color: #3498db;
  color: white;
  border: none;
  padding: 10px 16px;
  margin: 10px 5px 0 0;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
}

button:hover {
  background-color: #2980b9;
}

pre {
  background: #f1f1f1;
  padding: 15px;
  border-radius: 8px;
  overflow: auto;
  white-space: pre-wrap;
  word-wrap: break-word;
  margin-top: 10px;
  font-family: monospace;
  font-size: 14px;
}

footer {
  text-align: center;
  margin-top: 50px;
  color: #95a5a6;
  font-size: 0.9rem;
}

3. js/main.js(主逻辑)
javascript 复制代码
// 显示指定工具
function showTool(toolName) {
  // 隐藏所有工具
  const containers = document.querySelectorAll('.tool-container');
  containers.forEach(c => c.classList.remove('active'));

  // 获取或创建工具容器
  let toolContainer = document.getElementById(`tool-${toolName}`);
  if (!toolContainer) {
    toolContainer = document.createElement('div');
    toolContainer.id = `tool-${toolName}`;
    toolContainer.className = 'tool-container active';
    document.getElementById('tool-container').appendChild(toolContainer);
    
    // 调用对应工具的渲染函数
    if (window[`render${capitalize(toolName)}Tool`]) {
      window[`render${capitalize(toolName)}Tool`](toolContainer);
    }
  } else {
    toolContainer.classList.add('active');
  }
}

// 工具名首字母大写
function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

// 注册 Service Worker
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/tools-site/sw.js')
      .then(registration => {
        console.log('✅ Service Worker 注册成功:', registration.scope);
      })
      .catch(error => {
        console.log('❌ Service Worker 注册失败:', error);
      });
  });
}

4. manifest.json(PWA 配置)
json 复制代码
{
  "name": "工具小站",
  "short_name": "工具箱",
  "description": "轻量级在线工具集合:JSON格式化、Base64编解码、时间戳转换、命名转换",
  "start_url": "/tools-site/",
  "scope": "/tools-site/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#2c3e50",
  "orientation": "portrait-primary",
  "lang": "zh-CN",
  "icons": [
    {
      "src": "assets/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "assets/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "assets/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "assets/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "assets/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

5. sw.js(Service Worker - 离线缓存)
javascript 复制代码
const CACHE_NAME = 'tools-site-v3';
const urlsToCache = [
  '/tools-site/',
  '/tools-site/index.html',
  '/tools-site/manifest.json',
  '/tools-site/css/style.css',
  '/tools-site/js/main.js',
  '/tools-site/js/tools/json-formatter.js',
  '/tools-site/js/tools/base64-encoder.js',
  '/tools-site/js/tools/timestamp-converter.js',
  '/tools-site/js/tools/case-converter.js',
  '/tools-site/assets/favicon.svg',
  '/tools-site/assets/icon-192x192.png',
  '/tools-site/assets/icon-512x512.png'
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(urlsToCache))
  );
});

self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then(keys => 
      Promise.all(
        keys.filter(key => key !== CACHE_NAME)
            .map(key => caches.delete(key))
      )
    )
  );
});

self.addEventListener('fetch', (event) => {
  if (event.request.url.startsWith(self.location.origin)) {
    event.respondWith(
      caches.match(event.request)
        .then(response => response || fetch(event.request))
    );
  }
});

太好了!现在我们进入 第二步:完整实现 4 个工具模块 ,每个工具都独立封装在 js/tools/ 目录下,支持动态加载、交互完整、样式统一。


三,工具模块实现(4 个完整工具)


工具 1:json-formatter.js(JSON 格式化)

javascript 复制代码
// js/tools/json-formatter.js

// 渲染 JSON 格式化工具界面
function renderJsonTool(container) {
  container.innerHTML = `
    <h2>JSON 格式化</h2>
    <p>输入 JSON 字符串,支持格式化或压缩</p>
    
    <textarea id="jsonInput" placeholder="输入 JSON 字符串,例如:{\"name\":\"张三\",\"age\":25}"></textarea>
    
    <div class="button-group">
      <button onclick="formatJSON()">格式化</button>
      <button onclick="minifyJSON()">压缩</button>
      <button onclick="clearJSON()">清空</button>
    </div>
    
    <pre id="jsonOutput" class="output"></pre>
  `;
}

// 格式化 JSON
function formatJSON() {
  const input = document.getElementById('jsonInput').value.trim();
  const output = document.getElementById('jsonOutput');
  if (!input) {
    output.textContent = '请输入 JSON 内容';
    return;
  }
  try {
    const obj = JSON.parse(input);
    output.textContent = JSON.stringify(obj, null, 2);
  } catch (e) {
    output.textContent = '格式错误:' + e.message;
  }
}

// 压缩 JSON
function minifyJSON() {
  const input = document.getElementById('jsonInput').value.trim();
  const output = document.getElementById('jsonOutput');
  if (!input) {
    output.textContent = '请输入 JSON 内容';
    return;
  }
  try {
    const obj = JSON.parse(input);
    output.textContent = JSON.stringify(obj);
  } catch (e) {
    output.textContent = '格式错误:' + e.message;
  }
}

// 清空
function clearJSON() {
  document.getElementById('jsonInput').value = '';
  document.getElementById('jsonOutput').textContent = '';
}

工具 2:base64-encoder.js(Base64 编解码)

javascript 复制代码
// js/tools/base64-encoder.js

// 渲染 Base64 工具界面
function renderBase64Tool(container) {
  container.innerHTML = `
    <h2>Base64 编解码</h2>
    <p>文本与 Base64 字符串互转</p>
    
    <textarea id="base64Input" placeholder="输入要编码或解码的文本"></textarea>
    
    <div class="button-group">
      <button onclick="encodeBase64()">编码 → Base64</button>
      <button onclick="decodeBase64()">解码 ← Base64</button>
      <button onclick="clearBase64()">清空</button>
    </div>
    
    <pre id="base64Output" class="output"></pre>
  `;
}

// 编码为 Base64
function encodeBase64() {
  const input = document.getElementById('base64Input').value;
  const output = document.getElementById('base64Output');
  if (!input) {
    output.textContent = '请输入文本';
    return;
  }
  try {
    // 支持中文
    const encoded = btoa(unescape(encodeURIComponent(input)));
    output.textContent = encoded;
  } catch (e) {
    output.textContent = '编码失败:' + e.message;
  }
}

// 从 Base64 解码
function decodeBase64() {
  const input = document.getElementById('base64Input').value;
  const output = document.getElementById('base64Output');
  if (!input) {
    output.textContent = '请输入 Base64 字符串';
    return;
  }
  try {
    const decoded = decodeURIComponent(escape(atob(input)));
    output.textContent = decoded;
  } catch (e) {
    output.textContent = '解码失败:可能不是有效的 Base64';
  }
}

// 清空
function clearBase64() {
  document.getElementById('base64Input').value = '';
  document.getElementById('base64Output').textContent = '';
}

工具 3:timestamp-converter.js(时间戳转换)

javascript 复制代码
// js/tools/timestamp-converter.js

// 渲染时间戳工具界面
function renderTimestampTool(container) {
  container.innerHTML = `
    <h2>时间戳转换</h2>
    <p>Unix 时间戳 ↔ 日期时间(支持秒/毫秒)</p>
    
    <div class="input-group">
      <label>时间戳:</label>
      <input type="text" id="timestampInput" placeholder="输入时间戳(13位毫秒或10位秒)">
      <button onclick="convertTimestampToDate()">→ 转日期</button>
    </div>
    
    <div class="input-group">
      <label>日期时间:</label>
      <input type="datetime-local" id="dateInput">
      <button onclick="convertDateToTimestamp()">→ 转时间戳</button>
    </div>
    
    <div class="result-group">
      <h3>转换结果:</h3>
      <pre id="timestampResult" class="output"></pre>
    </div>
    
    <div class="button-group">
      <button onclick="setNowTimestamp()">设为当前时间</button>
      <button onclick="clearTimestamp()">清空</button>
    </div>
  `;
}

// 时间戳 → 日期
function convertTimestampToDate() {
  const input = document.getElementById('timestampInput').value.trim();
  const result = document.getElementById('timestampResult');
  if (!input) {
    result.textContent = '请输入时间戳';
    return;
  }
  const timestamp = Number(input);
  if (isNaN(timestamp)) {
    result.textContent = '请输入有效数字';
    return;
  }

  // 判断是秒还是毫秒
  const date = new Date(timestamp > 9999999999 ? timestamp : timestamp * 1000);
  if (isNaN(date.getTime())) {
    result.textContent = '无效的时间戳';
    return;
  }

  result.textContent = `日期:${formatDateTime(date)}\n时区:${date.toString().match(/\((.+)\)/)[1]}`;
}

// 日期 → 时间戳
function convertDateToTimestamp() {
  const input = document.getElementById('dateInput').value;
  const result = document.getElementById('timestampResult');
  if (!input) {
    result.textContent = '请选择日期时间';
    return;
  }
  const date = new Date(input);
  const timestampSec = Math.floor(date.getTime() / 1000);
  const timestampMs = date.getTime();

  result.textContent = `秒级时间戳:${timestampSec}\n毫秒级时间戳:${timestampMs}`;
}

// 设置当前时间
function setNowTimestamp() {
  const now = new Date();
  document.getElementById('dateInput').value = now.toISOString().slice(0, 16);
  document.getElementById('timestampInput').value = now.getTime();
  convertTimestampToDate();
}

// 清空
function clearTimestamp() {
  document.getElementById('timestampInput').value = '';
  document.getElementById('dateInput').value = '';
  document.getElementById('timestampResult').textContent = '';
}

// 格式化日期显示
function formatDateTime(date) {
  return date.toLocaleString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit'
  }).replace(/\//g, '-');
}

工具 4:case-converter.js(命名转换)

javascript 复制代码
// js/tools/case-converter.js

// 渲染命名转换工具界面
function renderCaseTool(container) {
  container.innerHTML = `
    <h2>命名转换</h2>
    <p>支持驼峰、下划线、横线等格式互转</p>
    
    <textarea id="caseInput" placeholder="输入字段名,例如:user_name 或 user-name 或 userName"></textarea>
    
    <div class="button-group">
      <button onclick="toCamelCase()">→ 驼峰命名 (userName)</button>
      <button onclick="toSnakeCase()">→ 下划线 (user_name)</button>
      <button onclick="toKebabCase()">→ 横线 (user-name)</button>
      <button onclick="toPascalCase()">→ 大驼峰 (UserName)</button>
    </div>
    
    <div class="button-group">
      <button onclick="clearCase()">清空</button>
    </div>
    
    <pre id="caseOutput" class="output"></pre>
  `;
}

// 转驼峰命名 (userName)
function toCamelCase() {
  const input = document.getElementById('caseInput').value;
  const output = document.getElementById('caseOutput');
  const result = convertCase(input, 'camel');
  output.textContent = result;
}

// 转下划线 (user_name)
function toSnakeCase() {
  const input = document.getElementById('caseInput').value;
  const output = document.getElementById('caseOutput');
  const result = convertCase(input, 'snake');
  output.textContent = result;
}

// 转横线 (user-name)
function toKebabCase() {
  const input = document.getElementById('caseInput').value;
  const output = document.getElementById('caseOutput');
  const result = convertCase(input, 'kebab');
  output.textContent = result;
}

// 转大驼峰 (UserName)
function toPascalCase() {
  const input = document.getElementById('caseInput').value;
  const output = document.getElementById('caseOutput');
  const result = convertCase(input, 'pascal');
  output.textContent = result;
}

// 清空
function clearCase() {
  document.getElementById('caseInput').value = '';
  document.getElementById('caseOutput').textContent = '';
}

// 核心转换逻辑
function convertCase(str, type) {
  if (!str) return '';

  // 统一先转成单词数组
  const words = str
    .replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, ' ')  // 非字母数字替换为空格
    .split(/\s+/)
    .filter(Boolean)
    .map(word => word.toLowerCase());

  if (words.length === 0) return str;

  switch (type) {
    case 'camel':
      return words.map((word, i) => 
        i === 0 ? word : capitalize(word)
      ).join('');
    
    case 'snake':
      return words.join('_');
    
    case 'kebab':
      return words.join('-');
    
    case 'pascal':
      return words.map(capitalize).join('');
    
    default:
      return str;
  }
}

// 首字母大写
function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

最终效果验证

功能 状态
点击"JSON 格式化"卡片 显示格式化界面,支持美化/压缩
点击"Base64 编解码" 文本与 Base64 互转
点击"时间戳转换" 秒/毫秒 ↔ 日期,支持本地时区
点击"命名转换" 驼峰/下划线/横线互转
手机访问 出现"安装"按钮
安装后离线打开 所有工具仍可使用(Service Worker 缓存)

相关推荐
Simon5231420 小时前
Spring AOP 五大通知类型
java·前端·spring
Asmewill20 小时前
LangGraph学习笔记八(SubGraph)
前端
叶落阁主20 小时前
AntV npm 投毒复盘:一次公司私服缓存恶意包引发的账号封禁事件
前端·安全·npm
vaexu20 小时前
Android 定时提醒的终极防线:我是如何用“双保险机制”攻克后台保活的?
前端
小村儿20 小时前
连载11- Claude code 的 Agent Teams——当子 Agent 开始互相说话
前端·后端·ai编程
潍坊老登20 小时前
关于 number类型从vue端传到golang后端是float而不是int的事
前端
茶底世界之下20 小时前
你的 Mac 里,藏着一支 AI 开发团队
前端·javascript
不爱说话郭德纲21 小时前
出门在外收到任务,我用 TRAE SOLO 把电脑“叫醒”干活
前端·ai编程
前端Hardy21 小时前
这个前端动画库,火了!
前端·javascript
小林攻城狮21 小时前
Vite项目使用@turbodocx/html-to-docx报错问题排查与解决方案
前端·ai编程