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

一,目标

构建一个模块化、可扩展、支持 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 缓存)

相关推荐
WindrunnerMax2 小时前
从零实现富文本编辑器#9-编辑器文本结构变更的受控处理
前端·架构·github
Mintopia2 小时前
静态内容页该用HTML还是Next.js展示更好
前端·html·next.js
LYFlied2 小时前
【每日算法】LeetCode 226. 翻转二叉树
前端·算法·leetcode·面试·职场和发展
无名无姓某罗2 小时前
jQuery 请求 SpringMVC 接口返回404错误排查
前端·spring·jquery
霁月的小屋2 小时前
Vue响应式数据全解析:从Vue2到Vue3,ref与reactive的实战指南
前端·javascript·vue.js
李少兄2 小时前
深入理解 Java Web 开发中的 HttpServletRequest 与 HttpServletResponse
java·开发语言·前端
holidaypenguin2 小时前
antd 5 + react 18 + vite 7 升级
前端·react.js
小满zs2 小时前
Next.js第十五章(Image)
前端·next.js
tangbin5830852 小时前
iOS Swift 可选值(Optional)详解
前端·ios