部分界面預覽
1. 整體架構設計
1.1 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變量定義
: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變量統一管理主題色,便於維護和主題切換。
響應式設計
@media (max-width: 768px) {
.container {
padding: 15px;
}
.split-container {
flex-direction: column;
}
.header-actions {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
}
媒體查詢實現移動端適配,在小屏幕上調整佈局。
暗黑模式實現
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 初始化與狀態管理
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 標籤頁系統
// 標籤頁切換
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 文本處理功能詳解
實時文本統計
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+/分割單詞。
正則表達式測試
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格式化與驗證
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
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文件處理
文件編碼
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 加密功能實現
哈希計算
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加密
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 輔助函數系統
狀態顯示
function showStatus(element, message, type) {
element.textContent = message;
element.className = 'status-message ' + type;
setTimeout(() => {
element.textContent = '';
element.className = 'status-message';
}, 3000);
}
統一的用户反饋機制,3秒後自動消失。
剪貼板操作
function copyToClipboard(element) {
element.select();
document.execCommand('copy');
// 取消選中
if (window.getSelection) {
window.getSelection().removeAllRanges();
} else if (document.selection) {
document.selection.empty();
}
}
兼容多種瀏覽器的複製功能實現。
4. 技術亮點總結
- 用户體驗優化
- 實時統計更新
- 操作狀態反饋
- 一鍵複製功能
- 暗黑模式切換
- 代碼質量
- 功能模塊化
- 錯誤處理完善
- 代碼複用度高
- 現代Web技術應用
- 使用
SS Grid和Flexbox佈局 - 利用
Web Crypto API進行加密 - 使用
File API處理文件
內部細節
- 完整的錯誤處理:所有操作都有try-catch包裝
- 用户體驗優化:實時反饋、狀態提示、一鍵複製
- 現代API使用:
FileReader、Crypto、localStorage等 - 代碼複用性:輔助函數封裝良好
- 響應式設計:完美適配各種屏幕尺寸
- 主題系統:完整的亮色/暗色主題支持
這個工具網頁展示了前端開發的多個重要方面:DOM操作、事件處理、數據轉換、文件處理、加密算法等,功能全面,是一個很好的學習範例。
5. 源代碼
<!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>