<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>藤原图片批量转换工具</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css">
<style>
:root {
--primary-color: #5e35b1;
--secondary-color: #4527a0;
--accent-color: #7e57c2;
--light-color: #f5f5f5;
--dark-color: #212121;
--success-color: #66bb6a;
--warning-color: #ef5350;
--info-color: #29b6f6;
--border-radius: 0;
--box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.3);
--transition: all 0.2s ease;
--pixel-border: 2px solid #000;
--bg-image: url('https://bicool-user-assets--small-file.oss-rg-china-mainland.aliyuncs.com/b635285ba0d56ff95d83fde0bbd0f1c2.webp');
--bg-overlay: rgba(0, 0, 0, 0.6);
}
.light-theme {
--primary-color: #6d4aff;
--secondary-color: #5035cc;
--accent-color: #8c6fff;
--light-color: #f5f5f5;
--dark-color: #333333;
--bg-overlay: rgba(255, 255, 255, 0.85);
--pixel-border: 2px solid #333;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Press Start 2P', 'Courier New', Courier, monospace;
image-rendering: pixelated;
}
@font-face {
font-family: 'Press Start 2P';
src: url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
}
body {
background: var(--bg-image) no-repeat center center fixed;
background-size: cover;
color: var(--light-color);
line-height: 1.6;
min-height: 100vh;
padding: 20px;
position: relative;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--bg-overlay);
z-index: -1;
}
@keyframes pixelFadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.container {
max-width: 1200px;
margin: 0 auto;
animation: pixelFadeIn 0.4s steps(4) forwards;
}
header {
text-align: center;
margin-bottom: 30px;
padding: 20px;
background: rgba(33, 33, 33, 0.9);
border: var(--pixel-border);
box-shadow: var(--box-shadow);
position: relative;
}
header::after {
content: '';
position: absolute;
bottom: -4px;
right: -4px;
width: 100%;
height: 100%;
background: var(--primary-color);
z-index: -1;
}
header h1 {
font-size: 2rem;
color: var(--accent-color);
margin-bottom: 15px;
font-weight: normal;
text-shadow: 2px 2px 0 #000;
}
header p {
color: #bdbdbd;
font-size: 0.9rem;
max-width: 700px;
margin: 0 auto;
line-height: 1.6;
}
/* 滚动公告样式 */
.announcement-container {
background: rgba(33, 33, 33, 0.9);
border: var(--pixel-border);
padding: 8px 10px;
margin-top: 15px;
overflow: hidden;
position: relative;
box-shadow: var(--box-shadow);
}
.announcement-content {
display: inline-block;
white-space: nowrap;
animation: scrollAnnouncement 30s linear infinite;
}
.announcement-content span {
margin-right: 50px;
color: var(--accent-color);
font-size: 0.8rem;
text-shadow: 1px 1px 0 #000;
}
@keyframes scrollAnnouncement {
0% { transform: translateX(100%); }
100% { transform: translateX(-100%); }
}
/* 水印设置样式 */
.watermark-container {
margin-top: 15px;
}
.watermark-tabs {
display: flex;
margin-bottom: 10px;
}
.watermark-tab {
flex: 1;
padding: 8px;
text-align: center;
background: rgba(33, 33, 33, 0.9);
color: #e0e0e0;
cursor: pointer;
border: var(--pixel-border);
border-bottom: none;
font-size: 0.8rem;
}
.watermark-tab.active {
background: var(--accent-color);
color: white;
}
.watermark-content {
background: rgba(33, 33, 33, 0.9);
border: var(--pixel-border);
padding: 15px;
}
.watermark-content > div {
display: none;
}
.watermark-content > div.active {
display: block;
}
.watermark-text-controls, .watermark-image-controls {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.watermark-position {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
gap: 5px;
margin: 10px 0;
}
.position-btn {
width: 30px;
height: 30px;
background: rgba(66, 66, 66, 0.9);
border: var(--pixel-border);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.position-btn.active {
background: var(--accent-color);
}
.position-btn i {
font-size: 0.7rem;
color: #e0e0e0;
}
.watermark-preview {
margin-top: 15px;
border: var(--pixel-border);
background: #000;
height: 150px;
position: relative;
overflow: hidden;
}
.watermark-preview img {
width: 100%;
height: 100%;
object-fit: contain;
}
.watermark-preview .watermark-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
pointer-events: none;
}
.watermark-preview .watermark-text {
color: white;
font-size: 24px;
text-shadow: 1px 1px 2px #000;
opacity: 0.7;
}
.watermark-preview .watermark-image {
max-width: 50%;
max-height: 50%;
opacity: 0.7;
}
.watermark-file-input {
display: none;
}
.watermark-file-btn {
background: var(--accent-color);
color: white;
border: var(--pixel-border);
padding: 8px;
cursor: pointer;
font-size: 0.8rem;
text-align: center;
display: block;
margin-bottom: 10px;
transition: var(--transition);
}
.watermark-file-btn:hover {
background: var(--primary-color);
}
/* 格式转换预览样式 */
.format-preview-container {
margin-top: 15px;
}
.format-preview-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 10px;
margin-top: 10px;
}
.format-preview-item {
border: var(--pixel-border);
background: rgba(0, 0, 0, 0.5);
padding: 5px;
text-align: center;
}
.format-preview-item h4 {
font-size: 0.8rem;
color: var(--accent-color);
margin-bottom: 5px;
text-shadow: 1px 1px 0 #000;
}
.format-preview-item .preview-img {
width: 100%;
height: 100px;
object-fit: contain;
margin-bottom: 5px;
background: #000;
}
.format-preview-item .format-info {
font-size: 0.7rem;
color: #e0e0e0;
}
/* 历史记录样式 */
.history-container {
margin-top: 20px;
background: rgba(33, 33, 33, 0.9);
border: var(--pixel-border);
padding: 15px;
}
.history-title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.history-title h3 {
font-size: 1rem;
color: var(--accent-color);
text-shadow: 1px 1px 0 #000;
}
.history-list {
max-height: 300px;
overflow-y: auto;
border: var(--pixel-border);
background: rgba(0, 0, 0, 0.3);
}
.history-item {
padding: 10px;
border-bottom: 1px dashed #424242;
cursor: pointer;
transition: var(--transition);
}
.history-item:last-child {
border-bottom: none;
}
.history-item:hover {
background: rgba(66, 66, 66, 0.9);
}
.history-item .history-date {
font-size: 0.8rem;
color: var(--accent-color);
margin-bottom: 5px;
}
.history-item .history-details {
font-size: 0.7rem;
color: #e0e0e0;
}
.history-item .history-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 5px;
}
.history-item .history-btn {
background: var(--dark-color);
color: white;
border: var(--pixel-border);
padding: 3px 8px;
font-size: 0.7rem;
cursor: pointer;
transition: var(--transition);
}
.history-item .history-btn:hover {
background: var(--accent-color);
}
.history-empty {
padding: 20px;
text-align: center;
color: #9e9e9e;
font-size: 0.8rem;
}
.clear-history-btn {
background: var(--warning-color);
color: white;
border: var(--pixel-border);
padding: 5px 10px;
font-size: 0.7rem;
cursor: pointer;
transition: var(--transition);
}
.clear-history-btn:hover {
background: #d32f2f;
}
.theme-toggle {
position: absolute;
top: 10px;
right: 10px;
background: var(--accent-color);
border: var(--pixel-border);
width: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: var(--transition);
box-shadow: var(--box-shadow);
}
.theme-toggle:hover {
transform: translate(-2px, -2px);
box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.3);
}
.theme-toggle i {
color: white;
font-size: 1.2rem;
text-shadow: 1px 1px 0 #000;
}
.upload-area {
border: var(--pixel-border);
padding: 40px 20px;
text-align: center;
cursor: pointer;
transition: var(--transition);
background-color: rgba(33, 33, 33, 0.9);
margin-bottom: 30px;
box-shadow: var(--box-shadow);
position: relative;
overflow: hidden;
}
.upload-area:hover {
background-color: rgba(66, 66, 66, 0.9);
transform: translate(-2px, -2px);
box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.3);
}
.upload-area i {
font-size: 50px;
color: var(--accent-color);
margin-bottom: 15px;
filter: drop-shadow(2px 2px 0 #000);
}
.upload-area p {
font-size: 1.1rem;
color: #e0e0e0;
font-weight: normal;
margin-bottom: 5px;
}
.upload-area .subtext {
font-size: 0.8rem;
color: #9e9e9e;
}
/* 上传区域动画效果 */
.upload-area::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(
to bottom right,
rgba(126, 87, 194, 0.1),
rgba(126, 87, 194, 0.05),
rgba(126, 87, 194, 0.1)
);
transform: rotate(45deg);
animation: shine 3s infinite linear;
pointer-events: none;
}
@keyframes shine {
0% { transform: translateX(-100%) rotate(45deg); }
100% { transform: translateX(100%) rotate(45deg); }
}
#fileInput {
display: none;
}
/* URL 输入区域样式 */
.url-input-container {
display: flex;
margin-top: 15px;
padding: 0 20px;
}
.url-input-container input {
flex: 1;
padding: 8px;
border: var(--pixel-border);
background-color: rgba(66, 66, 66, 0.9);
color: #e0e0e0;
box-shadow: inset 0 0 0 2px #000;
}
.url-input-container button {
background: var(--accent-color);
color: white;
border: var(--pixel-border);
padding: 8px 15px;
cursor: pointer;
transition: var(--transition);
text-shadow: 1px 1px 0 #000;
box-shadow: var(--box-shadow);
margin-left: 10px;
}
.url-input-container button:hover {
background: var(--primary-color);
transform: translate(-2px, -2px);
box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.3);
}
.url-input-container button:active {
transform: translate(0, 0);
box-shadow: var(--box-shadow);
}
.editor-container {
background-color: rgba(33, 33, 33, 0.9);
border: var(--pixel-border);
padding: 20px;
box-shadow: var(--box-shadow);
margin-bottom: 20px;
transition: var(--transition);
position: relative;
}
.editor-container:hover {
transform: translate(-2px, -2px);
box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.3);
}
.editor-container::after {
content: '';
position: absolute;
bottom: -4px;
right: -4px;
width: 100%;
height: 100%;
background: var(--secondary-color);
z-index: -1;
}
.section-title {
font-size: 1.2rem;
color: var(--accent-color);
margin-bottom: 15px;
padding-bottom: 8px;
border-bottom: 2px solid var(--accent-color);
text-shadow: 2px 2px 0 #000;
display: flex;
align-items: center;
gap: 10px;
}
.control-group {
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 2px dotted #424242;
}
.control-group:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.control-group h3 {
margin-bottom: 15px;
color: var(--accent-color);
font-size: 1rem;
display: flex;
align-items: center;
gap: 8px;
text-shadow: 1px 1px 0 #000;
}
.control-group h3 i {
color: var(--accent-color);
filter: drop-shadow(1px 1px 0 #000);
}
select, input[type="number"], input[type="text"] {
width: 100%;
padding: 8px;
border: var(--pixel-border);
margin-bottom: 10px;
font-size: 0.9rem;
transition: var(--transition);
background-color: rgba(66, 66, 66, 0.9);
color: #e0e0e0;
box-shadow: inset 0 0 0 2px #000;
}
select:focus, input[type="number"]:focus, input[type="text"]:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: inset 0 0 0 2px #000, 0 0 0 2px var(--accent-color);
}
.size-controls {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.size-controls div {
flex: 1;
position: relative;
}
.size-controls input {
width: 100%;
display: block;
padding-right: 30px;
}
.size-controls span {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
color: #9e9e9e;
font-size: 0.8rem;
}
.ratio-controls {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 15px;
flex-wrap: wrap;
}
.ratio-controls label {
display: flex;
align-items: center;
gap: 5px;
white-space: nowrap;
cursor: pointer;
font-size: 0.8rem;
color: #e0e0e0;
}
.ratio-controls input[type="checkbox"] {
width: 16px;
height: 16px;
accent-color: var(--accent-color);
cursor: pointer;
}
.ratio-controls select {
flex: 1;
margin-bottom: 0;
min-width: 120px;
}
.edit-controls {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 10px;
}
.edit-controls div {
margin-bottom: 0;
}
.edit-controls label {
display: block;
margin-bottom: 5px;
font-size: 0.8rem;
color: #e0e0e0;
}
input[type="range"] {
width: 100%;
height: 6px;
-webkit-appearance: none;
background: #424242;
border: var(--pixel-border);
outline: none;
margin-bottom: 10px;
box-shadow: inset 0 0 0 2px #000;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
background: var(--accent-color);
border: var(--pixel-border);
cursor: pointer;
transition: var(--transition);
}
input[type="range"]::-webkit-slider-thumb:hover {
background: var(--primary-color);
transform: scale(1.1);
}
.quality-group {
margin-top: 10px;
background: rgba(66, 66, 66, 0.7);
padding: 10px;
border: var(--pixel-border);
box-shadow: inset 0 0 0 2px #000;
}
.quality-group label {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
font-size: 0.8rem;
}
.quality-group span {
color: var(--accent-color);
text-shadow: 1px 1px 0 #000;
}
.btn {
background: var(--primary-color);
color: white;
border: var(--pixel-border);
padding: 12px 20px;
cursor: pointer;
font-size: 1rem;
transition: var(--transition);
text-shadow: 1px 1px 0 #000;
box-shadow: var(--box-shadow);
position: relative;
display: inline-block;
}
.btn:hover {
background: var(--secondary-color);
transform: translate(-2px, -2px);
box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.3);
}
.btn:active {
transform: translate(0, 0);
box-shadow: var(--box-shadow);
}
.btn::after {
content: '';
position: absolute;
bottom: -4px;
right: -4px;
width: 100%;
height: 100%;
background: var(--secondary-color);
z-index: -1;
}
.btn-secondary {
background: var(--dark-color);
}
.btn-secondary::after {
background: #000;
}
.btn-success {
background: var(--success-color);
}
.btn-success::after {
background: #388e3c;
}
.btn-info {
background: var(--info-color);
}
.btn-info::after {
background: #0288d1;
}
.btn-full {
width: 100%;
text-align: center;
}
.loading {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 1000;
}
.spinner {
border: 4px solid rgba(94, 53, 177, 0.1);
border-top: 4px solid var(--accent-color);
border-radius: 0;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin-bottom: 15px;
box-shadow: var(--box-shadow);
image-rendering: pixelated;
}
.loading p {
font-size: 1rem;
color: var(--light-color);
text-shadow: 1px 1px 0 #000;
}
.loading .progress-container {
width: 300px;
height: 20px;
background: rgba(0, 0, 0, 0.5);
border: var(--pixel-border);
margin-top: 15px;
position: relative;
}
.loading .progress-bar {
height: 100%;
background: var(--accent-color);
width: 0%;
transition: width 0.3s;
}
.loading .progress-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 0.8rem;
text-shadow: 1px 1px 0 #000;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.tooltip {
position: relative;
display: inline-block;
margin-left: 3px;
}
.tooltip i {
color: var(--accent-color);
font-size: 0.8rem;
filter: drop-shadow(1px 1px 0 #000);
}
.tooltip .tooltiptext {
visibility: hidden;
width: 180px;
background-color: #000;
color: #fff;
text-align: center;
padding: 8px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.3s;
font-size: 0.7rem;
border: var(--pixel-border);
box-shadow: var(--box-shadow);
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
/* 像素动画效果 */
@keyframes pixelFlicker {
0% { opacity: 0.8; }
50% { opacity: 1; }
100% { opacity: 0.8; }
}
.pixel-flicker {
animation: pixelFlicker 1s steps(2) infinite;
}
/* 像素按钮按下效果 */
.btn-pixel:active {
box-shadow: 1px 1px 0 #000;
transform: translate(3px, 3px);
}
/* 切换按钮样式 */
.toggle-container {
display: flex;
margin-bottom: 15px;
border: var(--pixel-border);
overflow: hidden;
}
.toggle-btn {
flex: 1;
padding: 8px;
text-align: center;
background: rgba(33, 33, 33, 0.9);
color: #e0e0e0;
cursor: pointer;
transition: var(--transition);
font-size: 0.8rem;
border: none;
border-right: var(--pixel-border);
}
.toggle-btn:last-child {
border-right: none;
}
.toggle-btn.active {
background: var(--accent-color);
color: white;
}
.toggle-btn:hover:not(.active) {
background: rgba(66, 66, 66, 0.9);
}
/* 错误消息样式 */
.error-message {
background: var(--warning-color);
color: white;
padding: 10px;
margin-top: 10px;
border: var(--pixel-border);
font-size: 0.8rem;
text-align: center;
display: none;
}
/* 图片网格样式 */
.image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
margin-top: 20px;
}
.image-item {
border: var(--pixel-border);
background: rgba(0, 0, 0, 0.5);
position: relative;
aspect-ratio: 1/1;
overflow: hidden;
cursor: pointer;
transition: var(--transition);
}
.image-item:hover {
transform: translate(-2px, -2px);
box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.3);
}
.image-item.selected {
border: 2px solid var(--accent-color);
box-shadow: 0 0 0 2px var(--accent-color);
}
.image-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.image-item .image-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
opacity: 0;
transition: var(--transition);
}
.image-item:hover .image-overlay {
opacity: 1;
}
.image-item .image-name {
color: white;
font-size: 0.7rem;
text-align: center;
padding: 5px;
background: rgba(0, 0, 0, 0.7);
width: 100%;
position: absolute;
bottom: 0;
left: 0;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.image-item .select-indicator {
position: absolute;
top: 5px;
right: 5px;
width: 20px;
height: 20px;
background: var(--accent-color);
border: var(--pixel-border);
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 0.7rem;
}
.image-item .remove-btn {
position: absolute;
top: 5px;
left: 5px;
width: 20px;
height: 20px;
background: var(--warning-color);
border: var(--pixel-border);
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 0.7rem;
cursor: pointer;
z-index: 10;
}
.image-item .remove-btn:hover {
background: #d32f2f;
}
/* 拖拽排序相关样式 */
.image-item.dragging {
opacity: 0.7;
border: 2px dashed var(--accent-color);
transform: scale(0.95);
z-index: 100;
}
.image-item .drag-handle {
position: absolute;
bottom: 5px;
left: 5px;
width: 20px;
height: 20px;
background: var(--accent-color);
border: var(--pixel-border);
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 0.7rem;
cursor: move;
z-index: 10;
}
.image-item .drag-handle:hover {
background: var(--primary-color);
}
.drop-indicator {
position: absolute;
border: 2px dashed var(--accent-color);
background: rgba(126, 87, 194, 0.2);
z-index: 99;
pointer-events: none;
}
/* 批量操作工具栏 */
.batch-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
flex-wrap: wrap;
gap: 10px;
background: rgba(33, 33, 33, 0.7);
padding: 10px;
border: var(--pixel-border);
box-shadow: var(--box-shadow);
}
.batch-toolbar .selection-controls {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.batch-toolbar .info-text {
font-size: 0.8rem;
color: #bdbdbd;
background: rgba(0, 0, 0, 0.3);
padding: 5px 10px;
border-radius: 0;
border: 1px solid #424242;
}
.batch-toolbar .btn {
padding: 8px 15px;
font-size: 0.8rem;
}
/* 两列布局 */
.two-column {
display: flex;
gap: 20px;
}
.column {
flex: 1;
}
.column.sidebar {
flex: 0 0 300px;
}
@media (max-width: 768px) {
.two-column {
flex-direction: column;
}
.column.sidebar {
flex: 1;
}
.batch-toolbar {
flex-direction: column;
align-items: stretch;
}
.batch-toolbar .selection-controls {
flex-wrap: wrap;
justify-content: center;
}
.batch-toolbar .btn {
width: 100%;
}
}
/* 预览图片 */
.preview-container {
margin-top: 20px;
border: var(--pixel-border);
background: rgba(0, 0, 0, 0.5);
padding: 10px;
text-align: center;
position: relative;
}
.preview-container h3 {
font-size: 1rem;
color: var(--accent-color);
margin-bottom: 10px;
text-shadow: 1px 1px 0 #000;
}
.preview-container img {
max-width: 100%;
max-height: 300px;
display: block;
margin: 0 auto;
}
.preview-container .preview-placeholder {
height: 200px;
display: flex;
justify-content: center;
align-items: center;
color: #9e9e9e;
font-size: 0.9rem;
background: rgba(0, 0, 0, 0.3);
border: 1px dashed #424242;
}
.preview-zoom-btn {
position: absolute;
top: 10px;
right: 10px;
background: var(--accent-color);
border: var(--pixel-border);
width: 30px;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
color: white;
cursor: pointer;
z-index: 10;
transition: var(--transition);
}
.preview-zoom-btn:hover {
background: var(--primary-color);
transform: translate(-2px, -2px);
box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.3);
}
/* 分页控制 */
.pagination {
display: flex;
justify-content: center;
margin-top: 20px;
gap: 10px;
background: rgba(33, 33, 33, 0.7);
padding: 10px;
border: var(--pixel-border);
}
.pagination button {
padding: 5px 10px;
background: var(--dark-color);
color: white;
border: var(--pixel-border);
cursor: pointer;
transition: var(--transition);
}
.pagination button:hover:not(:disabled) {
background: var(--accent-color);
transform: translate(-2px, -2px);
box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.3);
}
.pagination button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pagination .page-info {
display: flex;
align-items: center;
color: #bdbdbd;
font-size: 0.8rem;
background: rgba(0, 0, 0, 0.3);
padding: 5px 10px;
border: 1px solid #424242;
}
/* 预加载指示器样式 */
.preload-indicator {
position: absolute;
bottom: 5px;
right: 5px;
width: 16px;
height: 16px;
border: 2px solid rgba(126, 87, 194, 0.3);
border-top: 2px solid var(--accent-color);
border-radius: 0;
animation: spin 1s linear infinite;
z-index: 5;
}
.image-item .preload-status {
position: absolute;
bottom: 25px;
right: 5px;
background: rgba(0, 0, 0, 0.7);
color: white;
font-size: 0.6rem;
padding: 2px 4px;
border: var(--pixel-border);
z-index: 5;
}
/* 预加载控制面板 */
.preload-panel {
margin-top: 15px;
padding: 10px;
background: rgba(33, 33, 33, 0.9);
border: var(--pixel-border);
box-shadow: var(--box-shadow);
}
.preload-panel h3 {
font-size: 0.9rem;
color: var(--accent-color);
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 8px;
text-shadow: 1px 1px 0 #000;
}
.preload-options {
display: flex;
flex-wrap: wrap;
gap: 10px;
background: rgba(0, 0, 0, 0.3);
padding: 8px;
border: 1px solid #424242;
}
.preload-options label {
display: flex;
align-items: center;
gap: 5px;
font-size: 0.7rem;
color: #e0e0e0;
cursor: pointer;
}
.preload-options input[type="checkbox"] {
width: 14px;
height: 14px;
accent-color: var(--accent-color);
}
.preload-stats {
margin-top: 10px;
font-size: 0.7rem;
color: #bdbdbd;
background: rgba(0, 0, 0, 0.3);
padding: 8px;
border: 1px solid #424242;
}
.memory-usage {
height: 6px;
background: #424242;
margin-top: 5px;
border: var(--pixel-border);
position: relative;
}
.memory-usage-bar {
height: 100%;
background: var(--accent-color);
width: 0%;
transition: width 0.3s;
}
/* 图片放大预览 */
.image-zoom-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
display: none;
}
.image-zoom-content {
position: relative;
max-width: 90%;
max-height: 90%;
border: var(--pixel-border);
background: #000;
padding: 10px;
box-shadow: var(--box-shadow);
}
.image-zoom-content img {
max-width: 100%;
max-height: calc(90vh - 40px);
display: block;
}
.image-zoom-close {
position: absolute;
top: -15px;
right: -15px;
width: 30px;
height: 30px;
background: var(--warning-color);
border: var(--pixel-border);
display: flex;
justify-content: center;
align-items: center;
color: white;
cursor: pointer;
z-index: 10;
transition: var(--transition);
}
.image-zoom-close:hover {
background: #d32f2f;
transform: scale(1.1);
}
.image-zoom-info {
margin-top: 10px;
color: white;
font-size: 0.8rem;
text-align: center;
background: rgba(0, 0, 0, 0.5);
padding: 5px;
border: 1px solid #424242;
}
/* 响应式调整 */
@media (max-width: 480px) {
.image-grid {
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
}
header h1 {
font-size: 1.5rem;
}
.btn {
font-size: 0.8rem;
padding: 10px 15px;
}
.section-title {
font-size: 1rem;
}
}
/* 标签页样式 */
.tabs {
display: flex;
border-bottom: var(--pixel-border);
margin-bottom: 15px;
flex-wrap: wrap;
}
.tab {
padding: 8px 15px;
background: rgba(33, 33, 33, 0.9);
color: #e0e0e0;
cursor: pointer;
transition: var(--transition);
font-size: 0.8rem;
border-top: var(--pixel-border);
border-left: var(--pixel-border);
border-right: var(--pixel-border);
border-bottom: none;
margin-right: 5px;
position: relative;
top: 2px;
}
.tab.active {
background: var(--accent-color);
color: white;
border-bottom: 2px solid var(--accent-color);
}
.tab:hover:not(.active) {
background: rgba(66, 66, 66, 0.9);
}
.tab-content {
display: none;
background: rgba(33, 33, 33, 0.7);
padding: 15px;
border: var(--pixel-border);
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.3);
}
.tab-content.active {
display: block;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* 裁剪工具样式 */
.crop-container {
position: relative;
max-width: 100%;
margin: 0 auto;
overflow: hidden;
border: var(--pixel-border);
background: #000;
}
.crop-controls {
margin-top: 15px;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.crop-controls .btn {
flex: 1;
min-width: 120px;
}
/* 滤镜预览样式 */
.filter-preview-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 10px;
margin-top: 15px;
}
.filter-preview-item {
border: var(--pixel-border);
background: rgba(0, 0, 0, 0.5);
padding: 5px;
text-align: center;
cursor: pointer;
transition: var(--transition);
}
.filter-preview-item:hover {
transform: translate(-2px, -2px);
box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.3);
}
.filter-preview-item.active {
border: 2px solid var(--accent-color);
box-shadow: 0 0 0 2px var(--accent-color);
}
.filter-preview-item img {
width: 100%;
aspect-ratio: 1/1;
object-fit: cover;
margin-bottom: 5px;
}
.filter-preview-item .filter-name {
font-size: 0.6rem;
color: #e0e0e0;
}
/* 重命名面板样式 */
.rename-panel {
margin-top: 15px;
padding: 15px;
background: rgba(33, 33, 33, 0.9);
border: var(--pixel-border);
}
.rename-pattern {
margin-bottom: 15px;
}
.rename-pattern label {
display: block;
margin-bottom: 5px;
font-size: 0.8rem;
color: #e0e0e0;
}
.rename-pattern-help {
font-size: 0.7rem;
color: #9e9e9e;
margin-top: 5px;
background: rgba(0, 0, 0, 0.3);
padding: 8px;
border: 1px solid #424242;
}
.rename-pattern-help code {
background: rgba(0, 0, 0, 0.3);
padding: 2px 4px;
border-radius: 3px;
font-family: monospace;
color: var(--accent-color);
}
.rename-preview {
margin-top: 15px;
font-size: 0.8rem;
color: #e0e0e0;
background: rgba(0, 0, 0, 0.3);
padding: 8px;
border: 1px solid #424242;
}
.rename-preview-item {
display: flex;
justify-content: space-between;
padding: 5px 0;
border-bottom: 1px dotted #424242;
}
.rename-preview-item:last-child {
border-bottom: none;
}
.rename-preview-item .old-name {
color: #9e9e9e;
}
.rename-preview-item .new-name {
color: var(--accent-color);
}
/* 压缩选项样式 */
.compression-presets {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 15px;
}
.compression-preset {
flex: 1;
min-width: 100px;
padding: 8px;
background: rgba(33, 33, 33, 0.9);
border: var(--pixel-border);
text-align: center;
cursor: pointer;
transition: var(--transition);
}
.compression-preset:hover {
background: rgba(66, 66, 66, 0.9);
transform: translate(-2px, -2px);
box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.3);
}
.compression-preset.active {
background: var(--accent-color);
color: white;
}
.compression-preset .preset-name {
font-size: 0.8rem;
margin-bottom: 5px;
}
.compression-preset .preset-info {
font-size: 0.6rem;
color: #9e9e9e;
}
.compression-preset.active .preset-info {
color: rgba(255, 255, 255, 0.7);
}
/* 保存/加载设置样式 */
.saved-settings {
margin-top: 15px;
}
.saved-settings-list {
max-height: 200px;
overflow-y: auto;
margin-top: 10px;
border: var(--pixel-border);
background: rgba(0, 0, 0, 0.3);
}
.saved-setting-item {
padding: 8px;
border-bottom: 1px dotted #424242;
display: flex;
justify-content: space-between;
align-items: center;
}
.saved-setting-item:last-child {
border-bottom: none;
}
.saved-setting-name {
font-size: 0.8rem;
color: #e0e0e0;
}
.saved-setting-date {
font-size: 0.7rem;
color: #9e9e9e;
}
.saved-setting-actions {
display: flex;
gap: 5px;
}
.saved-setting-actions button {
width: 24px;
height: 24px;
display: flex;
justify-content: center;
align-items: center;
background: var(--dark-color);
border: var(--pixel-border);
color: white;
cursor: pointer;
transition: var(--transition);
}
.saved-setting-actions button:hover {
background: var(--accent-color);
transform: translate(-1px, -1px);
box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.3);
}
.saved-setting-actions .delete-btn:hover {
background: var(--warning-color);
}
/* 通知样式 */
.notification {
position: fixed;
bottom: 20px;
right: 20px;
padding: 15px;
background: rgba(33, 33, 33, 0.9);
border: var(--pixel-border);
box-shadow: var(--box-shadow);
color: white;
font-size: 0.9rem;
z-index: 1000;
max-width: 300px;
transform: translateY(100px);
opacity: 0;
transition: transform 0.3s, opacity 0.3s;
}
.notification.show {
transform: translateY(0);
opacity: 1;
}
.notification.success {
border-left: 4px solid var(--success-color);
}
.notification.error {
border-left: 4px solid var(--warning-color);
}
.notification.info {
border-left: 4px solid var(--info-color);
}
.watermark-advanced-options {
margin-top: 10px;
padding: 10px;
background: rgba(33, 33, 33, 0.7);
border: var(--pixel-border);
}
.watermark-advanced-options h4 {
font-size: 0.8rem;
color: var(--accent-color);
margin-bottom: 8px;
text-shadow: 1px 1px 0 #000;
}
.watermark-option-group {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
margin-bottom: 10px;
background: rgba(0, 0, 0, 0.3);
padding: 8px;
border: 1px solid #424242;
}
.watermark-option {
display: flex;
align-items: center;
gap: 5px;
}
.watermark-option label {
font-size: 0.7rem;
color: #e0e0e0;
}
.watermark-option input[type="checkbox"] {
width: 14px;
height: 14px;
accent-color: var(--accent-color);
}
.watermark-pattern-options {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 5px;
margin-top: 8px;
}
.watermark-pattern {
border: var(--pixel-border);
background: rgba(0, 0, 0, 0.3);
height: 40px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: var(--transition);
}
.watermark-pattern:hover {
background: rgba(66, 66, 66, 0.5);
transform: translate(-2px, -2px);
box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.3);
}
.watermark-pattern.active {
border: 2px solid var(--accent-color);
background: rgba(126, 87, 194, 0.3);
}
.watermark-pattern i {
font-size: 0.8rem;
color: var(--accent-color);
}
/* 上传状态指示器 */
.upload-status {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
}
.upload-status.active {
opacity: 1;
pointer-events: auto;
}
.upload-status .spinner {
margin-bottom: 10px;
}
.upload-status p {
color: white;
font-size: 0.9rem;
text-align: center;
margin-bottom: 10px;
}
.upload-status .progress-bar-small {
width: 80%;
height: 10px;
background: rgba(0, 0, 0, 0.5);
border: var(--pixel-border);
position: relative;
}
.upload-status .progress-fill {
height: 100%;
background: var(--accent-color);
width: 0%;
transition: width 0.3s;
}
/* 格式标签页样式 */
.format-tab-content {
background: rgba(33, 33, 33, 0.7);
padding: 15px;
border: var(--pixel-border);
margin-top: 15px;
}
.format-comparison {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
margin-top: 15px;
}
.format-card {
border: var(--pixel-border);
background: rgba(0, 0, 0, 0.5);
padding: 10px;
text-align: center;
transition: var(--transition);
}
.format-card:hover {
transform: translate(-2px, -2px);
box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.3);
}
.format-card h4 {
font-size: 1rem;
color: var(--accent-color);
margin-bottom: 10px;
text-shadow: 1px 1px 0 #000;
}
.format-card img {
width: 100%;
height: 150px;
object-fit: contain;
margin-bottom: 10px;
background: #000;
border: 1px solid #424242;
}
.format-info-list {
text-align: left;
font-size: 0.7rem;
color: #e0e0e0;
margin-top: 10px;
}
.format-info-list li {
padding: 5px 0;
border-bottom: 1px dotted #424242;
display: flex;
justify-content: space-between;
}
.format-info-list li:last-child {
border-bottom: none;
}
.format-info-list .info-value {
color: var(--accent-color);
}
/* 水印标签页样式 */
.watermark-tab-content {
background: rgba(33, 33, 33, 0.7);
padding: 15px;
border: var(--pixel-border);
margin-top: 15px;
}
.watermark-templates {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 10px;
margin-top: 15px;
}
.watermark-template {
border: var(--pixel-border);
background: rgba(0, 0, 0, 0.5);
padding: 10px;
text-align: center;
cursor: pointer;
transition: var(--transition);
}
.watermark-template:hover {
transform: translate(-2px, -2px);
box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.3);
}
.watermark-template.active {
border: 2px solid var(--accent-color);
background: rgba(126, 87, 194, 0.3);
}
.watermark-template img {
width: 100%;
height: 100px;
object-fit: contain;
margin-bottom: 5px;
background: #000;
border: 1px solid #424242;
}
.watermark-template .template-name {
font-size: 0.7rem;
color: #e0e0e0;
}
/* 上传进度指示器 */
.upload-progress {
position: fixed;
bottom: 20px;
left: 20px;
background: rgba(33, 33, 33, 0.9);
border: var(--pixel-border);
padding: 15px;
width: 250px;
box-shadow: var(--box-shadow);
z-index: 1000;
display: none;
}
.upload-progress h4 {
font-size: 0.9rem;
color: var(--accent-color);
margin-bottom: 10px;
text-shadow: 1px 1px 0 #000;
}
.upload-progress .progress-bar {
height: 10px;
background: rgba(0, 0, 0, 0.5);
border: var(--pixel-border);
position: relative;
margin-bottom: 5px;
}
.upload-progress .progress-fill {
height: 100%;
background: var(--accent-color);
width: 0%;
transition: width 0.3s;
}
.upload-progress .progress-text {
font-size: 0.7rem;
color: #e0e0e0;
text-align: right;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1><i class="fas fa-exchange-alt"></i> 藤原图片批量转换工具</h1>
<p>上传多张图片,批量转换格式,调整大小 - 复古像素风格工具</p>
<div class="announcement-container">
<div class="announcement-content" id="announcementContent">
<span>🔥 新功能上线:图片水印功能已上线! </span>
<span>🌟 新功能上线:批量处理历史记录功能已上线! </span>
<span>✨ 新功能上线:图片格式转换预览功能已上线! </span>
<span>📢 欢迎使用藤原图片批量转换工具,有任何问题请联系我们!</span>
</div>
</div>
<div class="theme-toggle" id="themeToggle">
<i class="fas fa-sun"></i>
</div>
</header>
<div id="uploadContainer">
<!-- 切换按钮 -->
<div class="toggle-container">
<button class="toggle-btn active" id="fileToggle">本地文件上传</button>
<button class="toggle-btn" id="urlToggle">网络图片链接</button>
</div>
<!-- 文件上传区域 -->
<div class="upload-area" id="uploadArea">
<i class="fas fa-cloud-upload-alt pixel-flicker"></i>
<p>拖放图片或点击上传</p>
<p class="subtext">支持多选图片 - JPG, PNG, GIF, WEBP</p>
<input type="file" id="fileInput" accept="image/*" multiple>
<!-- 上传状态指示器 -->
<div class="upload-status" id="uploadStatus">
<div class="spinner"></div>
<p id="uploadStatusText">正在上传图片...</p>
<div class="progress-bar-small">
<div class="progress-fill" id="uploadProgressFill"></div>
</div>
</div>
</div>
<!-- URL输入区域 -->
<div class="url-input-container" id="urlInputContainer" style="display: none;">
<input type="text" id="imageUrlInput" placeholder="输入图片URL (例如: https://example.com/image.jpg)">
<button id="fetchUrlBtn" class="btn-pixel">获取图片</button>
</div>
<!-- 错误消息 -->
<div class="error-message" id="errorMessage"></div>
</div>
<div class="editor-container" id="batchEditorContainer" style="display: none;">
<div class="section-title">
<i class="fas fa-images"></i> 批量图片处理
<div class="info-text" id="selectedCountInfo"></div>
</div>
<div class="batch-toolbar">
<div class="selection-controls">
<button class="btn btn-secondary btn-pixel" id="selectAllBtn">
<i class="fas fa-check-square"></i> 全选
</button>
<button class="btn btn-secondary btn-pixel" id="deselectAllBtn">
<i class="fas fa-square"></i> 取消全选
</button>
<button class="btn btn-secondary btn-pixel" id="removeSelectedBtn">
<i class="fas fa-trash-alt"></i> 删除选中
</button>
<button class="btn btn-secondary btn-pixel" id="toggleDragBtn">
<i class="fas fa-sort"></i> 启用排序
</button>
</div>
<div>
<button class="btn btn-pixel" id="addMoreBtn">
<i class="fas fa-plus"></i> 添加更多图片
</button>
</div>
</div>
<div class="two-column">
<div class="column">
<!-- 图片网格 -->
<div class="image-grid" id="imageGrid"></div>
<!-- 分页控制 -->
<div class="pagination" id="pagination">
<button id="prevPageBtn" disabled><i class="fas fa-chevron-left"></i></button>
<div class="page-info">第 <span id="currentPage">1</span> 页,共 <span id="totalPages">1</span> 页</div>
<button id="nextPageBtn" disabled><i class="fas fa-chevron-right"></i></button>
</div>
<!-- 预加载控制面板 -->
<div class="preload-panel">
<h3><i class="fas fa-bolt"></i> 预加载设置</h3>
<div class="preload-options">
<label>
<input type="checkbox" id="enablePreload" checked>
启用预加载
</label>
<label>
<input type="checkbox" id="preloadNextPage" checked>
预加载下一页
</label>
<label>
<input type="checkbox" id="preloadHighRes">
高分辨率预加载
</label>
</div>
<div class="preload-stats">
<div>已预加载: <span id="preloadedCount">0</span>/<span id="totalImagesToPreload">0</span> 张图片</div>
<div>内存使用: <span id="memoryUsage">0 MB</span></div>
<div class="memory-usage">
<div class="memory-usage-bar" id="memoryUsageBar"></div>
</div>
</div>
</div>
</div>
<div class="column sidebar">
<!-- 标签页导航 -->
<div class="tabs">
<div class="tab active" data-tab="basic">基本设置</div>
<div class="tab" data-tab="advanced">高级编辑</div>
<div class="tab" data-tab="rename">重命名</div>
<div class="tab" data-tab="settings">保存/加载</div>
<div class="tab" data-tab="watermark">水印</div>
<div class="tab" data-tab="format">格式预览</div>
<div class="tab" data-tab="history">历史记录</div>
</div>
<!-- 基本设置标签页 -->
<div class="tab-content active" id="basic-tab">
<!-- 转换设置 -->
<div class="control-group">
<h3><i class="fas fa-file-export"></i> 格式转换</h3>
<select id="formatSelect">
<option value="jpeg">JPEG (高兼容性)</option>
<option value="png">PNG (透明背景)</option>
<option value="webp">WEBP (高质量压缩)</option>
<option value="gif">GIF (动画支持)</option>
</select>
<div id="jpegQualityGroup" class="quality-group">
<label>
输出质量:
<span id="qualityValue">80</span>%
<div class="tooltip">
<i class="fas fa-info-circle"></i>
<span class="tooltiptext">质量越高文件越大,推荐80%以获得最佳平衡</span>
</div>
</label>
<input type="range" id="qualityRange" min="1" max="100" value="80">
</div>
</div>
<div class="control-group">
<h3><i class="fas fa-expand"></i> 调整大小</h3>
<div class="size-controls">
<div>
<label for="widthInput">宽度</label>
<input type="number" id="widthInput" min="1" placeholder="原始宽度">
<span>px</span>
</div>
<div>
<label for="heightInput">高度</label>
<input type="number" id="heightInput" min="1" placeholder="原始高度">
<span>px</span>
</div>
</div>
<div class="ratio-controls">
<label>
<input type="checkbox" id="lockRatio" checked>
锁定宽高比
</label>
<select id="presetRatio">
<option value="free">自由比例</option>
<option value="1:1">1:1 (正方形)</option>
<option value="4:3">4:3 (标准)</option>
<option value="16:9">16:9 (宽屏)</option>
<option value="9:16">9:16 (竖屏)</option>
</select>
</div>
</div>
<!-- 压缩选项 -->
<div class="control-group">
<h3><i class="fas fa-compress-alt"></i> 压缩选项</h3>
<div class="compression-presets">
<div class="compression-preset active" data-preset="balanced">
<div class="preset-name">平衡</div>
<div class="preset-info">质量80%</div>
</div>
<div class="compression-preset" data-preset="high-quality">
<div class="preset-name">高质量</div>
<div class="preset-info">质量95%</div>
</div>
<div class="compression-preset" data-preset="small-size">
<div class="preset-name">小体积</div>
<div class="preset-info">质量60%</div>
</div>
<div class="compression-preset" data-preset="tiny">
<div class="preset-name">极小</div>
<div class="preset-info">质量30%</div>
</div>
</div>
<div class="quality-group">
<label>
压缩强度:
<span id="compressionValue">中等</span>
</label>
<input type="range" id="compressionRange" min="1" max="100" value="50">
</div>
<div class="size-estimate">
<div>预计输出大小: <span id="sizeEstimate">未知</span></div>
<div>预计节省空间: <span id="sizeSaving">未知</span></div>
</div>
</div>
</div>
<!-- 高级编辑标签页 -->
<div class="tab-content" id="advanced-tab">
<!-- 裁剪工具 -->
<div class="control-group">
<h3><i class="fas fa-crop-alt"></i> 图片裁剪</h3>
<div class="crop-container" id="cropContainer">
<img id="cropImage" style="display: none; max-width: 100%;">
<div id="cropPlaceholder" class="preview-placeholder">
<p>选择一张图片进行裁剪</p>
</div>
</div>
<div class="crop-controls">
<button class="btn btn-secondary btn-pixel" id="cropResetBtn">
<i class="fas fa-undo"></i> 重置
</button>
<button class="btn btn-pixel" id="cropApplyBtn">
<i class="fas fa-check"></i> 应用裁剪
</button>
<button class="btn btn-pixel" id="cropApplyAllBtn">
<i class="fas fa-check-double"></i> 应用到全部
</button>
</div>
</div>
<!-- 滤镜效果 -->
<div class="control-group">
<h3><i class="fas fa-magic"></i> 滤镜效果</h3>
<div class="filter-preview-grid" id="filterPreviewGrid">
<!-- 滤镜预览将通过JS动态生成 -->
</div>
<div class="edit-controls">
<div>
<label for="brightnessRange">亮度</label>
<input type="range" id="brightnessRange" min="-100" max="100" value="0">
</div>
<div>
<label for="contrastRange">对比度</label>
<input type="range" id="contrastRange" min="-100" max="100" value="0">
</div>
<div>
<label for="saturationRange">饱和度</label>
<input type="range" id="saturationRange" min="-100" max="100" value="0">
</div>
<div>
<label for="blurRange">模糊</label>
<input type="range" id="blurRange" min="0" max="10" value="0">
</div>
</div>
</div>
</div>
<!-- 重命名标签页 -->
<div class="tab-content" id="rename-tab">
<div class="control-group">
<h3><i class="fas fa-font"></i> 批量重命名</h3>
<div class="rename-panel">
<div class="rename-pattern">
<label for="renamePattern">命名模式:</label>
<input type="text" id="renamePattern" value="{name}_{index}">
<div class="rename-pattern-help">
可用变量:
<code>{name}</code> 原文件名
<code>{index}</code> 序号
<code>{date}</code> 日期
<code>{random}</code> 随机字符
</div>
</div>
<div class="rename-options">
<label>
<input type="checkbox" id="renameAddPrefix" checked>
添加前缀
</label>
<input type="text" id="renamePrefix" value="img_" placeholder="前缀">
</div>
<div class="rename-options">
<label>
<input type="checkbox" id="renameAddSuffix">
添加后缀
</label>
<input type="text" id="renameSuffix" value="" placeholder="后缀">
</div>
<div class="rename-options">
<label>
<input type="checkbox" id="renameStartIndex">
起始序号
</label>
<input type="number" id="renameStartIndexValue" value="1" min="0">
</div>
<div class="rename-preview">
<h4>重命名预览:</h4>
<div id="renamePreviewList">
<!-- 重命名预览将通过JS动态生成 -->
</div>
</div>
</div>
</div>
</div>
<!-- 保存/加载设置标签页 -->
<div class="tab-content" id="settings-tab">
<div class="control-group">
<h3><i class="fas fa-save"></i> 保存/加载设置</h3>
<div class="save-settings">
<input type="text" id="settingName" placeholder="设置名称">
<button class="btn btn-pixel" id="saveSettingsBtn">
<i class="fas fa-save"></i> 保存当前设置
</button>
</div>
<div class="saved-settings">
<h4>已保存的设置:</h4>
<div class="saved-settings-list" id="savedSettingsList">
<!-- 已保存的设置将通过JS动态生成 -->
</div>
</div>
</div>
<div class="control-group">
<h3><i class="fas fa-cog"></i> 应用设置</h3>
<div class="settings-options">
<label>
<input type="checkbox" id="autoSaveSettings" checked>
自动保存设置
</label>
<label>
<input type="checkbox" id="loadLastSettings" checked>
启动时加载上次设置
</label>
</div>
<button class="btn btn-secondary btn-pixel" id="resetSettingsBtn">
<i class="fas fa-undo"></i> 重置所有设置
</button>
</div>
</div>
<!-- 水印标签页 -->
<div class="tab-content" id="watermark-tab">
<div class="control-group">
<h3><i class="fas fa-stamp"></i> 水印设置</h3>
<div class="watermark-tabs">
<div class="watermark-tab active" data-tab="text">文字水印</div>
<div class="watermark-tab" data-tab="image">图片水印</div>
</div>
<div class="watermark-content">
<!-- 文字水印设置 -->
<div id="textWatermark" class="active">
<div class="watermark-text-controls">
<div>
<label for="watermarkText">水印文字</label>
<input type="text" id="watermarkText" value="藤原图片转换器" placeholder="输入水印文字">
</div>
<div>
<label for="watermarkFontSize">字体大小</label>
<input type="range" id="watermarkFontSize" min="10" max="100" value="24">
</div>
<div>
<label for="watermarkColor">文字颜色</label>
<input type="color" id="watermarkColor" value="#ffffff">
</div>
<div>
<label for="watermarkOpacity">透明度</label>
<input type="range" id="watermarkOpacity" min="10" max="100" value="70">
</div>
<div>
<label for="watermarkRotation">旋转角度</label>
<input type="range" id="watermarkRotation" min="-45" max="45" value="0">
</div>
</div>
<div class="watermark-advanced-options">
<h4>高级选项</h4>
<div class="watermark-option-group">
<div class="watermark-option">
<input type="checkbox" id="watermarkShadow">
<label for="watermarkShadow">添加阴影</label>
</div>
<div class="watermark-option">
<input type="checkbox" id="watermarkBorder">
<label for="watermarkBorder">添加边框</label>
</div>
<div class="watermark-option">
<input type="checkbox" id="watermarkPattern">
<label for="watermarkPattern">平铺水印</label>
</div>
</div>
<div class="watermark-pattern-options">
<div class="watermark-pattern active" data-pattern="none">
<i class="fas fa-ban"></i>
</div>
<div class="watermark-pattern" data-pattern="sparse">
<i class="fas fa-th-large"></i>
</div>
<div class="watermark-pattern" data-pattern="medium">
<i class="fas fa-th"></i>
</div>
<div class="watermark-pattern" data-pattern="dense">
<i class="fas fa-border-all"></i>
</div>
</div>
</div>
</div>
<!-- 图片水印设置 -->
<div id="imageWatermark" style="display: none;">
<div class="watermark-image-controls">
<div>
<label for="watermarkImageInput">水印图片</label>
<div class="watermark-image-upload">
<label for="watermarkImageInput" class="watermark-file-btn">选择图片</label>
<div class="watermark-image-preview" id="watermarkImagePreview"></div>
</div>
<input type="file" id="watermarkImageInput" class="watermark-file-input" accept="image/*">
<div id="watermarkImageName" style="font-size: 0.7rem; margin-top: 5px; color: #9e9e9e;">未选择图片</div>
</div>
<div>
<label for="watermarkImageSize">图片大小</label>
<input type="range" id="watermarkImageSize" min="5" max="50" value="20">
</div>
<div>
<label for="watermarkImageOpacity">透明度</label>
<input type="range" id="watermarkImageOpacity" min="10" max="100" value="70">
</div>
</div>
<div class="watermark-advanced-options">
<h4>高级选项</h4>
<div class="watermark-option-group">
<div class="watermark-option">
<input type="checkbox" id="watermarkImageShadow">
<label for="watermarkImageShadow">添加阴影</label>
</div>
<div class="watermark-option">
<input type="checkbox" id="watermarkImageBorder">
<label for="watermarkImageBorder">添加边框</label>
</div>
<div class="watermark-option">
<input type="checkbox" id="watermarkImagePattern">
<label for="watermarkImagePattern">平铺水印</label>
</div>
</div>
<div class="watermark-pattern-options">
<div class="watermark-pattern active" data-pattern="none">
<i class="fas fa-ban"></i>
</div>
<div class="watermark-pattern" data-pattern="sparse">
<i class="fas fa-th-large"></i>
</div>
<div class="watermark-pattern" data-pattern="medium">
<i class="fas fa-th"></i>
</div>
<div class="watermark-pattern" data-pattern="dense">
<i class="fas fa-border-all"></i>
</div>
</div>
</div>
</div>
</div>
<div class="watermark-position">
<h4 style="grid-column: span 3; margin-bottom: 5px; font-size: 0.8rem; color: var(--accent-color);">水印位置</h4>
<div class="position-btn" data-position="top-left">
<i class="fas fa-arrow-up-left"></i>
</div>
<div class="position-btn" data-position="top-center">
<i class="fas fa-arrow-up"></i>
</div>
<div class="position-btn" data-position="top-right">
<i class="fas fa-arrow-up-right"></i>
</div>
<div class="position-btn" data-position="middle-left">
<i class="fas fa-arrow-left"></i>
</div>
<div class="position-btn active" data-position="middle-center">
<i class="fas fa-dot-circle"></i>
</div>
<div class="position-btn" data-position="middle-right">
<i class="fas fa-arrow-right"></i>
</div>
<div class="position-btn" data-position="bottom-left">
<i class="fas fa-arrow-down-left"></i>
</div>
<div class="position-btn" data-position="bottom-center">
<i class="fas fa-arrow-down"></i>
</div>
<div class="position-btn" data-position="bottom-right">
<i class="fas fa-arrow-down-right"></i>
</div>
</div>
<div class="watermark-preview">
<h4 style="margin-bottom: 5px; font-size: 0.8rem; color: var(--accent-color);">水印预览</h4>
<img src="data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%3E%3Crect%20fill%3D%22%23424242%22%20width%3D%22100%22%20height%3D%22100%22%2F%3E%3Ctext%20fill%3D%22%23fff%22%20font-family%3D%22sans-serif%22%20font-size%3D%2212%22%20text-anchor%3D%22middle%22%20x%3D%2250%22%20y%3D%2250%22%3E%E9%A2%84%E8%A7%88%3C%2Ftext%3E%3C%2Fsvg%3E" alt="水印预览">
<div class="watermark-overlay"></div>
</div>
</div>
</div>
<!-- 格式预览标签页 -->
<div class="tab-content" id="format-tab">
<div class="control-group">
<h3><i class="fas fa-eye"></i> 格式预览</h3>
<div class="format-preview-grid" id="formatPreviewGrid">
<!-- 格式预览将通过JavaScript动态生成 -->
</div>
</div>
</div>
<!-- 历史记录标签页 -->
<div class="tab-content" id="history-tab">
<div class="control-group">
<h3><i class="fas fa-history"></i> 处理历史记录</h3>
<div class="history-list" id="historyList">
<div class="history-empty">暂无处理历史记录</div>
</div>
<button class="clear-history-btn" id="clearHistoryBtn">清空历史</button>
</div>
</div>
<!-- 预览区域 -->
<div class="preview-container">
<h3>预览效果</h3>
<div id="previewPlaceholder" class="preview-placeholder">
<p>选择一张图片查看预览效果</p>
</div>
<canvas id="previewCanvas" style="display: none;"></canvas>
<div class="preview-zoom-btn" id="previewZoomBtn" style="display: none;">
<i class="fas fa-search-plus"></i>
</div>
</div>
<button id="convertBtn" class="btn btn-success btn-pixel btn-full">
<i class="fas fa-cogs"></i> 批量转换并下载
</button>
</div>
</div>
</div>
<!-- 图片放大预览模态框 -->
<div class="image-zoom-modal" id="imageZoomModal">
<div class="image-zoom-content">
<div class="image-zoom-close" id="imageZoomClose">
<i class="fas fa-times"></i>
</div>
<img id="zoomImage" src="/placeholder.svg" alt="放大预览">
<div class="image-zoom-info" id="zoomImageInfo"></div>
</div>
</div>
<!-- 通知 -->
<div class="notification" id="notification"></div>
<!-- 上传进度指示器 -->
<div class="upload-progress" id="uploadProgress">
<h4>上传进度</h4>
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<div class="progress-text" id="progressText">0%</div>
</div>
<div class="loading" id="loading" style="display: none;">
<div class="spinner"></div>
<p id="loadingText">正在处理您的图片...</p>
<div class="progress-container">
<div class="progress-bar" id="progressBar"></div>
<div class="progress-text" id="progressText">0%</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// DOM元素
const uploadContainer = document.getElementById('uploadContainer');
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const batchEditorContainer = document.getElementById('batchEditorContainer');
const previewCanvas = document.getElementById('previewCanvas');
const previewPlaceholder = document.getElementById('previewPlaceholder');
const formatSelect = document.getElementById('formatSelect');
const qualityRange = document.getElementById('qualityRange');
const qualityValue = document.getElementById('qualityValue');
const widthInput = document.getElementById('widthInput');
const heightInput = document.getElementById('heightInput');
const lockRatio = document.getElementById('lockRatio');
const presetRatio = document.getElementById('presetRatio');
const brightnessRange = document.getElementById('brightnessRange');
const contrastRange = document.getElementById('contrastRange');
const saturationRange = document.getElementById('saturationRange');
const blurRange = document.getElementById('blurRange');
const loading = document.getElementById('loading');
const loadingText = document.getElementById('loadingText');
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');
const jpegQualityGroup = document.getElementById('jpegQualityGroup');
const imageGrid = document.getElementById('imageGrid');
const selectedCountInfo = document.getElementById('selectedCountInfo');
const previewZoomBtn = document.getElementById('previewZoomBtn');
const imageZoomModal = document.getElementById('imageZoomModal');
const zoomImage = document.getElementById('zoomImage');
const zoomImageInfo = document.getElementById('zoomImageInfo');
const imageZoomClose = document.getElementById('imageZoomClose');
const themeToggle = document.getElementById('themeToggle');
const tabs = document.querySelectorAll('.tab');
const tabContents = document.querySelectorAll('.tab-content');
const cropImage = document.getElementById('cropImage');
const cropPlaceholder = document.getElementById('cropPlaceholder');
const cropResetBtn = document.getElementById('cropResetBtn');
const cropApplyBtn = document.getElementById('cropApplyBtn');
const cropApplyAllBtn = document.getElementById('cropApplyAllBtn');
const filterPreviewGrid = document.getElementById('filterPreviewGrid');
const compressionRange = document.getElementById('compressionRange');
const compressionValue = document.getElementById('compressionValue');
const compressionPresets = document.querySelectorAll('.compression-preset');
const sizeEstimate = document.getElementById('sizeEstimate');
const sizeSaving = document.getElementById('sizeSaving');
const renamePattern = document.getElementById('renamePattern');
const renameAddPrefix = document.getElementById('renameAddPrefix');
const renamePrefix = document.getElementById('renamePrefix');
const renameAddSuffix = document.getElementById('renameAddSuffix');
const renameSuffix = document.getElementById('renameSuffix');
const renameStartIndex = document.getElementById('renameStartIndex');
const renameStartIndexValue = document.getElementById('renameStartIndexValue');
const renamePreviewList = document.getElementById('renamePreviewList');
const settingName = document.getElementById('settingName');
const saveSettingsBtn = document.getElementById('saveSettingsBtn');
const savedSettingsList = document.getElementById('savedSettingsList');
const autoSaveSettings = document.getElementById('autoSaveSettings');
const loadLastSettings = document.getElementById('loadLastSettings');
const resetSettingsBtn = document.getElementById('resetSettingsBtn');
const notification = document.getElementById('notification');
const uploadStatus = document.getElementById('uploadStatus');
const uploadStatusText = document.getElementById('uploadStatusText');
const uploadProgressFill = document.getElementById('uploadProgressFill');
const uploadProgress = document.getElementById('uploadProgress');
const progressFill = document.getElementById('progressFill');
// 批量处理相关元素
const selectAllBtn = document.getElementById('selectAllBtn');
const deselectAllBtn = document.getElementById('deselectAllBtn');
const removeSelectedBtn = document.getElementById('removeSelectedBtn');
const addMoreBtn = document.getElementById('addMoreBtn');
const convertBtn = document.getElementById('convertBtn');
// 分页相关元素
const pagination = document.getElementById('pagination');
const prevPageBtn = document.getElementById('prevPageBtn');
const nextPageBtn = document.getElementById('nextPageBtn');
const currentPageEl = document.getElementById('currentPage');
const totalPagesEl = document.getElementById('totalPages');
// URL相关元素
const fileToggle = document.getElementById('fileToggle');
const urlToggle = document.getElementById('urlToggle');
const urlInputContainer = document.getElementById('urlInputContainer');
const imageUrlInput = document.getElementById('imageUrlInput');
const fetchUrlBtn = document.getElementById('fetchUrlBtn');
const errorMessage = document.getElementById('errorMessage');
// 全局变量
let images = []; // 存储所有图片对象
let selectedImages = new Set(); // 存储选中的图片索引
let currentPage = 1;
let imagesPerPage = 12;
let currentPreviewIndex = -1;
let ctx = previewCanvas.getContext('2d');
let dragSrcEl = null;
let dragStartIndex = -1;
let isDraggingEnabled = false;
let cropper = null;
let currentFilter = 'none';
let currentCompressionPreset = 'balanced';
// 滤镜定义
const filters = {
'none': { name: '原图', filter: '' },
'grayscale': { name: '灰度', filter: 'grayscale(100%)' },
'sepia': { name: '复古', filter: 'sepia(100%)' },
'invert': { name: '反色', filter: 'invert(100%)' },
'blur': { name: '模糊', filter: 'blur(2px)' },
'brightness': { name: '明亮', filter: 'brightness(150%)' },
'contrast': { name: '高对比', filter: 'contrast(150%)' },
'hue-rotate': { name: '色相旋转', filter: 'hue-rotate(90deg)' },
'saturate': { name: '高饱和', filter: 'saturate(200%)' },
'vintage': { name: '复古胶片', filter: 'sepia(50%) contrast(120%) brightness(90%)' },
'cold': { name: '冷色调', filter: 'saturate(80%) hue-rotate(180deg)' },
'warm': { name: '暖色调', filter: 'saturate(120%) hue-rotate(30deg) brightness(110%)' },
// 新增滤镜
'noir': { name: '黑白电影', filter: 'grayscale(100%) contrast(120%) brightness(90%)' },
'dramatic': { name: '戏剧效果', filter: 'contrast(150%) brightness(90%) saturate(120%)' },
'faded': { name: '褪色', filter: 'brightness(110%) saturate(80%) contrast(90%)' },
'cyberpunk': { name: '赛博朋克', filter: 'hue-rotate(270deg) saturate(200%) contrast(140%) brightness(120%)' },
'pastel': { name: '柔和', filter: 'brightness(110%) saturate(90%) contrast(90%)' },
'retro': { name: '复古80年代', filter: 'sepia(30%) saturate(140%) hue-rotate(330deg) brightness(110%)' },
'neon': { name: '霓虹', filter: 'brightness(120%) contrast(120%) saturate(180%) hue-rotate(310deg)' },
'vaporwave': { name: '蒸汽波', filter: 'saturate(180%) brightness(110%) contrast(110%) hue-rotate(180deg)' },
'polaroid': { name: '宝丽来', filter: 'sepia(20%) brightness(105%) contrast(90%) saturate(110%)' },
'monochrome': { name: '单色', filter: 'grayscale(100%) brightness(120%) contrast(120%)' }
};
// 压缩预设
const compressionPresetValues = {
'balanced': { quality: 80, compressionLevel: 50 },
'high-quality': { quality: 95, compressionLevel: 20 },
'small-size': { quality: 60, compressionLevel: 70 },
'tiny': { quality: 30, compressionLevel: 90 }
};
// 预加载相关变量
let preloadQueue = [];
let isPreloading = false;
let preloadedImages = new Set();
let preloadingImages = new Set();
let estimatedMemoryUsage = 0;
let maxMemoryUsage = 500 * 1024 * 1024; // 500MB 默认限制
// 水印相关变量
let watermarkType = 'text'; // 'text' 或 'image'
let watermarkPosition = 'middle-center';
let watermarkImage = null;
// 历史记录相关变量
let processingHistory = [];
// 初始化
initEventListeners();
initFilterPreviews();
loadSettings();
function initEventListeners() {
// 标签页切换
tabs.forEach(tab => {
tab.addEventListener('click', () => {
tabs.forEach(t => t.classList.remove('active'));
tabContents.forEach(c => c.classList.remove('active'));
tab.classList.add('active');
const tabId = tab.getAttribute('data-tab');
document.getElementById(`${tabId}-tab`).classList.add('active');
});
});
// 主题切换
themeToggle.addEventListener('click', toggleTheme);
// 切换按钮事件
fileToggle.addEventListener('click', () => {
fileToggle.classList.add('active');
urlToggle.classList.remove('active');
uploadArea.style.display = 'block';
urlInputContainer.style.display = 'none';
errorMessage.style.display = 'none';
});
urlToggle.addEventListener('click', () => {
urlToggle.classList.add('active');
fileToggle.classList.remove('active');
uploadArea.style.display = 'none';
urlInputContainer.style.display = 'flex';
errorMessage.style.display = 'none';
});
// URL获取按钮事件
fetchUrlBtn.addEventListener('click', fetchImageFromUrl);
// 拖拽排序按钮事件
document.getElementById('toggleDragBtn').addEventListener('click', toggleDragSort);
// 上传区域点击事件
uploadArea.addEventListener('click', () => {
console.log('Upload area clicked');
fileInput.click();
});
// 拖放事件
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.style.backgroundColor = 'rgba(66, 66, 66, 0.9)';
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.style.backgroundColor = 'rgba(33, 33, 33, 0.9)';
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.style.backgroundColor = 'rgba(33, 33, 33, 0.9)';
if (e.dataTransfer.files.length) {
console.log('Files dropped:', e.dataTransfer.files.length);
handleFilesUpload(e.dataTransfer.files);
}
});
// 文件输入变化事件
fileInput.addEventListener('change', () => {
if (fileInput.files.length) {
console.log('Files selected:', fileInput.files.length);
handleFilesUpload(fileInput.files);
}
});
// 格式选择变化事件
formatSelect.addEventListener('change', () => {
if (formatSelect.value === 'jpeg' || formatSelect.value === 'webp') {
jpegQualityGroup.style.display = 'block';
} else {
jpegQualityGroup.style.display = 'none';
}
updatePreview();
updateSizeEstimate();
});
// 质量范围变化事件
qualityRange.addEventListener('input', () => {
qualityValue.textContent = qualityRange.value;
updatePreview();
updateSizeEstimate();
});
// 宽度/高度输入事件
widthInput.addEventListener('input', () => {
if (lockRatio.checked && widthInput.value && currentPreviewIndex >= 0) {
const img = images[currentPreviewIndex];
const ratio = img.width / img.height;
heightInput.value = Math.round(widthInput.value / ratio);
}
updatePreview();
updateSizeEstimate();
});
heightInput.addEventListener('input', () => {
if (lockRatio.checked && heightInput.value && currentPreviewIndex >= 0) {
const img = images[currentPreviewIndex];
const ratio = img.width / img.height;
widthInput.value = Math.round(heightInput.value * ratio);
}
updatePreview();
updateSizeEstimate();
});
// 锁定比例复选框事件
lockRatio.addEventListener('change', () => {
if (lockRatio.checked && widthInput.value && heightInput.value && currentPreviewIndex >= 0) {
const img = images[currentPreviewIndex];
img.customRatio = widthInput.value / heightInput.value;
}
});
// 预设比例选择事件
presetRatio.addEventListener('change', function() {
if (currentPreviewIndex < 0) return;
const ratio = this.value;
if (ratio === 'free') {
lockRatio.checked = false;
return;
}
lockRatio.checked = true;
const [w, h] = ratio.split(':').map(Number);
const newRatio = w / h;
const img = images[currentPreviewIndex];
if (widthInput.value) {
heightInput.value = Math.round(widthInput.value / newRatio);
} else if (heightInput.value) {
widthInput.value = Math.round(heightInput.value * newRatio);
} else {
// 默认按宽度调整
widthInput.value = img.width;
heightInput.value = Math.round(img.width / newRatio);
}
updatePreview();
updateSizeEstimate();
});
// 编辑控制事件
brightnessRange.addEventListener('input', updatePreview);
contrastRange.addEventListener('input', updatePreview);
saturationRange.addEventListener('input', updatePreview);
blurRange.addEventListener('input', updatePreview);
// 批量处理按钮事件
selectAllBtn.addEventListener('click', selectAllImages);
deselectAllBtn.addEventListener('click', deselectAllImages);
removeSelectedBtn.addEventListener('click', removeSelectedImages);
addMoreBtn.addEventListener('click', () => {
uploadContainer.style.display = 'block';
fileInput.value = '';
});
// 转换按钮事件
convertBtn.addEventListener('click', batchConvertImages);
// 分页按钮事件
prevPageBtn.addEventListener('click', () => {
if (currentPage > 1) {
currentPage--;
renderImageGrid();
// 开始预加载新页面的图片
startPreloading();
}
});
nextPageBtn.addEventListener('click', () => {
if (currentPage < Math.ceil(images.length / imagesPerPage)) {
currentPage++;
renderImageGrid();
// 开始预加载新页面的图片
startPreloading();
}
});
// 预加载选项事件
document.getElementById('enablePreload').addEventListener('change', updatePreloadSettings);
document.getElementById('preloadNextPage').addEventListener('change', updatePreloadSettings);
document.getElementById('preloadHighRes').addEventListener('change', updatePreloadSettings);
// 预览放大按钮事件
previewZoomBtn.addEventListener('click', () => {
if (currentPreviewIndex >= 0) {
openZoomModal(currentPreviewIndex);
}
});
// 关闭放大预览
imageZoomClose.addEventListener('click', () => {
imageZoomModal.style.display = 'none';
});
// 点击模态框背景关闭
imageZoomModal.addEventListener('click', (e) => {
if (e.target === imageZoomModal) {
imageZoomModal.style.display = 'none';
}
});
// ESC键关闭模态框
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && imageZoomModal.style.display === 'flex') {
imageZoomModal.style.display = 'none';
}
});
// 裁剪相关事件
cropResetBtn.addEventListener('click', resetCrop);
cropApplyBtn.addEventListener('click', applyCrop);
cropApplyAllBtn.addEventListener('click', applyCropToAll);
// 压缩预设事件
compressionPresets.forEach(preset => {
preset.addEventListener('click', () => {
compressionPresets.forEach(p => p.classList.remove('active'));
preset.classList.add('active');
const presetName = preset.getAttribute('data-preset');
currentCompressionPreset = presetName;
const presetValues = compressionPresetValues[presetName];
qualityRange.value = presetValues.quality;
qualityValue.textContent = presetValues.quality;
compressionRange.value = presetValues.compressionLevel;
updateCompressionValue();
updatePreview();
updateSizeEstimate();
});
});
// 压缩强度事件
compressionRange.addEventListener('input', () => {
updateCompressionValue();
updateSizeEstimate();
});
// 重命名相关事件
renamePattern.addEventListener('input', updateRenamePreview);
renameAddPrefix.addEventListener('change', updateRenamePreview);
renamePrefix.addEventListener('input', updateRenamePreview);
renameAddSuffix.addEventListener('change', updateRenamePreview);
renameSuffix.addEventListener('input', updateRenamePreview);
renameStartIndex.addEventListener('change', updateRenamePreview);
renameStartIndexValue.addEventListener('input', updateRenamePreview);
// 设置保存/加载事件
saveSettingsBtn.addEventListener('click', saveCurrentSettings);
resetSettingsBtn.addEventListener('click', resetAllSettings);
autoSaveSettings.addEventListener('change', () => {
localStorage.setItem('autoSaveSettings', autoSaveSettings.checked);
if (autoSaveSettings.checked) {
saveSettingsToLocalStorage('autoSave', getCurrentSettings());
}
});
loadLastSettings.addEventListener('change', () => {
localStorage.setItem('loadLastSettings', loadLastSettings.checked);
});
// 水印相关事件
document.querySelectorAll('.watermark-tab').forEach(tab => {
tab.addEventListener('click', function() {
document.querySelectorAll('.watermark-tab').forEach(t => t.classList.remove('active'));
this.classList.add('active');
const tabType = this.dataset.tab;
watermarkType = tabType;
document.querySelectorAll('.watermark-content > div').forEach(content => content.classList.remove('active'));
document.getElementById(tabType + 'Watermark').classList.add('active');
updateWatermarkPreview();
});
});
document.querySelectorAll('.position-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.position-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
watermarkPosition = this.dataset.position;
updateWatermarkPreview();
});
});
document.getElementById('watermarkText').addEventListener('input', updateWatermarkPreview);
document.getElementById('watermarkFontSize').addEventListener('input', updateWatermarkPreview);
document.getElementById('watermarkOpacity').addEventListener('input', updateWatermarkPreview);
document.getElementById('watermarkColor').addEventListener('input', updateWatermarkPreview);
document.getElementById('watermarkRotation').addEventListener('input', updateWatermarkPreview);
document.getElementById('watermarkImageSize').addEventListener('input', updateWatermarkPreview);
document.getElementById('watermarkImageOpacity').addEventListener('input', updateWatermarkPreview);
document.getElementById('watermarkImageInput').addEventListener('change', function(e) {
if (this.files && this.files[0]) {
const reader = new FileReader();
reader.onload = function(e) {
const img = new Image();
img.onload = function() {
watermarkImage = img;
document.getElementById('watermarkImageName').textContent =
document.getElementById('watermarkImageInput').files[0].name;
updateWatermarkPreview();
};
img.src = e.target.result;
};
reader.readAsDataURL(this.files[0]);
}
});
// 历史记录相关事件
document.getElementById('clearHistoryBtn').addEventListener('click', clearHistory);
// 格式选择变化时更新格式预览
formatSelect.addEventListener('change', updateFormatPreviews);
// 添加水印高级选项事件
document.getElementById('watermarkShadow').addEventListener('change', updateWatermarkPreview);
document.getElementById('watermarkBorder').addEventListener('change', updateWatermarkPreview);
document.getElementById('watermarkPattern').addEventListener('change', updateWatermarkPreview);
document.querySelectorAll('.watermark-pattern').forEach(pattern => {
pattern.addEventListener('click', function() {
document.querySelectorAll('.watermark-pattern').forEach(p => p.classList.remove('active'));
this.classList.add('active');
updateWatermarkPreview();
});
});
}
function handleFilesUpload(files) {
if (files.length === 0) return;
console.log('Starting file upload process...');
// 显示上传状态
uploadStatus.classList.add('active');
uploadStatusText.textContent = '正在准备上传...';
uploadProgressFill.style.width = '0%';
// 显示上传进度指示器
uploadProgress.style.display = 'block';
progressFill.style.width = '0%';
const validFiles = Array.from(files).filter(file => file.type.match('image.*'));
let loadedCount = 0;
if (validFiles.length === 0) {
uploadStatus.classList.remove('active');
uploadProgress.style.display = 'none';
showNotification('请选择有效的图片文件', 'error');
return;
}
console.log(`开始处理 ${validFiles.length} 个文件`);
validFiles.forEach((file, index) => {
const reader = new FileReader();
reader.onload = function(e) {
console.log(`文件 ${file.name} 读取成功`);
const img = new Image();
img.onload = function() {
console.log(`图片 ${file.name} 加载成功: ${img.width}x${img.height}`);
// 创建图片对象
const imageObj = {
element: img,
name: file.name,
type: file.type,
width: img.width,
height: img.height,
src: e.target.result,
size: file.size,
isUrl: false
};
// 添加到图片数组
images.push(imageObj);
selectedImages.add(images.length - 1);
// 更新进度
loadedCount++;
const progress = Math.round((loadedCount / validFiles.length) * 100);
uploadStatusText.textContent = `已加载 ${loadedCount}/${validFiles.length} 张图片`;
uploadProgressFill.style.width = `${progress}%`;
progressFill.style.width = `${progress}%`;
progressText.textContent = `${progress}%`;
// 所有图片加载完成
if (loadedCount === validFiles.length) {
uploadStatus.classList.remove('active');
uploadProgress.style.display = 'none';
showBatchEditor();
// 重置预加载状态
preloadedImages.clear();
preloadingImages.clear();
estimatedMemoryUsage = 0;
updatePreloadStats();
showNotification(`成功加载 ${loadedCount} 张图片`, 'success');
}
};
img.onerror = function(err) {
console.error(`图片 ${file.name} 加载失败:`, err);
loadedCount++;
const progress = Math.round((loadedCount / validFiles.length) * 100);
uploadStatusText.textContent = `加载失败: ${file.name}`;
uploadProgressFill.style.width = `${progress}%`;
progressFill.style.width = `${progress}%`;
progressText.textContent = `${progress}%`;
if (loadedCount === validFiles.length) {
uploadStatus.classList.remove('active');
uploadProgress.style.display = 'none';
if (images.length > 0) {
showBatchEditor();
} else {
showNotification('所有图片加载失败,请检查文件格式', 'error');
}
}
};
img.src = e.target.result;
};
reader.onerror = function(err) {
console.error(`文件 ${file.name} 读取失败:`, err);
loadedCount++;
const progress = Math.round((loadedCount / validFiles.length) * 100);
uploadStatusText.textContent = `读取失败: ${file.name}`;
uploadProgressFill.style.width = `${progress}%`;
progressFill.style.width = `${progress}%`;
progressText.textContent = `${progress}%`;
if (loadedCount === validFiles.length) {
uploadStatus.classList.remove('active');
uploadProgress.style.display = 'none';
if (images.length > 0) {
showBatchEditor();
} else {
showNotification('文件读取失败,请重试', 'error');
}
}
};
console.log(`开始读取文件: ${file.name}`);
reader.readAsDataURL(file);
});
}
// 显示批量编辑器
function showBatchEditor() {
uploadContainer.style.display = 'none';
batchEditorContainer.style.display = 'block';
// 渲染图片网格
renderImageGrid();
// 更新选中计数
updateSelectedCount();
// 开始预加载
startPreloading();
}
// 渲染图片网格
function renderImageGrid() {
// 清空网格
imageGrid.innerHTML = '';
// 计算当前页的图片范围
const startIndex = (currentPage - 1) * imagesPerPage;
const endIndex = Math.min(startIndex + imagesPerPage, images.length);
// 渲染当前页的图片
for (let i = startIndex; i < endIndex; i++) {
const img = images[i];
const isSelected = selectedImages.has(i);
// 创建图片项
const imageItem = document.createElement('div');
imageItem.className = 'image-item';
imageItem.dataset.index = (i - startIndex).toString();
if (isSelected) {
imageItem.classList.add('selected');
}
if (isDraggingEnabled) {
imageItem.setAttribute('draggable', 'true');
}
// 添加图片
const imgElement = document.createElement('img');
imgElement.src = img.src;
imgElement.alt = img.name;
imageItem.appendChild(imgElement);
// 添加图片名称
const imageName = document.createElement('div');
imageName.className = 'image-name';
imageName.textContent = img.name;
imageItem.appendChild(imageName);
// 添加选中指示器
if (isSelected) {
const selectIndicator = document.createElement('div');
selectIndicator.className = 'select-indicator';
selectIndicator.innerHTML = '<i class="fas fa-check"></i>';
imageItem.appendChild(selectIndicator);
}
// 添加删除按钮
const removeBtn = document.createElement('div');
removeBtn.className = 'remove-btn';
removeBtn.innerHTML = '<i class="fas fa-times"></i>';
removeBtn.addEventListener('click', (e) => {
e.stopPropagation();
removeImage(i);
});
imageItem.appendChild(removeBtn);
// 添加拖拽手柄
if (isDraggingEnabled) {
const dragHandle = document.createElement('div');
dragHandle.className = 'drag-handle';
dragHandle.innerHTML = '<i class="fas fa-grip-lines"></i>';
imageItem.appendChild(dragHandle);
}
// 添加点击事件
imageItem.addEventListener('click', () => {
toggleImageSelection(i);
previewImage(i);
});
// 添加拖拽事件
imageItem.addEventListener('dragstart', handleDragStart);
imageItem.addEventListener('dragover', handleDragOver);
imageItem.addEventListener('dragenter', handleDragEnter);
imageItem.addEventListener('dragleave', handleDragLeave);
imageItem.addEventListener('dragend', handleDragEnd);
imageItem.addEventListener('drop', handleDrop);
// 添加到网格
imageGrid.appendChild(imageItem);
}
// 更新分页
updatePagination();
}
// 更新分页
function updatePagination() {
const totalPages = Math.ceil(images.length / imagesPerPage);
currentPageEl.textContent = currentPage.toString();
totalPagesEl.textContent = totalPages.toString();
prevPageBtn.disabled = currentPage <= 1;
nextPageBtn.disabled = currentPage >= totalPages;
}
// 更新选中计数
function updateSelectedCount() {
selectedCountInfo.textContent = `已选择 ${selectedImages.size} / ${images.length} 张图片`;
}
// 切换图片选中状态
function toggleImageSelection(index) {
if (selectedImages.has(index)) {
selectedImages.delete(index);
} else {
selectedImages.add(index);
}
// 更新UI
renderImageGrid();
updateSelectedCount();
}
// 选择所有图片
function selectAllImages() {
for (let i = 0; i < images.length; i++) {
selectedImages.add(i);
}
// 更新UI
renderImageGrid();
updateSelectedCount();
}
// 取消选择所有图片
function deselectAllImages() {
selectedImages.clear();
// 更新UI
renderImageGrid();
updateSelectedCount();
}
// 删除选中的图片
function removeSelectedImages() {
if (selectedImages.size === 0) return;
if (!confirm(`确定要删除选中的 ${selectedImages.size} 张图片吗?`)) {
return;
}
// 从大到小排序,以便正确删除
const selectedIndices = Array.from(selectedImages).sort((a, b) => b - a);
// 删除选中的图片
selectedIndices.forEach(index => {
images.splice(index, 1);
});
// 清空选中集合
selectedImages.clear();
// 重置当前页码
if (currentPage > Math.ceil(images.length / imagesPerPage)) {
currentPage = Math.max(1, Math.ceil(images.length / imagesPerPage));
}
// 如果没有图片了,返回上传界面
if (images.length === 0) {
batchEditorContainer.style.display = 'none';
uploadContainer.style.display = 'block';
return;
}
// 更新UI
renderImageGrid();
updateSelectedCount();
// 重置预览
if (currentPreviewIndex >= images.length) {
currentPreviewIndex = -1;
previewPlaceholder.style.display = 'flex';
previewCanvas.style.display = 'none';
previewZoomBtn.style.display = 'none';
}
}
// 删除单个图片
function removeImage(index) {
if (!confirm(`确定要删除图片 "${images[index].name}" 吗?`)) {
return;
}
// 从选中集合中移除
selectedImages.delete(index);
// 更新大于该索引的选中项
const newSelectedImages = new Set();
selectedImages.forEach(i => {
if (i < index) {
newSelectedImages.add(i);
} else {
newSelectedImages.add(i - 1);
}
});
selectedImages = newSelectedImages;
// 删除图片
images.splice(index, 1);
// 重置当前页码
if (currentPage > Math.ceil(images.length / imagesPerPage)) {
currentPage = Math.max(1, Math.ceil(images.length / imagesPerPage));
}
// 如果没有图片了,返回上传界面
if (images.length === 0) {
batchEditorContainer.style.display = 'none';
uploadContainer.style.display = 'block';
return;
}
// 更新UI
renderImageGrid();
updateSelectedCount();
// 更新预览
if (currentPreviewIndex === index) {
currentPreviewIndex = -1;
previewPlaceholder.style.display = 'flex';
previewCanvas.style.display = 'none';
previewZoomBtn.style.display = 'none';
} else if (currentPreviewIndex > index) {
currentPreviewIndex--;
previewImage(currentPreviewIndex);
}
}
// 预览图片
function previewImage(index) {
if (index < 0 || index >= images.length) return;
currentPreviewIndex = index;
const img = images[index];
// 设置画布大小
const maxPreviewSize = 300;
let previewWidth = img.width;
let previewHeight = img.height;
if (previewWidth > maxPreviewSize) {
const scale = maxPreviewSize / previewWidth;
previewWidth = maxPreviewSize;
previewHeight = Math.round(previewHeight * scale);
}
if (previewHeight > maxPreviewSize) {
const scale = maxPreviewSize / previewHeight;
previewHeight = maxPreviewSize;
previewWidth = Math.round(previewWidth * scale);
}
previewCanvas.width = previewWidth;
previewCanvas.height = previewHeight;
// 显示画布,隐藏占位符
previewPlaceholder.style.display = 'none';
previewCanvas.style.display = 'block';
previewZoomBtn.style.display = 'block';
// 更新预览
updatePreview();
// 更新格式预览
updateFormatPreviews();
// 更新水印预览
updateWatermarkPreview();
// 更新裁剪预览
updateCropPreview();
// 更新重命名预览
updateRenamePreview();
}
// 更新预览
function updatePreview() {
if (currentPreviewIndex < 0) return;
const img = images[currentPreviewIndex];
// 清除画布
ctx.clearRect(0, 0, previewCanvas.width, previewCanvas.height);
// 绘制图像
ctx.drawImage(img.element, 0, 0, previewCanvas.width, previewCanvas.height);
// 应用滤镜
applyFilters();
// 应用水印
applyWatermark(previewCanvas, ctx);
}
// 应用滤镜
function applyFilters() {
// 获取滤镜参数
const brightness = 1 + brightnessRange.value / 100;
const contrast = 1 + contrastRange.value / 100;
const saturation = 1 + saturationRange.value / 100;
const blur = blurRange.value;
// 应用滤镜
const imageData = ctx.getImageData(0, 0, previewCanvas.width, previewCanvas.height);
const data = imageData.data;
// 亮度和对比度
for (let i = 0; i < data.length; i += 4) {
// 亮度
data[i] = data[i] * brightness;
data[i + 1] = data[i + 1] * brightness;
data[i + 2] = data[i + 2] * brightness;
// 对比度
data[i] = (data[i] - 128) * contrast + 128;
data[i + 1] = (data[i + 1] - 128) * contrast + 128;
data[i + 2] = (data[i + 2] - 128) * contrast + 128;
}
ctx.putImageData(imageData, 0, 0);
// 饱和度和模糊(使用CSS滤镜)
previewCanvas.style.filter = `saturate(${saturation}) blur(${blur}px)`;
}
// 更新水印预览
function updateWatermarkPreview() {
const previewImg = document.querySelector('.watermark-preview img');
const overlay = document.querySelector('.watermark-overlay');
// 如果当前有预览图片,使用它
if (currentPreviewIndex >= 0) {
previewImg.src = images[currentPreviewIndex].src;
}
// 清除现有内容
overlay.innerHTML = '';
// 根据位置设置样式
const [vertical, horizontal] = watermarkPosition.split('-');
overlay.style.justifyContent =
horizontal === 'left' ? 'flex-start' :
horizontal === 'right' ? 'flex-end' : 'center';
overlay.style.alignItems =
vertical === 'top' ? 'flex-start' :
vertical === 'bottom' ? 'flex-end' : 'center';
// 获取高级选项
const useShadow = document.getElementById('watermarkShadow').checked;
const useBorder = document.getElementById('watermarkBorder').checked;
const usePattern = document.getElementById('watermarkPattern').checked;
const patternType = document.querySelector('.watermark-pattern.active')?.dataset.pattern || 'none';
// 添加水印
if (watermarkType === 'text') {
const text = document.getElementById('watermarkText').value || '藤原图片转换器';
const fontSize = document.getElementById('watermarkFontSize').value;
const opacity = document.getElementById('watermarkOpacity').value / 100;
const color = document.getElementById('watermarkColor').value;
const rotation = document.getElementById('watermarkRotation').value;
const textElement = document.createElement('span');
textElement.className = 'watermark-text';
textElement.textContent = text;
textElement.style.fontSize = `${fontSize}px`;
textElement.style.opacity = opacity;
textElement.style.color = color;
textElement.style.transform = `rotate(${rotation}deg)`;
textElement.style.padding = '20px';
// 应用高级选项
if (useShadow) {
textElement.style.textShadow = '2px 2px 4px rgba(0, 0, 0, 0.7)';
}
if (useBorder) {
textElement.style.webkitTextStroke = '1px rgba(0, 0, 0, 0.5)';
}
overlay.appendChild(textElement);
// 如果使用平铺模式
if (usePattern && patternType !== 'none') {
overlay.style.justifyContent = 'space-between';
overlay.style.alignItems = 'space-between';
overlay.style.flexWrap = 'wrap';
overlay.style.width = '100%';
overlay.style.height = '100%';
// 根据模式创建多个水印
const count = patternType === 'dense' ? 9 : patternType === 'medium' ? 5 : 3;
// 移除原始水印
overlay.removeChild(textElement);
// 创建水印网格
for (let i = 0; i < count; i++) {
const clone = textElement.cloneNode(true);
overlay.appendChild(clone);
}
}
} else if (watermarkType === 'image' && watermarkImage) {
const size = document.getElementById('watermarkImageSize').value;
const opacity = document.getElementById('watermarkImageOpacity').value / 100;
const imgElement = document.createElement('img');
imgElement.className = 'watermark-image';
imgElement.src = watermarkImage.src;
imgElement.style.maxWidth = `${size}%`;
imgElement.style.maxHeight = `${size}%`;
imgElement.style.opacity = opacity;
// 应用高级选项
if (useShadow) {
imgElement.style.filter = 'drop-shadow(3px 3px 5px rgba(0, 0, 0, 0.7))';
}
if (useBorder) {
imgElement.style.border = '2px solid rgba(255, 255, 255, 0.7)';
imgElement.style.borderRadius = '4px';
}
overlay.appendChild(imgElement);
// 如果使用平铺模式
if (usePattern && patternType !== 'none') {
overlay.style.justifyContent = 'space-between';
overlay.style.alignItems = 'space-between';
overlay.style.flexWrap = 'wrap';
overlay.style.width = '100%';
overlay.style.height = '100%';
// 根据模式创建多个水印
const count = patternType === 'dense' ? 9 : patternType === 'medium' ? 5 : 3;
// 移除原始水印
overlay.removeChild(imgElement);
// 创建水印网格
for (let i = 0; i < count; i++) {
const clone = imgElement.cloneNode(true);
clone.style.maxWidth = `${size/2}%`;
clone.style.maxHeight = `${size/2}%`;
overlay.appendChild(clone);
}
}
}
}
// 应用水印
function applyWatermark(canvas, ctx) {
// 获取高级选项
const useShadow = document.getElementById('watermarkShadow').checked;
const useBorder = document.getElementById('watermarkBorder').checked;
const usePattern = document.getElementById('watermarkPattern').checked;
const patternType = document.querySelector('.watermark-pattern.active')?.dataset.pattern || 'none';
if (watermarkType === 'text') {
const text = document.getElementById('watermarkText').value || '藤原图片转换器';
const fontSize = parseInt(document.getElementById('watermarkFontSize').value);
const opacity = document.getElementById('watermarkOpacity').value / 100;
const color = document.getElementById('watermarkColor').value;
const rotation = parseInt(document.getElementById('watermarkRotation').value);
// 如果使用平铺模式
if (usePattern && patternType !== 'none') {
// 计算水印数量
const count = patternType === 'dense' ? 3 : patternType === 'medium' ? 2 : 1;
const spacing = canvas.width / (count + 1);
for (let x = 1; x <= count; x++) {
for (let y = 1; y <= count; y++) {
drawTextWatermark(
ctx,
text,
x * spacing,
y * spacing,
fontSize,
color,
opacity,
rotation,
useShadow,
useBorder
);
}
}
} else {
// 计算位置
let x, y;
const [vertical, horizontal] = watermarkPosition.split('-');
if (horizontal === 'left') {
x = fontSize;
} else if (horizontal === 'right') {
x = canvas.width - ctx.measureText(text).width - fontSize;
} else {
x = (canvas.width - ctx.measureText(text).width) / 2;
}
if (vertical === 'top') {
y = fontSize * 1.5;
} else if (vertical === 'bottom') {
y = canvas.height - fontSize;
} else {
y = canvas.height / 2;
}
drawTextWatermark(
ctx,
text,
x,
y,
fontSize,
color,
opacity,
rotation,
useShadow,
useBorder
);
}
} else if (watermarkType === 'image' && watermarkImage) {
const size = document.getElementById('watermarkImageSize').value / 100;
const opacity = document.getElementById('watermarkImageOpacity').value / 100;
// 计算水印尺寸
const maxWidth = canvas.width * size;
const maxHeight = canvas.height * size;
let width = watermarkImage.width;
let height = watermarkImage.height;
if (width > maxWidth) {
const scale = maxWidth / width;
width = maxWidth;
height = height * scale;
}
if (height > maxHeight) {
const scale = maxHeight / height;
height = maxHeight;
width = width * scale;
}
// 如果使用平铺模式
if (usePattern && patternType !== 'none') {
// 计算水印数量
const count = patternType === 'dense' ? 3 : patternType === 'medium' ? 2 : 1;
const spacingX = canvas.width / (count + 1);
const spacingY = canvas.height / (count + 1);
const smallWidth = width * 0.7;
const smallHeight = height * 0.7;
for (let x = 1; x <= count; x++) {
for (let y = 1; y <= count; y++) {
drawImageWatermark(
ctx,
watermarkImage,
x * spacingX - smallWidth/2,
y * spacingY - smallHeight/2,
smallWidth,
smallHeight,
opacity,
useShadow,
useBorder
);
}
}
} else {
// 计算位置
let x, y;
const [vertical, horizontal] = watermarkPosition.split('-');
if (horizontal === 'left') {
x = 10;
} else if (horizontal === 'right') {
x = canvas.width - width - 10;
} else {
x = (canvas.width - width) / 2;
}
if (vertical === 'top') {
y = 10;
} else if (vertical === 'bottom') {
y = canvas.height - height - 10;
} else {
y = (canvas.height - height) / 2;
}
drawImageWatermark(
ctx,
watermarkImage,
x,
y,
width,
height,
opacity,
useShadow,
useBorder
);
}
}
}
// 绘制文字水印
function drawTextWatermark(ctx, text, x, y, fontSize, color, opacity, rotation, useShadow, useBorder) {
// 保存当前上下文状态
ctx.save();
// 设置文字样式
ctx.fillStyle = color;
ctx.globalAlpha = opacity;
ctx.font = `${fontSize}px Arial`;
// 应用旋转
ctx.translate(x, y);
ctx.rotate(rotation * Math.PI / 180);
// 应用阴影
if (useShadow) {
ctx.shadowColor = 'rgba(0, 0, 0, 0.7)';
ctx.shadowBlur = 4;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
}
// 应用边框
if (useBorder) {
ctx.strokeStyle = 'rgba(0, 0, 0, 0.8)';
ctx.lineWidth = 2;
ctx.strokeText(text, 0, 0);
}
ctx.fillText(text, 0, 0);
// 恢复上下文状态
ctx.restore();
}
// 绘制图片水印
function drawImageWatermark(ctx, image, x, y, width, height, opacity, useShadow, useBorder) {
// 保存当前上下文状态
ctx.save();
// 设置透明度
ctx.globalAlpha = opacity;
// 应用阴影
if (useShadow) {
ctx.shadowColor = 'rgba(0, 0, 0, 0.7)';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
}
// 应用边框
if (useBorder) {
ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';
ctx.lineWidth = 2;
ctx.strokeRect(x - 2, y - 2, width + 4, height + 4);
}
// 绘制水印
ctx.drawImage(image, x, y, width, height);
// 恢复上下文状态
ctx.restore();
}
// 初始化滤镜预览
function initFilterPreviews() {
const filterGrid = document.getElementById('filterPreviewGrid');
filterGrid.innerHTML = '';
// 创建滤镜预览项
Object.entries(filters).forEach(([key, filter]) => {
const filterItem = document.createElement('div');
filterItem.className = 'filter-preview-item';
if (key === currentFilter) {
filterItem.classList.add('active');
}
// 创建预览图像
const img = document.createElement('img');
img.src = 'data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%3E%3Crect%20fill%3D%22%23424242%22%20width%3D%22100%22%20height%3D%22100%22%2F%3E%3Ctext%20fill%3D%22%23fff%22%20font-family%3D%22sans-serif%22%20font-size%3D%2212%22%20text-anchor%3D%22middle%22%20x%3D%2250%22%20y%3D%2250%22%3E%E6%BB%A4%E9%95%9C%3C%2Ftext%3E%3C%2Fsvg%3E';
img.style.filter = filter.filter;
// 创建滤镜名称
const name = document.createElement('div');
name.className = 'filter-name';
name.textContent = filter.name;
// 添加点击事件
filterItem.addEventListener('click', () => {
// 更新当前滤镜
currentFilter = key;
// 更新UI
document.querySelectorAll('.filter-preview-item').forEach(item => {
item.classList.remove('active');
});
filterItem.classList.add('active');
// 更新预览
updatePreview();
});
// 添加到滤镜项
filterItem.appendChild(img);
filterItem.appendChild(name);
// 添加到滤镜网格
filterGrid.appendChild(filterItem);
});
}
// 更新格式预览
function updateFormatPreviews() {
if (currentPreviewIndex < 0) return;
const previewGrid = document.getElementById('formatPreviewGrid');
previewGrid.innerHTML = '';
const img = images[currentPreviewIndex];
const formats = ['jpeg', 'png', 'webp', 'gif'];
formats.forEach(format => {
// 创建预览项
const previewItem = document.createElement('div');
previewItem.className = 'format-preview-item';
// 格式标题
const title = document.createElement('h4');
title.textContent = format.toUpperCase();
previewItem.appendChild(title);
// 创建临时画布
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
// 设置画布大小
const maxPreviewSize = 150;
let previewWidth = img.width;
let previewHeight = img.height;
if (previewWidth > maxPreviewSize) {
const scale = maxPreviewSize / previewWidth;
previewWidth = maxPreviewSize;
previewHeight = Math.round(previewHeight * scale);
}
if (previewHeight > maxPreviewSize) {
const scale = maxPreviewSize / previewHeight;
previewHeight = maxPreviewSize;
previewWidth = Math.round(previewWidth * scale);
}
tempCanvas.width = previewWidth;
tempCanvas.height = previewHeight;
// 绘制图像
tempCtx.drawImage(img.element, 0, 0, previewWidth, previewHeight);
// 创建预览图像
const previewImg = document.createElement('img');
previewImg.className = 'preview-img';
// 获取数据URL
let quality = 0.8;
let mimeType;
switch(format) {
case 'jpeg':
mimeType = 'image/jpeg';
break;
case 'png':
mimeType = 'image/png';
break;
case 'webp':
mimeType = 'image/webp';
break;
case 'gif':
mimeType = 'image/gif';
break;
}
try {
if (format === 'jpeg' || format === 'webp') {
previewImg.src = tempCanvas.toDataURL(mimeType, quality);
} else {
previewImg.src = tempCanvas.toDataURL(mimeType);
}
// 估算文件大小
const dataUrl = previewImg.src;
const base64 = dataUrl.split(',')[1];
const estimatedSize = Math.round((base64.length * 3/4) / 1024);
// 添加格式信息
const formatInfo = document.createElement('div');
formatInfo.className = 'format-info';
formatInfo.innerHTML = `
估计大小: ${estimatedSize} KB<br>
${getFormatDescription(format)}
`;
previewItem.appendChild(previewImg);
previewItem.appendChild(formatInfo);
previewGrid.appendChild(previewItem);
} catch (e) {
console.error(`无法生成 ${format} 预览:`, e);
// 添加错误信息
const errorInfo = document.createElement('div');
errorInfo.className = 'format-info';
errorInfo.textContent = `无法生成 ${format} 预览`;
previewItem.appendChild(errorInfo);
previewGrid.appendChild(previewItem);
}
});
}
// 获取格式描述
function getFormatDescription(format) {
switch(format) {
case 'jpeg':
return '高兼容性,不支持透明';
case 'png':
return '支持透明背景,文件较大';
case 'webp':
return '高压缩率,部分浏览器不支持';
case 'gif':
return '支持动画,色彩有限';
default:
return '';
}
}
// 更新裁剪预览
function updateCropPreview() {
if (currentPreviewIndex < 0) return;
const img = images[currentPreviewIndex];
// 显示裁剪图像,隐藏占位符
cropPlaceholder.style.display = 'none';
cropImage.style.display = 'block';
cropImage.src = img.src;
// 如果已经有裁剪器,销毁它
if (cropper) {
cropper.destroy();
}
// 创建新的裁剪器
cropper = new Cropper(cropImage, {
viewMode: 1,
dragMode: 'move',
aspectRatio: lockRatio.checked ? widthInput.value / heightInput.value : NaN,
autoCropArea: 0.8,
restore: false,
guides: true,
center: true,
highlight: false,
cropBoxMovable: true,
cropBoxResizable: true,
toggleDragModeOnDblclick: false
});
}
// 重置裁剪
function resetCrop() {
if (cropper) {
cropper.reset();
}
}
// 应用裁剪
function applyCrop() {
if (!cropper || currentPreviewIndex < 0) return;
// 获取裁剪数据
const canvas = cropper.getCroppedCanvas();
// 更新图片
const img = images[currentPreviewIndex];
img.element.src = canvas.toDataURL();
img.src = canvas.toDataURL();
img.width = canvas.width;
img.height = canvas.height;
// 更新预览
previewImage(currentPreviewIndex);
// 显示通知
showNotification('裁剪已应用', 'success');
}
// 应用裁剪到所有选中图片
function applyCropToAll() {
if (!cropper || selectedImages.size === 0) return;
// 获取裁剪数据
const cropData = cropper.getData();
// 应用到所有选中图片
selectedImages.forEach(index => {
if (index === currentPreviewIndex) return; // 跳过当前预览的图片,因为已经应用了
const img = images[index];
// 创建临时画布
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
// 设置画布大小
tempCanvas.width = cropData.width;
tempCanvas.height = cropData.height;
// 绘制裁剪后的图像
tempCtx.drawImage(
img.element,
cropData.x,
cropData.y,
cropData.width,
cropData.height,
0,
0,
cropData.width,
cropData.height
);
// 更新图片
img.element.src = tempCanvas.toDataURL();
img.src = tempCanvas.toDataURL();
img.width = tempCanvas.width;
img.height = tempCanvas.height;
});
// 更新预览
previewImage(currentPreviewIndex);
// 显示通知
showNotification(`裁剪已应用到 ${selectedImages.size} 张图片`, 'success');
}
// 更新压缩值显示
function updateCompressionValue() {
const value = compressionRange.value;
let text;
if (value < 20) {
text = '极低';
} else if (value < 40) {
text = '低';
} else if (value < 60) {
text = '中等';
} else if (value < 80) {
text = '高';
} else {
text = '极高';
}
compressionValue.textContent = text;
}
// 更新大小估计
function updateSizeEstimate() {
if (currentPreviewIndex < 0) return;
const img = images[currentPreviewIndex];
const format = formatSelect.value;
const quality = qualityRange.value / 100;
const compression = compressionRange.value / 100;
// 计算原始大小
const originalSize = img.size;
// 估算输出大小
let estimatedSize;
if (format === 'jpeg') {
estimatedSize = originalSize * quality * (1 - compression * 0.5);
} else if (format === 'png') {
estimatedSize = originalSize * (1 - compression * 0.3);
} else if (format === 'webp') {
estimatedSize = originalSize * quality * (1 - compression * 0.7);
} else {
estimatedSize = originalSize;
}
// 如果调整了大小,考虑尺寸变化
if (widthInput.value && heightInput.value) {
const originalArea = img.width * img.height;
const newArea = widthInput.value * heightInput.value;
const areaRatio = newArea / originalArea;
estimatedSize *= areaRatio;
}
// 更新UI
sizeEstimate.textContent = formatFileSize(estimatedSize);
// 计算节省空间
const saving = originalSize - estimatedSize;
const savingPercentage = Math.round((saving / originalSize) * 100);
if (saving > 0) {
sizeSaving.textContent = `${formatFileSize(saving)} (${savingPercentage}%)`;
sizeSaving.style.color = 'var(--success-color)';
} else {
sizeSaving.textContent = `增加 ${formatFileSize(Math.abs(saving))} (${Math.abs(savingPercentage)}%)`;
sizeSaving.style.color = 'var(--warning-color)';
}
}
// 更新重命名预览
function updateRenamePreview() {
if (images.length === 0) return;
const pattern = renamePattern.value || '{name}';
const addPrefix = renameAddPrefix.checked;
const prefix = renamePrefix.value || '';
const addSuffix = renameAddSuffix.checked;
const suffix = renameSuffix.value || '';
const useStartIndex = renameStartIndex.checked;
const startIndex = parseInt(renameStartIndexValue.value) || 1;
// 清空预览列表
renamePreviewList.innerHTML = '';
// 显示前5个图片的重命名预览
const previewCount = Math.min(5, images.length);
for (let i = 0; i < previewCount; i++) {
const img = images[i];
const originalName = img.name;
// 获取文件名和扩展名
const lastDotIndex = originalName.lastIndexOf('.');
const nameWithoutExt = lastDotIndex !== -1 ? originalName.substring(0, lastDotIndex) : originalName;
const extension = lastDotIndex !== -1 ? originalName.substring(lastDotIndex) : '';
// 生成新文件名
let newName = pattern
.replace('{name}', nameWithoutExt)
.replace('{index}', (useStartIndex ? startIndex + i : i + 1).toString().padStart(2, '0'))
.replace('{date}', new Date().toISOString().split('T')[0])
.replace('{random}', Math.random().toString(36).substring(2, 8));
if (addPrefix) {
newName = prefix + newName;
}
if (addSuffix) {
newName = newName + suffix;
}
// 添加扩展名
newName += extension;
// 创建预览项
const previewItem = document.createElement('div');
previewItem.className = 'rename-preview-item';
const oldNameEl = document.createElement('div');
oldNameEl.className = 'old-name';
oldNameEl.textContent = originalName;
const newNameEl = document.createElement('div');
newNameEl.className = 'new-name';
newNameEl.textContent = newName;
previewItem.appendChild(oldNameEl);
previewItem.appendChild(newNameEl);
renamePreviewList.appendChild(previewItem);
}
// 如果有更多图片,显示省略号
if (images.length > previewCount) {
const moreItem = document.createElement('div');
moreItem.className = 'rename-preview-item';
moreItem.textContent = `... 还有 ${images.length - previewCount} 张图片`;
renamePreviewList.appendChild(moreItem);
}
}
// 批量转换图片
function batchConvertImages() {
if (selectedImages.size === 0) {
alert('请至少选择一张图片');
return;
}
// 显示加载状态
loading.style.display = 'flex';
loadingText.textContent = '正在处理图片...';
progressBar.style.width = '0%';
progressText.textContent = '0%';
// 获取设置
const format = formatSelect.value;
const quality = qualityRange.value / 100;
const width = widthInput.value ? parseInt(widthInput.value) : null;
const height = heightInput.value ? parseInt(heightInput.value) : null;
// 保存处理历史
const settings = {
format,
quality,
width,
height,
brightness: 1 + brightnessRange.value / 100,
contrast: 1 + contrastRange.value / 100,
saturation: 1 + saturationRange.value / 100,
blur: blurRange.value,
watermark: document.getElementById('watermarkText').value || document.getElementById('watermarkImageInput').files.length > 0
};
saveProcessingHistory(settings);
// 创建ZIP文件
const zip = new JSZip();
const selectedIndices = Array.from(selectedImages);
let processedCount = 0;
// 获取重命名设置
const pattern = renamePattern.value || '{name}';
const addPrefix = renameAddPrefix.checked;
const prefix = renamePrefix.value || '';
const addSuffix = renameAddSuffix.checked;
const suffix = renameSuffix.value || '';
const useStartIndex = renameStartIndex.checked;
const startIndex = parseInt(renameStartIndexValue.value) || 1;
// 处理每张图片
selectedIndices.forEach((index, i) => {
const img = images[index];
// 创建画布
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 设置画布大小
canvas.width = width || img.width;
canvas.height = height || img.height;
// 绘制图像
ctx.drawImage(img.element, 0, 0, canvas.width, canvas.height);
// 应用滤镜
const brightness = 1 + brightnessRange.value / 100;
const contrast = 1 + contrastRange.value / 100;
// 亮度和对比度
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let j = 0; j < data.length; j += 4) {
// 亮度
data[j] = data[j] * brightness;
data[j + 1] = data[j + 1] * brightness;
data[j + 2] = data[j + 2] * brightness;
// 对比度
data[j] = (data[j] - 128) * contrast + 128;
data[j + 1] = (data[j + 1] - 128) * contrast + 128;
data[j + 2] = (data[j + 2] - 128) * contrast + 128;
}
ctx.putImageData(imageData, 0, 0);
// 应用水印
applyWatermark(canvas, ctx);
// 获取文件名和扩展名
const originalName = img.name;
const lastDotIndex = originalName.lastIndexOf('.');
const nameWithoutExt = lastDotIndex !== -1 ? originalName.substring(0, lastDotIndex) : originalName;
// 生成新文件名
let newName = pattern
.replace('{name}', nameWithoutExt)
.replace('{index}', (useStartIndex ? startIndex + i : i + 1).toString().padStart(2, '0'))
.replace('{date}', new Date().toISOString().split('T')[0])
.replace('{random}', Math.random().toString(36).substring(2, 8));
if (addPrefix) {
newName = prefix + newName;
}
if (addSuffix) {
newName = newName + suffix;
}
// 添加扩展名
newName += `.${format}`;
// 获取图像数据
let imageDataUrl;
if (format === 'jpeg' || format === 'webp') {
imageDataUrl = canvas.toDataURL(`image/${format}`, quality);
} else {
imageDataUrl = canvas.toDataURL(`image/${format}`);
}
// 添加到ZIP
const base64Data = imageDataUrl.split(',')[1];
zip.file(newName, base64Data, { base64: true });
// 更新进度
processedCount++;
const progress = Math.round((processedCount / selectedIndices.length) * 100);
progressBar.style.width = `${progress}%`;
progressText.textContent = `${progress}%`;
// 所有图片处理完成
if (processedCount === selectedIndices.length) {
// 生成ZIP文件
zip.generateAsync({ type: 'blob' }).then(content => {
// 下载ZIP文件
saveAs(content, `藤原图片批量转换_${new Date().toISOString().split('T')[0]}.zip`);
// 隐藏加载状态
loading.style.display = 'none';
// 显示通知
showNotification(`成功处理 ${processedCount} 张图片`, 'success');
});
}
});
}
// 从URL获取图片
function fetchImageFromUrl() {
const url = imageUrlInput.value.trim();
if (!url) {
errorMessage.textContent = '请输入有效的图片URL';
errorMessage.style.display = 'block';
return;
}
// 显示加载状态
loading.style.display = 'flex';
loadingText.textContent = '正在获取图片...';
progressBar.style.width = '0%';
progressText.textContent = '0%';
// 隐藏错误消息
errorMessage.style.display = 'none';
// 创建图像对象
const img = new Image();
img.onload = () => {
// 创建图片对象
const imageObj = {
element: img,
name: url.split('/').pop() || 'image.jpg',
type: 'image/jpeg', // 假设为JPEG
width: img.width,
height: img.height,
src: img.src,
size: 0, // 无法获取确切大小
isUrl: true
};
// 添加到图片数组
images.push(imageObj);
selectedImages.add(images.length - 1);
// 显示批量编辑器
showBatchEditor();
// 清空输入框
imageUrlInput.value = '';
// 显示通知
showNotification('成功获取图片', 'success');
};
img.onerror = () => {
// 隐藏加载状态
loading.style.display = 'none';
// 显示错误消息
errorMessage.textContent = '无法加载图片,请检查URL是否正确';
errorMessage.style.display = 'block';
};
// 开始加载图片
img.crossOrigin = 'anonymous';
img.src = url;
}
// 打开放大预览模态框
function openZoomModal(index) {
if (index < 0 || index >= images.length) return;
const img = images[index];
// 设置图片
zoomImage.src = img.src;
// 设置信息
zoomImageInfo.textContent = `${img.name} - ${img.width}x${img.height} - ${formatFileSize(img.size)}`;
// 显示模态框
imageZoomModal.style.display = 'flex';
}
// 切换主题
function toggleTheme() {
const body = document.body;
if (body.classList.contains('light-theme')) {
body.classList.remove('light-theme');
themeToggle.innerHTML = '<i class="fas fa-sun"></i>';
localStorage.setItem('theme', 'dark');
} else {
body.classList.add('light-theme');
themeToggle.innerHTML = '<i class="fas fa-moon"></i>';
localStorage.setItem('theme', 'light');
}
}
// 保存当前设置
function saveCurrentSettings() {
const name = settingName.value.trim() || `设置 ${new Date().toLocaleString()}`;
const settings = getCurrentSettings();
// 保存到本地存储
saveSettingsToLocalStorage(name, settings);
// 清空输入框
settingName.value = '';
// 更新已保存设置列表
updateSavedSettingsList();
// 显示通知
showNotification('设置已保存', 'success');
}
// 获取当前设置
function getCurrentSettings() {
return {
format: formatSelect.value,
quality: qualityRange.value,
width: widthInput.value,
height: heightInput.value,
lockRatio: lockRatio.checked,
presetRatio: presetRatio.value,
brightness: brightnessRange.value,
contrast: contrastRange.value,
saturation: saturationRange.value,
blur: blurRange.value,
filter: currentFilter,
compressionPreset: currentCompressionPreset,
compressionLevel: compressionRange.value,
renamePattern: renamePattern.value,
renameAddPrefix: renameAddPrefix.checked,
renamePrefix: renamePrefix.value,
renameAddSuffix: renameAddSuffix.checked,
renameSuffix: renameSuffix.value,
renameStartIndex: renameStartIndex.checked,
renameStartIndexValue: renameStartIndexValue.value,
watermarkType: watermarkType,
watermarkPosition: watermarkPosition,
watermarkText: document.getElementById('watermarkText').value,
watermarkFontSize: document.getElementById('watermarkFontSize').value,
watermarkColor: document.getElementById('watermarkColor').value,
watermarkOpacity: document.getElementById('watermarkOpacity').value,
watermarkRotation: document.getElementById('watermarkRotation').value,
watermarkImageSize: document.getElementById('watermarkImageSize').value,
watermarkImageOpacity: document.getElementById('watermarkImageOpacity').value,
watermarkShadow: document.getElementById('watermarkShadow').checked,
watermarkBorder: document.getElementById('watermarkBorder').checked,
watermarkPattern: document.getElementById('watermarkPattern').checked,
watermarkPatternType: document.querySelector('.watermark-pattern.active')?.dataset.pattern || 'none'
};
}
// 保存设置到本地存储
function saveSettingsToLocalStorage(name, settings) {
// 获取已保存的设置
let savedSettings = {};
try {
const savedSettingsJson = localStorage.getItem('savedSettings');
if (savedSettingsJson) {
savedSettings = JSON.parse(savedSettingsJson);
}
} catch (e) {
console.error('加载已保存设置失败:', e);
savedSettings = {};
}
// 添加新设置
savedSettings[name] = {
settings: settings,
date: new Date().toISOString()
};
// 保存到本地存储
localStorage.setItem('savedSettings', JSON.stringify(savedSettings));
}
// 更新已保存设置列表
function updateSavedSettingsList() {
// 获取已保存的设置
let savedSettings = {};
try {
const savedSettingsJson = localStorage.getItem('savedSettings');
if (savedSettingsJson) {
savedSettings = JSON.parse(savedSettingsJson);
}
} catch (e) {
console.error('加载已保存设置失败:', e);
savedSettings = {};
}
// 清空列表
savedSettingsList.innerHTML = '';
// 如果没有已保存的设置
if (Object.keys(savedSettings).length === 0) {
savedSettingsList.innerHTML = '<div class="saved-setting-empty">暂无已保存的设置</div>';
return;
}
// 添加设置项
Object.entries(savedSettings).forEach(([name, data]) => {
const settingItem = document.createElement('div');
settingItem.className = 'saved-setting-item';
const settingName = document.createElement('div');
settingName.className = 'saved-setting-name';
settingName.textContent = name;
const settingDate = document.createElement('div');
settingDate.className = 'saved-setting-date';
settingDate.textContent = new Date(data.date).toLocaleString();
const settingActions = document.createElement('div');
settingActions.className = 'saved-setting-actions';
const loadBtn = document.createElement('button');
loadBtn.innerHTML = '<i class="fas fa-upload"></i>';
loadBtn.title = '加载设置';
loadBtn.addEventListener('click', () => {
loadSettings(name);
});
const deleteBtn = document.createElement('button');
deleteBtn.innerHTML = '<i class="fas fa-trash-alt"></i>';
deleteBtn.title = '删除设置';
deleteBtn.className = 'delete-btn';
deleteBtn.addEventListener('click', () => {
deleteSettings(name);
});
settingActions.appendChild(loadBtn);
settingActions.appendChild(deleteBtn);
settingItem.appendChild(settingName);
settingItem.appendChild(settingDate);
settingItem.appendChild(settingActions);
savedSettingsList.appendChild(settingItem);
});
}
// 加载设置
function loadSettings(name) {
// 获取已保存的设置
let savedSettings = {};
try {
const savedSettingsJson = localStorage.getItem('savedSettings');
if (savedSettingsJson) {
savedSettings = JSON.parse(savedSettingsJson);
}
} catch (e) {
console.error('加载已保存设置失败:', e);
return;
}
// 如果没有指定名称,尝试加载上次设置
if (!name) {
if (loadLastSettings.checked) {
const autoSaveSettings = savedSettings['autoSave'];
if (autoSaveSettings) {
applySettings(autoSaveSettings.settings);
return;
}
}
return;
}
// 获取指定名称的设置
const settingData = savedSettings[name];
if (!settingData) {
alert(`找不到设置: ${name}`);
return;
}
// 应用设置
applySettings(settingData.settings);
// 显示通知
showNotification(`已加载设置: ${name}`, 'success');
}
// 应用设置
function applySettings(settings) {
// 格式设置
if (settings.format) formatSelect.value = settings.format;
// 质量设置
if (settings.quality) {
qualityRange.value = settings.quality;
qualityValue.textContent = settings.quality;
}
// 尺寸设置
if (settings.width) widthInput.value = settings.width;
if (settings.height) heightInput.value = settings.height;
if (settings.lockRatio !== undefined) lockRatio.checked = settings.lockRatio;
if (settings.presetRatio) presetRatio.value = settings.presetRatio;
// 编辑设置
if (settings.brightness) brightnessRange.value = settings.brightness;
if (settings.contrast) contrastRange.value = settings.contrast;
if (settings.saturation) saturationRange.value = settings.saturation;
if (settings.blur) blurRange.value = settings.blur;
if (settings.filter) {
currentFilter = settings.filter;
document.querySelectorAll('.filter-preview-item').forEach(item => {
item.classList.remove('active');
});
document.querySelector(`.filter-preview-item[data-filter="${settings.filter}"]`)?.classList.add('active');
}
// 压缩设置
if (settings.compressionPreset) {
currentCompressionPreset = settings.compressionPreset;
document.querySelectorAll('.compression-preset').forEach(preset => {
preset.classList.remove('active');
});
document.querySelector(`.compression-preset[data-preset="${settings.compressionPreset}"]`)?.classList.add('active');
}
if (settings.compressionLevel) {
compressionRange.value = settings.compressionLevel;
updateCompressionValue();
}
// 重命名设置
if (settings.renamePattern) renamePattern.value = settings.renamePattern;
if (settings.renameAddPrefix !== undefined) renameAddPrefix.checked = settings.renameAddPrefix;
if (settings.renamePrefix) renamePrefix.value = settings.renamePrefix;
if (settings.renameAddSuffix !== undefined) renameAddSuffix.checked = settings.renameAddSuffix;
if (settings.renameSuffix) renameSuffix.value = settings.renameSuffix;
if (settings.renameStartIndex !== undefined) renameStartIndex.checked = settings.renameStartIndex;
if (settings.renameStartIndexValue) renameStartIndexValue.value = settings.renameStartIndexValue;
// 水印设置
if (settings.watermarkType) {
watermarkType = settings.watermarkType;
document.querySelectorAll('.watermark-tab').forEach(tab => {
tab.classList.remove('active');
});
document.querySelector(`.watermark-tab[data-tab="${settings.watermarkType}"]`)?.classList.add('active');
document.querySelectorAll('.watermark-content > div').forEach(content => {
content.classList.remove('active');
});
document.getElementById(`${settings.watermarkType}Watermark`)?.classList.add('active');
}
if (settings.watermarkPosition) {
watermarkPosition = settings.watermarkPosition;
document.querySelectorAll('.position-btn').forEach(btn => {
btn.classList.remove('active');
});
document.querySelector(`.position-btn[data-position="${settings.watermarkPosition}"]`)?.classList.add('active');
}
if (settings.watermarkText) document.getElementById('watermarkText').value = settings.watermarkText;
if (settings.watermarkFontSize) document.getElementById('watermarkFontSize').value = settings.watermarkFontSize;
if (settings.watermarkColor) document.getElementById('watermarkColor').value = settings.watermarkColor;
if (settings.watermarkOpacity) document.getElementById('watermarkOpacity').value = settings.watermarkOpacity;
if (settings.watermarkRotation) document.getElementById('watermarkRotation').value = settings.watermarkRotation;
if (settings.watermarkImageSize) document.getElementById('watermarkImageSize').value = settings.watermarkImageSize;
if (settings.watermarkImageOpacity) document.getElementById('watermarkImageOpacity').value = settings.watermarkImageOpacity;
if (settings.watermarkShadow !== undefined) document.getElementById('watermarkShadow').checked = settings.watermarkShadow;
if (settings.watermarkBorder !== undefined) document.getElementById('watermarkBorder').checked = settings.watermarkBorder;
if (settings.watermarkPattern !== undefined) document.getElementById('watermarkPattern').checked = settings.watermarkPattern;
if (settings.watermarkPatternType) {
document.querySelectorAll('.watermark-pattern').forEach(pattern => {
pattern.classList.remove('active');
});
document.querySelector(`.watermark-pattern[data-pattern="${settings.watermarkPatternType}"]`)?.classList.add('active');
}
// 更新预览
updatePreview();
updateFormatPreviews();
updateWatermarkPreview();
updateRenamePreview();
}
// 删除设置
function deleteSettings(name) {
if (!confirm(`确定要删除设置 "${name}" 吗?`)) {
return;
}
// 获取已保存的设置
let savedSettings = {};
try {
const savedSettingsJson = localStorage.getItem('savedSettings');
if (savedSettingsJson) {
savedSettings = JSON.parse(savedSettingsJson);
}
} catch (e) {
console.error('加载已保存设置失败:', e);
return;
}
// 删除设置
delete savedSettings[name];
// 保存到本地存储
localStorage.setItem('savedSettings', JSON.stringify(savedSettings));
// 更新已保存设置列表
updateSavedSettingsList();
// 显示通知
showNotification(`已删除设置: ${name}`, 'success');
}
// 重置所有设置
function resetAllSettings() {
if (!confirm('确定要重置所有设置吗?这将删除所有已保存的设置和历史记录。')) {
return;
}
// 清除本地存储
localStorage.removeItem('savedSettings');
localStorage.removeItem('processingHistory');
localStorage.removeItem('theme');
// 重置UI
formatSelect.value = 'jpeg';
qualityRange.value = 80;
qualityValue.textContent = '80';
widthInput.value = '';
heightInput.value = '';
lockRatio.checked = true;
presetRatio.value = 'free';
brightnessRange.value = 0;
contrastRange.value = 0;
saturationRange.value = 0;
blurRange.value = 0;
currentFilter = 'none';
document.querySelectorAll('.filter-preview-item').forEach(item => {
item.classList.remove('active');
});
document.querySelector('.filter-preview-item[data-filter="none"]')?.classList.add('active');
currentCompressionPreset = 'balanced';
document.querySelectorAll('.compression-preset').forEach(preset => {
preset.classList.remove('active');
});
document.querySelector('.compression-preset[data-preset="balanced"]')?.classList.add('active');
compressionRange.value = 50;
updateCompressionValue();
renamePattern.value = '{name}_{index}';
renameAddPrefix.checked = true;
renamePrefix.value = 'img_';
renameAddSuffix.checked = false;
renameSuffix.value = '';
renameStartIndex.checked = false;
renameStartIndexValue.value = '1';
watermarkType = 'text';
document.querySelectorAll('.watermark-tab').forEach(tab => {
tab.classList.remove('active');
});
document.querySelector('.watermark-tab[data-tab="text"]')?.classList.add('active');
document.querySelectorAll('.watermark-content > div').forEach(content => {
content.classList.remove('active');
});
document.getElementById('textWatermark')?.classList.add('active');
watermarkPosition = 'middle-center';
document.querySelectorAll('.position-btn').forEach(btn => {
btn.classList.remove('active');
});
document.querySelector('.position-btn[data-position="middle-center"]')?.classList.add('active');
document.getElementById('watermarkText').value = '藤原图片转换器';
document.getElementById('watermarkFontSize').value = 24;
document.getElementById('watermarkColor').value = '#ffffff';
document.getElementById('watermarkOpacity').value = 70;
document.getElementById('watermarkRotation').value = 0;
document.getElementById('watermarkImageSize').value = 20;
document.getElementById('watermarkImageOpacity').value = 70;
document.getElementById('watermarkShadow').checked = false;
document.getElementById('watermarkBorder').checked = false;
document.getElementById('watermarkPattern').checked = false;
document.querySelectorAll('.watermark-pattern').forEach(pattern => {
pattern.classList.remove('active');
});
document.querySelector('.watermark-pattern[data-pattern="none"]')?.classList.add('active');
autoSaveSettings.checked = true;
loadLastSettings.checked = true;
document.body.classList.remove('light-theme');
themeToggle.innerHTML = '<i class="fas fa-sun"></i>';
// 更新已保存设置列表
updateSavedSettingsList();
// 更新历史记录列表
processingHistory = [];
updateHistoryList();
// 更新预览
updatePreview();
updateFormatPreviews();
updateWatermarkPreview();
updateRenamePreview();
// 显示通知
showNotification('所有设置已重置', 'success');
}
// 保存处理历史
function saveProcessingHistory(settings) {
const history = {
id: Date.now(),
date: new Date(),
settings: settings,
imageCount: selectedImages.size
};
processingHistory.unshift(history);
// 限制历史记录数量
if (processingHistory.length > 20) {
processingHistory.pop();
}
// 保存到本地存储
localStorage.setItem('processingHistory', JSON.stringify(processingHistory));
// 更新历史记录UI
updateHistoryList();
}
// 更新历史记录列表
function updateHistoryList() {
const historyList = document.getElementById('historyList');
if (processingHistory.length === 0) {
historyList.innerHTML = '<div class="history-empty">暂无处理历史记录</div>';
return;
}
historyList.innerHTML = '';
processingHistory.forEach(history => {
const historyItem = document.createElement('div');
historyItem.className = 'history-item';
const date = new Date(history.date);
const dateStr = `${date.getFullYear()}-${(date.getMonth()+1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
historyItem.innerHTML = `
<div class="history-date">${dateStr}</div>
<div class="history-details">
处理了 ${history.imageCount} 张图片
<br>格式: ${getFormatName(history.settings.format)}
${history.settings.width ? `<br>尺寸: ${history.settings.width} x ${history.settings.height}` : ''}
${history.settings.watermark ? '<br>应用了水印' : ''}
</div>
<div class="history-actions">
<button class="history-btn apply-history" data-id="${history.id}">应用设置</button>
<button class="history-btn delete-history" data-id="${history.id}">删除</button>
</div>
`;
historyList.appendChild(historyItem);
});
// 添加事件监听
document.querySelectorAll('.apply-history').forEach(btn => {
btn.addEventListener('click', function() {
const id = parseInt(this.dataset.id);
applyHistorySettings(id);
});
});
document.querySelectorAll('.delete-history').forEach(btn => {
btn.addEventListener('click', function() {
const id = parseInt(this.dataset.id);
deleteHistory(id);
});
});
}
// 获取格式名称
function getFormatName(format) {
switch(format) {
case 'jpeg':
return 'JPEG';
case 'png':
return 'PNG';
case 'webp':
return 'WebP';
case 'gif':
return 'GIF';
default:
return format.toUpperCase();
}
}
// 应用历史设置
function applyHistorySettings(id) {
const history = processingHistory.find(h => h.id === id);
if (!history) return;
const settings = history.settings;
// 应用格式设置
formatSelect.value = settings.format;
// 触发change事件
const event = new Event('change');
formatSelect.dispatchEvent(event);
// 应用质量设置
if (settings.quality) {
qualityRange.value = settings.quality;
qualityValue.textContent = qualityRange.value;
}
// 应用尺寸设置
if (settings.width) widthInput.value = settings.width;
if (settings.height) heightInput.value = settings.height;
// 应用编辑设置
if (settings.brightness) brightnessRange.value = settings.brightness;
if (settings.contrast) contrastRange.value = settings.contrast;
if (settings.saturation) saturationRange.value = settings.saturation;
if (settings.blur) blurRange.value = settings.blur;
// 更新预览
updatePreview();
updateFormatPreviews();
// 显示通知
showNotification('已应用历史设置', 'success');
}
// 删除历史记录
function deleteHistory(id) {
processingHistory = processingHistory.filter(h => h.id !== id);
// 保存到本地存储
localStorage.setItem('processingHistory', JSON.stringify(processingHistory));
// 更新历史记录UI
updateHistoryList();
// 显示通知
showNotification('已删除历史记录', 'success');
}
// 清空历史记录
function clearHistory() {
if (!confirm('确定要清空所有历史记录吗?')) return;
processingHistory = [];
localStorage.removeItem('processingHistory');
updateHistoryList();
// 显示通知
showNotification('已清空所有历史记录', 'success');
}
// 显示通知
function showNotification(message, type = 'info') {
notification.textContent = message;
notification.className = `notification ${type}`;
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
}
// 格式化文件大小
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// 拖拽相关函数
function handleDragStart(e) {
if (!isDraggingEnabled) return false;
dragSrcEl = this;
dragStartIndex = parseInt(this.dataset.index);
// 设置拖拽数据
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
// 添加样式
this.classList.add('dragging');
return true;
}
function handleDragOver(e) {
if (!isDraggingEnabled) return false;
if (e.preventDefault) {
e.preventDefault();
}
e.dataTransfer.dropEffect = 'move';
return false;
}
function handleDragEnter(e) {
if (!isDraggingEnabled) return;
this.classList.add('over');
}
function handleDragLeave(e) {
if (!isDraggingEnabled) return;
this.classList.remove('over');
}
function handleDragEnd(e) {
if (!isDraggingEnabled) return;
// 移除所有拖拽相关的样式
const items = document.querySelectorAll('.image-item');
items.forEach(item => {
item.classList.remove('dragging', 'over');
});
}
function handleDrop(e) {
if (!isDraggingEnabled) return false;
// 阻止默认行为
if (e.stopPropagation) {
e.stopPropagation();
}
// 如果拖拽源和目标相同,不做任何处理
if (dragSrcEl === this) {
return false;
}
// 获取拖放目标的索引
const dragEndIndex = parseInt(this.dataset.index);
// 排序逻辑
reorderImages(dragStartIndex, dragEndIndex);
return false;
}
function reorderImages(fromIndex, toIndex) {
// 获取实际页面中的索引
const startIndex = (currentPage - 1) * imagesPerPage;
const actualFromIndex = startIndex + fromIndex;
const actualToIndex = startIndex + toIndex;
// 更新图片数组
const [movedImage] = images.splice(actualFromIndex, 1);
images.splice(actualToIndex, 0, movedImage);
// 更新选中集合
const newSelectedImages = new Set();
selectedImages.forEach(index => {
if (index === actualFromIndex) {
newSelectedImages.add(actualToIndex);
} else if (index > actualFromIndex && index <= actualToIndex) {
newSelectedImages.add(index - 1);
} else if (index < actualFromIndex && index >= actualToIndex) {
newSelectedImages.add(index + 1);
} else {
newSelectedImages.add(index);
}
});
selectedImages = newSelectedImages;
// 更新当前预览索引
if (currentPreviewIndex === actualFromIndex) {
currentPreviewIndex = actualToIndex;
} else if (currentPreviewIndex > actualFromIndex && currentPreviewIndex <= actualToIndex) {
currentPreviewIndex--;
} else if (currentPreviewIndex < actualFromIndex && currentPreviewIndex >= actualToIndex) {
currentPreviewIndex++;
}
// 重新渲染图片网格
renderImageGrid();
}
function toggleDragSort() {
const btn = document.getElementById('toggleDragBtn');
isDraggingEnabled = !isDraggingEnabled;
if (isDraggingEnabled) {
btn.innerHTML = '<i class="fas fa-sort"></i> 禁用排序';
btn.classList.add('active');
// 更新所有图片项的draggable属性
document.querySelectorAll('.image-item').forEach(item => {
item.setAttribute('draggable', 'true');
});
} else {
btn.innerHTML = '<i class="fas fa-sort"></i> 启用排序';
btn.classList.remove('active');
// 更新所有图片项的draggable属性
document.querySelectorAll('.image-item').forEach(item => {
item.setAttribute('draggable', 'false');
});
}
}
// 预加载相关函数
function updatePreloadSettings() {
const enablePreload = document.getElementById('enablePreload').checked;
const preloadNextPage = document.getElementById('preloadNextPage').checked;
const preloadHighRes = document.getElementById('preloadHighRes').checked;
if (enablePreload) {
startPreloading();
} else {
// 清空预加载队列
preloadQueue = [];
preloadingImages.clear();
// 移除所有预加载指示器
document.querySelectorAll('.preload-indicator, .preload-status').forEach(el => el.remove());
}
// 更新预加载统计
updatePreloadStats();
}
function startPreloading() {
const enablePreload = document.getElementById('enablePreload').checked;
if (!enablePreload) return;
const preloadNextPage = document.getElementById('preloadNextPage').checked;
const preloadHighRes = document.getElementById('preloadHighRes').checked;
// 清空当前队列
preloadQueue = [];
// 计算需要预加载的图片范围
const startIndex = (currentPage - 1) * imagesPerPage;
let endIndex = Math.min(startIndex + imagesPerPage, images.length);
// 如果启用了下一页预加载,扩展范围
if (preloadNextPage) {
endIndex = Math.min(startIndex + imagesPerPage * 2, images.length);
}
// 将图片添加到预加载队列
for (let i = startIndex; i < endIndex; i++) {
if (!preloadedImages.has(i) && !preloadingImages.has(i)) {
preloadQueue.push({
index: i,
highRes: preloadHighRes
});
}
}
// 更新预加载统计
document.getElementById('totalImagesToPreload').textContent = preloadedImages.size + preloadQueue.length;
// 开始预加载过程
processPreloadQueue();
}
function processPreloadQueue() {
if (preloadQueue.length === 0 || isPreloading) return;
isPreloading = true;
const item = preloadQueue.shift();
const index = item.index;
const highRes = item.highRes;
// 标记为正在预加载
preloadingImages.add(index);
// 添加预加载指示器到UI
const imageItem = document.querySelector(`.image-item[data-index="${index - (currentPage - 1) * imagesPerPage}"]`);
if (imageItem) {
// 移除现有的指示器(如果有)
imageItem.querySelectorAll('.preload-indicator, .preload-status').forEach(el => el.remove());
// 添加新的指示器
const indicator = document.createElement('div');
indicator.className = 'preload-indicator';
imageItem.appendChild(indicator);
const status = document.createElement('div');
status.className = 'preload-status';
status.textContent = highRes ? '高分辨率预加载中' : '预加载中';
imageItem.appendChild(status);
}
// 创建一个新的图像对象进行预加载
const img = new Image();
img.onload = function() {
// 预加载成功
preloadingImages.delete(index);
preloadedImages.add(index);
// 更新内存使用估计
const imgSize = (img.width * img.height * 4) / (1024 * 1024); // 估计MB
estimatedMemoryUsage += imgSize;
// 更新UI
if (imageItem) {
imageItem.querySelectorAll('.preload-indicator, .preload-status').forEach(el => el.remove());
const status = document.createElement('div');
status.className = 'preload-status';
status.textContent = '已预加载';
status.style.background = 'rgba(102, 187, 106, 0.7)'; // 绿色背景
imageItem.appendChild(status);
// 3秒后移除状态指示
setTimeout(() => {
if (status.parentNode) {
status.remove();
}
}, 3000);
}
// 更新预加载统计
updatePreloadStats();
// 继续处理队列
isPreloading = false;
processPreloadQueue();
};
img.onerror = function() {
// 预加载失败
preloadingImages.delete(index);
// 更新UI
if (imageItem) {
imageItem.querySelectorAll('.preload-indicator, .preload-status').forEach(el => el.remove());
const status = document.createElement('div');
status.className = 'preload-status';
status.textContent = '预加载失败';
status.style.background = 'rgba(239, 83, 80, 0.7)'; // 红色背景
imageItem.appendChild(status);
// 3秒后移除状态指示
setTimeout(() => {
if (status.parentNode) {
status.remove();
}
}, 3000);
}
// 继续处理队列
isPreloading = false;
processPreloadQueue();
};
// 开始加载图片
img.src = images[index].src;
}
function updatePreloadStats() {
document.getElementById('preloadedCount').textContent = preloadedImages.size;
document.getElementById('totalImagesToPreload').textContent = preloadedImages.size + preloadQueue.length + preloadingImages.size;
// 更新内存使用显示
const memoryInMB = Math.round(estimatedMemoryUsage);
document.getElementById('memoryUsage').textContent = `${memoryInMB} MB`;
// 更新内存使用条
const percentage = Math.min((estimatedMemoryUsage / maxMemoryUsage) * 100, 100);
document.getElementById('memoryUsageBar').style.width = `${percentage}%`;
// 如果内存使用过高,改变颜色
if (percentage > 80) {
document.getElementById('memoryUsageBar').style.background = 'var(--warning-color)';
} else {
document.getElementById('memoryUsageBar').style.background = 'var(--accent-color)';
}
}
// 检查是否有保存的主题设置
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'light') {
document.body.classList.add('light-theme');
themeToggle.innerHTML = '<i class="fas fa-moon"></i>';
}
// 初始化水印预览
updateWatermarkPreview();
// 加载历史记录
try {
const savedHistory = localStorage.getItem('processingHistory');
if (savedHistory) {
processingHistory = JSON.parse(savedHistory);
updateHistoryList();
}
} catch (e) {
console.error('加载历史记录失败:', e);
processingHistory = [];
}
});
</script>
</body>
</html>
index.html
style.css
index.js