Initial commit: 台銀即期賣出匯率換算工具

This commit is contained in:
Timmy
2026-04-17 12:36:08 +08:00
commit 1091f049ba
16 changed files with 3812 additions and 0 deletions

130
bot_rates_gui.py Normal file
View File

@@ -0,0 +1,130 @@
import csv
import io
import threading
import tkinter as tk
import urllib.request
from datetime import datetime
from tkinter import ttk
URL = "https://rate.bot.com.tw/xrt/flcsv/0/day"
TARGETS = [
("USD", "美元"),
("JPY", "日幣"),
("EUR", "歐元"),
]
def fetch_rates():
req = urllib.request.Request(URL, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(req, timeout=10) as resp:
raw = resp.read().decode("utf-8-sig", errors="replace")
reader = csv.reader(io.StringIO(raw))
next(reader, None)
rates = {}
wanted = {code for code, _ in TARGETS}
for row in reader:
if not row:
continue
code = row[0].strip()
if code in wanted:
rates[code] = float(row[13])
return rates
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("台灣銀行 即期賣出匯率")
self.geometry("360x320")
self.configure(bg="#f5f6fa")
self.resizable(False, False)
style = ttk.Style(self)
try:
style.theme_use("vista")
except tk.TclError:
style.theme_use("clam")
style.configure("TFrame", background="#f5f6fa")
style.configure("Title.TLabel",
background="#f5f6fa",
foreground="#1f2937",
font=("Microsoft JhengHei UI", 14, "bold"))
style.configure("Name.TLabel",
background="#ffffff",
foreground="#374151",
font=("Microsoft JhengHei UI", 12))
style.configure("Rate.TLabel",
background="#ffffff",
foreground="#0f766e",
font=("Consolas", 14, "bold"))
style.configure("Hint.TLabel",
background="#f5f6fa",
foreground="#6b7280",
font=("Microsoft JhengHei UI", 9))
style.configure("Accent.TButton",
font=("Microsoft JhengHei UI", 10, "bold"),
padding=6)
container = ttk.Frame(self, padding=(18, 16))
container.pack(fill="both", expand=True)
ttk.Label(container, text="台灣銀行 即期賣出", style="Title.TLabel").pack(anchor="w")
ttk.Label(container, text="資料來源rate.bot.com.tw",
style="Hint.TLabel").pack(anchor="w", pady=(2, 12))
card = tk.Frame(container, bg="#ffffff", highlightthickness=1,
highlightbackground="#e5e7eb")
card.pack(fill="x")
self.rate_labels = {}
for i, (code, name) in enumerate(TARGETS):
row = tk.Frame(card, bg="#ffffff")
row.pack(fill="x", padx=14, pady=10)
ttk.Label(row, text=f"{name} {code}", style="Name.TLabel").pack(side="left")
lbl = ttk.Label(row, text="--", style="Rate.TLabel")
lbl.pack(side="right")
self.rate_labels[code] = lbl
if i < len(TARGETS) - 1:
tk.Frame(card, bg="#f1f2f6", height=1).pack(fill="x", padx=10)
bottom = ttk.Frame(container)
bottom.pack(fill="x", pady=(14, 0))
self.status = ttk.Label(bottom, text="載入中…", style="Hint.TLabel")
self.status.pack(side="left")
self.btn = ttk.Button(bottom, text="重新整理", style="Accent.TButton",
command=self.refresh)
self.btn.pack(side="right")
self.after(100, self.refresh)
def refresh(self):
self.btn.state(["disabled"])
self.status.configure(text="載入中…", foreground="#6b7280")
threading.Thread(target=self._worker, daemon=True).start()
def _worker(self):
try:
rates = fetch_rates()
self.after(0, self._on_success, rates)
except Exception as e:
self.after(0, self._on_error, e)
def _on_success(self, rates):
for code, _ in TARGETS:
value = rates.get(code)
text = f"{value:.4f}" if value is not None else "查無資料"
self.rate_labels[code].configure(text=text)
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.status.configure(text=f"更新時間:{now}", foreground="#6b7280")
self.btn.state(["!disabled"])
def _on_error(self, err):
self.status.configure(text=f"載入失敗:{err}", foreground="#b91c1c")
self.btn.state(["!disabled"])
if __name__ == "__main__":
App().mainloop()