modified: main.py

modified:   tools/zhangdan_flask.py
This commit is contained in:
2026-05-17 08:39:01 +08:00
parent 057cfc3292
commit ac548ae29d
2 changed files with 94 additions and 17 deletions
+83 -12
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env python3
"""
账单查询服务 - Flask版
@@ -6,13 +7,17 @@
import sqlite3
import os
import sys
import signal
import hashlib
import secrets
import threading
import time
from urllib.request import urlopen
from urllib.error import URLError
from flask import Flask, render_template_string, jsonify, request, session, redirect, url_for
from flask_cors import CORS
# ===== 🔧 常量 =====
PORT = 5001
PID_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".flask.pid")
@@ -34,7 +39,7 @@ def runwebui(web_port,web_passwd,datadb):
print(e)
def start_server(host, port, debug):
def start_server(host="0.0.0.0", port=PORT, debug=False):
"""在子线程中启动 Flask 服务,主线程不受影响"""
global _server, _server_thread
@@ -59,7 +64,6 @@ def start_server(host, port, debug):
def stop_server():
"""只关闭 Flask 子线程,主线程继续运行"""
global _server, _server_thread
if _server:
@@ -93,6 +97,7 @@ def server_status():
app = Flask(__name__)
app.secret_key = secrets.token_hex(32)
CORS(app)
def check_auth():
"""检查是否已登录"""
return session.get("authenticated") is True
@@ -303,12 +308,24 @@ def api_summary():
""", params)
by_wallet = [dict(r) for r in cur.fetchall()]
# 按交易名称汇总
cur.execute(f"""
SELECT payname, payto,
COUNT(*) as count,
SUM(CAST(paysize AS REAL)) as total
FROM zhangdan {where}
GROUP BY payname, payto
ORDER BY total DESC
""", params)
by_name = [dict(r) for r in cur.fetchall()]
conn.close()
return jsonify({
"overview": overview,
"by_type": by_type,
"by_channel": by_channel,
"by_wallet": by_wallet,
"by_name": by_name,
})
@@ -609,8 +626,12 @@ HTML_TEMPLATE = r"""<!DOCTYPE html>
}
.summary-item.income { border-left-color: #4ade80; }
.summary-item.expense { border-left-color: #f87171; }
.summary-item .s-label { font-size: 0.9em; }
.summary-item .s-left { flex: 1; min-width: 0; }
.summary-item .s-right { text-align: right; flex-shrink: 0; margin-left: 12px; }
.summary-item .s-label { font-size: 0.9em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.summary-item .s-meta { font-size: 0.75em; color: #888; }
.summary-item .s-count { font-size: 1.1em; font-weight: 700; color: #60a5fa; }
.summary-item .s-count span { font-size: 0.7em; font-weight: 400; color: #888; }
.summary-item .s-value { font-weight: 700; font-size: 1.05em; }
/* 表格 */
@@ -735,6 +756,7 @@ HTML_TEMPLATE = r"""<!DOCTYPE html>
}
.summary-item { padding: 10px 12px; border-radius: 8px; }
.summary-item .s-label { font-size: 0.85em; }
.summary-item .s-count { font-size: 0.95em; }
.summary-item .s-value { font-size: 0.95em; }
/* 表格 */
@@ -776,6 +798,7 @@ HTML_TEMPLATE = r"""<!DOCTYPE html>
<div class="tab active" data-tab="type" onclick="switchTab('type')">按类型</div>
<div class="tab" data-tab="channel" onclick="switchTab('channel')">按渠道</div>
<div class="tab" data-tab="wallet" onclick="switchTab('wallet')">按钱包</div>
<div class="tab" data-tab="name" onclick="switchTab('name')">按名称</div>
</div>
<div class="summary-grid" id="summaryGrid"></div>
@@ -1019,17 +1042,20 @@ function renderSummary(tab) {
let items = [];
if (tab === "type") items = summaryData.by_type || [];
else if (tab === "channel") items = summaryData.by_channel || [];
else items = summaryData.by_wallet || [];
else if (tab === "wallet") items = summaryData.by_wallet || [];
else if (tab === "name") items = summaryData.by_name || [];
grid.innerHTML = items.map(r => {
const isIn = r.payto === "收入";
const label = r.paytype || r.payqd || r.paybox || r.payname || "未知";
return `<div class="summary-item ${isIn ? 'income' : 'expense'}">
<div>
<div class="s-label">${r.paytype || r.payqd || r.paybox || "未知"}</div>
<div class="s-meta">${r.count} 笔 · ${r.payto}</div>
<div class="s-left">
<div class="s-label">${label}</div>
<div class="s-meta">${r.payto}</div>
</div>
<div class="s-value" style="color:${isIn ? '#4ade80' : '#f87171'}">
¥${Number(r.total).toFixed(2)}
<div class="s-right">
<div class="s-count">${r.count} <span>笔</span></div>
<div class="s-value" style="color:${isIn ? '#4ade80' : '#f87171'}">¥${Number(r.total).toFixed(2)}</div>
</div>
</div>`;
}).join("");
@@ -1052,7 +1078,7 @@ function shutdown() {
</div>
</div>`;
})
.catch(() => alert("关闭失败,请重试"));
.catch(() => alert("关闭程序已启动"));
}
init();
@@ -1060,3 +1086,48 @@ init();
</body>
</html>
"""
# if __name__ == "__main__":
# command = sys.argv[1] if len(sys.argv) > 1 else "start"
# # ---------- stop ----------
# if command == "stop":
# # 尝试通过 HTTP 调 shutdown 接口
# try:
# urlopen(f"http://localhost:{PORT}/shutdown?token={SHUTDOWN_TOKEN}", timeout=3)
# print("🛑 已发送关闭指令")
# except URLError:
# print("⚠️ 服务未运行或无法连接")
# sys.exit(0)
# # ---------- status ----------
# elif command == "status":
# info = server_status()
# if info["running"]:
# print(f"✅ Flask 服务运行中 → http://localhost:{info['port']}")
# else:
# print("❌ Flask 服务未运行")
# sys.exit(0)
# # ---------- restart ----------
# elif command == "restart":
# stop_server()
# time.sleep(0.5)
# print("🔄 正在重启...")
# # ---------- start (默认) ----------
# elif command != "start":
# print(f"用法: python3 {sys.argv[0]} [start|stop|restart|status]")
# sys.exit(1)
# # 启动 Flask 子线程
# start_server(host="0.0.0.0", port=PORT, debug=True)
# # 主线程继续做其他事(不会被 Flask 阻塞)
# print("📌 主线程继续运行中... (Ctrl+C 退出)")
# try:
# while True:
# time.sleep(1)
# except KeyboardInterrupt:
# print("\n👋 主线程退出")
# stop_server()