Add new tools and improve homepage sorting

This commit is contained in:
chao5
2025-12-23 10:52:20 +08:00
parent e5cfe80c37
commit 008ded08cf
23 changed files with 4034 additions and 3 deletions
+63 -2
View File
@@ -188,6 +188,30 @@
margin-bottom: 20px;
}
.sort-wrapper {
display: flex;
align-items: center;
gap: 8px;
padding: 0 12px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
color: var(--text-muted);
}
.sort-select {
background: transparent;
border: none;
color: var(--text);
font-size: 14px;
padding: 12px 4px 12px 0;
cursor: pointer;
}
.sort-select:focus {
outline: none;
}
.search-input-wrapper {
flex: 1;
position: relative;
@@ -248,7 +272,7 @@
/* Tools Grid */
.tools-section {
padding: 40px 0 80px;
padding: 30px 0 70px;
}
.section-header {
@@ -274,6 +298,7 @@
gap: 20px;
}
.tool-card {
background: var(--bg-card);
border: 1px solid var(--border);
@@ -332,6 +357,19 @@
gap: 4px;
}
.tool-updated {
font-size: 12px;
color: var(--text-muted);
display: inline-flex;
align-items: center;
gap: 6px;
margin-top: 6px;
}
.tool-updated i {
font-size: 12px;
}
.tool-description {
color: var(--text-muted);
font-size: 14px;
@@ -537,6 +575,13 @@
<i class="fas fa-search search-icon"></i>
<input type="text" class="search-input" id="search-input" placeholder="搜索工具名称、描述或标签...">
</div>
<div class="sort-wrapper">
<i class="far fa-clock"></i>
<select class="sort-select" id="sort-select">
<option value="updated-desc">更新时间:新 → 旧</option>
<option value="updated-asc">更新时间:旧 → 新</option>
</select>
</div>
</div>
<div class="category-tabs" id="category-tabs">
<button class="category-tab active" data-category="all">
@@ -609,6 +654,7 @@
let toolsData = null;
let currentCategory = 'all';
let searchQuery = '';
let sortOption = 'updated-desc';
async function loadTools() {
try {
@@ -641,6 +687,8 @@
<i class="fas fa-th-large"></i> 全部
</button>
`;
const allButton = container.querySelector('[data-category="all"]');
allButton.addEventListener('click', () => selectCategory('all'));
toolsData.categories.forEach(cat => {
const btn = document.createElement('button');
@@ -661,7 +709,7 @@
}
function filterTools() {
return toolsData.tools.filter(tool => {
const filtered = toolsData.tools.filter(tool => {
const matchesCategory = currentCategory === 'all' || tool.category === currentCategory;
const matchesSearch = !searchQuery ||
tool.name.toLowerCase().includes(searchQuery) ||
@@ -669,6 +717,11 @@
tool.tags.some(tag => tag.toLowerCase().includes(searchQuery));
return matchesCategory && matchesSearch;
});
const withTimestamp = (tool) => new Date(tool.updatedAt || tool.createdAt || 0).getTime();
if (sortOption === 'updated-asc') {
return filtered.sort((a, b) => withTimestamp(a) - withTimestamp(b));
}
return filtered.sort((a, b) => withTimestamp(b) - withTimestamp(a));
}
function renderTools() {
@@ -689,6 +742,7 @@
container.innerHTML = filteredTools.map(tool => {
const category = toolsData.categories.find(c => c.id === tool.category);
const updatedAt = tool.updatedAt || tool.createdAt || '未知';
return `
<a href="${tool.detail}" class="tool-card" style="--tool-color: ${tool.color}">
${tool.featured ? '<span class="featured-badge">精选</span>' : ''}
@@ -699,6 +753,7 @@
<div class="tool-info">
<div class="tool-title">${tool.name}</div>
<div class="tool-category">${category?.icon || ''} ${category?.name || '未分类'}</div>
<div class="tool-updated"><i class="far fa-clock"></i> ${updatedAt}</div>
</div>
</div>
<div class="tool-description">${tool.description}</div>
@@ -713,12 +768,18 @@
}).join('');
}
// Search functionality
document.getElementById('search-input').addEventListener('input', (e) => {
searchQuery = e.target.value.toLowerCase();
renderTools();
});
document.getElementById('sort-select').addEventListener('change', (e) => {
sortOption = e.target.value;
renderTools();
});
// Initialize
loadTools();
</script>
+200
View File
@@ -521,6 +521,206 @@
"updatedAt": "2025-12-23",
"featured": true,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "text-deduplicator",
"name": "文本去重工具",
"slug": "text-deduplicator",
"category": "text",
"tags": ["text", "dedupe", "cleanup", "list"],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "按行去重并保留原始顺序",
"longDescription": "文本去重工具支持按行去重、忽略大小写和移除空行,适合清理清单和名单。",
"icon": "🧹",
"color": "#10b981",
"entry": "tools/text-deduplicator/app.html",
"detail": "tools/text-deduplicator/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": true,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "text-sorter",
"name": "文本排序工具",
"slug": "text-sorter",
"category": "text",
"tags": ["text", "sorter", "list", "utility"],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "按行排序文本,支持数字排序",
"longDescription": "文本排序工具支持升序/降序、数字排序和去重,适合整理清单和列表。",
"icon": "📑",
"color": "#f59e0b",
"entry": "tools/text-sorter/app.html",
"detail": "tools/text-sorter/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": true,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "line-numberer",
"name": "行号添加器",
"slug": "line-numberer",
"category": "text",
"tags": ["text", "line", "number", "editor"],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "为文本自动添加行号",
"longDescription": "行号添加器可自定义起始值、步进和分隔符,适合代码与清单。",
"icon": "🔢",
"color": "#3b82f6",
"entry": "tools/line-numberer/app.html",
"detail": "tools/line-numberer/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": true,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "text-cleaner",
"name": "文本清理器",
"slug": "text-cleaner",
"category": "text",
"tags": ["text", "cleaner", "format", "utility"],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "清理文本中的空格与空行",
"longDescription": "文本清理器支持合并空格、删除空行并规范换行格式。",
"icon": "🧽",
"color": "#14b8a6",
"entry": "tools/text-cleaner/app.html",
"detail": "tools/text-cleaner/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": true,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "csv-to-json",
"name": "CSV 转 JSON",
"slug": "csv-to-json",
"category": "converter",
"tags": ["csv", "json", "converter", "table"],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "将 CSV 表格转换为 JSON",
"longDescription": "CSV 转 JSON 工具支持表头解析、分隔符设置和引号字段。",
"icon": "📄",
"color": "#6366f1",
"entry": "tools/csv-to-json/app.html",
"detail": "tools/csv-to-json/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": true,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "json-to-csv",
"name": "JSON 转 CSV",
"slug": "json-to-csv",
"category": "converter",
"tags": ["json", "csv", "converter", "export"],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "将 JSON 数组导出为 CSV",
"longDescription": "JSON 转 CSV 工具可自动生成表头并处理引号转义。",
"icon": "🧾",
"color": "#ec4899",
"entry": "tools/json-to-csv/app.html",
"detail": "tools/json-to-csv/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": true,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "url-parser",
"name": "URL 解析器",
"slug": "url-parser",
"category": "developer",
"tags": ["url", "parser", "developer", "web"],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "解析 URL 的各个组成部分",
"longDescription": "URL 解析器可拆分协议、域名、路径、参数和锚点。",
"icon": "🌐",
"color": "#0ea5e9",
"entry": "tools/url-parser/app.html",
"detail": "tools/url-parser/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": true,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "contrast-checker",
"name": "颜色对比度检测",
"slug": "contrast-checker",
"category": "developer",
"tags": ["color", "contrast", "accessibility", "design"],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "检测前景与背景颜色对比度",
"longDescription": "颜色对比度检测基于 WCAG 标准并给出 AA/AAA 结果。",
"icon": "🌓",
"color": "#8b5cf6",
"entry": "tools/contrast-checker/app.html",
"detail": "tools/contrast-checker/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": true,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "password-strength",
"name": "密码强度检测",
"slug": "password-strength",
"category": "utility",
"tags": ["password", "security", "checker", "utility"],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "检测密码强度和安全性",
"longDescription": "密码强度检测统计长度与字符类型并给出改进建议。",
"icon": "🛡️",
"color": "#ef4444",
"entry": "tools/password-strength/app.html",
"detail": "tools/password-strength/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": true,
"repo": "https://github.com/justhtmls/html-tools"
},
{
"id": "date-diff",
"name": "日期差值计算器",
"slug": "date-diff",
"category": "utility",
"tags": ["date", "time", "calculator", "utility"],
"author": "JustHTMLs",
"authorUrl": "https://github.com/justhtmls",
"version": "1.0.0",
"description": "计算两个日期之间的差值",
"longDescription": "日期差值计算器输出天、小时、分钟和秒等多维度结果。",
"icon": "📆",
"color": "#22c55e",
"entry": "tools/date-diff/app.html",
"detail": "tools/date-diff/index.html",
"createdAt": "2025-12-23",
"updatedAt": "2025-12-23",
"featured": true,
"repo": "https://github.com/justhtmls/html-tools"
}
],
"categories": [
+256
View File
@@ -0,0 +1,256 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>颜色对比度检测</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #8b5cf6 0%, #6d28d9 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.4rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 18px;
padding: 24px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
}
.color-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
}
.color-block {
background: #f5f3ff;
border-radius: 14px;
padding: 14px;
display: flex;
flex-direction: column;
gap: 10px;
}
.color-block label {
font-size: 13px;
color: #4c1d95;
}
.color-block input[type="text"] {
padding: 8px 10px;
border-radius: 8px;
border: 1px solid #ddd6fe;
font-size: 14px;
}
.preview {
margin-top: 20px;
border-radius: 14px;
padding: 18px;
text-align: center;
font-weight: 600;
}
.ratio {
margin-top: 16px;
font-size: 20px;
font-weight: 700;
color: #4c1d95;
text-align: center;
}
.badges {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
margin-top: 16px;
}
.badge {
padding: 10px 12px;
border-radius: 10px;
font-size: 14px;
background: #ede9fe;
color: #4c1d95;
display: flex;
justify-content: space-between;
}
.badge.pass {
background: #dcfce7;
color: #166534;
}
.actions {
margin-top: 16px;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.btn {
border: none;
padding: 10px 18px;
border-radius: 10px;
cursor: pointer;
font-weight: 600;
color: white;
background: #8b5cf6;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>颜色对比度检测</h1>
<p>符合 WCAG 标准的对比度计算</p>
</div>
<div class="card">
<div class="color-row">
<div class="color-block">
<label>前景色</label>
<input type="color" id="color-1" value="#111827">
<input type="text" id="color-1-text" value="#111827">
</div>
<div class="color-block">
<label>背景色</label>
<input type="color" id="color-2" value="#ffffff">
<input type="text" id="color-2-text" value="#ffffff">
</div>
</div>
<div class="preview" id="preview">示例文本 ABC 123</div>
<div class="ratio" id="ratio">对比度:--</div>
<div class="badges" id="badges"></div>
<div class="actions">
<button class="btn" id="swap-btn">交换颜色</button>
</div>
</div>
</div>
<script>
const color1 = document.getElementById('color-1');
const color2 = document.getElementById('color-2');
const color1Text = document.getElementById('color-1-text');
const color2Text = document.getElementById('color-2-text');
const preview = document.getElementById('preview');
const ratio = document.getElementById('ratio');
const badges = document.getElementById('badges');
function hexToRgb(hex) {
const cleaned = hex.replace('#', '');
if (cleaned.length !== 6) {
return null;
}
const num = parseInt(cleaned, 16);
if (Number.isNaN(num)) {
return null;
}
return {
r: (num >> 16) & 255,
g: (num >> 8) & 255,
b: num & 255
};
}
function channelToLinear(channel) {
const value = channel / 255;
return value <= 0.03928 ? value / 12.92 : Math.pow((value + 0.055) / 1.055, 2.4);
}
function luminance(rgb) {
return 0.2126 * channelToLinear(rgb.r) + 0.7152 * channelToLinear(rgb.g) + 0.0722 * channelToLinear(rgb.b);
}
function update() {
const rgb1 = hexToRgb(color1Text.value.trim());
const rgb2 = hexToRgb(color2Text.value.trim());
if (!rgb1 || !rgb2) {
ratio.textContent = '对比度:请输入有效颜色';
badges.innerHTML = '';
return;
}
color1.value = `#${color1Text.value.replace('#', '')}`;
color2.value = `#${color2Text.value.replace('#', '')}`;
const l1 = luminance(rgb1);
const l2 = luminance(rgb2);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
const contrast = (lighter + 0.05) / (darker + 0.05);
preview.style.color = color1Text.value;
preview.style.background = color2Text.value;
ratio.textContent = `对比度:${contrast.toFixed(2)} : 1`;
const checks = [
{ label: '普通文本 AA (≥ 4.5)', pass: contrast >= 4.5 },
{ label: '大文本 AA (≥ 3.0)', pass: contrast >= 3.0 },
{ label: '普通文本 AAA (≥ 7.0)', pass: contrast >= 7.0 },
{ label: '大文本 AAA (≥ 4.5)', pass: contrast >= 4.5 }
];
badges.innerHTML = checks
.map((item) => `
<div class="badge ${item.pass ? 'pass' : ''}">
<span>${item.label}</span>
<strong>${item.pass ? '通过' : '不通过'}</strong>
</div>
`)
.join('');
}
function syncFromPicker(event) {
const target = event.target.id === 'color-1' ? color1Text : color2Text;
target.value = event.target.value;
update();
}
function swapColors() {
const temp = color1Text.value;
color1Text.value = color2Text.value;
color2Text.value = temp;
update();
}
color1.addEventListener('input', syncFromPicker);
color2.addEventListener('input', syncFromPicker);
color1Text.addEventListener('input', update);
color2Text.addEventListener('input', update);
document.getElementById('swap-btn').addEventListener('click', swapColors);
update();
</script>
</body>
</html>
+145
View File
@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>颜色对比度检测 - JustHTMLs</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #8b5cf6 0%, #6d28d9 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #333;
margin-bottom: 20px;
font-size: 1.5rem;
}
.card p {
color: #666;
line-height: 1.8;
margin-bottom: 15px;
}
.features {
list-style: none;
margin-top: 20px;
}
.features li {
padding: 10px 0;
color: #555;
border-bottom: 1px solid #eee;
}
.features li:before {
content: "✓";
color: #8b5cf6;
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 14px 30px;
background: linear-gradient(135deg, #8b5cf6 0%, #6d28d9 100%);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: all 0.3s;
margin: 10px 10px 10px 0;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
.btn-secondary {
background: #6c757d;
}
.preview {
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
}
.preview iframe {
width: 100%;
height: 450px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>颜色对比度检测</h1>
<p>检测文本与背景颜色可读性</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>颜色对比度检测基于 WCAG 标准,适合设计师和前端开发者。</p>
<h2>功能特点</h2>
<ul class="features">
<li>计算 WCAG 对比度</li>
<li>显示 AA/AAA 通过情况</li>
<li>实时预览前景/背景</li>
<li>一键交换颜色</li>
</ul>
<div style="margin-top: 30px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/contrast-checker" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
</body>
</html>
+281
View File
@@ -0,0 +1,281 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSV 转 JSON</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1100px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.4rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 18px;
padding: 24px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
}
.options {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 18px;
}
.options label {
display: flex;
align-items: center;
gap: 6px;
background: #eef2ff;
padding: 8px 12px;
border-radius: 10px;
font-size: 14px;
color: #312e81;
}
.options input {
border: 1px solid #c7d2fe;
padding: 6px 8px;
border-radius: 8px;
width: 60px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 16px;
}
textarea {
width: 100%;
min-height: 260px;
border: 2px solid #c7d2fe;
border-radius: 12px;
padding: 12px;
font-size: 14px;
line-height: 1.6;
resize: vertical;
}
textarea:focus {
outline: none;
border-color: #6366f1;
}
.actions {
display: flex;
gap: 10px;
margin-top: 16px;
flex-wrap: wrap;
}
.btn {
border: none;
padding: 10px 18px;
border-radius: 10px;
cursor: pointer;
font-weight: 600;
color: white;
background: #6366f1;
}
.btn.secondary {
background: #6b7280;
}
.hint {
margin-top: 12px;
color: #4338ca;
font-size: 13px;
}
.error {
color: #b91c1c;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>CSV 转 JSON</h1>
<p>支持自定义分隔符、带表头解析</p>
</div>
<div class="card">
<div class="options">
<label>分隔符
<input type="text" id="delimiter" value="," maxlength="1">
</label>
<label><input type="checkbox" id="has-header" checked> 首行是表头</label>
</div>
<div class="grid">
<div>
<h3 style="margin-bottom: 8px; color: #111827;">CSV 输入</h3>
<textarea id="input" placeholder="name,age\nAlice,18\nBob,20"></textarea>
</div>
<div>
<h3 style="margin-bottom: 8px; color: #111827;">JSON 输出</h3>
<textarea id="output" readonly placeholder="结果会显示在这里"></textarea>
</div>
</div>
<div class="actions">
<button class="btn" id="convert-btn">转换</button>
<button class="btn secondary" id="copy-btn">复制结果</button>
<button class="btn secondary" id="clear-btn">清空</button>
</div>
<div class="hint" id="status">支持双引号字段和换行。</div>
</div>
</div>
<script>
const input = document.getElementById('input');
const output = document.getElementById('output');
const delimiterInput = document.getElementById('delimiter');
const hasHeader = document.getElementById('has-header');
const status = document.getElementById('status');
function parseCSV(text, delimiter) {
const rows = [];
let row = [];
let field = '';
let inQuotes = false;
for (let i = 0; i < text.length; i += 1) {
const char = text[i];
const next = text[i + 1];
if (inQuotes) {
if (char === '"') {
if (next === '"') {
field += '"';
i += 1;
} else {
inQuotes = false;
}
} else {
field += char;
}
} else {
if (char === '"') {
inQuotes = true;
} else if (char === delimiter) {
row.push(field);
field = '';
} else if (char === '\n') {
row.push(field);
rows.push(row);
row = [];
field = '';
} else if (char === '\r') {
continue;
} else {
field += char;
}
}
}
row.push(field);
if (row.length > 1 || row[0] !== '') {
rows.push(row);
}
return rows;
}
function convert() {
const delimiter = delimiterInput.value || ',';
const raw = input.value.replace(/\r\n/g, '\n');
try {
const rows = parseCSV(raw, delimiter);
if (!rows.length) {
output.value = '';
status.textContent = '没有可转换的数据。';
status.classList.remove('error');
return;
}
let result;
if (hasHeader.checked) {
const headers = rows.shift().map((cell) => cell.trim());
result = rows.map((cells) => {
const obj = {};
headers.forEach((header, index) => {
obj[header || `col${index + 1}`] = cells[index] ?? '';
});
return obj;
});
} else {
result = rows.map((cells) => {
const obj = {};
cells.forEach((cell, index) => {
obj[`col${index + 1}`] = cell;
});
return obj;
});
}
output.value = JSON.stringify(result, null, 2);
status.textContent = `已转换 ${result.length} 行数据。`;
status.classList.remove('error');
} catch (error) {
output.value = '';
status.textContent = '解析失败,请检查 CSV 格式。';
status.classList.add('error');
}
}
function copyResult() {
if (!output.value) {
return;
}
output.select();
output.setSelectionRange(0, output.value.length);
document.execCommand('copy');
}
function clearAll() {
input.value = '';
output.value = '';
status.textContent = '支持双引号字段和换行。';
status.classList.remove('error');
}
document.getElementById('convert-btn').addEventListener('click', convert);
document.getElementById('copy-btn').addEventListener('click', copyResult);
document.getElementById('clear-btn').addEventListener('click', clearAll);
[input, delimiterInput, hasHeader].forEach((el) => {
el.addEventListener('input', convert);
el.addEventListener('change', convert);
});
</script>
</body>
</html>
+145
View File
@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSV 转 JSON - JustHTMLs</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #333;
margin-bottom: 20px;
font-size: 1.5rem;
}
.card p {
color: #666;
line-height: 1.8;
margin-bottom: 15px;
}
.features {
list-style: none;
margin-top: 20px;
}
.features li {
padding: 10px 0;
color: #555;
border-bottom: 1px solid #eee;
}
.features li:before {
content: "✓";
color: #6366f1;
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 14px 30px;
background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: all 0.3s;
margin: 10px 10px 10px 0;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
.btn-secondary {
background: #6c757d;
}
.preview {
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
}
.preview iframe {
width: 100%;
height: 450px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>CSV 转 JSON</h1>
<p>把表格数据转换成 JSON</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>CSV 转 JSON 工具支持表头解析,适合快速把表格数据转换为结构化 JSON。</p>
<h2>功能特点</h2>
<ul class="features">
<li>支持表头解析</li>
<li>自定义分隔符</li>
<li>兼容双引号字段</li>
<li>输出格式化 JSON</li>
</ul>
<div style="margin-top: 30px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/csv-to-json" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
</body>
</html>
+217
View File
@@ -0,0 +1,217 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>日期差值计算器</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.4rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 18px;
padding: 24px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
}
.inputs {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 14px;
margin-bottom: 16px;
}
.inputs label {
font-size: 14px;
color: #14532d;
background: #dcfce7;
padding: 10px;
border-radius: 10px;
display: flex;
flex-direction: column;
gap: 6px;
}
.inputs input {
padding: 8px 10px;
border-radius: 8px;
border: 1px solid #86efac;
}
.actions {
display: flex;
gap: 10px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.btn {
border: none;
padding: 10px 18px;
border-radius: 10px;
cursor: pointer;
font-weight: 600;
color: white;
background: #22c55e;
}
.btn.secondary {
background: #6b7280;
}
.result {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 12px;
}
.result div {
background: #f0fdf4;
padding: 12px;
border-radius: 10px;
font-size: 14px;
color: #14532d;
}
.status {
margin-top: 10px;
font-size: 13px;
color: #14532d;
}
.status.error {
color: #b91c1c;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>日期差值计算器</h1>
<p>计算两个日期之间的天数、小时数和总秒数</p>
</div>
<div class="card">
<div class="inputs">
<label>开始时间
<input type="datetime-local" id="start">
</label>
<label>结束时间
<input type="datetime-local" id="end">
</label>
</div>
<div class="actions">
<button class="btn" id="calc-btn">计算差值</button>
<button class="btn secondary" id="swap-btn">交换时间</button>
<button class="btn secondary" id="clear-btn">清空</button>
</div>
<div class="result" id="result"></div>
<div class="status" id="status">请选择开始和结束时间。</div>
</div>
</div>
<script>
const startInput = document.getElementById('start');
const endInput = document.getElementById('end');
const result = document.getElementById('result');
const status = document.getElementById('status');
function formatNumber(value) {
return value.toLocaleString('zh-CN');
}
function calculate() {
const startValue = startInput.value;
const endValue = endInput.value;
if (!startValue || !endValue) {
result.innerHTML = '';
status.textContent = '请选择开始和结束时间。';
status.classList.remove('error');
return;
}
const startDate = new Date(startValue);
const endDate = new Date(endValue);
if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime())) {
result.innerHTML = '';
status.textContent = '时间格式无效。';
status.classList.add('error');
return;
}
let diffMs = endDate - startDate;
const direction = diffMs >= 0 ? '结束时间在之后' : '结束时间在之前';
diffMs = Math.abs(diffMs);
const seconds = Math.floor(diffMs / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
const weeks = (days / 7).toFixed(2);
result.innerHTML = `
<div>总天数:${formatNumber(days)} 天</div>
<div>总小时:${formatNumber(hours)} 小时</div>
<div>总分钟:${formatNumber(minutes)} 分钟</div>
<div>总秒数:${formatNumber(seconds)} 秒</div>
<div>总周数:${weeks} 周</div>
`;
status.textContent = `${direction}(差值已取绝对值)`;
status.classList.remove('error');
}
function swapDates() {
const temp = startInput.value;
startInput.value = endInput.value;
endInput.value = temp;
calculate();
}
function clearAll() {
startInput.value = '';
endInput.value = '';
result.innerHTML = '';
status.textContent = '请选择开始和结束时间。';
status.classList.remove('error');
}
document.getElementById('calc-btn').addEventListener('click', calculate);
document.getElementById('swap-btn').addEventListener('click', swapDates);
document.getElementById('clear-btn').addEventListener('click', clearAll);
[startInput, endInput].forEach((el) => {
el.addEventListener('change', calculate);
});
</script>
</body>
</html>
+145
View File
@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>日期差值计算器 - JustHTMLs</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #333;
margin-bottom: 20px;
font-size: 1.5rem;
}
.card p {
color: #666;
line-height: 1.8;
margin-bottom: 15px;
}
.features {
list-style: none;
margin-top: 20px;
}
.features li {
padding: 10px 0;
color: #555;
border-bottom: 1px solid #eee;
}
.features li:before {
content: "✓";
color: #22c55e;
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 14px 30px;
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: all 0.3s;
margin: 10px 10px 10px 0;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
.btn-secondary {
background: #6c757d;
}
.preview {
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
}
.preview iframe {
width: 100%;
height: 450px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>日期差值计算器</h1>
<p>计算两个时间点的差值</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>日期差值计算器支持日期与时间计算,输出多种维度的结果。</p>
<h2>功能特点</h2>
<ul class="features">
<li>计算天/小时/分钟/秒</li>
<li>支持时间交换</li>
<li>显示差值方向</li>
<li>本地计算无需上传</li>
</ul>
<div style="margin-top: 30px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/date-diff" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
</body>
</html>
+248
View File
@@ -0,0 +1,248 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JSON 转 CSV</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #ec4899 0%, #db2777 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1100px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.4rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 18px;
padding: 24px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
}
.options {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 18px;
}
.options label {
display: flex;
align-items: center;
gap: 6px;
background: #fce7f3;
padding: 8px 12px;
border-radius: 10px;
font-size: 14px;
color: #9d174d;
}
.options input {
border: 1px solid #fbcfe8;
padding: 6px 8px;
border-radius: 8px;
width: 60px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 16px;
}
textarea {
width: 100%;
min-height: 260px;
border: 2px solid #fbcfe8;
border-radius: 12px;
padding: 12px;
font-size: 14px;
line-height: 1.6;
resize: vertical;
}
textarea:focus {
outline: none;
border-color: #ec4899;
}
.actions {
display: flex;
gap: 10px;
margin-top: 16px;
flex-wrap: wrap;
}
.btn {
border: none;
padding: 10px 18px;
border-radius: 10px;
cursor: pointer;
font-weight: 600;
color: white;
background: #ec4899;
}
.btn.secondary {
background: #6b7280;
}
.hint {
margin-top: 12px;
color: #9d174d;
font-size: 13px;
}
.error {
color: #b91c1c;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>JSON 转 CSV</h1>
<p>适合导出表格数据,支持自定义分隔符</p>
</div>
<div class="card">
<div class="options">
<label>分隔符
<input type="text" id="delimiter" value="," maxlength="1">
</label>
<label><input type="checkbox" id="include-header" checked> 输出表头</label>
</div>
<div class="grid">
<div>
<h3 style="margin-bottom: 8px; color: #111827;">JSON 输入</h3>
<textarea id="input" placeholder='[{"name":"Alice","age":18}]'></textarea>
</div>
<div>
<h3 style="margin-bottom: 8px; color: #111827;">CSV 输出</h3>
<textarea id="output" readonly placeholder="结果会显示在这里"></textarea>
</div>
</div>
<div class="actions">
<button class="btn" id="convert-btn">转换</button>
<button class="btn secondary" id="copy-btn">复制结果</button>
<button class="btn secondary" id="clear-btn">清空</button>
</div>
<div class="hint" id="status">输入 JSON 数组即可转换。</div>
</div>
</div>
<script>
const input = document.getElementById('input');
const output = document.getElementById('output');
const delimiterInput = document.getElementById('delimiter');
const includeHeader = document.getElementById('include-header');
const status = document.getElementById('status');
function escapeCSV(value, delimiter) {
if (value === null || value === undefined) {
return '';
}
const str = String(value);
if (str.includes('"') || str.includes('\n') || str.includes('\r') || str.includes(delimiter)) {
return `"${str.replace(/"/g, '""')}"`;
}
return str;
}
function convert() {
const delimiter = delimiterInput.value || ',';
try {
const data = JSON.parse(input.value || '[]');
if (!Array.isArray(data)) {
throw new Error('not-array');
}
if (data.length === 0) {
output.value = '';
status.textContent = '没有可转换的数据。';
status.classList.remove('error');
return;
}
let csvRows = [];
if (Array.isArray(data[0])) {
csvRows = data.map((row) => row.map((cell) => escapeCSV(cell, delimiter)).join(delimiter));
} else {
const headers = [];
data.forEach((item) => {
Object.keys(item || {}).forEach((key) => {
if (!headers.includes(key)) {
headers.push(key);
}
});
});
if (includeHeader.checked) {
csvRows.push(headers.map((h) => escapeCSV(h, delimiter)).join(delimiter));
}
data.forEach((item) => {
const row = headers.map((key) => escapeCSV(item ? item[key] : '', delimiter));
csvRows.push(row.join(delimiter));
});
}
output.value = csvRows.join('\n');
status.textContent = `已转换 ${data.length} 行数据。`;
status.classList.remove('error');
} catch (error) {
output.value = '';
status.textContent = '解析失败,请确认 JSON 格式正确。';
status.classList.add('error');
}
}
function copyResult() {
if (!output.value) {
return;
}
output.select();
output.setSelectionRange(0, output.value.length);
document.execCommand('copy');
}
function clearAll() {
input.value = '';
output.value = '';
status.textContent = '输入 JSON 数组即可转换。';
status.classList.remove('error');
}
document.getElementById('convert-btn').addEventListener('click', convert);
document.getElementById('copy-btn').addEventListener('click', copyResult);
document.getElementById('clear-btn').addEventListener('click', clearAll);
[input, delimiterInput, includeHeader].forEach((el) => {
el.addEventListener('input', convert);
el.addEventListener('change', convert);
});
</script>
</body>
</html>
+145
View File
@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JSON 转 CSV - JustHTMLs</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #ec4899 0%, #db2777 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #333;
margin-bottom: 20px;
font-size: 1.5rem;
}
.card p {
color: #666;
line-height: 1.8;
margin-bottom: 15px;
}
.features {
list-style: none;
margin-top: 20px;
}
.features li {
padding: 10px 0;
color: #555;
border-bottom: 1px solid #eee;
}
.features li:before {
content: "✓";
color: #ec4899;
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 14px 30px;
background: linear-gradient(135deg, #ec4899 0%, #db2777 100%);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: all 0.3s;
margin: 10px 10px 10px 0;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
.btn-secondary {
background: #6c757d;
}
.preview {
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
}
.preview iframe {
width: 100%;
height: 450px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>JSON 转 CSV</h1>
<p>导出可直接打开的表格文件</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>JSON 转 CSV 工具可将数组数据导出为 CSV,方便在表格软件中使用。</p>
<h2>功能特点</h2>
<ul class="features">
<li>解析 JSON 数组</li>
<li>自动生成表头</li>
<li>支持自定义分隔符</li>
<li>自动处理引号转义</li>
</ul>
<div style="margin-top: 30px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/json-to-csv" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
</body>
</html>
+220
View File
@@ -0,0 +1,220 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>行号添加器</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.4rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 18px;
padding: 24px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
}
.options {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 12px;
margin-bottom: 18px;
}
.options label {
font-size: 14px;
color: #1e3a8a;
display: flex;
flex-direction: column;
gap: 6px;
background: #eff6ff;
padding: 10px;
border-radius: 10px;
}
.options input {
padding: 8px 10px;
border-radius: 8px;
border: 1px solid #bfdbfe;
}
.toggle {
flex-direction: row;
align-items: center;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
textarea {
width: 100%;
min-height: 240px;
border: 2px solid #bfdbfe;
border-radius: 12px;
padding: 12px;
font-size: 14px;
line-height: 1.6;
resize: vertical;
}
textarea:focus {
outline: none;
border-color: #3b82f6;
}
.actions {
display: flex;
gap: 10px;
margin-top: 16px;
flex-wrap: wrap;
}
.btn {
border: none;
padding: 10px 18px;
border-radius: 10px;
cursor: pointer;
font-weight: 600;
color: white;
background: #2563eb;
}
.btn.secondary {
background: #6b7280;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>行号添加器</h1>
<p>为文本快速添加行号,适合代码、清单和日志</p>
</div>
<div class="card">
<div class="options">
<label>起始行号
<input type="number" id="start" value="1">
</label>
<label>步进
<input type="number" id="step" value="1">
</label>
<label>分隔符
<input type="text" id="separator" value=". ">
</label>
<label class="toggle"><input type="checkbox" id="skip-empty" checked> 跳过空行</label>
<label>补零位数
<input type="number" id="pad" value="2" min="0">
</label>
</div>
<div class="grid">
<div>
<h3 style="margin-bottom: 8px; color: #111827;">原始文本</h3>
<textarea id="input" placeholder="每行一段文字"></textarea>
</div>
<div>
<h3 style="margin-bottom: 8px; color: #111827;">带行号结果</h3>
<textarea id="output" readonly placeholder="结果会显示在这里"></textarea>
</div>
</div>
<div class="actions">
<button class="btn" id="apply-btn">生成行号</button>
<button class="btn secondary" id="copy-btn">复制结果</button>
<button class="btn secondary" id="clear-btn">清空</button>
</div>
</div>
</div>
<script>
const input = document.getElementById('input');
const output = document.getElementById('output');
const startInput = document.getElementById('start');
const stepInput = document.getElementById('step');
const separatorInput = document.getElementById('separator');
const skipEmptyInput = document.getElementById('skip-empty');
const padInput = document.getElementById('pad');
function padNumber(value, width) {
if (width <= 0) {
return String(value);
}
return String(value).padStart(width, '0');
}
function addLineNumbers() {
const lines = input.value.replace(/\r\n/g, '\n').split('\n');
const start = Number(startInput.value) || 1;
const step = Number(stepInput.value) || 1;
const separator = separatorInput.value || '. ';
const pad = Number(padInput.value) || 0;
let index = start;
const result = lines.map((line) => {
if (skipEmptyInput.checked && line.trim() === '') {
return line;
}
const prefix = padNumber(index, pad);
index += step;
return `${prefix}${separator}${line}`;
});
output.value = result.join('\n');
}
function copyResult() {
if (!output.value) {
return;
}
output.select();
output.setSelectionRange(0, output.value.length);
document.execCommand('copy');
}
function clearAll() {
input.value = '';
output.value = '';
}
document.getElementById('apply-btn').addEventListener('click', addLineNumbers);
document.getElementById('copy-btn').addEventListener('click', copyResult);
document.getElementById('clear-btn').addEventListener('click', clearAll);
[input, startInput, stepInput, separatorInput, skipEmptyInput, padInput].forEach((el) => {
el.addEventListener('input', addLineNumbers);
el.addEventListener('change', addLineNumbers);
});
</script>
</body>
</html>
+145
View File
@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>行号添加器 - JustHTMLs</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #333;
margin-bottom: 20px;
font-size: 1.5rem;
}
.card p {
color: #666;
line-height: 1.8;
margin-bottom: 15px;
}
.features {
list-style: none;
margin-top: 20px;
}
.features li {
padding: 10px 0;
color: #555;
border-bottom: 1px solid #eee;
}
.features li:before {
content: "✓";
color: #3b82f6;
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 14px 30px;
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: all 0.3s;
margin: 10px 10px 10px 0;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
.btn-secondary {
background: #6c757d;
}
.preview {
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
}
.preview iframe {
width: 100%;
height: 450px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>行号添加器</h1>
<p>为文本自动添加行号</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>行号添加器让文本阅读与引用更清晰,适合代码片段和长文清单。</p>
<h2>功能特点</h2>
<ul class="features">
<li>自定义起始行号与步进</li>
<li>支持补零位数</li>
<li>可跳过空行</li>
<li>分隔符可自定义</li>
</ul>
<div style="margin-top: 30px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/line-numberer" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
</body>
</html>
+224
View File
@@ -0,0 +1,224 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>密码强度检测</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #ef4444 0%, #b91c1c 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.4rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 18px;
padding: 24px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
}
.input-row {
display: flex;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 16px;
}
.input-row input[type="password"],
.input-row input[type="text"] {
flex: 1 1 280px;
padding: 12px 14px;
border-radius: 10px;
border: 2px solid #fecaca;
font-size: 15px;
}
.input-row label {
display: flex;
align-items: center;
gap: 6px;
background: #fee2e2;
padding: 8px 12px;
border-radius: 10px;
font-size: 14px;
color: #7f1d1d;
}
.meter {
height: 10px;
background: #fee2e2;
border-radius: 999px;
overflow: hidden;
margin-bottom: 12px;
}
.meter-fill {
height: 100%;
width: 0;
background: #ef4444;
transition: width 0.2s ease;
}
.summary {
font-size: 18px;
font-weight: 700;
color: #7f1d1d;
margin-bottom: 8px;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
}
.stats div {
background: #fef2f2;
padding: 10px 12px;
border-radius: 10px;
color: #7f1d1d;
font-size: 14px;
}
.tips {
margin-top: 16px;
color: #7f1d1d;
font-size: 14px;
}
.tips ul {
margin-top: 8px;
padding-left: 18px;
}
.tips li {
margin-bottom: 6px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>密码强度检测</h1>
<p>本地分析密码强度,不会上传任何数据</p>
</div>
<div class="card">
<div class="input-row">
<input type="password" id="password" placeholder="输入密码进行检测">
<label><input type="checkbox" id="show"> 显示密码</label>
</div>
<div class="meter">
<div class="meter-fill" id="meter-fill"></div>
</div>
<div class="summary" id="summary">请先输入密码</div>
<div class="stats" id="stats"></div>
<div class="tips">
建议提升:
<ul id="tips-list"></ul>
</div>
</div>
</div>
<script>
const passwordInput = document.getElementById('password');
const showToggle = document.getElementById('show');
const meterFill = document.getElementById('meter-fill');
const summary = document.getElementById('summary');
const stats = document.getElementById('stats');
const tipsList = document.getElementById('tips-list');
function calculate() {
const value = passwordInput.value;
if (!value) {
summary.textContent = '请先输入密码';
meterFill.style.width = '0%';
stats.innerHTML = '';
tipsList.innerHTML = '';
return;
}
const length = value.length;
const hasLower = /[a-z]/.test(value);
const hasUpper = /[A-Z]/.test(value);
const hasNumber = /[0-9]/.test(value);
const hasSymbol = /[^a-zA-Z0-9]/.test(value);
const typeCount = [hasLower, hasUpper, hasNumber, hasSymbol].filter(Boolean).length;
let score = 0;
if (length >= 8) score += 1;
if (length >= 12) score += 1;
if (length >= 16) score += 1;
score += typeCount;
const maxScore = 7;
const percentage = Math.min(100, Math.round((score / maxScore) * 100));
meterFill.style.width = `${percentage}%`;
let level = '弱';
if (score >= 6) level = '非常强';
else if (score >= 5) level = '强';
else if (score >= 4) level = '良好';
else if (score >= 3) level = '一般';
summary.textContent = `强度等级:${level}`;
const pool = (hasLower ? 26 : 0) + (hasUpper ? 26 : 0) + (hasNumber ? 10 : 0) + (hasSymbol ? 33 : 0);
const entropy = pool > 0 ? (length * Math.log2(pool)).toFixed(1) : '0';
stats.innerHTML = `
<div>长度:${length} 位</div>
<div>字符类型:${typeCount} 种</div>
<div>估算熵值:${entropy} bits</div>
<div>包含:${hasLower ? '小写' : ''} ${hasUpper ? '大写' : ''} ${hasNumber ? '数字' : ''} ${hasSymbol ? '符号' : ''}</div>
`;
const tips = [];
if (length < 12) tips.push('长度建议至少 12 位');
if (!hasUpper) tips.push('增加大写字母');
if (!hasLower) tips.push('增加小写字母');
if (!hasNumber) tips.push('加入数字');
if (!hasSymbol) tips.push('加入符号');
if (/(\w)\1{2,}/.test(value)) tips.push('避免重复字符');
tipsList.innerHTML = tips.length
? tips.map((tip) => `<li>${tip}</li>`).join('')
: '<li>已达到不错的强度</li>';
}
showToggle.addEventListener('change', () => {
passwordInput.type = showToggle.checked ? 'text' : 'password';
});
passwordInput.addEventListener('input', calculate);
calculate();
</script>
</body>
</html>
+145
View File
@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>密码强度检测 - JustHTMLs</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #ef4444 0%, #b91c1c 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #333;
margin-bottom: 20px;
font-size: 1.5rem;
}
.card p {
color: #666;
line-height: 1.8;
margin-bottom: 15px;
}
.features {
list-style: none;
margin-top: 20px;
}
.features li {
padding: 10px 0;
color: #555;
border-bottom: 1px solid #eee;
}
.features li:before {
content: "✓";
color: #ef4444;
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 14px 30px;
background: linear-gradient(135deg, #ef4444 0%, #b91c1c 100%);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: all 0.3s;
margin: 10px 10px 10px 0;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
.btn-secondary {
background: #6c757d;
}
.preview {
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
}
.preview iframe {
width: 100%;
height: 450px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>密码强度检测</h1>
<p>快速评估密码安全性</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>密码强度检测在本地完成分析,帮助你优化密码强度。</p>
<h2>功能特点</h2>
<ul class="features">
<li>检测长度与字符类型</li>
<li>估算熵值</li>
<li>强度等级提示</li>
<li>给出改进建议</li>
</ul>
<div style="margin-top: 30px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/password-strength" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
</body>
</html>
+206
View File
@@ -0,0 +1,206 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文本清理器</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #14b8a6 0%, #0f766e 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.4rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 18px;
padding: 24px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
}
.options {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 18px;
}
.options label {
display: flex;
align-items: center;
gap: 6px;
background: #f0fdfa;
padding: 8px 12px;
border-radius: 10px;
font-size: 14px;
color: #134e4a;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
textarea {
width: 100%;
min-height: 240px;
border: 2px solid #99f6e4;
border-radius: 12px;
padding: 12px;
font-size: 14px;
line-height: 1.6;
resize: vertical;
}
textarea:focus {
outline: none;
border-color: #14b8a6;
}
.actions {
display: flex;
gap: 10px;
margin-top: 16px;
flex-wrap: wrap;
}
.btn {
border: none;
padding: 10px 18px;
border-radius: 10px;
cursor: pointer;
font-weight: 600;
color: white;
background: #14b8a6;
}
.btn.secondary {
background: #6b7280;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>文本清理器</h1>
<p>一键清理空格、空行和格式噪音</p>
</div>
<div class="card">
<div class="options">
<label><input type="checkbox" id="trim" checked> 去掉首尾空格</label>
<label><input type="checkbox" id="collapse-spaces" checked> 合并多余空格</label>
<label><input type="checkbox" id="remove-empty"> 删除空行</label>
<label><input type="checkbox" id="collapse-empty" checked> 连续空行合并</label>
</div>
<div class="grid">
<div>
<h3 style="margin-bottom: 8px; color: #111827;">原始文本</h3>
<textarea id="input" placeholder="粘贴需要清理的文本"></textarea>
</div>
<div>
<h3 style="margin-bottom: 8px; color: #111827;">清理结果</h3>
<textarea id="output" readonly placeholder="结果会显示在这里"></textarea>
</div>
</div>
<div class="actions">
<button class="btn" id="clean-btn">开始清理</button>
<button class="btn secondary" id="copy-btn">复制结果</button>
<button class="btn secondary" id="clear-btn">清空</button>
</div>
</div>
</div>
<script>
const input = document.getElementById('input');
const output = document.getElementById('output');
const trim = document.getElementById('trim');
const collapseSpaces = document.getElementById('collapse-spaces');
const removeEmpty = document.getElementById('remove-empty');
const collapseEmpty = document.getElementById('collapse-empty');
function cleanText() {
const lines = input.value.replace(/\r\n/g, '\n').split('\n');
const cleaned = [];
let previousEmpty = false;
lines.forEach((line) => {
let current = line;
if (collapseSpaces.checked) {
current = current.replace(/\s+/g, ' ');
}
if (trim.checked) {
current = current.trim();
}
if (current === '') {
if (removeEmpty.checked) {
return;
}
if (collapseEmpty.checked) {
if (previousEmpty) {
return;
}
previousEmpty = true;
}
} else {
previousEmpty = false;
}
cleaned.push(current);
});
output.value = cleaned.join('\n');
}
function copyResult() {
if (!output.value) {
return;
}
output.select();
output.setSelectionRange(0, output.value.length);
document.execCommand('copy');
}
function clearAll() {
input.value = '';
output.value = '';
}
document.getElementById('clean-btn').addEventListener('click', cleanText);
document.getElementById('copy-btn').addEventListener('click', copyResult);
document.getElementById('clear-btn').addEventListener('click', clearAll);
[input, trim, collapseSpaces, removeEmpty, collapseEmpty].forEach((el) => {
el.addEventListener('input', cleanText);
el.addEventListener('change', cleanText);
});
</script>
</body>
</html>
+145
View File
@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文本清理器 - JustHTMLs</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #14b8a6 0%, #0f766e 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #333;
margin-bottom: 20px;
font-size: 1.5rem;
}
.card p {
color: #666;
line-height: 1.8;
margin-bottom: 15px;
}
.features {
list-style: none;
margin-top: 20px;
}
.features li {
padding: 10px 0;
color: #555;
border-bottom: 1px solid #eee;
}
.features li:before {
content: "✓";
color: #14b8a6;
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 14px 30px;
background: linear-gradient(135deg, #14b8a6 0%, #0f766e 100%);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: all 0.3s;
margin: 10px 10px 10px 0;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
.btn-secondary {
background: #6c757d;
}
.preview {
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
}
.preview iframe {
width: 100%;
height: 450px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>文本清理器</h1>
<p>一键清理空格与空行</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>文本清理器帮助你快速整理粘贴的内容,减少格式噪音。</p>
<h2>功能特点</h2>
<ul class="features">
<li>合并多余空格</li>
<li>删除空行或合并空行</li>
<li>统一换行格式</li>
<li>适合清洗杂乱文本</li>
</ul>
<div style="margin-top: 30px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/text-cleaner" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
</body>
</html>
+222
View File
@@ -0,0 +1,222 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文本去重工具</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.4rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 18px;
padding: 24px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 18px;
}
.controls label {
display: flex;
align-items: center;
gap: 6px;
background: #f8f9fa;
padding: 8px 12px;
border-radius: 10px;
font-size: 14px;
color: #333;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
textarea {
width: 100%;
min-height: 240px;
border: 2px solid #e5e7eb;
border-radius: 12px;
padding: 12px;
font-size: 14px;
line-height: 1.6;
resize: vertical;
}
textarea:focus {
outline: none;
border-color: #10b981;
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 16px;
}
.btn {
border: none;
padding: 10px 18px;
border-radius: 10px;
cursor: pointer;
font-weight: 600;
color: white;
background: #10b981;
}
.btn.secondary {
background: #6b7280;
}
.stats {
margin-top: 16px;
display: flex;
gap: 12px;
flex-wrap: wrap;
color: #065f46;
font-weight: 600;
}
.stats span {
background: #ecfdf3;
padding: 6px 10px;
border-radius: 999px;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>文本去重工具</h1>
<p>按行去重、保持顺序,适合清理名单和清单</p>
</div>
<div class="card">
<div class="controls">
<label><input type="checkbox" id="trim" checked> 去掉首尾空格</label>
<label><input type="checkbox" id="remove-empty" checked> 移除空行</label>
<label><input type="checkbox" id="case-insensitive"> 忽略大小写</label>
</div>
<div class="grid">
<div>
<h3 style="margin-bottom: 8px; color: #111827;">原始文本</h3>
<textarea id="input" placeholder="每行一个条目"></textarea>
</div>
<div>
<h3 style="margin-bottom: 8px; color: #111827;">去重结果</h3>
<textarea id="output" readonly placeholder="结果会显示在这里"></textarea>
</div>
</div>
<div class="actions">
<button class="btn" id="dedupe-btn">开始去重</button>
<button class="btn secondary" id="copy-btn">复制结果</button>
<button class="btn secondary" id="clear-btn">清空</button>
</div>
<div class="stats" id="stats">
<span>总行数:0</span>
<span>唯一行数:0</span>
<span>已移除:0</span>
</div>
</div>
</div>
<script>
const input = document.getElementById('input');
const output = document.getElementById('output');
const trimToggle = document.getElementById('trim');
const removeEmptyToggle = document.getElementById('remove-empty');
const caseInsensitiveToggle = document.getElementById('case-insensitive');
const stats = document.getElementById('stats');
function dedupe() {
const text = input.value.replace(/\r\n/g, '\n');
const lines = text.split('\n');
const seen = new Set();
const result = [];
const prepared = lines
.map((line) => (trimToggle.checked ? line.trim() : line))
.filter((line) => !(removeEmptyToggle.checked && line === ''));
prepared.forEach((line) => {
const key = caseInsensitiveToggle.checked ? line.toLowerCase() : line;
if (seen.has(key)) {
return;
}
seen.add(key);
result.push(line);
});
output.value = result.join('\n');
const removed = prepared.length - result.length;
stats.innerHTML = `
<span>总行数:${prepared.length}</span>
<span>唯一行数:${result.length}</span>
<span>已移除:${removed}</span>
`;
}
function copyResult() {
if (!output.value) {
return;
}
output.select();
output.setSelectionRange(0, output.value.length);
document.execCommand('copy');
}
function clearAll() {
input.value = '';
output.value = '';
stats.innerHTML = '<span>总行数:0</span><span>唯一行数:0</span><span>已移除:0</span>';
}
document.getElementById('dedupe-btn').addEventListener('click', dedupe);
document.getElementById('copy-btn').addEventListener('click', copyResult);
document.getElementById('clear-btn').addEventListener('click', clearAll);
[input, trimToggle, removeEmptyToggle, caseInsensitiveToggle].forEach((el) => {
el.addEventListener('input', dedupe);
el.addEventListener('change', dedupe);
});
</script>
</body>
</html>
+145
View File
@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文本去重工具 - JustHTMLs</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #333;
margin-bottom: 20px;
font-size: 1.5rem;
}
.card p {
color: #666;
line-height: 1.8;
margin-bottom: 15px;
}
.features {
list-style: none;
margin-top: 20px;
}
.features li {
padding: 10px 0;
color: #555;
border-bottom: 1px solid #eee;
}
.features li:before {
content: "✓";
color: #10b981;
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 14px 30px;
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: all 0.3s;
margin: 10px 10px 10px 0;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
.btn-secondary {
background: #6c757d;
}
.preview {
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
}
.preview iframe {
width: 100%;
height: 450px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>文本去重工具</h1>
<p>按行去重,保留原始顺序</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>文本去重工具可以帮助你快速清理重复内容,适合名单、关键词、清单等场景。</p>
<h2>功能特点</h2>
<ul class="features">
<li>按行去重保持顺序</li>
<li>支持忽略大小写</li>
<li>可移除空行与空格</li>
<li>一键复制结果</li>
</ul>
<div style="margin-top: 30px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/text-deduplicator" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
</body>
</html>
+217
View File
@@ -0,0 +1,217 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文本排序工具</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.4rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 18px;
padding: 24px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
}
.options {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 18px;
}
.options label,
.options select {
background: #fff7ed;
border-radius: 10px;
padding: 8px 12px;
font-size: 14px;
color: #78350f;
border: none;
}
.options label {
display: flex;
align-items: center;
gap: 6px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
textarea {
width: 100%;
min-height: 240px;
border: 2px solid #fed7aa;
border-radius: 12px;
padding: 12px;
font-size: 14px;
line-height: 1.6;
resize: vertical;
}
textarea:focus {
outline: none;
border-color: #f59e0b;
}
.actions {
display: flex;
gap: 10px;
margin-top: 16px;
flex-wrap: wrap;
}
.btn {
border: none;
padding: 10px 18px;
border-radius: 10px;
cursor: pointer;
font-weight: 600;
color: white;
background: #f59e0b;
}
.btn.secondary {
background: #6b7280;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>文本排序工具</h1>
<p>按行排序,支持数字、去重、正反序</p>
</div>
<div class="card">
<div class="options">
<select id="order">
<option value="asc">升序</option>
<option value="desc">降序</option>
</select>
<label><input type="checkbox" id="numeric"> 数字排序</label>
<label><input type="checkbox" id="trim" checked> 去掉首尾空格</label>
<label><input type="checkbox" id="remove-empty" checked> 移除空行</label>
<label><input type="checkbox" id="unique"> 去重</label>
</div>
<div class="grid">
<div>
<h3 style="margin-bottom: 8px; color: #111827;">原始文本</h3>
<textarea id="input" placeholder="每行一个条目"></textarea>
</div>
<div>
<h3 style="margin-bottom: 8px; color: #111827;">排序结果</h3>
<textarea id="output" readonly placeholder="结果会显示在这里"></textarea>
</div>
</div>
<div class="actions">
<button class="btn" id="sort-btn">开始排序</button>
<button class="btn secondary" id="copy-btn">复制结果</button>
<button class="btn secondary" id="clear-btn">清空</button>
</div>
</div>
</div>
<script>
const input = document.getElementById('input');
const output = document.getElementById('output');
const order = document.getElementById('order');
const numeric = document.getElementById('numeric');
const trim = document.getElementById('trim');
const removeEmpty = document.getElementById('remove-empty');
const unique = document.getElementById('unique');
function sortLines() {
const raw = input.value.replace(/\r\n/g, '\n');
let lines = raw.split('\n').map((line) => (trim.checked ? line.trim() : line));
if (removeEmpty.checked) {
lines = lines.filter((line) => line !== '');
}
if (unique.checked) {
const seen = new Set();
lines = lines.filter((line) => {
if (seen.has(line)) {
return false;
}
seen.add(line);
return true;
});
}
const sorted = lines.slice().sort((a, b) => {
if (numeric.checked) {
const na = Number(a);
const nb = Number(b);
if (!Number.isNaN(na) && !Number.isNaN(nb)) {
return na - nb;
}
}
return a.localeCompare(b, 'zh-Hans-CN');
});
if (order.value === 'desc') {
sorted.reverse();
}
output.value = sorted.join('\n');
}
function copyResult() {
if (!output.value) {
return;
}
output.select();
output.setSelectionRange(0, output.value.length);
document.execCommand('copy');
}
function clearAll() {
input.value = '';
output.value = '';
}
document.getElementById('sort-btn').addEventListener('click', sortLines);
document.getElementById('copy-btn').addEventListener('click', copyResult);
document.getElementById('clear-btn').addEventListener('click', clearAll);
[input, order, numeric, trim, removeEmpty, unique].forEach((el) => {
el.addEventListener('input', sortLines);
el.addEventListener('change', sortLines);
});
</script>
</body>
</html>
+145
View File
@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文本排序工具 - JustHTMLs</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #333;
margin-bottom: 20px;
font-size: 1.5rem;
}
.card p {
color: #666;
line-height: 1.8;
margin-bottom: 15px;
}
.features {
list-style: none;
margin-top: 20px;
}
.features li {
padding: 10px 0;
color: #555;
border-bottom: 1px solid #eee;
}
.features li:before {
content: "✓";
color: #f59e0b;
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 14px 30px;
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: all 0.3s;
margin: 10px 10px 10px 0;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
.btn-secondary {
background: #6c757d;
}
.preview {
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
}
.preview iframe {
width: 100%;
height: 450px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>文本排序工具</h1>
<p>快速排序列表与清单</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>文本排序工具支持按行排序,可用于整理名单、选项、数据等。</p>
<h2>功能特点</h2>
<ul class="features">
<li>升序/降序排序</li>
<li>支持数字排序</li>
<li>可去重与移除空行</li>
<li>适用于各种清单</li>
</ul>
<div style="margin-top: 30px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/text-sorter" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
</body>
</html>
+1 -1
View File
@@ -268,7 +268,7 @@
const timestampMs = now;
document.getElementById('current-timestamp').textContent = timestampSeconds;
document.getElementById('current-datetime').textContent = formatDateTime(now);
document.getElementById('current-datetime').textContent = formatDateTime(timestampMs, true);
}
// 格式化日期时间
+229
View File
@@ -0,0 +1,229 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>URL 解析器</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #0ea5e9 0%, #0369a1 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.4rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 18px;
padding: 24px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
}
.input-row {
display: flex;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 18px;
}
.input-row input[type="text"] {
flex: 1 1 320px;
padding: 12px 14px;
border-radius: 10px;
border: 2px solid #bae6fd;
font-size: 14px;
}
.input-row label {
display: flex;
align-items: center;
gap: 6px;
background: #e0f2fe;
padding: 8px 12px;
border-radius: 10px;
font-size: 14px;
color: #0c4a6e;
}
.btn {
border: none;
padding: 10px 18px;
border-radius: 10px;
cursor: pointer;
font-weight: 600;
color: white;
background: #0ea5e9;
}
.result-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 12px;
margin-top: 18px;
}
.result-item {
background: #f0f9ff;
padding: 10px 12px;
border-radius: 10px;
}
.result-item strong {
display: block;
font-size: 12px;
color: #0c4a6e;
margin-bottom: 4px;
}
.query-table {
margin-top: 18px;
border-collapse: collapse;
width: 100%;
}
.query-table th,
.query-table td {
border-bottom: 1px solid #bae6fd;
padding: 8px 6px;
text-align: left;
font-size: 14px;
color: #0c4a6e;
}
.status {
margin-top: 10px;
font-size: 13px;
color: #0c4a6e;
}
.status.error {
color: #b91c1c;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>URL 解析器</h1>
<p>快速拆分协议、域名、路径和参数</p>
</div>
<div class="card">
<div class="input-row">
<input type="text" id="url-input" placeholder="https://example.com/path?foo=1&bar=2">
<label><input type="checkbox" id="auto-protocol" checked> 自动补全协议</label>
<button class="btn" id="parse-btn">解析</button>
</div>
<div class="result-grid" id="result"></div>
<table class="query-table" id="query-table" style="display:none;">
<thead>
<tr>
<th>参数名</th>
<th>参数值</th>
</tr>
</thead>
<tbody></tbody>
</table>
<div class="status" id="status">输入 URL 后点击解析。</div>
</div>
</div>
<script>
const urlInput = document.getElementById('url-input');
const autoProtocol = document.getElementById('auto-protocol');
const result = document.getElementById('result');
const status = document.getElementById('status');
const queryTable = document.getElementById('query-table');
const queryBody = queryTable.querySelector('tbody');
function formatItem(label, value) {
return `
<div class="result-item">
<strong>${label}</strong>
<div>${value || '-'}</div>
</div>
`;
}
function parseUrl() {
const raw = urlInput.value.trim();
if (!raw) {
status.textContent = '请输入需要解析的 URL。';
status.classList.add('error');
result.innerHTML = '';
queryTable.style.display = 'none';
return;
}
let formatted = raw;
if (autoProtocol.checked && !/^https?:\/\//i.test(formatted)) {
formatted = `https://${formatted}`;
}
try {
const url = new URL(formatted);
result.innerHTML = [
formatItem('协议', url.protocol.replace(':', '')),
formatItem('主机', url.host),
formatItem('域名', url.hostname),
formatItem('端口', url.port || '默认'),
formatItem('路径', url.pathname),
formatItem('查询参数', url.search || '-'),
formatItem('锚点', url.hash || '-'),
formatItem('完整地址', url.href)
].join('');
queryBody.innerHTML = '';
if ([...url.searchParams.keys()].length > 0) {
queryTable.style.display = 'table';
url.searchParams.forEach((value, key) => {
const row = document.createElement('tr');
row.innerHTML = `<td>${key}</td><td>${value}</td>`;
queryBody.appendChild(row);
});
} else {
queryTable.style.display = 'none';
}
status.textContent = '解析完成。';
status.classList.remove('error');
} catch (error) {
result.innerHTML = '';
queryTable.style.display = 'none';
status.textContent = 'URL 无法解析,请检查格式。';
status.classList.add('error');
}
}
document.getElementById('parse-btn').addEventListener('click', parseUrl);
urlInput.addEventListener('input', parseUrl);
autoProtocol.addEventListener('change', parseUrl);
</script>
</body>
</html>
+145
View File
@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" href="../../favicon.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>URL 解析器 - JustHTMLs</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #0ea5e9 0%, #0369a1 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.card h2 {
color: #333;
margin-bottom: 20px;
font-size: 1.5rem;
}
.card p {
color: #666;
line-height: 1.8;
margin-bottom: 15px;
}
.features {
list-style: none;
margin-top: 20px;
}
.features li {
padding: 10px 0;
color: #555;
border-bottom: 1px solid #eee;
}
.features li:before {
content: "✓";
color: #0ea5e9;
font-weight: bold;
margin-right: 10px;
}
.btn {
display: inline-block;
padding: 14px 30px;
background: linear-gradient(135deg, #0ea5e9 0%, #0369a1 100%);
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
transition: all 0.3s;
margin: 10px 10px 10px 0;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
.btn-secondary {
background: #6c757d;
}
.preview {
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
}
.preview iframe {
width: 100%;
height: 450px;
border: none;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>URL 解析器</h1>
<p>拆分 URL 的各个组成部分</p>
</div>
<div class="card">
<h2>关于这个工具</h2>
<p>URL 解析器可帮助你快速查看协议、域名、路径、参数和锚点。</p>
<h2>功能特点</h2>
<ul class="features">
<li>解析协议、域名、端口</li>
<li>展示路径与参数</li>
<li>自动补全协议</li>
<li>参数表格展示</li>
</ul>
<div style="margin-top: 30px;">
<a href="app.html" class="btn">开始使用</a>
<a href="https://github.com/justhtmls/html-tools/tree/main/tools/url-parser" target="_blank" class="btn btn-secondary">查看源码</a>
<a href="../../index.html" class="btn btn-secondary">返回首页</a>
</div>
</div>
<div class="card">
<h2>快速预览</h2>
<div class="preview">
<iframe src="app.html" loading="lazy"></iframe>
</div>
</div>
</div>
</body>
</html>