修复前端

This commit is contained in:
dekun
2026-05-22 11:29:34 +08:00
parent 371dec6999
commit 427f94e0e8
4 changed files with 246 additions and 114 deletions
+29 -3
View File
@@ -6343,6 +6343,8 @@ def api_key_kline():
@app.route("/add_key", methods=["POST"])
@login_required
def add_key():
conn = None
try:
d = request.form
symbol = normalize_symbol_input(d.get("symbol"))
if not symbol:
@@ -6366,13 +6368,16 @@ def add_key():
flash("日成交量排名读取失败,请稍后重试")
return redirect("/key_monitor")
if rank > KEY_DAILY_VOLUME_RANK_MAX:
flash(f"{symbol} 当前日成交量排名为 {rank}/{total},不在前{KEY_DAILY_VOLUME_RANK_MAX},已拒绝添加关键位")
flash(
f"{symbol} 当前日成交量排名为 {rank}/{total},不在前{KEY_DAILY_VOLUME_RANK_MAX},已拒绝添加关键位"
)
return redirect("/key_monitor")
conn = get_db()
if mt in KEY_MONITOR_AUTO_TYPES:
occupied = get_active_position_count(conn)
if occupied >= MAX_ACTIVE_POSITIONS:
conn.close()
conn = None
flash(
f"当前持仓已达上限({occupied}/{MAX_ACTIVE_POSITIONS}):无法添加「箱体突破 / 收敛突破」。"
"请平仓后再试,或使用「关键阻力位/关键支撑位」(仅单次提醒)。"
@@ -6383,8 +6388,16 @@ def add_key():
ensure_markets_loaded()
except Exception:
pass
upper_px = round_price_to_exchange(ex_sym_key, float(d["upper"]))
lower_px = round_price_to_exchange(ex_sym_key, float(d["lower"]))
try:
upper_raw = float(d.get("upper") or 0)
lower_raw = float(d.get("lower") or 0)
except (TypeError, ValueError):
conn.close()
conn = None
flash("上下沿须为有效数字")
return redirect("/key_monitor")
upper_px = round_price_to_exchange(ex_sym_key, upper_raw)
lower_px = round_price_to_exchange(ex_sym_key, lower_raw)
be_flag = parse_breakeven_enabled_form(d.get("breakeven_enabled"))
if is_fib_key_monitor_type(mt):
ok_fib, err_fib = _add_fib_key_monitor(
@@ -6392,6 +6405,7 @@ def add_key():
)
conn.commit()
conn.close()
conn = None
if not ok_fib:
flash(err_fib or "斐波监控添加失败")
return redirect("/key_monitor")
@@ -6411,14 +6425,17 @@ def add_key():
manual_tp = 0
if manual_tp <= 0:
conn.close()
conn = None
flash("趋势单方案须填写有效止盈价")
return redirect("/key_monitor")
if direction_sel == "long" and manual_tp <= upper_px:
conn.close()
conn = None
flash("做多趋势单:止盈价应高于上沿(阻力)")
return redirect("/key_monitor")
if direction_sel == "short" and manual_tp >= lower_px:
conn.close()
conn = None
flash("做空趋势单:止盈价应低于下沿(支撑)")
return redirect("/key_monitor")
mtpx = round_price_to_exchange(ex_sym_key, manual_tp)
@@ -6432,6 +6449,7 @@ def add_key():
)
conn.commit()
conn.close()
conn = None
ctr = False
try:
coin4h_status, _, _ = _status_by_ema55(symbol, "4h")
@@ -6449,6 +6467,14 @@ def add_key():
"⚠️ 4h EMA55 提示:当前与所选方向逆势;「箱体突破/收敛突破」在条件满足时仍会按计划自动市价开仓,请注意仓位。"
)
return redirect("/key_monitor")
except Exception as e:
if conn is not None:
try:
conn.close()
except Exception:
pass
flash(f"添加关键位失败:{e}")
return redirect("/key_monitor")
@app.route("/add_order", methods=["POST"])
@login_required
+31 -3
View File
@@ -38,27 +38,55 @@ def _row_to_dict(row):
return dict(row) if row is not None else {}
_FAIL_HINTS = (
"失败",
"错误",
"拒绝",
"无效",
"缺少",
"无法",
"过期",
"未达",
"不能为空",
"已有",
"不允许",
"异常",
)
def _invoke_view(view_name: str, path: str, form=None) -> dict:
views = _ctx().get("views") or {}
view = views.get(view_name)
if not view:
return {"ok": False, "messages": [f"未配置视图 {view_name}"]}
data = form if form is not None else request.form
if hasattr(data, "items") and not isinstance(data, dict):
data = {k: v for k, v in data.items()}
with current_app.test_request_context(path, method="POST", data=data):
session["logged_in"] = True
try:
view()
except Exception as e:
return {"ok": False, "messages": [str(e)]}
try:
msgs = [str(x) for x in get_flashed_messages()]
except Exception as e:
return {"ok": False, "messages": [f"读取提示信息失败: {e}"]}
ok = True
for m in msgs:
if any(k in m for k in ("失败", "错误", "拒绝", "无效", "缺少", "无法", "过期")):
if any(k in m for k in _FAIL_HINTS):
ok = False
break
return {"ok": ok, "messages": msgs}
def _hub_json(view_name: str, path: str, form=None):
try:
return jsonify(_invoke_view(view_name, path, form=form))
except Exception as e:
return jsonify({"ok": False, "messages": [str(e)]})
def install_on_app(
app,
*,
@@ -155,12 +183,12 @@ def register_hub_routes(app):
@app.route("/api/hub/add_order", methods=["POST"])
@_hub_auth_required
def api_hub_add_order():
return jsonify(_invoke_view("add_order", "/trade"))
return _hub_json("add_order", "/add_order")
@app.route("/api/hub/add_key", methods=["POST"])
@_hub_auth_required
def api_hub_add_key():
return jsonify(_invoke_view("add_key", "/key_monitor"))
return _hub_json("add_key", "/add_key")
@app.route("/api/hub/trend/preview", methods=["POST"])
@_hub_auth_required
+67 -8
View File
@@ -182,6 +182,46 @@ async def _fetch_agent_status(client: httpx.AsyncClient, ex: dict) -> dict:
}
def _exchange_brief(ex: dict | None) -> dict | None:
if not ex:
return None
return {
"id": ex.get("id"),
"key": ex.get("key"),
"name": ex.get("name"),
}
def _form_plain_dict(form) -> dict[str, str]:
out: dict[str, str] = {}
for k, v in form.multi_items() if hasattr(form, "multi_items") else form.items():
if hasattr(v, "read"):
continue
out[str(k)] = "" if v is None else str(v)
return out
def _parse_http_json_body(r: httpx.Response) -> dict:
text = (r.text or "").strip()
if not text:
return {"ok": False, "status": r.status_code, "text": "(empty body)"}
try:
data = r.json()
if isinstance(data, dict):
return data
return {"ok": False, "status": r.status_code, "text": text[:500]}
except Exception:
snippet = text[:500]
if snippet.lstrip().lower().startswith("<!") or "internal server error" in snippet.lower():
return {
"ok": False,
"status": r.status_code,
"messages": [f"实例返回 HTML 错误(HTTP {r.status_code}),请查看该 Flask 日志"],
"text": snippet,
}
return {"ok": False, "status": r.status_code, "messages": [snippet], "text": snippet}
async def _fetch_flask_json(
client: httpx.AsyncClient, ex: dict, path: str, method: str = "GET", data=None
) -> dict | None:
@@ -194,8 +234,11 @@ async def _fetch_flask_json(
else:
r = await client.post(f"{base}{path}", headers=_hub_headers(), data=data, timeout=120.0)
if r.status_code >= 400:
return {"ok": False, "status": r.status_code, "text": (r.text or "")[:500]}
return r.json()
parsed = _parse_http_json_body(r)
parsed.setdefault("ok", False)
parsed.setdefault("status", r.status_code)
return parsed
return _parse_http_json_body(r)
except Exception as e:
return {"ok": False, "error": str(e)}
@@ -308,10 +351,18 @@ async def api_trade_order(exchange_id: str, request: Request):
ex = _find_exchange(exchange_id)
if not ex or not ex.get("enabled"):
raise HTTPException(status_code=404, detail="账户未启用")
form = await request.form()
try:
form = _form_plain_dict(await request.form())
async with httpx.AsyncClient() as client:
result = await _fetch_flask_json(client, ex, "/api/hub/add_order", "POST", dict(form))
return {"exchange": ex, "result": result}
result = await _fetch_flask_json(client, ex, "/api/hub/add_order", "POST", form)
return {"exchange": _exchange_brief(ex), "result": result}
except HTTPException:
raise
except Exception as e:
return JSONResponse(
{"exchange": _exchange_brief(ex), "result": {"ok": False, "messages": [str(e)]}},
status_code=200,
)
@app.post("/api/trade/key/{exchange_id}")
@@ -321,10 +372,18 @@ async def api_trade_key(exchange_id: str, request: Request):
raise HTTPException(status_code=404, detail="账户未启用")
if "key" not in (ex.get("capabilities") or []):
raise HTTPException(status_code=400, detail="该账户不支持关键位")
form = await request.form()
try:
form = _form_plain_dict(await request.form())
async with httpx.AsyncClient() as client:
result = await _fetch_flask_json(client, ex, "/api/hub/add_key", "POST", dict(form))
return {"exchange": ex, "result": result}
result = await _fetch_flask_json(client, ex, "/api/hub/add_key", "POST", form)
return {"exchange": _exchange_brief(ex), "result": result}
except HTTPException:
raise
except Exception as e:
return JSONResponse(
{"exchange": _exchange_brief(ex), "result": {"ok": False, "messages": [str(e)]}},
status_code=200,
)
@app.post("/api/trade/trend/preview/{exchange_id}")
+22 -3
View File
@@ -294,15 +294,34 @@
}
}
async function parseJsonResponse(r) {
const text = await r.text();
if (!text) return {};
try {
return JSON.parse(text);
} catch (e) {
const snippet = text.slice(0, 200);
throw new Error(
`HTTP ${r.status} 响应不是 JSON${snippet}${text.length > 200 ? "…" : ""}`
);
}
}
async function submitForm(path, formEl) {
const id = document.getElementById("trade-account").value;
const fd = new FormData(formEl);
try {
const r = await fetch(path + encodeURIComponent(id), { method: "POST", body: fd });
const j = await r.json();
const j = await parseJsonResponse(r);
const res = j.result || {};
const msgs = (res.messages || []).join("\n") || JSON.stringify(res, null, 2);
showToast(msgs, !res.ok);
const msgs =
(res.messages || []).join("\n") ||
res.error ||
res.msg ||
res.text ||
JSON.stringify(res, null, 2);
const failed = res.ok === false || r.status >= 400 || !!res.error || !!res.text;
showToast(msgs || (failed ? "操作失败" : "已提交"), failed);
if (res.ok && res.preview) {
showTrendPreview(res);
}