Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 454c938728 | |||
| 7bde3ce551 | |||
| 7ad0538039 | |||
| 5aa802e960 |
10
app.py
10
app.py
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
图片编辑器 - Image Editor v1.2.0
|
图片编辑器 - Image Editor v1.2.3
|
||||||
前端图片处理工具:合并、分割、挖孔填充、圆形切图、文字图片等
|
前端图片处理工具:合并、分割、挖孔填充、圆形切图、文字图片等
|
||||||
v1.2.0: 右侧面板实时配置,无需弹框
|
v1.2.3: 执行操作后保持当前配置面板
|
||||||
|
|
||||||
端口: 19018
|
端口: 19018
|
||||||
"""
|
"""
|
||||||
@@ -25,7 +25,7 @@ def index():
|
|||||||
|
|
||||||
@app.route('/api/health')
|
@app.route('/api/health')
|
||||||
def health():
|
def health():
|
||||||
return jsonify({'status': 'ok', 'version': '1.2.0', 'time': datetime.now().isoformat()})
|
return jsonify({'status': 'ok', 'version': '1.2.2', 'time': datetime.now().isoformat()})
|
||||||
|
|
||||||
@app.route('/api/save', methods=['POST'])
|
@app.route('/api/save', methods=['POST'])
|
||||||
def save_image():
|
def save_image():
|
||||||
@@ -64,9 +64,9 @@ def list_images():
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print("=" * 50)
|
print("=" * 50)
|
||||||
print("图片编辑器 - Image Editor v1.2.0")
|
print("图片编辑器 - Image Editor v1.2.2")
|
||||||
print("=" * 50)
|
print("=" * 50)
|
||||||
print("右侧面板实时配置,操作更流畅")
|
print("右侧面板始终显示,首页显示快捷入口")
|
||||||
print(f"访问地址: http://localhost:19018")
|
print(f"访问地址: http://localhost:19018")
|
||||||
print("=" * 50)
|
print("=" * 50)
|
||||||
app.run(host='0.0.0.0', port=19018, debug=True)
|
app.run(host='0.0.0.0', port=19018, debug=True)
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>图片编辑器 - Image Editor v1.2.0</title>
|
<title>图片编辑器 - Image Editor v1.2.3</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
@@ -145,14 +145,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧配置面板 -->
|
<!-- 右侧配置面板 - 始终显示 -->
|
||||||
<div id="rightPanel" class="right-panel hidden-panel w-80 bg-white shadow-lg border-l">
|
<div id="rightPanel" class="right-panel w-80 bg-white shadow-lg border-l">
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h2 id="panelTitle" class="font-bold text-gray-800">配置</h2>
|
<h2 id="panelTitle" class="font-bold text-gray-800">操作面板</h2>
|
||||||
<button onclick="closePanel()" class="text-gray-500 hover:text-gray-700">
|
|
||||||
<i class="ri-close-line text-xl"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 配置内容区域 -->
|
<!-- 配置内容区域 -->
|
||||||
@@ -195,10 +192,26 @@
|
|||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
const container = document.getElementById('canvasContainer');
|
const container = document.getElementById('canvasContainer');
|
||||||
|
|
||||||
|
// 返回首页面板
|
||||||
|
function goHome() {
|
||||||
|
document.querySelectorAll('.tool-btn').forEach(btn => btn.classList.remove('active'));
|
||||||
|
state.currentTool = null;
|
||||||
|
showPanel('home');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新当前工具面板(执行操作后保持当前配置)
|
||||||
|
function refreshCurrentPanel() {
|
||||||
|
if (state.currentTool) {
|
||||||
|
showPanel(state.currentTool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
function init() {
|
function init() {
|
||||||
setupDragDrop();
|
setupDragDrop();
|
||||||
setupCanvas();
|
setupCanvas();
|
||||||
|
// 显示默认首页面板
|
||||||
|
showPanel('home');
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupCanvas() {
|
function setupCanvas() {
|
||||||
@@ -230,7 +243,7 @@
|
|||||||
|
|
||||||
state.currentTool = tool;
|
state.currentTool = tool;
|
||||||
|
|
||||||
// 显示右侧配置面板
|
// 更新右侧配置面板内容(不隐藏)
|
||||||
showPanel(tool);
|
showPanel(tool);
|
||||||
|
|
||||||
// 上传图片直接触发文件选择
|
// 上传图片直接触发文件选择
|
||||||
@@ -239,9 +252,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示右侧面板
|
// 显示右侧面板内容
|
||||||
function showPanel(tool) {
|
function showPanel(tool) {
|
||||||
const panel = document.getElementById('rightPanel');
|
const panel = document.getElementById('rightPanel');
|
||||||
|
// 确保面板始终显示
|
||||||
panel.classList.remove('hidden-panel');
|
panel.classList.remove('hidden-panel');
|
||||||
|
|
||||||
const configs = getPanelConfig(tool);
|
const configs = getPanelConfig(tool);
|
||||||
@@ -250,16 +264,59 @@
|
|||||||
document.getElementById('panelActions').innerHTML = configs.actions;
|
document.getElementById('panelActions').innerHTML = configs.actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭面板
|
|
||||||
function closePanel() {
|
|
||||||
const panel = document.getElementById('rightPanel');
|
|
||||||
panel.classList.add('hidden-panel');
|
|
||||||
document.querySelectorAll('.tool-btn').forEach(btn => btn.classList.remove('active'));
|
|
||||||
state.currentTool = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取各工具的配置面板内容
|
// 获取各工具的配置面板内容
|
||||||
function getPanelConfig(tool) {
|
function getPanelConfig(tool) {
|
||||||
|
// 默认首页面板
|
||||||
|
if (!tool || tool === 'home') {
|
||||||
|
return {
|
||||||
|
title: '欢迎使用',
|
||||||
|
content: `
|
||||||
|
<div class="space-y-4">
|
||||||
|
<p class="text-sm text-gray-600">点击顶部功能按钮开始编辑图片</p>
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<button onclick="setTool('upload')" class="p-3 bg-blue-50 rounded-lg hover:bg-blue-100 text-center">
|
||||||
|
<i class="ri-upload-cloud-line text-2xl text-blue-500"></i>
|
||||||
|
<p class="text-xs mt-1">上传图片</p>
|
||||||
|
</button>
|
||||||
|
<button onclick="setTool('merge')" class="p-3 bg-green-50 rounded-lg hover:bg-green-100 text-center">
|
||||||
|
<i class="ri-stack-line text-2xl text-green-500"></i>
|
||||||
|
<p class="text-xs mt-1">合并图片</p>
|
||||||
|
</button>
|
||||||
|
<button onclick="setTool('split')" class="p-3 bg-purple-50 rounded-lg hover:bg-purple-100 text-center">
|
||||||
|
<i class="ri-layout-grid-line text-2xl text-purple-500"></i>
|
||||||
|
<p class="text-xs mt-1">分割图片</p>
|
||||||
|
</button>
|
||||||
|
<button onclick="setTool('circle')" class="p-3 bg-orange-50 rounded-lg hover:bg-orange-100 text-center">
|
||||||
|
<i class="ri-checkbox-blank-circle-line text-2xl text-orange-500"></i>
|
||||||
|
<p class="text-xs mt-1">圆形切图</p>
|
||||||
|
</button>
|
||||||
|
<button onclick="setTool('text')" class="p-3 bg-pink-50 rounded-lg hover:bg-pink-100 text-center">
|
||||||
|
<i class="ri-font-size text-2xl text-pink-500"></i>
|
||||||
|
<p class="text-xs mt-1">文字图片</p>
|
||||||
|
</button>
|
||||||
|
<button onclick="setTool('resize')" class="p-3 bg-cyan-50 rounded-lg hover:bg-cyan-100 text-center">
|
||||||
|
<i class="ri-expand-diagonal-line text-2xl text-cyan-500"></i>
|
||||||
|
<p class="text-xs mt-1">调整大小</p>
|
||||||
|
</button>
|
||||||
|
<button onclick="setTool('crop')" class="p-3 bg-yellow-50 rounded-lg hover:bg-yellow-100 text-center">
|
||||||
|
<i class="ri-crop-line text-2xl text-yellow-500"></i>
|
||||||
|
<p class="text-xs mt-1">裁剪</p>
|
||||||
|
</button>
|
||||||
|
<button onclick="setTool('hole')" class="p-3 bg-red-50 rounded-lg hover:bg-red-100 text-center">
|
||||||
|
<i class="ri-contrast-drop-line text-2xl text-red-500"></i>
|
||||||
|
<p class="text-xs mt-1">挖孔填充</p>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="border-t pt-3">
|
||||||
|
<p class="text-xs text-gray-500">提示:所有操作都支持撤销恢复</p>
|
||||||
|
<p class="text-xs text-gray-500 mt-1">快捷键:Ctrl+Z 撤销,Ctrl+Y 恢复</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
actions: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
switch(tool) {
|
switch(tool) {
|
||||||
case 'upload':
|
case 'upload':
|
||||||
return {
|
return {
|
||||||
@@ -278,7 +335,7 @@
|
|||||||
return {
|
return {
|
||||||
title: '合并图片',
|
title: '合并图片',
|
||||||
content: `<p class="text-sm text-red-500">请先上传至少2张图片</p>`,
|
content: `<p class="text-sm text-red-500">请先上传至少2张图片</p>`,
|
||||||
actions: `<button onclick="closePanel()" class="px-4 py-2 bg-gray-200 rounded-lg">关闭</button>`
|
actions: `<button onclick="goHome()" class="px-4 py-2 bg-gray-200 rounded-lg">关闭</button>`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,7 +412,7 @@
|
|||||||
return {
|
return {
|
||||||
title: '分割图片',
|
title: '分割图片',
|
||||||
content: `<p class="text-sm text-red-500">请先上传图片</p>`,
|
content: `<p class="text-sm text-red-500">请先上传图片</p>`,
|
||||||
actions: `<button onclick="closePanel()" class="px-4 py-2 bg-gray-200 rounded-lg">关闭</button>`
|
actions: `<button onclick="goHome()" class="px-4 py-2 bg-gray-200 rounded-lg">关闭</button>`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -386,7 +443,7 @@
|
|||||||
return {
|
return {
|
||||||
title: '挖孔填充',
|
title: '挖孔填充',
|
||||||
content: `<p class="text-sm text-red-500">请先上传图片</p>`,
|
content: `<p class="text-sm text-red-500">请先上传图片</p>`,
|
||||||
actions: `<button onclick="closePanel()" class="px-4 py-2 bg-gray-200 rounded-lg">关闭</button>`
|
actions: `<button onclick="goHome()" class="px-4 py-2 bg-gray-200 rounded-lg">关闭</button>`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -421,7 +478,10 @@
|
|||||||
<p class="text-xs text-gray-500">点击"开始选择"后,在画布上拖动选择区域</p>
|
<p class="text-xs text-gray-500">点击"开始选择"后,在画布上拖动选择区域</p>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
actions: `<button onclick="startHoleSelection()" class="px-4 py-2 bg-blue-500 text-white rounded-lg w-full">开始选择</button>`
|
actions: `
|
||||||
|
<button onclick="startHoleSelection()" class="px-4 py-2 bg-blue-500 text-white rounded-lg w-full mb-2">开始选择</button>
|
||||||
|
<button onclick="goHome()" class="px-4 py-2 bg-gray-200 rounded-lg w-full">关闭面板</button>
|
||||||
|
`
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'circle':
|
case 'circle':
|
||||||
@@ -429,7 +489,7 @@
|
|||||||
return {
|
return {
|
||||||
title: '圆形切图',
|
title: '圆形切图',
|
||||||
content: `<p class="text-sm text-red-500">请先上传图片</p>`,
|
content: `<p class="text-sm text-red-500">请先上传图片</p>`,
|
||||||
actions: `<button onclick="closePanel()" class="px-4 py-2 bg-gray-200 rounded-lg">关闭</button>`
|
actions: `<button onclick="goHome()" class="px-4 py-2 bg-gray-200 rounded-lg">关闭</button>`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -515,7 +575,7 @@
|
|||||||
return {
|
return {
|
||||||
title: '调整大小',
|
title: '调整大小',
|
||||||
content: `<p class="text-sm text-red-500">请先上传图片</p>`,
|
content: `<p class="text-sm text-red-500">请先上传图片</p>`,
|
||||||
actions: `<button onclick="closePanel()" class="px-4 py-2 bg-gray-200 rounded-lg">关闭</button>`
|
actions: `<button onclick="goHome()" class="px-4 py-2 bg-gray-200 rounded-lg">关闭</button>`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const img = state.images[0];
|
const img = state.images[0];
|
||||||
@@ -546,7 +606,7 @@
|
|||||||
return {
|
return {
|
||||||
title: '裁剪',
|
title: '裁剪',
|
||||||
content: `<p class="text-sm text-red-500">请先上传图片</p>`,
|
content: `<p class="text-sm text-red-500">请先上传图片</p>`,
|
||||||
actions: `<button onclick="closePanel()" class="px-4 py-2 bg-gray-200 rounded-lg">关闭</button>`
|
actions: `<button onclick="goHome()" class="px-4 py-2 bg-gray-200 rounded-lg">关闭</button>`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -939,7 +999,7 @@
|
|||||||
const mergedData = canvas.toDataURL('image/png');
|
const mergedData = canvas.toDataURL('image/png');
|
||||||
state.images = [];
|
state.images = [];
|
||||||
loadImage(mergedData, 'merged');
|
loadImage(mergedData, 'merged');
|
||||||
closePanel();
|
refreshCurrentPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
function applySplit() {
|
function applySplit() {
|
||||||
@@ -976,7 +1036,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('splitResults').classList.remove('hidden');
|
document.getElementById('splitResults').classList.remove('hidden');
|
||||||
closePanel();
|
refreshCurrentPanel();
|
||||||
saveState(`分割 (${cols}x${rows})`);
|
saveState(`分割 (${cols}x${rows})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1117,7 +1177,7 @@
|
|||||||
const circleData = canvas.toDataURL('image/png');
|
const circleData = canvas.toDataURL('image/png');
|
||||||
state.images = [];
|
state.images = [];
|
||||||
loadImage(circleData, 'circle');
|
loadImage(circleData, 'circle');
|
||||||
closePanel();
|
refreshCurrentPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTextImage() {
|
function createTextImage() {
|
||||||
@@ -1168,7 +1228,7 @@
|
|||||||
const textData = canvas.toDataURL('image/png');
|
const textData = canvas.toDataURL('image/png');
|
||||||
state.images = [];
|
state.images = [];
|
||||||
loadImage(textData, 'text');
|
loadImage(textData, 'text');
|
||||||
closePanel();
|
refreshCurrentPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyResize() {
|
function applyResize() {
|
||||||
@@ -1186,7 +1246,7 @@
|
|||||||
const resizedData = canvas.toDataURL('image/png');
|
const resizedData = canvas.toDataURL('image/png');
|
||||||
state.images = [];
|
state.images = [];
|
||||||
loadImage(resizedData, 'resized');
|
loadImage(resizedData, 'resized');
|
||||||
closePanel();
|
refreshCurrentPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyCrop() {
|
function applyCrop() {
|
||||||
@@ -1206,7 +1266,7 @@
|
|||||||
const croppedData = canvas.toDataURL('image/png');
|
const croppedData = canvas.toDataURL('image/png');
|
||||||
state.images = [];
|
state.images = [];
|
||||||
loadImage(croppedData, 'cropped');
|
loadImage(croppedData, 'cropped');
|
||||||
closePanel();
|
refreshCurrentPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveImage() {
|
function saveImage() {
|
||||||
@@ -1245,7 +1305,6 @@
|
|||||||
document.getElementById('historyList').innerHTML = '<span class="text-gray-400 text-xs">暂无操作</span>';
|
document.getElementById('historyList').innerHTML = '<span class="text-gray-400 text-xs">暂无操作</span>';
|
||||||
showEmptyHint();
|
showEmptyHint();
|
||||||
document.getElementById('splitResults').classList.add('hidden');
|
document.getElementById('splitResults').classList.add('hidden');
|
||||||
closePanel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听合并面板渲染
|
// 监听合并面板渲染
|
||||||
|
|||||||
Reference in New Issue
Block a user