modified: main.py
modified: tools/zhangdan_flask.py
This commit is contained in:
@@ -173,17 +173,23 @@ class MyPlugin(Star):
|
|||||||
self.sqllitaa.commit()
|
self.sqllitaa.commit()
|
||||||
cur.close()
|
cur.close()
|
||||||
self.sqllitaa.close()
|
self.sqllitaa.close()
|
||||||
|
# 删除源文件
|
||||||
|
file_list = os.listdir(f"{self.data_path}")
|
||||||
try:
|
try:
|
||||||
# 删除源文件
|
|
||||||
file_list = os.listdir(f"{self.data_path}")
|
|
||||||
for nam in file_list:
|
for nam in file_list:
|
||||||
if "zhangdan" not in nam:
|
if "zhangdan" not in nam:
|
||||||
os.remove(str(self.data_path)+"\\"+nam)
|
os.remove(str(self.data_path)+"\\"+nam)
|
||||||
file_listaa = os.listdir(f"{self.data_path}/zhangdan/")
|
file_listaa = os.listdir(f"{self.data_path}/zhangdan/")
|
||||||
for namaa in file_listaa:
|
for namaa in file_listaa:
|
||||||
os.remove(str(self.data_path)+"\\zhangdan\\"+namaa)
|
os.remove(str(self.data_path)+"\\zhangdan\\"+namaa)
|
||||||
except IOError as e:
|
except Exception as e:
|
||||||
logger.error(f"文件删除失败 原因{e}")
|
for nam in file_list:
|
||||||
|
if "zhangdan" not in nam:
|
||||||
|
os.remove(str(self.data_path)+"/"+nam)
|
||||||
|
file_listaa = os.listdir(f"{self.data_path}/zhangdan/")
|
||||||
|
for namaa in file_listaa:
|
||||||
|
os.remove(str(self.data_path)+"/zhangdan/"+namaa)
|
||||||
|
|
||||||
yield event.plain_result("保存完成__使用/chazhang 查询最后10条记录")
|
yield event.plain_result("保存完成__使用/chazhang 查询最后10条记录")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
yield event.plain_result(f"保存失败{e}")
|
yield event.plain_result(f"保存失败{e}")
|
||||||
@@ -217,7 +223,7 @@ class MyPlugin(Star):
|
|||||||
yuju = []
|
yuju = []
|
||||||
try:
|
try:
|
||||||
if ".csv" in filename:
|
if ".csv" in filename:
|
||||||
ata = pd.read_csv(filename, encoding='GB2312', on_bad_lines='skip',header=22)
|
ata = pd.read_csv(filename, encoding='gb18030', on_bad_lines='skip',header=22)
|
||||||
# 读取表格数据
|
# 读取表格数据
|
||||||
for biao in range(10):
|
for biao in range(10):
|
||||||
# 暂存当行数据
|
# 暂存当行数据
|
||||||
|
|||||||
+83
-12
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
账单查询服务 - Flask版
|
账单查询服务 - Flask版
|
||||||
@@ -6,13 +7,17 @@
|
|||||||
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
import hashlib
|
||||||
import secrets
|
import secrets
|
||||||
import threading
|
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 import Flask, render_template_string, jsonify, request, session, redirect, url_for
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ===== 🔧 常量 =====
|
# ===== 🔧 常量 =====
|
||||||
PORT = 5001
|
PORT = 5001
|
||||||
PID_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".flask.pid")
|
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)
|
print(e)
|
||||||
|
|
||||||
|
|
||||||
def start_server(host, port, debug):
|
def start_server(host="0.0.0.0", port=PORT, debug=False):
|
||||||
"""在子线程中启动 Flask 服务,主线程不受影响"""
|
"""在子线程中启动 Flask 服务,主线程不受影响"""
|
||||||
global _server, _server_thread
|
global _server, _server_thread
|
||||||
|
|
||||||
@@ -59,7 +64,6 @@ def start_server(host, port, debug):
|
|||||||
|
|
||||||
def stop_server():
|
def stop_server():
|
||||||
"""只关闭 Flask 子线程,主线程继续运行"""
|
"""只关闭 Flask 子线程,主线程继续运行"""
|
||||||
|
|
||||||
global _server, _server_thread
|
global _server, _server_thread
|
||||||
|
|
||||||
if _server:
|
if _server:
|
||||||
@@ -93,6 +97,7 @@ def server_status():
|
|||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = secrets.token_hex(32)
|
app.secret_key = secrets.token_hex(32)
|
||||||
CORS(app)
|
CORS(app)
|
||||||
|
|
||||||
def check_auth():
|
def check_auth():
|
||||||
"""检查是否已登录"""
|
"""检查是否已登录"""
|
||||||
return session.get("authenticated") is True
|
return session.get("authenticated") is True
|
||||||
@@ -303,12 +308,24 @@ def api_summary():
|
|||||||
""", params)
|
""", params)
|
||||||
by_wallet = [dict(r) for r in cur.fetchall()]
|
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()
|
conn.close()
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"overview": overview,
|
"overview": overview,
|
||||||
"by_type": by_type,
|
"by_type": by_type,
|
||||||
"by_channel": by_channel,
|
"by_channel": by_channel,
|
||||||
"by_wallet": by_wallet,
|
"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.income { border-left-color: #4ade80; }
|
||||||
.summary-item.expense { border-left-color: #f87171; }
|
.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-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; }
|
.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 { padding: 10px 12px; border-radius: 8px; }
|
||||||
.summary-item .s-label { font-size: 0.85em; }
|
.summary-item .s-label { font-size: 0.85em; }
|
||||||
|
.summary-item .s-count { font-size: 0.95em; }
|
||||||
.summary-item .s-value { 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 active" data-tab="type" onclick="switchTab('type')">按类型</div>
|
||||||
<div class="tab" data-tab="channel" onclick="switchTab('channel')">按渠道</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="wallet" onclick="switchTab('wallet')">按钱包</div>
|
||||||
|
<div class="tab" data-tab="name" onclick="switchTab('name')">按名称</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="summary-grid" id="summaryGrid"></div>
|
<div class="summary-grid" id="summaryGrid"></div>
|
||||||
|
|
||||||
@@ -1019,17 +1042,20 @@ function renderSummary(tab) {
|
|||||||
let items = [];
|
let items = [];
|
||||||
if (tab === "type") items = summaryData.by_type || [];
|
if (tab === "type") items = summaryData.by_type || [];
|
||||||
else if (tab === "channel") items = summaryData.by_channel || [];
|
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 => {
|
grid.innerHTML = items.map(r => {
|
||||||
const isIn = r.payto === "收入";
|
const isIn = r.payto === "收入";
|
||||||
|
const label = r.paytype || r.payqd || r.paybox || r.payname || "未知";
|
||||||
return `<div class="summary-item ${isIn ? 'income' : 'expense'}">
|
return `<div class="summary-item ${isIn ? 'income' : 'expense'}">
|
||||||
<div>
|
<div class="s-left">
|
||||||
<div class="s-label">${r.paytype || r.payqd || r.paybox || "未知"}</div>
|
<div class="s-label">${label}</div>
|
||||||
<div class="s-meta">${r.count} 笔 · ${r.payto}</div>
|
<div class="s-meta">${r.payto}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="s-value" style="color:${isIn ? '#4ade80' : '#f87171'}">
|
<div class="s-right">
|
||||||
¥${Number(r.total).toFixed(2)}
|
<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>
|
||||||
</div>`;
|
</div>`;
|
||||||
}).join("");
|
}).join("");
|
||||||
@@ -1052,7 +1078,7 @@ function shutdown() {
|
|||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
})
|
})
|
||||||
.catch(() => alert("关闭失败,请重试"));
|
.catch(() => alert("关闭程序已启动"));
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
@@ -1060,3 +1086,48 @@ init();
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</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()
|
||||||
|
|||||||
Reference in New Issue
Block a user