First commit

This commit is contained in:
shouchih_chen 2025-06-21 13:22:28 +08:00
commit e2b14ca7fe
6 changed files with 186 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
__pycache__/*
uploads/*
generated/*
*.pyc
*.pyo

BIN
_ref/style.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

49
app.py Normal file
View File

@ -0,0 +1,49 @@
import os
import uuid
from flask import Flask, render_template, request, send_file
app = Flask(__name__)
UPLOAD_FOLDER = 'uploads'
GENERATED_IMAGES_FOLDER = 'generated_images'
ALLOWED_EXTENSIONS = {'csv'}
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['GENERATED_IMAGES_FOLDER'] = GENERATED_IMAGES_FOLDER
# 確保目錄存在
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(GENERATED_IMAGES_FOLDER, exist_ok=True)
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route("/")
def index():
return render_template("index.html")
@app.route("/hello")
def hello():
return "Hello, World!"
@app.route('/upload_csv', methods=['POST'])
def upload_csv():
print("Received request to upload CSV file")
if 'csvFile' not in request.files:
return "No file part", 400
file = request.files['csvFile']
if file.filename == '':
return "No selected file", 400
if file and allowed_file(file.filename):
unique_filename = str(uuid.uuid4()) + '.csv'
csv_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
file.save(csv_path)
return "File uploaded successfully", 200
if __name__ == '__main__':
# 當在生產環境部署時,不建議使用 debug=True
# 可以指定 host='0.0.0.0' 讓外部可訪問 (在防火牆允許的情況下)
app.run(debug=True, host='127.0.0.1', port=5000)

2
generate_images.py Normal file
View File

@ -0,0 +1,2 @@
import sys
from PySide6.QtCore import QCoreApplication, QTimer, QObject, Signal, Slot

67
templates/index.html Normal file
View File

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSV 轉圖片工具</title>
</head>
<body>
<h1>上傳 CSV 產生圖片</h1>
<form id="uploadForm" enctype="multipart/form-data">
<input type="file" id="csvFile" name="csvFile" accept=".csv">
<button type="submit">生成圖片</button>
</form>
<div id="status"></div>
<div id="downloadLink" style="display: none;">
<a id="downloadAnchor" href="#" download="generated_image.png">點擊下載圖片</a>
</div>
<script>
document.getElementById('uploadForm').addEventListener('submit', async function(event) {
event.preventDefault(); // 阻止表單預設提交行為
const fileInput = document.getElementById('csvFile');
const statusDiv = document.getElementById('status');
const downloadLinkDiv = document.getElementById('downloadLink');
const downloadAnchor = document.getElementById('downloadAnchor');
if (!fileInput.files.length) {
statusDiv.textContent = '請選擇一個 CSV 檔案。';
return;
}
statusDiv.textContent = '檔案上傳中,請稍候...';
downloadLinkDiv.style.display = 'none';
const formData = new FormData();
formData.append('csvFile', fileInput.files[0]);
try {
const response = await fetch('/upload_csv', { // 請確保後端路由與此匹配
method: 'POST',
body: formData
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`伺服器錯誤: ${response.status} - ${errorText}`);
}
// 假設後端會返回圖片的 Blob 或直接是檔案的 URL
// 如果後端直接返回圖片檔案,可以直接創建 Blob URL
const imageBlob = await response.blob();
const imageUrl = URL.createObjectURL(imageBlob);
downloadAnchor.href = imageUrl;
downloadAnchor.download = 'generated_image.png'; // 可以從後端獲取圖片名稱
statusDiv.textContent = '圖片已成功生成!';
downloadLinkDiv.style.display = 'block';
} catch (error) {
console.error('上傳或生成圖片失敗:', error);
statusDiv.textContent = `生成圖片失敗: ${error.message || '未知錯誤'}`;
}
});
</script>
</body>
</html>

63
架構圖.drawio Normal file
View File

@ -0,0 +1,63 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/27.0.9 Chrome/134.0.6998.205 Electron/35.4.0 Safari/537.36" version="27.0.9">
<diagram name="第 1 页" id="l6rvUWkf71IvbISpAjO9">
<mxGraphModel dx="788" dy="477" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="T8w4GiLeSybUu9a6AjsZ-7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="T8w4GiLeSybUu9a6AjsZ-1" target="T8w4GiLeSybUu9a6AjsZ-3">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="T8w4GiLeSybUu9a6AjsZ-1" value="index.html" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="90" y="190" width="100" height="60" as="geometry" />
</mxCell>
<mxCell id="T8w4GiLeSybUu9a6AjsZ-2" value="" style="endArrow=none;dashed=1;html=1;rounded=0;strokeWidth=3;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="240" y="400" as="sourcePoint" />
<mxPoint x="240" y="90" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="T8w4GiLeSybUu9a6AjsZ-10" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="T8w4GiLeSybUu9a6AjsZ-3" target="T8w4GiLeSybUu9a6AjsZ-4">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="T8w4GiLeSybUu9a6AjsZ-14" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.75;exitDx=0;exitDy=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;" edge="1" parent="1" source="T8w4GiLeSybUu9a6AjsZ-3" target="T8w4GiLeSybUu9a6AjsZ-1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="T8w4GiLeSybUu9a6AjsZ-3" value="Flask" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="300" y="190" width="100" height="60" as="geometry" />
</mxCell>
<mxCell id="T8w4GiLeSybUu9a6AjsZ-12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="T8w4GiLeSybUu9a6AjsZ-4">
<mxGeometry relative="1" as="geometry">
<mxPoint x="580" y="290" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="T8w4GiLeSybUu9a6AjsZ-4" value="圓形鑰匙圈產生器" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="530" y="190" width="100" height="60" as="geometry" />
</mxCell>
<mxCell id="T8w4GiLeSybUu9a6AjsZ-5" value="前端" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
<mxGeometry x="110" y="110" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="T8w4GiLeSybUu9a6AjsZ-6" value="後端" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
<mxGeometry x="320" y="110" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="T8w4GiLeSybUu9a6AjsZ-9" value="POST" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="240" y="190" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="T8w4GiLeSybUu9a6AjsZ-11" value="run python" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="420" y="190" width="70" height="30" as="geometry" />
</mxCell>
<mxCell id="T8w4GiLeSybUu9a6AjsZ-16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.75;entryDx=0;entryDy=0;" edge="1" parent="1" source="T8w4GiLeSybUu9a6AjsZ-13" target="T8w4GiLeSybUu9a6AjsZ-3">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="T8w4GiLeSybUu9a6AjsZ-13" value="圖片產生至某個資料夾" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="530" y="290" width="100" height="60" as="geometry" />
</mxCell>
<mxCell id="T8w4GiLeSybUu9a6AjsZ-15" value="Send File" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="240" y="230" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="T8w4GiLeSybUu9a6AjsZ-17" value="Load Pictures form image folder" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="430" y="320" width="100" height="40" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>