修复前端
This commit is contained in:
+123
-97
@@ -6343,112 +6343,138 @@ def api_key_kline():
|
|||||||
@app.route("/add_key", methods=["POST"])
|
@app.route("/add_key", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def add_key():
|
def add_key():
|
||||||
d = request.form
|
conn = None
|
||||||
symbol = normalize_symbol_input(d.get("symbol"))
|
try:
|
||||||
if not symbol:
|
d = request.form
|
||||||
flash("symbol 不能为空")
|
symbol = normalize_symbol_input(d.get("symbol"))
|
||||||
return redirect("/key_monitor")
|
if not symbol:
|
||||||
direction_sel = (d.get("direction") or "").strip().lower()
|
flash("symbol 不能为空")
|
||||||
if direction_sel not in ("long", "short"):
|
return redirect("/key_monitor")
|
||||||
flash("请选择做多或做空")
|
direction_sel = (d.get("direction") or "").strip().lower()
|
||||||
return redirect("/key_monitor")
|
if direction_sel not in ("long", "short"):
|
||||||
mt = (d.get("type") or "").strip()
|
flash("请选择做多或做空")
|
||||||
allowed_types = (
|
return redirect("/key_monitor")
|
||||||
tuple(KEY_MONITOR_AUTO_TYPES)
|
mt = (d.get("type") or "").strip()
|
||||||
+ tuple(KEY_MONITOR_ALERT_ONLY_TYPES)
|
allowed_types = (
|
||||||
+ tuple(FIB_KEY_MONITOR_TYPES)
|
tuple(KEY_MONITOR_AUTO_TYPES)
|
||||||
)
|
+ tuple(KEY_MONITOR_ALERT_ONLY_TYPES)
|
||||||
if mt not in allowed_types:
|
+ tuple(FIB_KEY_MONITOR_TYPES)
|
||||||
flash("监控类型无效")
|
)
|
||||||
return redirect("/key_monitor")
|
if mt not in allowed_types:
|
||||||
rank, total = _daily_volume_rank(symbol)
|
flash("监控类型无效")
|
||||||
if rank is None:
|
return redirect("/key_monitor")
|
||||||
flash("日成交量排名读取失败,请稍后重试")
|
rank, total = _daily_volume_rank(symbol)
|
||||||
return redirect("/key_monitor")
|
if rank is None:
|
||||||
if rank > KEY_DAILY_VOLUME_RANK_MAX:
|
flash("日成交量排名读取失败,请稍后重试")
|
||||||
flash(f"{symbol} 当前日成交量排名为 {rank}/{total},不在前{KEY_DAILY_VOLUME_RANK_MAX},已拒绝添加关键位")
|
return redirect("/key_monitor")
|
||||||
return redirect("/key_monitor")
|
if rank > KEY_DAILY_VOLUME_RANK_MAX:
|
||||||
conn = get_db()
|
|
||||||
if mt in KEY_MONITOR_AUTO_TYPES:
|
|
||||||
occupied = get_active_position_count(conn)
|
|
||||||
if occupied >= MAX_ACTIVE_POSITIONS:
|
|
||||||
conn.close()
|
|
||||||
flash(
|
flash(
|
||||||
f"当前持仓已达上限({occupied}/{MAX_ACTIVE_POSITIONS}):无法添加「箱体突破 / 收敛突破」。"
|
f"{symbol} 当前日成交量排名为 {rank}/{total},不在前{KEY_DAILY_VOLUME_RANK_MAX},已拒绝添加关键位"
|
||||||
"请平仓后再试,或使用「关键阻力位/关键支撑位」(仅单次提醒)。"
|
|
||||||
)
|
)
|
||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
ex_sym_key = normalize_exchange_symbol(symbol)
|
conn = get_db()
|
||||||
try:
|
if mt in KEY_MONITOR_AUTO_TYPES:
|
||||||
ensure_markets_loaded()
|
occupied = get_active_position_count(conn)
|
||||||
except Exception:
|
if occupied >= MAX_ACTIVE_POSITIONS:
|
||||||
pass
|
conn.close()
|
||||||
upper_px = round_price_to_exchange(ex_sym_key, float(d["upper"]))
|
conn = None
|
||||||
lower_px = round_price_to_exchange(ex_sym_key, float(d["lower"]))
|
flash(
|
||||||
be_flag = parse_breakeven_enabled_form(d.get("breakeven_enabled"))
|
f"当前持仓已达上限({occupied}/{MAX_ACTIVE_POSITIONS}):无法添加「箱体突破 / 收敛突破」。"
|
||||||
if is_fib_key_monitor_type(mt):
|
"请平仓后再试,或使用「关键阻力位/关键支撑位」(仅单次提醒)。"
|
||||||
ok_fib, err_fib = _add_fib_key_monitor(
|
)
|
||||||
conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=be_flag,
|
return redirect("/key_monitor")
|
||||||
|
ex_sym_key = normalize_exchange_symbol(symbol)
|
||||||
|
try:
|
||||||
|
ensure_markets_loaded()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
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(
|
||||||
|
conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=be_flag,
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
conn = None
|
||||||
|
if not ok_fib:
|
||||||
|
flash(err_fib or "斐波监控添加失败")
|
||||||
|
return redirect("/key_monitor")
|
||||||
|
flash(
|
||||||
|
f"斐波监控已添加,限价单已挂出({symbol} 日成交量排名 {rank}/{total})"
|
||||||
|
f"|移动保本:{'开' if be_flag else '关'}"
|
||||||
|
)
|
||||||
|
return redirect("/key_monitor")
|
||||||
|
sl_tp_mode = "standard"
|
||||||
|
manual_tp = None
|
||||||
|
if mt in KEY_MONITOR_AUTO_TYPES:
|
||||||
|
sl_tp_mode = normalize_sl_tp_mode(d.get("sl_tp_mode"))
|
||||||
|
if sl_tp_mode == "trend_manual":
|
||||||
|
try:
|
||||||
|
manual_tp = float(d.get("manual_take_profit") or 0)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
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)
|
||||||
|
if mtpx is not None:
|
||||||
|
manual_tp = float(mtpx)
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO key_monitors "
|
||||||
|
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?)",
|
||||||
|
(symbol, mt, direction_sel, upper_px, lower_px, sl_tp_mode, manual_tp, be_flag),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
if not ok_fib:
|
conn = None
|
||||||
flash(err_fib or "斐波监控添加失败")
|
ctr = False
|
||||||
return redirect("/key_monitor")
|
try:
|
||||||
flash(
|
coin4h_status, _, _ = _status_by_ema55(symbol, "4h")
|
||||||
f"斐波监控已添加,限价单已挂出({symbol} 日成交量排名 {rank}/{total})"
|
ctr = (direction_sel == "long" and coin4h_status == "空头") or (
|
||||||
f"|移动保本:{'开' if be_flag else '关'}"
|
direction_sel == "short" and coin4h_status == "多头"
|
||||||
)
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
extra = ""
|
||||||
|
if mt in KEY_MONITOR_AUTO_TYPES:
|
||||||
|
extra = f"|方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'开' if be_flag else '关'}"
|
||||||
|
flash(f"添加成功({symbol} 日成交量排名 {rank}/{total}){extra}")
|
||||||
|
if ctr:
|
||||||
|
flash(
|
||||||
|
"⚠️ 4h EMA55 提示:当前与所选方向逆势;「箱体突破/收敛突破」在条件满足时仍会按计划自动市价开仓,请注意仓位。"
|
||||||
|
)
|
||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
sl_tp_mode = "standard"
|
except Exception as e:
|
||||||
manual_tp = None
|
if conn is not None:
|
||||||
if mt in KEY_MONITOR_AUTO_TYPES:
|
|
||||||
sl_tp_mode = normalize_sl_tp_mode(d.get("sl_tp_mode"))
|
|
||||||
if sl_tp_mode == "trend_manual":
|
|
||||||
try:
|
try:
|
||||||
manual_tp = float(d.get("manual_take_profit") or 0)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
manual_tp = 0
|
|
||||||
if manual_tp <= 0:
|
|
||||||
conn.close()
|
conn.close()
|
||||||
flash("趋势单方案须填写有效止盈价")
|
except Exception:
|
||||||
return redirect("/key_monitor")
|
pass
|
||||||
if direction_sel == "long" and manual_tp <= upper_px:
|
flash(f"添加关键位失败:{e}")
|
||||||
conn.close()
|
return redirect("/key_monitor")
|
||||||
flash("做多趋势单:止盈价应高于上沿(阻力)")
|
|
||||||
return redirect("/key_monitor")
|
|
||||||
if direction_sel == "short" and manual_tp >= lower_px:
|
|
||||||
conn.close()
|
|
||||||
flash("做空趋势单:止盈价应低于下沿(支撑)")
|
|
||||||
return redirect("/key_monitor")
|
|
||||||
mtpx = round_price_to_exchange(ex_sym_key, manual_tp)
|
|
||||||
if mtpx is not None:
|
|
||||||
manual_tp = float(mtpx)
|
|
||||||
conn.execute(
|
|
||||||
"INSERT INTO key_monitors "
|
|
||||||
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled) "
|
|
||||||
"VALUES (?,?,?,?,?,?,?,?)",
|
|
||||||
(symbol, mt, direction_sel, upper_px, lower_px, sl_tp_mode, manual_tp, be_flag),
|
|
||||||
)
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
ctr = False
|
|
||||||
try:
|
|
||||||
coin4h_status, _, _ = _status_by_ema55(symbol, "4h")
|
|
||||||
ctr = (direction_sel == "long" and coin4h_status == "空头") or (
|
|
||||||
direction_sel == "short" and coin4h_status == "多头"
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
extra = ""
|
|
||||||
if mt in KEY_MONITOR_AUTO_TYPES:
|
|
||||||
extra = f"|方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'开' if be_flag else '关'}"
|
|
||||||
flash(f"添加成功({symbol} 日成交量排名 {rank}/{total}){extra}")
|
|
||||||
if ctr:
|
|
||||||
flash(
|
|
||||||
"⚠️ 4h EMA55 提示:当前与所选方向逆势;「箱体突破/收敛突破」在条件满足时仍会按计划自动市价开仓,请注意仓位。"
|
|
||||||
)
|
|
||||||
return redirect("/key_monitor")
|
|
||||||
|
|
||||||
@app.route("/add_order", methods=["POST"])
|
@app.route("/add_order", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
|
|||||||
+32
-4
@@ -38,27 +38,55 @@ def _row_to_dict(row):
|
|||||||
return dict(row) if row is not None else {}
|
return dict(row) if row is not None else {}
|
||||||
|
|
||||||
|
|
||||||
|
_FAIL_HINTS = (
|
||||||
|
"失败",
|
||||||
|
"错误",
|
||||||
|
"拒绝",
|
||||||
|
"无效",
|
||||||
|
"缺少",
|
||||||
|
"无法",
|
||||||
|
"过期",
|
||||||
|
"未达",
|
||||||
|
"不能为空",
|
||||||
|
"已有",
|
||||||
|
"不允许",
|
||||||
|
"异常",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _invoke_view(view_name: str, path: str, form=None) -> dict:
|
def _invoke_view(view_name: str, path: str, form=None) -> dict:
|
||||||
views = _ctx().get("views") or {}
|
views = _ctx().get("views") or {}
|
||||||
view = views.get(view_name)
|
view = views.get(view_name)
|
||||||
if not view:
|
if not view:
|
||||||
return {"ok": False, "messages": [f"未配置视图 {view_name}"]}
|
return {"ok": False, "messages": [f"未配置视图 {view_name}"]}
|
||||||
data = form if form is not None else request.form
|
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):
|
with current_app.test_request_context(path, method="POST", data=data):
|
||||||
session["logged_in"] = True
|
session["logged_in"] = True
|
||||||
try:
|
try:
|
||||||
view()
|
view()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"ok": False, "messages": [str(e)]}
|
return {"ok": False, "messages": [str(e)]}
|
||||||
msgs = [str(x) for x in get_flashed_messages()]
|
try:
|
||||||
|
msgs = [str(x) for x in get_flashed_messages()]
|
||||||
|
except Exception as e:
|
||||||
|
return {"ok": False, "messages": [f"读取提示信息失败: {e}"]}
|
||||||
ok = True
|
ok = True
|
||||||
for m in msgs:
|
for m in msgs:
|
||||||
if any(k in m for k in ("失败", "错误", "拒绝", "无效", "缺少", "无法", "过期")):
|
if any(k in m for k in _FAIL_HINTS):
|
||||||
ok = False
|
ok = False
|
||||||
break
|
break
|
||||||
return {"ok": ok, "messages": msgs}
|
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(
|
def install_on_app(
|
||||||
app,
|
app,
|
||||||
*,
|
*,
|
||||||
@@ -155,12 +183,12 @@ def register_hub_routes(app):
|
|||||||
@app.route("/api/hub/add_order", methods=["POST"])
|
@app.route("/api/hub/add_order", methods=["POST"])
|
||||||
@_hub_auth_required
|
@_hub_auth_required
|
||||||
def api_hub_add_order():
|
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"])
|
@app.route("/api/hub/add_key", methods=["POST"])
|
||||||
@_hub_auth_required
|
@_hub_auth_required
|
||||||
def api_hub_add_key():
|
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"])
|
@app.route("/api/hub/trend/preview", methods=["POST"])
|
||||||
@_hub_auth_required
|
@_hub_auth_required
|
||||||
|
|||||||
+69
-10
@@ -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(
|
async def _fetch_flask_json(
|
||||||
client: httpx.AsyncClient, ex: dict, path: str, method: str = "GET", data=None
|
client: httpx.AsyncClient, ex: dict, path: str, method: str = "GET", data=None
|
||||||
) -> dict | None:
|
) -> dict | None:
|
||||||
@@ -194,8 +234,11 @@ async def _fetch_flask_json(
|
|||||||
else:
|
else:
|
||||||
r = await client.post(f"{base}{path}", headers=_hub_headers(), data=data, timeout=120.0)
|
r = await client.post(f"{base}{path}", headers=_hub_headers(), data=data, timeout=120.0)
|
||||||
if r.status_code >= 400:
|
if r.status_code >= 400:
|
||||||
return {"ok": False, "status": r.status_code, "text": (r.text or "")[:500]}
|
parsed = _parse_http_json_body(r)
|
||||||
return r.json()
|
parsed.setdefault("ok", False)
|
||||||
|
parsed.setdefault("status", r.status_code)
|
||||||
|
return parsed
|
||||||
|
return _parse_http_json_body(r)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"ok": False, "error": str(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)
|
ex = _find_exchange(exchange_id)
|
||||||
if not ex or not ex.get("enabled"):
|
if not ex or not ex.get("enabled"):
|
||||||
raise HTTPException(status_code=404, detail="账户未启用")
|
raise HTTPException(status_code=404, detail="账户未启用")
|
||||||
form = await request.form()
|
try:
|
||||||
async with httpx.AsyncClient() as client:
|
form = _form_plain_dict(await request.form())
|
||||||
result = await _fetch_flask_json(client, ex, "/api/hub/add_order", "POST", dict(form))
|
async with httpx.AsyncClient() as client:
|
||||||
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}")
|
@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="账户未启用")
|
raise HTTPException(status_code=404, detail="账户未启用")
|
||||||
if "key" not in (ex.get("capabilities") or []):
|
if "key" not in (ex.get("capabilities") or []):
|
||||||
raise HTTPException(status_code=400, detail="该账户不支持关键位")
|
raise HTTPException(status_code=400, detail="该账户不支持关键位")
|
||||||
form = await request.form()
|
try:
|
||||||
async with httpx.AsyncClient() as client:
|
form = _form_plain_dict(await request.form())
|
||||||
result = await _fetch_flask_json(client, ex, "/api/hub/add_key", "POST", dict(form))
|
async with httpx.AsyncClient() as client:
|
||||||
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}")
|
@app.post("/api/trade/trend/preview/{exchange_id}")
|
||||||
|
|||||||
@@ -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) {
|
async function submitForm(path, formEl) {
|
||||||
const id = document.getElementById("trade-account").value;
|
const id = document.getElementById("trade-account").value;
|
||||||
const fd = new FormData(formEl);
|
const fd = new FormData(formEl);
|
||||||
try {
|
try {
|
||||||
const r = await fetch(path + encodeURIComponent(id), { method: "POST", body: fd });
|
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 res = j.result || {};
|
||||||
const msgs = (res.messages || []).join("\n") || JSON.stringify(res, null, 2);
|
const msgs =
|
||||||
showToast(msgs, !res.ok);
|
(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) {
|
if (res.ok && res.preview) {
|
||||||
showTrendPreview(res);
|
showTrendPreview(res);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user