mirror of
https://github.com/justhtmls/html-tools.git
synced 2026-09-01 15:32:50 +08:00
Add new tools and improve homepage sorting
This commit is contained in:
+63
-2
@@ -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
@@ -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": [
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
// 格式化日期时间
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user