【HTML】前端工具箱实现【文本处理/JSON工具/加解密/校验和/ASCII/时间戳转换等】【附完整源代码】

部分界面预览




1. 整体架构设计

1.1 HTML结构

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <!-- 元数据和样式 -->
</head>
<body>
    <div class="container">
        <div class="header-actions">
            <h1>全能前端处理工具</h1>
            <div class="theme-switch">
                <!-- 暗黑模式切换 -->
            </div>
        </div>
        
        <div class="tabs">
            <!-- 6个功能标签页 -->
        </div>
        
        <!-- 各个功能区域 -->
        <div id="text-tools" class="tab-content active">
            <!-- 文本处理功能 -->
        </div>
        
        <!-- 其他5个功能区域 -->
    </div>
    <script>
        // 所有JavaScript代码
    </script>
</body>
</html>
  • 使用语义化的HTML5文档结构
  • 采用标签页(tab)设计,将功能分为六大类:
    • 文本处理
    • JSON工具
    • Base64工具
    • 编码/加密
    • ASCII工具
    • 时间工具

1.2 核心功能分析

文本处理功能
  • 文本统计:实时计算字符数、单词数、行数等
  • 大小写转换:支持多种格式转换
  • 正则表达式测试:支持多种标志和替换功能
  • HTML转义/反转义
  • URL编码/解码
JSON工具
  • 格式化与压缩:美化或压缩JSON数据
  • 格式转换:JSON转XML、CSV、YAML
  • 路径查询:简单的JSON路径查询功能
Base64工具
  • 文本编码/解码
  • 文件编码:将文件转换为Base64
  • 图片预览:直接预览Base64编码的图片
编码/加密工具
  • 哈希计算:支持MD5、SHA-1、SHA-256、SHA-512
  • AES加密/解密:使用Web Crypto API实现
ASCII工具
  • 文本与ASCII码互转:支持多种输出格式
  • ASCII码表:动态生成完整的ASCII码表
时间工具
  • 时间戳转换:Unix时间戳与日期互转
  • 日期计算:计算两个日期之间的差值

2. CSS设计

2. CSS设计系统

  • 使用CSS变量定义主题色彩,便于维护和切换
  • 响应式设计,适配不同屏幕尺寸
  • 暗黑模式支持
  • 清晰的视觉层次和交互反馈
CSS变量定义
css 复制代码
:root {
    --primary-color: #4a6fa5;
    --secondary-color: #6b8cae;
    --light-color: #f8f9fa;
    --dark-color: #343a40;
    --success-color: #28a745;
    --danger-color: #dc3545;
    --warning-color: #ffc107;
    --info-color: #17a2b8;
}

使用CSS变量统一管理主题色,便于维护和主题切换。

响应式设计
css 复制代码
@media (max-width: 768px) {
    .container {
        padding: 15px;
    }
    
    .split-container {
        flex-direction: column;
    }
    
    .header-actions {
        flex-direction: column;
        align-items: flex-start;
        gap: 10px;
    }
}

媒体查询实现移动端适配,在小屏幕上调整布局。

暗黑模式实现
css 复制代码
body.dark-mode {
    background-color: #1a1a1a;
    color: #e0e0e0;
}

body.dark-mode .container {
    background-color: #2d2d2d;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}

通过body类名切换实现完整的暗黑主题。

3. JavaScript核心功能实现

3.1 初始化与状态管理

javascript 复制代码
document.addEventListener('DOMContentLoaded', function() {
    // 暗黑模式切换
    const darkModeToggle = document.getElementById('dark-mode-toggle');
    darkModeToggle.addEventListener('change', () => {
        document.body.classList.toggle('dark-mode', darkModeToggle.checked);
        localStorage.setItem('darkMode', darkModeToggle.checked);
    });
    
    // 初始化暗黑模式状态
    if (localStorage.getItem('darkMode')) {
        darkModeToggle.checked = localStorage.getItem('darkMode') === 'true';
        document.body.classList.toggle('dark-mode', darkModeToggle.checked);
    }
});

使用localStorage持久化用户偏好设置。

3.2 标签页系统

javascript 复制代码
// 标签页切换
const tabs = document.querySelectorAll('.tab');
const tabContents = document.querySelectorAll('.tab-content');

tabs.forEach(tab => {
    tab.addEventListener('click', () => {
        // 移除所有active类
        tabs.forEach(t => t.classList.remove('active'));
        tabContents.forEach(content => content.classList.remove('active'));
        
        // 添加active类到当前标签和内容
        tab.classList.add('active');
        const tabId = tab.getAttribute('data-tab');
        document.getElementById(tabId).classList.add('active');
        
        // 保存当前标签页
        localStorage.setItem('lastTab', tabId);
    });
});

通过data属性关联标签和内容,实现简单的SPA效果。

3.3 文本处理功能详解

实时文本统计
javascript 复制代码
statsInput.addEventListener('input', () => {
    const text = statsInput.value;
    document.getElementById('char-count').textContent = text.length;
    
    const words = text.trim() ? text.trim().split(/\s+/) : [];
    document.getElementById('word-count').textContent = words.length;
    
    const lines = text.split('\n');
    document.getElementById('line-count').textContent = lines.length;
    
    const nonEmptyLines = lines.filter(line => line.trim());
    document.getElementById('non-empty-line-count').textContent = nonEmptyLines.length;
});

使用input事件实时更新统计信息,正则表达式/\s+/分割单词。

正则表达式测试
javascript 复制代码
document.getElementById('test-regex').addEventListener('click', () => {
    try {
        const text = regexInput.value;
        const pattern = document.getElementById('regex-pattern').value;
        const flags = document.getElementById('regex-flags').value;
        const regex = new RegExp(pattern, flags);
        const matches = text.match(regex);
        
        if (matches) {
            regexOutput.value = `找到 ${matches.length} 处匹配:\n${matches.join('\n')}`;
            
            // 高亮显示匹配内容
            let highlighted = text;
            matches.forEach(match => {
                highlighted = highlighted.replaceAll(match, `<span class="match-highlight">${match}</span>`);
            });
            
            regexMatches.innerHTML = `<h4>匹配位置:</h4><div style="border:1px solid #ddd;padding:10px;">${highlighted}</div>`;
        }
    } catch (e) {
        // 错误处理
    }
});

动态创建正则表达式,提供可视化匹配结果。

3.4 JSON工具实现

JSON格式化与验证
javascript 复制代码
document.getElementById('format-json').addEventListener('click', () => {
    try {
        const jsonObj = JSON.parse(jsonInput.value);
        jsonOutput.value = JSON.stringify(jsonObj, null, 2); // 缩进2空格
        showStatus(jsonStatus, 'JSON格式化成功!', 'success');
    } catch (e) {
        showStatus(jsonStatus, `JSON格式化失败: ${e.message}`, 'error');
    }
});

利用JSON.parse和JSON.stringify实现格式化和验证。

JSON转XML
javascript 复制代码
function jsonToXml(jsonObj, nodeName = 'root') {
    let xml = '';
    
    if (typeof jsonObj === 'object' && jsonObj !== null) {
        if (Array.isArray(jsonObj)) {
            jsonObj.forEach((item, index) => {
                xml += jsonToXml(item, nodeName + '_' + index);
            });
        } else {
            xml += '<' + nodeName + '>';
            for (const key in jsonObj) {
                if (jsonObj.hasOwnProperty(key)) {
                    xml += jsonToXml(jsonObj[key], key);
                }
            }
            xml += '</' + nodeName + '>';
        }
    } else {
        xml += '<' + nodeName + '>' + jsonObj + '</' + nodeName + '>';
    }
    
    return xml;
}

递归遍历JSON对象,构建XML字符串。

3.5 Base64文件处理

文件编码
javascript 复制代码
document.getElementById('encode-file').addEventListener('click', () => {
    const file = fileUpload.files[0];
    if (!file) {
        showStatus(document.getElementById('file-status'), '请先选择文件', 'error');
        return;
    }
    
    const reader = new FileReader();
    reader.onload = (e) => {
        const base64 = e.target.result.split(',')[1]; // 移除data URL前缀
        document.getElementById('file-output').value = base64;
        showStatus(document.getElementById('file-status'), '文件编码成功!', 'success');
    };
    reader.readAsDataURL(file);
});

使用FileReader API读取文件并转换为Base64。

3.6 加密功能实现

哈希计算
javascript 复制代码
async function calculateHash(text, algorithm) {
    // 使用Web Crypto API计算哈希
    const encoder = new TextEncoder();
    const data = encoder.encode(text);
    const hashBuffer = await crypto.subtle.digest(algorithm, data);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}

使用现代Web Crypto API,支持多种哈希算法。

AES加密
javascript 复制代码
async function aesEncrypt(text, key, iv) {
    const encoder = new TextEncoder();
    const keyMaterial = await crypto.subtle.importKey(
        'raw',
        encoder.encode(key),
        { name: 'AES-CBC' },
        false,
        ['encrypt']
    );
    
    const ivArray = iv ? encoder.encode(iv) : crypto.getRandomValues(new Uint8Array(16));
    const data = encoder.encode(text);
    
    const encrypted = await crypto.subtle.encrypt(
        {
            name: 'AES-CBC',
            iv: ivArray
        },
        keyMaterial,
        data
    );
    
    // 返回IV和加密数据的Base64组合
    const result = new Uint8Array(ivArray.length + encrypted.byteLength);
    result.set(ivArray, 0);
    result.set(new Uint8Array(encrypted), ivArray.length);
    
    return btoa(String.fromCharCode.apply(null, result));
}

完整的AES-CBC加密实现,包含IV处理。

3.7 辅助函数系统

状态显示
javascript 复制代码
function showStatus(element, message, type) {
    element.textContent = message;
    element.className = 'status-message ' + type;
    setTimeout(() => {
        element.textContent = '';
        element.className = 'status-message';
    }, 3000);
}

统一的用户反馈机制,3秒后自动消失。

剪贴板操作
javascript 复制代码
function copyToClipboard(element) {
    element.select();
    document.execCommand('copy');
    
    // 取消选中
    if (window.getSelection) {
        window.getSelection().removeAllRanges();
    } else if (document.selection) {
        document.selection.empty();
    }
}

兼容多种浏览器的复制功能实现。

4. 技术亮点总结

  1. 用户体验优化

    • 实时统计更新
    • 操作状态反馈
    • 一键复制功能
    • 暗黑模式切换
  2. 代码质量

    • 功能模块化
    • 错误处理完善
    • 代码复用度高
  3. 现代Web技术应用

    • 使用SS GridFlexbox布局
    • 利用Web Crypto API进行加密
    • 使用File API处理文件

内部细节

  1. 完整的错误处理:所有操作都有try-catch包装
  2. 用户体验优化:实时反馈、状态提示、一键复制
  3. 现代API使用FileReaderCryptolocalStorage
  4. 代码复用性:辅助函数封装良好
  5. 响应式设计:完美适配各种屏幕尺寸
  6. 主题系统:完整的亮色/暗色主题支持

这个工具网页展示了前端开发的多个重要方面:DOM操作、事件处理、数据转换、文件处理、加密算法等,功能全面,是一个很好的学习范例。

5. 源代码

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>全能前端处理工具</title>
    <style>
        :root {
            --primary-color: #4a6fa5;
            --secondary-color: #6b8cae;
            --light-color: #f8f9fa;
            --dark-color: #343a40;
            --success-color: #28a745;
            --danger-color: #dc3545;
            --warning-color: #ffc107;
            --info-color: #17a2b8;
        }
        
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            line-height: 1.6;
            background-color: #f5f5f5;
            color: var(--dark-color);
            padding: 20px;
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
        }
        
        h1 {
            text-align: center;
            margin-bottom: 30px;
            color: var(--primary-color);
        }
        
        .header-actions {
            display: flex;
            justify-content: space-between;
            margin-bottom: 20px;
            align-items: center;
        }
        
        .theme-switch {
            display: flex;
            align-items: center;
        }
        
        .theme-switch label {
            margin-right: 10px;
        }
        
        .tabs {
            display: flex;
            margin-bottom: 20px;
            border-bottom: 1px solid #ddd;
            flex-wrap: wrap;
        }
        
        .tab {
            padding: 10px 20px;
            cursor: pointer;
            background-color: #4a6fa5;
            border: none;
            border-radius: 5px 5px 0 0;
            margin-right: 5px;
            transition: all 0.3s;
            margin-bottom: 5px;
        }
        
        .tab:hover {
            background-color: #1782B8;
        }
        
        .tab.active {
            background-color: var(--primary-color); 
            background-color: #28A745;
            color: white;
        }
        
        .tab-content {
            display: none;
            padding: 20px;
            border: 1px solid #ddd;
            border-radius: 0 0 5px 5px;
            background-color: white;
        }
        
        .tab-content.active {
            display: block;
        }
        
        .tool-section {
            margin-bottom: 30px;
            border-bottom: 1px dashed #eee;
            padding-bottom: 20px;
        }
        
        .tool-section h2 {
            margin-bottom: 15px;
            color: var(--secondary-color);
            font-size: 1.3rem;
        }
        
        textarea {
            width: 100%;
            min-height: 150px;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            resize: vertical;
            font-family: Consolas, Monaco, 'Andale Mono', monospace;
            font-size: 14px;
        }
        
        .input-group {
            display: flex;
            gap: 10px;
            margin: 10px 0;
            align-items: center;
        }
        
        .input-group input, .input-group select {
            padding: 8px 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        
        .button-group {
            display: flex;
            gap: 10px;
            margin: 10px 0;
            flex-wrap: wrap;
        }
        
        button {
            padding: 8px 15px;
            background-color: var(--primary-color);
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            transition: all 0.3s;
            display: flex;
            align-items: center;
            gap: 5px;
        }
        
        button:hover {
            background-color: var(--secondary-color);
        }
        
        button.secondary {
            background-color: #6c757d;
        }
        
        button.secondary:hover {
            background-color: #5a6268;
        }
        
        button.success {
            background-color: var(--success-color);
        }
        
        button.success:hover {
            background-color: #218838;
        }
        
        button.danger {
            background-color: var(--danger-color);
        }
        
        button.danger:hover {
            background-color: #c82333;
        }
        
        button.warning {
            background-color: var(--warning-color);
            color: var(--dark-color);
        }
        
        button.warning:hover {
            background-color: #e0a800;
        }
        
        button.info {
            background-color: var(--info-color);
        }
        
        button.info:hover {
            background-color: #138496;
        }
        
        .result-area {
            margin-top: 20px;
        }
        
        .copy-btn {
            margin-top: 10px;
        }
        
        .status-message {
            margin-top: 10px;
            padding: 8px;
            border-radius: 4px;
            display: none;
        }
        
        .status-message.success {
            display: block;
            background-color: #d4edda;
            color: #155724;
        }
        
        .status-message.error {
            display: block;
            background-color: #f8d7da;
            color: #721c24;
        }
        
        .status-message.info {
            display: block;
            background-color: #d1ecf1;
            color: #0c5460;
        }
        
        .match-highlight {
            background-color: yellow;
            padding: 0 2px;
        }
        
        .file-upload {
            display: none;
        }
        
        .file-upload-label {
            display: inline-block;
            padding: 8px 15px;
            background-color: var(--info-color);
            color: white;
            border-radius: 4px;
            cursor: pointer;
            transition: background-color 0.3s;
        }
        
        .file-upload-label:hover {
            background-color: #138496;
        }
        
        .file-info {
            margin-left: 10px;
            font-size: 0.9em;
            color: #666;
        }
        
        .split-container {
            display: flex;
            gap: 20px;
        }
        
        .split-panel {
            flex: 1;
        }
        
        .stats-container {
            display: flex;
            gap: 15px;
            margin-top: 10px;
            flex-wrap: wrap;
        }
        
        .stat-box {
            background-color: #f8f9fa;
            border: 1px solid #ddd;
            border-radius: 4px;
            padding: 8px 12px;
            min-width: 120px;
        }
        
        .stat-label {
            font-size: 0.8em;
            color: #666;
        }
        
        .stat-value {
            font-weight: bold;
            font-size: 1.2em;
        }
        
        /* ASCII码表样式 */
        .ascii-table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 15px;
        }
        
        .ascii-table th, .ascii-table td {
            border: 1px solid #ddd;
            padding: 8px;
            text-align: center;
        }
        
        .ascii-table th {
            background-color: var(--primary-color);
            color: white;
        }
        
        .ascii-table tr:nth-child(even) {
            background-color: #f2f2f2;
        }
        
        .ascii-table tr:hover {
            background-color: #e9e9e9;
        }
        
        .ascii-char {
            font-weight: bold;
        }
        
        .ascii-control {
            color: #666;
            font-style: italic;
        }
        
        /* 暗黑模式 */
        body.dark-mode {
            background-color: #1a1a1a;
            color: #e0e0e0;
        }
        
        body.dark-mode .container {
            background-color: #2d2d2d;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
        }
        
        body.dark-mode .tab {
            background-color: #3d3d3d;
            color: #e0e0e0;
        }
        
        body.dark-mode .tab:hover {
            background-color: #4d4d4d;
        }
        
        body.dark-mode .tab.active {
            background-color: var(--primary-color);
        }
        
        body.dark-mode .tab-content {
            background-color: #2d2d2d;
            border-color: #444;
        }
        
        body.dark-mode textarea {
            background-color: #333;
            color: #e0e0e0;
            border-color: #444;
        }
        
        body.dark-mode .input-group input, 
        body.dark-mode .input-group select {
            background-color: #333;
            color: #e0e0e0;
            border-color: #444;
        }
        
        body.dark-mode .stat-box {
            background-color: #333;
            border-color: #444;
        }
        
        body.dark-mode .match-highlight {
            background-color: #705700;
            color: #fff;
        }
        
        body.dark-mode .ascii-table th,
        body.dark-mode .ascii-table td {
            border-color: #444;
        }
        
        body.dark-mode .ascii-table th {
            background-color: #3d3d3d;
        }
        
        body.dark-mode .ascii-table tr:nth-child(even) {
            background-color: #333;
        }
        
        body.dark-mode .ascii-table tr:hover {
            background-color: #3a3a3a;
        }
        
        body.dark-mode .ascii-control {
            color: #aaa;
        }
        
        @media (max-width: 768px) {
            .container {
                padding: 15px;
            }
            
            .split-container {
                flex-direction: column;
            }
            
            .header-actions {
                flex-direction: column;
                align-items: flex-start;
                gap: 10px;
            }
            
            .input-group {
                flex-direction: column;
                align-items: flex-start;
            }
            
            .input-group input, .input-group select {
                width: 100%;
            }
            
            .ascii-table {
                font-size: 0.8em;
            }
            
            .ascii-table th, .ascii-table td {
                padding: 4px;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header-actions">
            <h1>全能前端处理工具</h1>
            <div class="theme-switch">
                <label for="dark-mode-toggle">暗黑模式:</label>
                <label class="switch">
                    <input type="checkbox" id="dark-mode-toggle">
                    <span class="slider round"></span>
                </label>
            </div>
        </div>
        
        <div class="tabs">
            <button class="tab active" data-tab="text-tools">文本处理</button>
            <button class="tab" data-tab="json-tools">JSON工具</button>
            <button class="tab" data-tab="base64-tools">Base64工具</button>
            <button class="tab" data-tab="encode-tools">编码/加密</button>
            <button class="tab" data-tab="ascii-tools">ASCII工具</button>
            <button class="tab" data-tab="date-tools">时间工具</button>
        </div>
        
        <!-- 文本处理工具 -->
        <div id="text-tools" class="tab-content active">
            <div class="tool-section">
                <h2>文本统计</h2>
                <textarea id="stats-input" placeholder="输入要统计的文本..."></textarea>
                <div class="stats-container">
                    <div class="stat-box">
                        <div class="stat-label">字符数</div>
                        <div class="stat-value" id="char-count">0</div>
                    </div>
                    <div class="stat-box">
                        <div class="stat-label">单词数</div>
                        <div class="stat-value" id="word-count">0</div>
                    </div>
                    <div class="stat-box">
                        <div class="stat-label">行数</div>
                        <div class="stat-value" id="line-count">0</div>
                    </div>
                    <div class="stat-box">
                        <div class="stat-label">非空行</div>
                        <div class="stat-value" id="non-empty-line-count">0</div>
                    </div>
                </div>
            </div>
            
            <div class="tool-section">
                <h2>文本大小写转换</h2>
                <textarea id="text-input" placeholder="请输入要处理的文本..."></textarea>
                <div class="button-group">
                    <button id="to-upper">转换为大写</button>
                    <button id="to-lower">转换为小写</button>
                    <button id="capitalize">首字母大写</button>
                    <button id="title-case">标题格式</button>
                    <button id="trim-text">去除空格</button>
                    <button id="reverse-text">反转文本</button>
                </div>
                <div class="result-area">
                    <label for="text-output">处理结果:</label>
                    <textarea id="text-output" readonly></textarea>
                    <button id="copy-text" class="copy-btn">复制结果</button>
                </div>
                <div id="text-status" class="status-message"></div>
            </div>
                
            <div class="tool-section">
                <h2>正则表达式测试</h2>
                <textarea id="regex-input" placeholder="输入要测试的文本..."></textarea>
                <div class="input-group">
                    <input type="text" id="regex-pattern" placeholder="正则表达式" style="flex: 2;">
                    <input type="text" id="regex-replace" placeholder="替换文本(可选)" style="flex: 1;">
                    <select id="regex-flags">
                        <option value="g">全局(g)</option>
                        <option value="i">不区分大小写(i)</option>
                        <option value="m">多行(m)</option>
                        <option value="gi">全局+不区分大小写(gi)</option>
                        <option value="gm">全局+多行(gm)</option>
                        <option value="im">不区分大小写+多行(im)</option>
                        <option value="gim">全部(gim)</option>
                    </select>
                </div>
                <div class="button-group">
                    <button id="test-regex" class="success">测试匹配</button>
                    <button id="replace-regex">替换文本</button>
                    <button id="regex-help" class="info">正则帮助</button>
                </div>
                <div class="result-area">
                    <label for="regex-output">结果:</label>
                    <textarea id="regex-output" readonly></textarea>
                    <div id="regex-matches" style="margin-top: 10px;"></div>
                    <button id="copy-regex" class="copy-btn">复制结果</button>
                </div>
                <div id="regex-status" class="status-message"></div>
            </div>
            
            <div class="tool-section">
                <h2>HTML转义/反转义</h2>
                <div class="split-container">
                    <div class="split-panel">
                        <label for="html-raw">原始HTML:</label>
                        <textarea id="html-raw" placeholder="输入原始HTML..."></textarea>
                    </div>
                    <div class="split-panel">
                        <label for="html-escaped">转义结果:</label>
                        <textarea id="html-escaped" placeholder="转义后的HTML..."></textarea>
                    </div>
                </div>
                <div class="button-group">
                    <button id="escape-html" class="success">转义HTML</button>
                    <button id="unescape-html" class="secondary">反转义HTML</button>
                    <button id="copy-html-escaped" class="copy-btn">复制转义结果</button>
                    <button id="copy-html-raw" class="copy-btn">复制原始HTML</button>
                </div>
            </div>
            
            <div class="tool-section">
                <h2>URL编码/解码</h2>
                <div class="split-container">
                    <div class="split-panel">
                        <label for="url-decoded">原始URL:</label>
                        <textarea id="url-decoded" placeholder="输入原始URL..."></textarea>
                    </div>
                    <div class="split-panel">
                        <label for="url-encoded">编码结果:</label>
                        <textarea id="url-encoded" placeholder="编码后的URL..."></textarea>
                    </div>
                </div>
                <div class="button-group">
                    <button id="encode-url" class="success">编码URL</button>
                    <button id="decode-url" class="secondary">解码URL</button>
                    <button id="copy-url-encoded" class="copy-btn">复制编码结果</button>
                    <button id="copy-url-decoded" class="copy-btn">复制原始URL</button>
                </div>
            </div>
        </div>
        
        <!-- JSON工具 -->
        <div id="json-tools" class="tab-content">
            <div class="tool-section">
                <h2>JSON格式化与压缩</h2>
                <textarea id="json-input" placeholder='请输入JSON字符串,例如: {"name":"John","age":30}'></textarea>
                <div class="button-group">
                    <button id="format-json" class="success">格式化JSON</button>
                    <button id="minify-json" class="secondary">压缩JSON</button>
                    <button id="validate-json" class="secondary">验证JSON</button>
                    <button id="clear-json" class="danger">清空</button>
                </div>
                <div class="result-area">
                    <label for="json-output">处理结果:</label>
                    <textarea id="json-output" readonly></textarea>
                    <button id="copy-json" class="copy-btn">复制结果</button>
                </div>
                <div id="json-status" class="status-message"></div>
            </div>
            
            <div class="tool-section">
                <h2>JSON转换</h2>
                <textarea id="json-convert-input" placeholder='输入要转换的JSON...'></textarea>
                <div class="button-group">
                    <button id="json-to-xml" class="success">转XML</button>
                    <button id="json-to-csv" class="success">转CSV</button>
                    <button id="json-to-yaml" class="success">转YAML</button>
                    <button id="clear-json-convert" class="danger">清空</button>
                </div>
                <div class="result-area">
                    <label for="json-convert-output">转换结果:</label>
                    <textarea id="json-convert-output" readonly></textarea>
                    <button id="copy-json-convert" class="copy-btn">复制结果</button>
                </div>
                <div id="json-convert-status" class="status-message"></div>
            </div>
            
            <div class="tool-section">
                <h2>JSON路径查询</h2>
                <textarea id="json-path-input" placeholder='输入JSON数据...'></textarea>
                <div class="input-group">
                    <input type="text" id="json-path" placeholder="JSON路径 (如 $.store.book[0].title)" style="flex: 1;">
                    <button id="execute-json-path" class="success">查询</button>
                </div>
                <div class="result-area">
                    <label for="json-path-output">查询结果:</label>
                    <textarea id="json-path-output" readonly></textarea>
                    <button id="copy-json-path" class="copy-btn">复制结果</button>
                </div>
                <div id="json-path-status" class="status-message"></div>
            </div>
        </div>
        
        <!-- Base64工具 -->
        <div id="base64-tools" class="tab-content">
            <div class="tool-section">
                <h2>Base64文本编码/解码</h2>
                <textarea id="base64-input" placeholder="请输入要编码或解码的文本..."></textarea>
                <div class="button-group">
                    <button id="encode-base64" class="success">Base64编码</button>
                    <button id="decode-base64" class="secondary">Base64解码</button>
                    <button id="clear-base64" class="danger">清空</button>
                </div>
                <div class="result-area">
                    <label for="base64-output">处理结果:</label>
                    <textarea id="base64-output" readonly></textarea>
                    <button id="copy-base64" class="copy-btn">复制结果</button>
                </div>
                <div id="base64-status" class="status-message"></div>
            </div>
            
            <div class="tool-section">
                <h2>Base64文件编码</h2>
                <div class="input-group">
                    <label for="file-upload" class="file-upload-label">
                        <span>选择文件</span>
                        <input type="file" id="file-upload" class="file-upload">
                    </label>
                    <span id="file-info" class="file-info">未选择文件</span>
                </div>
                <div class="button-group">
                    <button id="encode-file" class="success">编码文件</button>
                    <button id="clear-file" class="danger">清空</button>
                </div>
                <div class="result-area">
                    <label for="file-output">Base64编码结果:</label>
                    <textarea id="file-output" readonly></textarea>
                    <button id="copy-file" class="copy-btn">复制结果</button>
                </div>
                <div id="file-status" class="status-message"></div>
            </div>
            
            <div class="tool-section">
                <h2>Base64图片预览</h2>
                <textarea id="image-base64-input" placeholder="输入Base64编码的图片数据 (以data:image/开头)..."></textarea>
                <div class="button-group">
                    <button id="preview-image" class="success">预览图片</button>
                    <button id="clear-image" class="danger">清空</button>
                </div>
                <div class="result-area">
                    <label>图片预览:</label>
                    <div id="image-preview-container" style="margin-top: 10px; text-align: center;">
                        <img id="image-preview" style="max-width: 100%; max-height: 300px; display: none;">
                        <div id="image-error" style="color: var(--danger-color); display: none;"></div>
                    </div>
                </div>
            </div>
        </div>
        
        <!-- 编码/加密工具 -->
        <div id="encode-tools" class="tab-content">
            <div class="tool-section">
                <h2>哈希计算</h2>
                <textarea id="hash-input" placeholder="输入要计算哈希的文本..."></textarea>
                <div class="button-group">
                    <button id="md5-hash" class="success">MD5</button>
                    <button id="sha1-hash" class="success">SHA-1</button>
                    <button id="sha256-hash" class="success">SHA-256</button>
                    <button id="sha512-hash" class="success">SHA-512</button>
                    <button id="clear-hash" class="danger">清空</button>
                </div>
                <div class="result-area">
                    <label for="hash-output">哈希结果:</label>
                    <textarea id="hash-output" readonly></textarea>
                    <button id="copy-hash" class="copy-btn">复制结果</button>
                </div>
            </div>
            
            <div class="tool-section">
                <h2>AES加密/解密</h2>
                <textarea id="aes-input" placeholder="输入要加密/解密的文本..."></textarea>
                <div class="input-group">
                    <input type="password" id="aes-key" placeholder="密钥" style="flex: 1;">
                    <input type="text" id="aes-iv" placeholder="IV (可选)" style="flex: 1;">
                </div>
                <div class="button-group">
                    <button id="aes-encrypt" class="success">AES加密</button>
                    <button id="aes-decrypt" class="secondary">AES解密</button>
                    <button id="clear-aes" class="danger">清空</button>
                </div>
                <div class="result-area">
                    <label for="aes-output">结果:</label>
                    <textarea id="aes-output" readonly></textarea>
                    <button id="copy-aes" class="copy-btn">复制结果</button>
                </div>
                <div id="aes-status" class="status-message"></div>
            </div>
        </div>
        
        <!-- ASCII工具 -->
        <div id="ascii-tools" class="tab-content">
            <div class="tool-section">
                <h2>字符串与ASCII码转换</h2>
                <div class="split-container">
                    <div class="split-panel">
                        <label for="ascii-text-input">文本:</label>
                        <textarea id="ascii-text-input" placeholder="输入要转换的文本..."></textarea>
                    </div>
                    <div class="split-panel">
                        <label for="ascii-code-input">ASCII码 (空格或逗号分隔):</label>
                        <textarea id="ascii-code-input" placeholder="输入ASCII码,如: 72 101 108 108 111 或 72,101,108,108,111"></textarea>
                    </div>
                </div>
                <div class="button-group">
                    <button id="text-to-ascii" class="success">文本 → ASCII码</button>
                    <button id="ascii-to-text" class="secondary">ASCII码 → 文本</button>
                    <button id="clear-ascii" class="danger">清空</button>
                    <button id="copy-ascii-text" class="copy-btn">复制文本</button>
                    <button id="copy-ascii-code" class="copy-btn">复制ASCII码</button>
                </div>
                <div class="input-group">
                    <label for="ascii-format">输出格式:</label>
                    <select id="ascii-format">
                        <option value="space">空格分隔 (72 101 108 108 111)</option>
                        <option value="comma">逗号分隔 (72,101,108,108,111)</option>
                        <option value="hex">十六进制 (0x48 0x65 0x6C 0x6C 0x6F)</option>
                        <option value="hex-no-prefix">十六进制无前缀 (48 65 6C 6C 6F)</option>
                    </select>
                </div>
                <div id="ascii-status" class="status-message"></div>
            </div>
            
            <div class="tool-section">
                <h2>ASCII码表</h2>
                <div id="ascii-table" style="overflow-x: auto;">
                    <!-- ASCII码表将通过JavaScript动态生成 -->
                </div>
            </div>
        </div>
        
        <!-- 时间工具 -->
        <div id="date-tools" class="tab-content">
            <div class="tool-section">
                <h2>时间戳转换</h2>
                <div class="input-group">
                    <input type="number" id="timestamp-input" placeholder="输入Unix时间戳...">
                    <button id="timestamp-to-date" class="success">转日期</button>
                    <button id="current-timestamp" class="secondary">当前时间戳</button>
                </div>
                <div class="input-group" style="margin-top: 10px;">
                    <input type="datetime-local" id="date-input">
                    <button id="date-to-timestamp" class="success">转时间戳</button>
                </div>
                <div class="result-area">
                    <label for="timestamp-output">结果:</label>
                    <textarea id="timestamp-output" readonly></textarea>
                    <button id="copy-timestamp" class="copy-btn">复制结果</button>
                </div>
            </div>
            
            <div class="tool-section">
                <h2>日期计算</h2>
                <div class="input-group">
                    <input type="datetime-local" id="date-start">
                    <input type="datetime-local" id="date-end">
                    <button id="calculate-diff" class="success">计算差值</button>
                </div>
                <div class="result-area">
                    <label for="date-diff-output">时间差:</label>
                    <textarea id="date-diff-output" readonly></textarea>
                </div>
            </div>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            // 暗黑模式切换
            const darkModeToggle = document.getElementById('dark-mode-toggle');
            darkModeToggle.addEventListener('change', () => {
                document.body.classList.toggle('dark-mode', darkModeToggle.checked);
                localStorage.setItem('darkMode', darkModeToggle.checked);
            });
            
            // 初始化暗黑模式状态
            if (localStorage.getItem('darkMode')) {
                darkModeToggle.checked = localStorage.getItem('darkMode') === 'true';
                document.body.classList.toggle('dark-mode', darkModeToggle.checked);
            }
            
            // 标签页切换
            const tabs = document.querySelectorAll('.tab');
            const tabContents = document.querySelectorAll('.tab-content');
            
            tabs.forEach(tab => {
                tab.addEventListener('click', () => {
                    // 移除所有active类
                    tabs.forEach(t => t.classList.remove('active'));
                    tabContents.forEach(content => content.classList.remove('active'));
                    
                    // 添加active类到当前标签和内容
                    tab.classList.add('active');
                    const tabId = tab.getAttribute('data-tab');
                    document.getElementById(tabId).classList.add('active');
                    
                    // 保存当前标签页
                    localStorage.setItem('lastTab', tabId);
                });
            });
            
            // 恢复上次打开的标签页
            const lastTab = localStorage.getItem('lastTab') || 'text-tools';
            document.querySelector(`.tab[data-tab="${lastTab}"]`).click();
            
            // 文本处理功能
            const textInput = document.getElementById('text-input');
            const textOutput = document.getElementById('text-output');
            const textStatus = document.getElementById('text-status');
            
            document.getElementById('to-upper').addEventListener('click', () => {
                textOutput.value = textInput.value.toUpperCase();
                showStatus(textStatus, '转换为大写成功!', 'success');
            });
            
            document.getElementById('to-lower').addEventListener('click', () => {
                textOutput.value = textInput.value.toLowerCase();
                showStatus(textStatus, '转换为小写成功!', 'success');
            });
            
            document.getElementById('capitalize').addEventListener('click', () => {
                if (textInput.value.length > 0) {
                    textOutput.value = textInput.value.charAt(0).toUpperCase() + textInput.value.slice(1).toLowerCase();
                    showStatus(textStatus, '首字母大写成功!', 'success');
                }
            });
            
            document.getElementById('title-case').addEventListener('click', () => {
                textOutput.value = textInput.value.toLowerCase().split(' ').map(word => 
                    word.charAt(0).toUpperCase() + word.slice(1)
                ).join(' ');
                showStatus(textStatus, '标题格式转换成功!', 'success');
            });
            
            document.getElementById('trim-text').addEventListener('click', () => {
                textOutput.value = textInput.value.replace(/\s/g, '');
                showStatus(textStatus, '去除空格成功!', 'success');
            });
            
            document.getElementById('reverse-text').addEventListener('click', () => {
                textOutput.value = textInput.value.split('').reverse().join('');
                showStatus(textStatus, '文本反转成功!', 'success');
            });
            
            document.getElementById('copy-text').addEventListener('click', () => {
                if (textOutput.value) {
                    copyToClipboard(textOutput);
                    showStatus(textStatus, '结果已复制到剪贴板!', 'success');
                }
            });
            
            // 文本统计功能
            const statsInput = document.getElementById('stats-input');
            
            statsInput.addEventListener('input', () => {
                const text = statsInput.value;
                document.getElementById('char-count').textContent = text.length;
                
                const words = text.trim() ? text.trim().split(/\s+/) : [];
                document.getElementById('word-count').textContent = words.length;
                
                const lines = text.split('\n');
                document.getElementById('line-count').textContent = lines.length;
                
                const nonEmptyLines = lines.filter(line => line.trim());
                document.getElementById('non-empty-line-count').textContent = nonEmptyLines.length;
            });
            
            // 正则表达式功能
            const regexInput = document.getElementById('regex-input');
            const regexOutput = document.getElementById('regex-output');
            const regexStatus = document.getElementById('regex-status');
            const regexMatches = document.getElementById('regex-matches');
            
            document.getElementById('test-regex').addEventListener('click', () => {
                try {
                    const text = regexInput.value;
                    const pattern = document.getElementById('regex-pattern').value;
                    const flags = document.getElementById('regex-flags').value;
                    const regex = new RegExp(pattern, flags);
                    const matches = text.match(regex);
                    
                    if (matches) {
                        regexOutput.value = `找到 ${matches.length} 处匹配:\n${matches.join('\n')}`;
                        
                        // 高亮显示匹配内容
                        let highlighted = text;
                        matches.forEach(match => {
                            highlighted = highlighted.replaceAll(match, `<span class="match-highlight">${match}</span>`);
                        });
                        
                        regexMatches.innerHTML = `<h4>匹配位置:</h4><div style="border:1px solid #ddd;padding:10px;">${highlighted}</div>`;
                    } else {
                        regexOutput.value = "没有找到匹配内容";
                        regexMatches.innerHTML = '';
                    }
                    showStatus(regexStatus, '正则匹配成功!', 'success');
                } catch (e) {
                    regexOutput.value = `正则表达式错误: ${e.message}`;
                    showStatus(regexStatus, `正则匹配失败: ${e.message}`, 'error');
                }
            });
            
            document.getElementById('replace-regex').addEventListener('click', () => {
                try {
                    const text = regexInput.value;
                    const pattern = document.getElementById('regex-pattern').value;
                    const replacement = document.getElementById('regex-replace').value || '';
                    const flags = document.getElementById('regex-flags').value;
                    const regex = new RegExp(pattern, flags);
                    
                    regexOutput.value = text.replace(regex, replacement);
                    showStatus(regexStatus, '替换成功!', 'success');
                } catch (e) {
                    regexOutput.value = `替换错误: ${e.message}`;
                    showStatus(regexStatus, `替换失败: ${e.message}`, 'error');
                }
            });
            
            document.getElementById('regex-help').addEventListener('click', () => {
                const helpText = `
常用正则表达式示例:
• 数字: \\d 或 [0-9]
• 非数字: \\D 或 [^0-9]
• 单词字符: \\w 或 [a-zA-Z0-9_]
• 非单词字符: \\W
• 空白字符: \\s
• 非空白字符: \\S
• 开始: ^
• 结束: $
• 任意字符: .
• 量词: * (0或多个), + (1或多个), ? (0或1个), {n} (n个), {n,} (至少n个), {n,m} (n到m个)

示例:
• 邮箱: ^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$
• 手机号: ^1[3-9]\\d{9}$
• URL: ^https?:\\/\\/[\\w.-]+\\.[a-zA-Z]{2,}(\\/\\S*)?$
                `;
                regexOutput.value = helpText;
                showStatus(regexStatus, '正则表达式帮助已显示', 'info');
            });
            
            document.getElementById('copy-regex').addEventListener('click', () => {
                if (regexOutput.value) {
                    copyToClipboard(regexOutput);
                    showStatus(regexStatus, '结果已复制到剪贴板!', 'success');
                }
            });
            
            // HTML转义/反转义
            document.getElementById('escape-html').addEventListener('click', () => {
                const raw = document.getElementById('html-raw').value;
                document.getElementById('html-escaped').value = escapeHtml(raw);
            });
            
            document.getElementById('unescape-html').addEventListener('click', () => {
                const escaped = document.getElementById('html-escaped').value;
                document.getElementById('html-raw').value = unescapeHtml(escaped);
            });
            
            document.getElementById('copy-html-escaped').addEventListener('click', () => {
                copyToClipboard(document.getElementById('html-escaped'));
            });
            
            document.getElementById('copy-html-raw').addEventListener('click', () => {
                copyToClipboard(document.getElementById('html-raw'));
            });
            
            // URL编码/解码
            document.getElementById('encode-url').addEventListener('click', () => {
                const decoded = document.getElementById('url-decoded').value;
                document.getElementById('url-encoded').value = encodeURIComponent(decoded);
            });
            
            document.getElementById('decode-url').addEventListener('click', () => {
                const encoded = document.getElementById('url-encoded').value;
                document.getElementById('url-decoded').value = decodeURIComponent(encoded);
            });
            
            document.getElementById('copy-url-encoded').addEventListener('click', () => {
                copyToClipboard(document.getElementById('url-encoded'));
            });
            
            document.getElementById('copy-url-decoded').addEventListener('click', () => {
                copyToClipboard(document.getElementById('url-decoded'));
            });
            
            // JSON处理功能
            const jsonInput = document.getElementById('json-input');
            const jsonOutput = document.getElementById('json-output');
            const jsonStatus = document.getElementById('json-status');
            
            document.getElementById('format-json').addEventListener('click', () => {
                try {
                    const jsonObj = JSON.parse(jsonInput.value);
                    jsonOutput.value = JSON.stringify(jsonObj, null, 2);
                    showStatus(jsonStatus, 'JSON格式化成功!', 'success');
                } catch (e) {
                    showStatus(jsonStatus, `JSON格式化失败: ${e.message}`, 'error');
                }
            });
            
            document.getElementById('minify-json').addEventListener('click', () => {
                try {
                    const jsonObj = JSON.parse(jsonInput.value);
                    jsonOutput.value = JSON.stringify(jsonObj);
                    showStatus(jsonStatus, 'JSON压缩成功!', 'success');
                } catch (e) {
                    showStatus(jsonStatus, `JSON压缩失败: ${e.message}`, 'error');
                }
            });
            
            document.getElementById('validate-json').addEventListener('click', () => {
                try {
                    JSON.parse(jsonInput.value);
                    showStatus(jsonStatus, 'JSON验证成功!', 'success');
                } catch (e) {
                    showStatus(jsonStatus, `JSON验证失败: ${e.message}`, 'error');
                }
            });
            
            document.getElementById('clear-json').addEventListener('click', () => {
                jsonInput.value = '';
                jsonOutput.value = '';
                jsonStatus.textContent = '';
                jsonStatus.className = 'status-message';
            });
            
            document.getElementById('copy-json').addEventListener('click', () => {
                if (jsonOutput.value) {
                    copyToClipboard(jsonOutput);
                    showStatus(jsonStatus, '结果已复制到剪贴板!', 'success');
                }
            });
            
            // JSON转换功能
            document.getElementById('json-to-xml').addEventListener('click', () => {
                try {
                    const jsonObj = JSON.parse(document.getElementById('json-convert-input').value);
                    document.getElementById('json-convert-output').value = jsonToXml(jsonObj);
                    showStatus(document.getElementById('json-convert-status'), 'JSON转XML成功!', 'success');
                } catch (e) {
                    showStatus(document.getElementById('json-convert-status'), `JSON转XML失败: ${e.message}`, 'error');
                }
            });
            
            document.getElementById('json-to-csv').addEventListener('click', () => {
                try {
                    const jsonObj = JSON.parse(document.getElementById('json-convert-input').value);
                    document.getElementById('json-convert-output').value = jsonToCsv(jsonObj);
                    showStatus(document.getElementById('json-convert-status'), 'JSON转CSV成功!', 'success');
                } catch (e) {
                    showStatus(document.getElementById('json-convert-status'), `JSON转CSV失败: ${e.message}`, 'error');
                }
            });
            
            document.getElementById('json-to-yaml').addEventListener('click', () => {
                try {
                    const jsonObj = JSON.parse(document.getElementById('json-convert-input').value);
                    document.getElementById('json-convert-output').value = jsonToYaml(jsonObj);
                    showStatus(document.getElementById('json-convert-status'), 'JSON转YAML成功!', 'success');
                } catch (e) {
                    showStatus(document.getElementById('json-convert-status'), `JSON转YAML失败: ${e.message}`, 'error');
                }
            });
            
            document.getElementById('clear-json-convert').addEventListener('click', () => {
                document.getElementById('json-convert-input').value = '';
                document.getElementById('json-convert-output').value = '';
                document.getElementById('json-convert-status').textContent = '';
                document.getElementById('json-convert-status').className = 'status-message';
            });
            
            document.getElementById('copy-json-convert').addEventListener('click', () => {
                if (document.getElementById('json-convert-output').value) {
                    copyToClipboard(document.getElementById('json-convert-output'));
                    showStatus(document.getElementById('json-convert-status'), '结果已复制到剪贴板!', 'success');
                }
            });
            
            // JSON路径查询
            document.getElementById('execute-json-path').addEventListener('click', () => {
                try {
                    const jsonObj = JSON.parse(document.getElementById('json-path-input').value);
                    const path = document.getElementById('json-path').value;
                    
                    if (!path) {
                        showStatus(document.getElementById('json-path-status'), '请输入JSON路径', 'error');
                        return;
                    }
                    
                    // 简单的JSON路径查询实现
                    const result = evaluateJsonPath(jsonObj, path);
                    document.getElementById('json-path-output').value = JSON.stringify(result, null, 2);
                    showStatus(document.getElementById('json-path-status'), '查询成功!', 'success');
                } catch (e) {
                    showStatus(document.getElementById('json-path-status'), `查询失败: ${e.message}`, 'error');
                }
            });
            
            document.getElementById('copy-json-path').addEventListener('click', () => {
                if (document.getElementById('json-path-output').value) {
                    copyToClipboard(document.getElementById('json-path-output'));
                    showStatus(document.getElementById('json-path-status'), '结果已复制到剪贴板!', 'success');
                }
            });
            
            // Base64处理功能
            const base64Input = document.getElementById('base64-input');
            const base64Output = document.getElementById('base64-output');
            const base64Status = document.getElementById('base64-status');
            
            document.getElementById('encode-base64').addEventListener('click', () => {
                try {
                    base64Output.value = btoa(unescape(encodeURIComponent(base64Input.value)));
                    showStatus(base64Status, 'Base64编码成功!', 'success');
                } catch (e) {
                    showStatus(base64Status, `Base64编码失败: ${e.message}`, 'error');
                }
            });
            
            document.getElementById('decode-base64').addEventListener('click', () => {
                try {
                    base64Output.value = decodeURIComponent(escape(atob(base64Input.value)));
                    showStatus(base64Status, 'Base64解码成功!', 'success');
                } catch (e) {
                    showStatus(base64Status, `Base64解码失败: ${e.message}`, 'error');
                }
            });
            
            document.getElementById('clear-base64').addEventListener('click', () => {
                base64Input.value = '';
                base64Output.value = '';
                base64Status.textContent = '';
                base64Status.className = 'status-message';
            });
            
            document.getElementById('copy-base64').addEventListener('click', () => {
                if (base64Output.value) {
                    copyToClipboard(base64Output);
                    showStatus(base64Status, '结果已复制到剪贴板!', 'success');
                }
            });
            
            // Base64文件编码
            const fileUpload = document.getElementById('file-upload');
            const fileInfo = document.getElementById('file-info');
            
            fileUpload.addEventListener('change', (e) => {
                const file = e.target.files[0];
                if (file) {
                    fileInfo.textContent = `${file.name} (${formatFileSize(file.size)})`;
                } else {
                    fileInfo.textContent = '未选择文件';
                }
            });
            
            document.getElementById('encode-file').addEventListener('click', () => {
                const file = fileUpload.files[0];
                if (!file) {
                    showStatus(document.getElementById('file-status'), '请先选择文件', 'error');
                    return;
                }
                
                const reader = new FileReader();
                reader.onload = (e) => {
                    const base64 = e.target.result.split(',')[1];
                    document.getElementById('file-output').value = base64;
                    showStatus(document.getElementById('file-status'), '文件编码成功!', 'success');
                };
                reader.onerror = () => {
                    showStatus(document.getElementById('file-status'), '文件读取失败', 'error');
                };
                reader.readAsDataURL(file);
            });
            
            document.getElementById('clear-file').addEventListener('click', () => {
                fileUpload.value = '';
                fileInfo.textContent = '未选择文件';
                document.getElementById('file-output').value = '';
                document.getElementById('file-status').textContent = '';
                document.getElementById('file-status').className = 'status-message';
            });
            
            document.getElementById('copy-file').addEventListener('click', () => {
                if (document.getElementById('file-output').value) {
                    copyToClipboard(document.getElementById('file-output'));
                    showStatus(document.getElementById('file-status'), '结果已复制到剪贴板!', 'success');
                }
            });
            
            // Base64图片预览
            document.getElementById('preview-image').addEventListener('click', () => {
                const base64 = document.getElementById('image-base64-input').value.trim();
                const imgPreview = document.getElementById('image-preview');
                const imgError = document.getElementById('image-error');
                
                if (!base64) {
                    imgError.textContent = '请输入Base64编码的图片数据';
                    imgError.style.display = 'block';
                    imgPreview.style.display = 'none';
                    return;
                }
                
                try {
                    imgPreview.src = base64.startsWith('data:image/') ? base64 : `data:image/png;base64,${base64}`;
                    imgPreview.style.display = 'block';
                    imgError.style.display = 'none';
                } catch (e) {
                    imgError.textContent = `图片预览失败: ${e.message}`;
                    imgError.style.display = 'block';
                    imgPreview.style.display = 'none';
                }
            });
            
            document.getElementById('clear-image').addEventListener('click', () => {
                document.getElementById('image-base64-input').value = '';
                document.getElementById('image-preview').style.display = 'none';
                document.getElementById('image-error').style.display = 'none';
            });
            
            // 哈希计算
            document.getElementById('md5-hash').addEventListener('click', async () => {
                const text = document.getElementById('hash-input').value;
                if (!text) {
                    document.getElementById('hash-output').value = '请输入要计算哈希的文本';
                    return;
                }
                
                try {
                    const hash = await calculateHash(text, 'MD5');
                    document.getElementById('hash-output').value = hash;
                } catch (e) {
                    document.getElementById('hash-output').value = `MD5计算失败: ${e.message}`;
                }
            });
            
            document.getElementById('sha1-hash').addEventListener('click', async () => {
                const text = document.getElementById('hash-input').value;
                if (!text) {
                    document.getElementById('hash-output').value = '请输入要计算哈希的文本';
                    return;
                }
                
                try {
                    const hash = await calculateHash(text, 'SHA-1');
                    document.getElementById('hash-output').value = hash;
                } catch (e) {
                    document.getElementById('hash-output').value = `SHA-1计算失败: ${e.message}`;
                }
            });
            
            document.getElementById('sha256-hash').addEventListener('click', async () => {
                const text = document.getElementById('hash-input').value;
                if (!text) {
                    document.getElementById('hash-output').value = '请输入要计算哈希的文本';
                    return;
                }
                
                try {
                    const hash = await calculateHash(text, 'SHA-256');
                    document.getElementById('hash-output').value = hash;
                } catch (e) {
                    document.getElementById('hash-output').value = `SHA-256计算失败: ${e.message}`;
                }
            });
            
            document.getElementById('sha512-hash').addEventListener('click', async () => {
                const text = document.getElementById('hash-input').value;
                if (!text) {
                    document.getElementById('hash-output').value = '请输入要计算哈希的文本';
                    return;
                }
                
                try {
                    const hash = await calculateHash(text, 'SHA-512');
                    document.getElementById('hash-output').value = hash;
                } catch (e) {
                    document.getElementById('hash-output').value = `SHA-512计算失败: ${e.message}`;
                }
            });
            
            document.getElementById('clear-hash').addEventListener('click', () => {
                document.getElementById('hash-input').value = '';
                document.getElementById('hash-output').value = '';
            });
            
            document.getElementById('copy-hash').addEventListener('click', () => {
                copyToClipboard(document.getElementById('hash-output'));
            });
            
            // AES加密/解密
            document.getElementById('aes-encrypt').addEventListener('click', async () => {
                try {
                    const text = document.getElementById('aes-input').value;
                    const key = document.getElementById('aes-key').value;
                    const iv = document.getElementById('aes-iv').value || undefined;
                    
                    if (!text || !key) {
                        showStatus(document.getElementById('aes-status'), '请输入文本和密钥', 'error');
                        return;
                    }
                    
                    const encrypted = await aesEncrypt(text, key, iv);
                    document.getElementById('aes-output').value = encrypted;
                    showStatus(document.getElementById('aes-status'), 'AES加密成功!', 'success');
                } catch (e) {
                    document.getElementById('aes-output').value = `AES加密失败: ${e.message}`;
                    showStatus(document.getElementById('aes-status'), `AES加密失败: ${e.message}`, 'error');
                }
            });
            
            document.getElementById('aes-decrypt').addEventListener('click', async () => {
                try {
                    const text = document.getElementById('aes-input').value;
                    const key = document.getElementById('aes-key').value;
                    const iv = document.getElementById('aes-iv').value || undefined;
                    
                    if (!text || !key) {
                        showStatus(document.getElementById('aes-status'), '请输入文本和密钥', 'error');
                        return;
                    }
                    
                    const decrypted = await aesDecrypt(text, key, iv);
                    document.getElementById('aes-output').value = decrypted;
                    showStatus(document.getElementById('aes-status'), 'AES解密成功!', 'success');
                } catch (e) {
                    document.getElementById('aes-output').value = `AES解密失败: ${e.message}`;
                    showStatus(document.getElementById('aes-status'), `AES解密失败: ${e.message}`, 'error');
                }
            });
            
            document.getElementById('clear-aes').addEventListener('click', () => {
                document.getElementById('aes-input').value = '';
                document.getElementById('aes-output').value = '';
                document.getElementById('aes-key').value = '';
                document.getElementById('aes-iv').value = '';
                document.getElementById('aes-status').textContent = '';
                document.getElementById('aes-status').className = 'status-message';
            });
            
            document.getElementById('copy-aes').addEventListener('click', () => {
                copyToClipboard(document.getElementById('aes-output'));
            });
            
            // 时间戳转换
            document.getElementById('timestamp-to-date').addEventListener('click', () => {
                const timestamp = parseInt(document.getElementById('timestamp-input').value);
                if (isNaN(timestamp)) {
                    document.getElementById('timestamp-output').value = '请输入有效的时间戳';
                    return;
                }
                
                const date = new Date(timestamp * 1000);
                document.getElementById('timestamp-output').value = date.toLocaleString();
                document.getElementById('date-input').value = formatDateTimeLocal(date);
            });
            
            document.getElementById('current-timestamp').addEventListener('click', () => {
                const now = Math.floor(Date.now() / 1000);
                document.getElementById('timestamp-input').value = now;
                document.getElementById('timestamp-output').value = new Date(now * 1000).toLocaleString();
                document.getElementById('date-input').value = formatDateTimeLocal(new Date());
            });
            
            document.getElementById('date-to-timestamp').addEventListener('click', () => {
                const dateStr = document.getElementById('date-input').value;
                if (!dateStr) {
                    document.getElementById('timestamp-output').value = '请选择日期时间';
                    return;
                }
                
                const date = new Date(dateStr);
                const timestamp = Math.floor(date.getTime() / 1000);
                document.getElementById('timestamp-input').value = timestamp;
                document.getElementById('timestamp-output').value = `时间戳: ${timestamp}\n本地时间: ${date.toLocaleString()}`;
            });
            
            document.getElementById('copy-timestamp').addEventListener('click', () => {
                copyToClipboard(document.getElementById('timestamp-output'));
            });
            
            // 日期计算
            document.getElementById('calculate-diff').addEventListener('click', () => {
                const startStr = document.getElementById('date-start').value;
                const endStr = document.getElementById('date-end').value;
                
                if (!startStr || !endStr) {
                    document.getElementById('date-diff-output').value = '请选择开始和结束日期时间';
                    return;
                }
                
                const start = new Date(startStr);
                const end = new Date(endStr);
                
                if (start > end) {
                    document.getElementById('date-diff-output').value = '结束时间必须晚于开始时间';
                    return;
                }
                
                const diffMs = end - start;
                const diffSec = Math.floor(diffMs / 1000);
                const diffMin = Math.floor(diffSec / 60);
                const diffHour = Math.floor(diffMin / 60);
                const diffDay = Math.floor(diffHour / 24);
                
                const result = [
                    `总毫秒数: ${diffMs}`,
                    `总秒数: ${diffSec}`,
                    `总分钟数: ${diffMin}`,
                    `总小时数: ${diffHour}`,
                    `总天数: ${diffDay}`,
                    `详细: ${diffDay}天 ${diffHour % 24}小时 ${diffMin % 60}分钟 ${diffSec % 60}秒`
                ].join('\n');
                
                document.getElementById('date-diff-output').value = result;
            });
            
            // 初始化日期时间输入为当前时间
            document.getElementById('date-input').value = formatDateTimeLocal(new Date());
            document.getElementById('date-start').value = formatDateTimeLocal(new Date());
            document.getElementById('date-end').value = formatDateTimeLocal(new Date(Date.now() + 3600000)); // 1小时后
            
            // ASCII工具功能
            const asciiTextInput = document.getElementById('ascii-text-input');
            const asciiCodeInput = document.getElementById('ascii-code-input');
            const asciiStatus = document.getElementById('ascii-status');
            
            // 文本转ASCII码
            document.getElementById('text-to-ascii').addEventListener('click', () => {
                const text = asciiTextInput.value;
                if (!text) {
                    showStatus(asciiStatus, '请输入要转换的文本', 'error');
                    return;
                }
                
                const format = document.getElementById('ascii-format').value;
                let asciiCodes = [];
                
                for (let i = 0; i < text.length; i++) {
                    asciiCodes.push(text.charCodeAt(i));
                }
                
                let result;
                switch (format) {
                    case 'space':
                        result = asciiCodes.join(' ');
                        break;
                    case 'comma':
                        result = asciiCodes.join(',');
                        break;
                    case 'hex':
                        result = asciiCodes.map(c => '0x' + c.toString(16).toUpperCase()).join(' ');
                        break;
                    case 'hex-no-prefix':
                        result = asciiCodes.map(c => c.toString(16).toUpperCase()).join(' ');
                        break;
                    default:
                        result = asciiCodes.join(' ');
                }
                
                asciiCodeInput.value = result;
                showStatus(asciiStatus, '文本转换为ASCII码成功!', 'success');
            });
            
            // ASCII码转文本
            document.getElementById('ascii-to-text').addEventListener('click', () => {
                const codeStr = asciiCodeInput.value.trim();
                if (!codeStr) {
                    showStatus(asciiStatus, '请输入要转换的ASCII码', 'error');
                    return;
                }
                
                try {
                    // 处理不同格式的ASCII码输入
                    let codes = [];
                    if (codeStr.includes(',')) {
                        // 逗号分隔
                        codes = codeStr.split(',').map(s => parseInt(s.trim()));
                    } else if (codeStr.includes(' ')) {
                        // 空格分隔
                        codes = codeStr.split(/\s+/).map(s => {
                            // 处理十六进制格式 (0x... 或 纯十六进制)
                            if (s.startsWith('0x') || s.startsWith('0X')) {
                                return parseInt(s, 16);
                            } else if (/^[0-9A-Fa-f]+$/.test(s)) {
                                // 可能是十六进制无前缀
                                return parseInt(s, 16);
                            } else {
                                return parseInt(s);
                            }
                        });
                    } else {
                        // 单个ASCII码
                        codes = [parseInt(codeStr)];
                    }
                    
                    // 检查是否有无效的ASCII码
                    if (codes.some(isNaN)) {
                        throw new Error('包含无效的ASCII码');
                    }
                    
                    // 将ASCII码转换为字符串
                    let text = '';
                    for (const code of codes) {
                        if (code < 0 || code > 255) {
                            throw new Error(`ASCII码 ${code} 超出范围 (0-255)`);
                        }
                        text += String.fromCharCode(code);
                    }
                    
                    asciiTextInput.value = text;
                    showStatus(asciiStatus, 'ASCII码转换为文本成功!', 'success');
                } catch (e) {
                    showStatus(asciiStatus, `转换失败: ${e.message}`, 'error');
                }
            });
            
            // 清空ASCII工具
            document.getElementById('clear-ascii').addEventListener('click', () => {
                asciiTextInput.value = '';
                asciiCodeInput.value = '';
                asciiStatus.textContent = '';
                asciiStatus.className = 'status-message';
            });
            
            // 复制ASCII文本
            document.getElementById('copy-ascii-text').addEventListener('click', () => {
                if (asciiTextInput.value) {
                    copyToClipboard(asciiTextInput);
                    showStatus(asciiStatus, '文本已复制到剪贴板!', 'success');
                }
            });
            
            // 复制ASCII码
            document.getElementById('copy-ascii-code').addEventListener('click', () => {
                if (asciiCodeInput.value) {
                    copyToClipboard(asciiCodeInput);
                    showStatus(asciiStatus, 'ASCII码已复制到剪贴板!', 'success');
                }
            });
            
            // 生成ASCII码表
            generateAsciiTable();
            
            // 辅助函数
            function showStatus(element, message, type) {
                element.textContent = message;
                element.className = 'status-message ' + type;
                setTimeout(() => {
                    element.textContent = '';
                    element.className = 'status-message';
                }, 3000);
            }
            
            function copyToClipboard(element) {
                element.select();
                document.execCommand('copy');
                
                // 取消选中
                if (window.getSelection) {
                    window.getSelection().removeAllRanges();
                } else if (document.selection) {
                    document.selection.empty();
                }
            }
            
            function escapeHtml(text) {
                const div = document.createElement('div');
                div.textContent = text;
                return div.innerHTML;
            }
            
            function unescapeHtml(html) {
                const div = document.createElement('div');
                div.innerHTML = html;
                return div.textContent;
            }
            
            function formatFileSize(bytes) {
                if (bytes === 0) return '0 Bytes';
                const k = 1024;
                const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
                const i = Math.floor(Math.log(bytes) / Math.log(k));
                return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
            }
            
            function jsonToXml(jsonObj, nodeName = 'root') {
                let xml = '';
                
                if (typeof jsonObj === 'object' && jsonObj !== null) {
                    if (Array.isArray(jsonObj)) {
                        jsonObj.forEach((item, index) => {
                            xml += jsonToXml(item, nodeName + '_' + index);
                        });
                    } else {
                        xml += '<' + nodeName + '>';
                        for (const key in jsonObj) {
                            if (jsonObj.hasOwnProperty(key)) {
                                xml += jsonToXml(jsonObj[key], key);
                            }
                        }
                        xml += '</' + nodeName + '>';
                    }
                } else {
                    xml += '<' + nodeName + '>' + jsonObj + '</' + nodeName + '>';
                }
                
                return xml;
            }
            
            function jsonToCsv(jsonObj) {
                if (Array.isArray(jsonObj)) {
                    if (jsonObj.length === 0) return '';
                    
                    // 获取所有可能的键作为表头
                    const headers = new Set();
                    jsonObj.forEach(item => {
                        if (typeof item === 'object' && item !== null) {
                            Object.keys(item).forEach(key => headers.add(key));
                        }
                    });
                    
                    const headerArray = Array.from(headers);
                    let csv = headerArray.join(',') + '\n';
                    
                    // 添加数据行
                    jsonObj.forEach(item => {
                        const row = headerArray.map(header => {
                            const value = item[header];
                            if (value === undefined || value === null) return '';
                            // 处理包含逗号或引号的值
                            const strValue = String(value).replace(/"/g, '""');
                            return `"${strValue}"`;
                        });
                        csv += row.join(',') + '\n';
                    });
                    
                    return csv;
                } else if (typeof jsonObj === 'object' && jsonObj !== null) {
                    // 单个对象转换为单行CSV
                    const headers = Object.keys(jsonObj);
                    const row = headers.map(header => {
                        const value = jsonObj[header];
                        if (value === undefined || value === null) return '';
                        const strValue = String(value).replace(/"/g, '""');
                        return `"${strValue}"`;
                    });
                    return headers.join(',') + '\n' + row.join(',');
                } else {
                    return String(jsonObj);
                }
            }
            
            function jsonToYaml(jsonObj, indent = 0) {
                const spaces = '  '.repeat(indent);
                
                if (typeof jsonObj === 'object' && jsonObj !== null) {
                    if (Array.isArray(jsonObj)) {
                        if (jsonObj.length === 0) return '[]';
                        
                        let yaml = '';
                        jsonObj.forEach(item => {
                            yaml += spaces + '- ' + jsonToYaml(item, indent + 1).trimStart() + '\n';
                        });
                        return yaml;
                    } else {
                        const keys = Object.keys(jsonObj);
                        if (keys.length === 0) return '{}';
                        
                        let yaml = '';
                        keys.forEach(key => {
                            const value = jsonObj[key];
                            yaml += spaces + key + ': ' + jsonToYaml(value, indent + 1).trimStart() + '\n';
                        });
                        return yaml;
                    }
                } else if (typeof jsonObj === 'string') {
                    // 简单字符串处理
                    if (jsonObj.includes('\n')) {
                        return '|\n' + spaces + '  ' + jsonObj.replace(/\n/g, '\n' + spaces + '  ');
                    }
                    return jsonObj;
                } else if (jsonObj === null) {
                    return 'null';
                } else {
                    return String(jsonObj);
                }
            }
            
            function evaluateJsonPath(obj, path) {
                // 简单实现,不支持完整JSONPath语法
                try {
                    // 尝试作为JavaScript表达式执行
                    const func = new Function('obj', 'return obj' + (path.startsWith('$') ? path.slice(1) : path));
                    return func(obj);
                } catch (e) {
                    throw new Error('不支持的JSON路径表达式');
                }
            }
            
            async function calculateHash(text, algorithm) {
                // 使用Web Crypto API计算哈希
                const encoder = new TextEncoder();
                const data = encoder.encode(text);
                const hashBuffer = await crypto.subtle.digest(algorithm, data);
                const hashArray = Array.from(new Uint8Array(hashBuffer));
                return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
            }
            
            async function aesEncrypt(text, key, iv) {
                const encoder = new TextEncoder();
                const keyMaterial = await crypto.subtle.importKey(
                    'raw',
                    encoder.encode(key),
                    { name: 'AES-CBC' },
                    false,
                    ['encrypt']
                );
                
                const ivArray = iv ? encoder.encode(iv) : crypto.getRandomValues(new Uint8Array(16));
                const data = encoder.encode(text);
                
                const encrypted = await crypto.subtle.encrypt(
                    {
                        name: 'AES-CBC',
                        iv: ivArray
                    },
                    keyMaterial,
                    data
                );
                
                // 返回IV和加密数据的Base64组合
                const result = new Uint8Array(ivArray.length + encrypted.byteLength);
                result.set(ivArray, 0);
                result.set(new Uint8Array(encrypted), ivArray.length);
                
                return btoa(String.fromCharCode.apply(null, result));
            }
            
            async function aesDecrypt(encryptedText, key) {
                try {
                    const encoder = new TextEncoder();
                    const decoder = new TextDecoder();
                    
                    // 解码Base64
                    const binaryString = atob(encryptedText);
                    const bytes = new Uint8Array(binaryString.length);
                    for (let i = 0; i < binaryString.length; i++) {
                        bytes[i] = binaryString.charCodeAt(i);
                    }
                    
                    // 提取IV (前16字节)和加密数据
                    const iv = bytes.slice(0, 16);
                    const data = bytes.slice(16);
                    
                    const keyMaterial = await crypto.subtle.importKey(
                        'raw',
                        encoder.encode(key),
                        { name: 'AES-CBC' },
                        false,
                        ['decrypt']
                    );
                    
                    const decrypted = await crypto.subtle.decrypt(
                        {
                            name: 'AES-CBC',
                            iv: iv
                        },
                        keyMaterial,
                        data
                    );
                    
                    return decoder.decode(decrypted);
                } catch (e) {
                    throw new Error('解密失败: ' + e.message);
                }
            }
            
            function formatDateTimeLocal(date) {
                const year = date.getFullYear();
                const month = String(date.getMonth() + 1).padStart(2, '0');
                const day = String(date.getDate()).padStart(2, '0');
                const hours = String(date.getHours()).padStart(2, '0');
                const minutes = String(date.getMinutes()).padStart(2, '0');
                
                return `${year}-${month}-${day}T${hours}:${minutes}`;
            }
            
            function generateAsciiTable() {
                const table = document.createElement('table');
                table.className = 'ascii-table';
                
                // 表头
                const thead = document.createElement('thead');
                const headerRow = document.createElement('tr');
                ['十进制', '十六进制', '字符', '描述'].forEach(text => {
                    const th = document.createElement('th');
                    th.textContent = text;
                    headerRow.appendChild(th);
                });
                thead.appendChild(headerRow);
                table.appendChild(thead);
                
                // 表体
                const tbody = document.createElement('tbody');
                
                // 生成0-127的ASCII码
                for (let i = 0; i <= 127; i++) {
                    const tr = document.createElement('tr');
                    
                    // 十进制
                    const tdDec = document.createElement('td');
                    tdDec.textContent = i;
                    tr.appendChild(tdDec);
                    
                    // 十六进制
                    const tdHex = document.createElement('td');
                    tdHex.textContent = '0x' + i.toString(16).toUpperCase();
                    tr.appendChild(tdHex);
                    
                    // 字符
                    const tdChar = document.createElement('td');
                    const charSpan = document.createElement('span');
                    charSpan.className = i < 32 ? 'ascii-control' : 'ascii-char';
                    
                    if (i < 32) {
                        // 控制字符
                        charSpan.textContent = getControlCharName(i);
                    } else if (i === 32) {
                        charSpan.textContent = 'Space';
                    } else if (i === 127) {
                        charSpan.textContent = 'DEL';
                    } else {
                        charSpan.textContent = String.fromCharCode(i);
                    }
                    
                    tdChar.appendChild(charSpan);
                    tr.appendChild(tdChar);
                    
                    // 描述
                    const tdDesc = document.createElement('td');
                    tdDesc.textContent = getAsciiDescription(i);
                    tr.appendChild(tdDesc);
                    
                    tbody.appendChild(tr);
                }
                
                table.appendChild(tbody);
                document.getElementById('ascii-table').appendChild(table);
            }
            
            function getControlCharName(code) {
                const controlChars = [
                    'NUL', 'SOH', 'STX', 'ETX', 'EOT', 'ENQ', 'ACK', 'BEL',
                    'BS', 'HT', 'LF', 'VT', 'FF', 'CR', 'SO', 'SI',
                    'DLE', 'DC1', 'DC2', 'DC3', 'DC4', 'NAK', 'SYN', 'ETB',
                    'CAN', 'EM', 'SUB', 'ESC', 'FS', 'GS', 'RS', 'US'
                ];
                return controlChars[code] || '';
            }
            
            function getAsciiDescription(code) {
                const descriptions = {
                    0: '空字符',
                    1: '标题开始',
                    2: '正文开始',
                    3: '正文结束',
                    4: '传输结束',
                    5: '请求',
                    6: '确认回应',
                    7: '响铃',
                    8: '退格',
                    9: '水平制表符',
                    10: '换行',
                    11: '垂直制表符',
                    12: '换页',
                    13: '回车',
                    14: '取消变换',
                    15: '启用变换',
                    16: '数据链路转义',
                    17: '设备控制1',
                    18: '设备控制2',
                    19: '设备控制3',
                    20: '设备控制4',
                    21: '拒绝接收',
                    22: '同步空闲',
                    23: '传输块结束',
                    24: '取消',
                    25: '介质中断',
                    26: '替换',
                    27: '逃逸',
                    28: '文件分隔符',
                    29: '组分隔符',
                    30: '记录分隔符',
                    31: '单元分隔符',
                    32: '空格',
                    127: '删除'
                };
                
                return descriptions[code] || (code >= 33 && code <= 126 ? '可打印字符' : '');
            }
        });
    </script>
</body>
</html>
相关推荐
s9123601012 小时前
【Rust】使用lldb 调试core dump
前端·javascript·rust
前端开发呀2 小时前
🔥 99%由 Trae AI 开发的 React KeepAlive 组件,竟然如此优雅!✨
前端·trae
不是鱼3 小时前
Canvas学习笔记(一)
前端·javascript·canvas
我有一棵树3 小时前
React 中 useRef 和 useState 的使用场景区别
前端·javascript·react.js
喵个咪3 小时前
Qt6 QML 实现DateTimePicker组件
前端·qt
yinuo3 小时前
CSS奇技淫巧:用你意想不到的4种属性实现裁剪遮罩效果
前端
晓翔仔3 小时前
网络安全之Web入侵场景
前端·安全·web安全·网络安全·信息安全
想努力找到前端实习的呆呆鸟3 小时前
Uniapp如何下载图片到本地相册
前端·vue.js·微信小程序
fmk10233 小时前
Vue中的provide与inject
前端·javascript·vue.js