feat: 更新项目代码

This commit is contained in:
2026-04-01 22:03:57 +08:00
parent 1c81d4e6ea
commit 284b5a5260
53 changed files with 6240 additions and 22665 deletions

View File

@@ -0,0 +1,342 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PNG ↔ JPG 图片转换器</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap');
body { font-family: 'Noto Sans SC
', system-ui, sans-serif; }
.drop-zone {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.drop-zone.dragover {
border-color: #3b82f6;
background-color: #eff6ff;
transform: scale(1.02);
}
canvas { display: none; }
</style>
</head>
<body class="bg-gradient-to-br from-blue-50 to-indigo-50 min-h-screen py-12">
<div class="max-w-6xl mx-auto px-6">
<!-- 头部 -->
<div class="text-center mb-10">
<div class="inline-flex items-center gap-3 bg-white shadow-md rounded-3xl px-8 py-4">
<div class="w-12 h-12 bg-gradient-to-br from-blue-500 to-purple-600 rounded-2xl flex items-center justify-center text-white text-3xl">🖼️</div>
<h1 class="text-4xl font-bold text-gray-900 tracking-tight">PNG ↔ JPG 转换器</h1>
</div>
<p class="mt-4 text-gray-600 max-w-md mx-auto">纯浏览器本地转换 • 零上传 • 安全隐私 • 秒级处理</p>
</div>
<div class="bg-white rounded-3xl shadow-2xl overflow-hidden">
<div class="p-8">
<!-- 上传区域 -->
<div id="drop-zone"
class="drop-zone border-4 border-dashed border-gray-300 rounded-3xl p-16 text-center cursor-pointer hover:border-blue-500">
<input type="file" id="file-input" accept="image/png,image/jpeg" class="hidden">
<div class="mx-auto w-20 h-20 bg-blue-100 rounded-full flex items-center justify-center mb-6">
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M4 16v-4m0 0l4 4m-4-4l4-4m12 4v4m0 0l-4-4m4 4l-4 4" />
</svg>
</div>
<p class="text-2xl font-semibold text-gray-700">点击或拖拽图片到此区域</p>
<p class="text-sm text-gray-500 mt-3">支持 PNG / JPG 格式,最大 50MB</p>
</div>
<!-- 主内容区 -->
<div id="main-content" class="hidden mt-12 grid grid-cols-1 lg:grid-cols-12 gap-8">
<!-- 原图 -->
<div class="lg:col-span-5 bg-gray-50 rounded-3xl p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold text-lg text-gray-800 flex items-center gap-2">
<span class="w-3 h-3 bg-green-500 rounded-full"></span>
原图
</h3>
<span id="original-info" class="text-xs text-gray-500 font-mono"></span>
</div>
<div class="aspect-video bg-white rounded-2xl overflow-hidden border border-gray-200 flex items-center justify-center">
<img id="original-img" class="max-h-full max-w-full object-contain" alt="原图预览">
</div>
</div>
<!-- 控制面板 -->
<div class="lg:col-span-2 flex flex-col justify-center gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">转换目标格式</label>
<div class="flex gap-2">
<button id="btn-jpg"
class="flex-1 py-3 px-6 rounded-2xl font-medium transition-all active:scale-95 border-2 border-transparent bg-white shadow-sm hover:shadow-md data-[active=true]:border-blue-600 data-[active=true]:bg-blue-50">
转为 JPG
</button>
<button id="btn-png"
class="flex-1 py-3 px-6 rounded-2xl font-medium transition-all active:scale-95 border-2 border-transparent bg-white shadow-sm hover:shadow-md data-[active=true]:border-blue-600 data-[active=true]:bg-blue-50">
转为 PNG
</button>
</div>
</div>
<!-- JPG 选项 -->
<div id="jpg-options" class="hidden space-y-5">
<div>
<div class="flex justify-between text-sm mb-1.5">
<span class="font-medium text-gray-700">JPG 质量</span>
<span id="quality-value" class="font-mono text-blue-600">92%</span>
</div>
<input type="range" id="quality-slider" min="10" max="100" value="92"
class="w-full accent-blue-600">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5">透明背景填充颜色PNG→JPG</label>
<div class="flex gap-3 items-center">
<input type="color" id="bg-color" value="#ffffff"
class="w-12 h-10 rounded-xl border border-gray-300 cursor-pointer">
<div id="bg-color-hex" class="font-mono text-sm text-gray-500">#ffffff</div>
</div>
</div>
</div>
<button onclick="convertImage()"
class="mt-4 w-full bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 text-white font-semibold py-4 rounded-3xl shadow-lg transition-all active:scale-95 flex items-center justify-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7-7 7" />
</svg>
开始转换
</button>
</div>
<!-- 转换后 -->
<div class="lg:col-span-5 bg-gray-50 rounded-3xl p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold text-lg text-gray-800 flex items-center gap-2">
<span class="w-3 h-3 bg-purple-500 rounded-full"></span>
转换结果
</h3>
<span id="converted-info" class="text-xs text-gray-500 font-mono"></span>
</div>
<div class="aspect-video bg-white rounded-2xl overflow-hidden border border-gray-200 flex items-center justify-center relative">
<img id="converted-img" class="max-h-full max-w-full object-contain" alt="转换后预览">
<div id="converted-placeholder"
class="absolute inset-0 flex flex-col items-center justify-center text-gray-400">
<div class="w-16 h-16 border-4 border-gray-200 border-t-blue-500 rounded-full animate-spin mb-4"></div>
<p class="text-sm">转换后图片将显示在这里</p>
</div>
</div>
</div>
</div>
</div>
<!-- 下载栏 -->
<div id="download-bar" class="hidden border-t bg-gray-50 px-8 py-5 flex items-center justify-between">
<div class="text-sm text-gray-600">
文件已转换完成 • <span id="file-size" class="font-mono"></span>
</div>
<button onclick="downloadImage()"
class="bg-emerald-600 hover:bg-emerald-700 text-white px-8 py-3 rounded-2xl font-semibold flex items-center gap-3 transition-all active:scale-95">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v-4m0 0l4 4m-4-4l4-4m12 4v4m0 0l-4-4m4 4l-4 4" />
</svg>
下载转换后的图片
</button>
</div>
</div>
<div class="text-center mt-8 text-xs text-gray-400">
完全本地运行 • 无需联网 • 图片不会离开您的浏览器
</div>
</div>
<canvas id="canvas" class="hidden"></canvas>
<script>
// 全局变量
let originalFile = null;
let originalDataURL = null;
let convertedDataURL = null;
let outputFormat = 'image/jpeg';
let currentQuality = 0.92;
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d', { willReadFrequently: true });
// Tailwind 脚本已加载,无需额外初始化
// 拖拽与点击上传
const dropZone = document.getElementById('drop-zone');
const fileInput = document.getElementById('file-input');
dropZone.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) handleFile(e.target.files[0]);
});
// 拖拽事件
['dragover', 'dragenter'].forEach(evt => {
dropZone.addEventListener(evt, (e) => {
e.preventDefault();
dropZone.classList.add('dragover');
});
});
['dragleave', 'dragend'].forEach(evt => {
dropZone.addEventListener(evt, () => dropZone.classList.remove('dragover'));
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('dragover');
if (e.dataTransfer.files.length > 0) {
handleFile(e.dataTransfer.files[0]);
}
});
function handleFile(file) {
if (!file.type.startsWith('image/') || !['image/png', 'image/jpeg'].includes(file.type)) {
alert('❌ 请上传 PNG 或 JPG 格式的图片!');
return;
}
if (file.size > 50 * 1024 * 1024) {
alert('❌ 文件过大!请上传小于 50MB 的图片。');
return;
}
originalFile = file;
const reader = new FileReader();
reader.onload = function(e) {
originalDataURL = e.target.result;
document.getElementById('original-img').src = originalDataURL;
// 显示信息
const img = new Image();
img.onload = () => {
const sizeKB = (file.size / 1024).toFixed(1);
document.getElementById('original-info').textContent =
`${img.width}×${img.height}${sizeKB} KB`;
};
img.src = originalDataURL;
// 显示主界面
document.getElementById('main-content').classList.remove('hidden');
document.getElementById('download-bar').classList.add('hidden');
document.getElementById('converted-img').src = '';
document.getElementById('converted-placeholder').style.display = 'flex';
};
reader.readAsDataURL(file);
}
// 格式切换
const btnJPG = document.getElementById('btn-jpg');
const btnPNG = document.getElementById('btn-png');
const jpgOptions = document.getElementById('jpg-options');
btnJPG.addEventListener('click', () => {
outputFormat = 'image/jpeg';
btnJPG.dataset.active = 'true';
btnPNG.dataset.active = 'false';
jpgOptions.classList.remove('hidden');
});
btnPNG.addEventListener('click', () => {
outputFormat = 'image/png';
btnPNG.dataset.active = 'true';
btnJPG.dataset.active = 'false';
jpgOptions.classList.add('hidden');
});
// 默认选择 JPG
btnJPG.click();
// 质量滑块
const qualitySlider = document.getElementById('quality-slider');
const qualityValue = document.getElementById('quality-value');
qualitySlider.addEventListener('input', () => {
currentQuality = qualitySlider.value / 100;
qualityValue.textContent = qualitySlider.value + '%';
});
// 背景颜色
const bgColorInput = document.getElementById('bg-color');
const bgColorHex = document.getElementById('bg-color-hex');
bgColorInput.addEventListener('input', () => {
bgColorHex.textContent = bgColorInput.value.toUpperCase();
});
// 转换函数
window.convertImage = function() {
if (!originalDataURL) {
alert('请先上传图片!');
return;
}
const loadingPlaceholder = document.getElementById('converted-placeholder');
loadingPlaceholder.style.display = 'none';
const img = new Image();
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (outputFormat === 'image/jpeg') {
// PNG→JPG 时填充背景色
ctx.fillStyle = bgColorInput.value;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// 绘制图片
ctx.drawImage(img, 0, 0);
// 生成新 DataURL
convertedDataURL = canvas.toDataURL(outputFormat, currentQuality);
// 显示转换结果
document.getElementById('converted-img').src = convertedDataURL;
// 计算新大小
const newSize = Math.round((convertedDataURL.length * 3 / 4) / 1024);
document.getElementById('converted-info').textContent =
`${img.width}×${img.height}${newSize} KB`;
// 显示下载栏
document.getElementById('download-bar').classList.remove('hidden');
document.getElementById('file-size').textContent = `${newSize} KB`;
};
img.src = originalDataURL;
};
// 下载函数
window.downloadImage = function() {
if (!convertedDataURL) return;
const link = document.createElement('a');
link.href = convertedDataURL;
// 生成新文件名
let originalName = originalFile.name;
const ext = outputFormat === 'image/jpeg' ? '.jpg' : '.png';
const baseName = originalName.substring(0, originalName.lastIndexOf('.')) || originalName;
link.download = baseName + ext;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
// 键盘快捷键
document.addEventListener('keydown', (e) => {
if (e.key === '/' && document.getElementById('main-content').classList.contains('hidden')) {
fileInput.click();
}
});
</script>
</body>
</html>