Add personal license agreement and rename product section to tradable symbols.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+549
-548
File diff suppressed because it is too large
Load Diff
+205
-204
@@ -1,204 +1,205 @@
|
||||
/* 科技感增强层 — 与 base.html 变量配合 */
|
||||
|
||||
.tech-bg{
|
||||
position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden;
|
||||
}
|
||||
.tech-grid{
|
||||
position:absolute;inset:0;
|
||||
background-image:
|
||||
linear-gradient(var(--bg-grid) 1px,transparent 1px),
|
||||
linear-gradient(90deg,var(--bg-grid) 1px,transparent 1px);
|
||||
background-size:32px 32px;
|
||||
mask-image:radial-gradient(ellipse 85% 75% at 50% 35%,#000 20%,transparent 75%);
|
||||
}
|
||||
.tech-glow{
|
||||
position:absolute;width:70vmax;height:70vmax;
|
||||
top:-25%;left:50%;transform:translateX(-50%);
|
||||
background:radial-gradient(circle,var(--ambient-glow) 0%,transparent 65%);
|
||||
animation:tech-pulse 8s ease-in-out infinite;
|
||||
}
|
||||
.tech-glow-2{
|
||||
position:absolute;width:50vmax;height:50vmax;
|
||||
bottom:-20%;right:-10%;
|
||||
background:radial-gradient(circle,var(--ambient-glow-2) 0%,transparent 70%);
|
||||
animation:tech-pulse 10s ease-in-out infinite reverse;
|
||||
}
|
||||
.tech-scanline{
|
||||
position:absolute;inset:0;
|
||||
background:repeating-linear-gradient(
|
||||
0deg,
|
||||
transparent,
|
||||
transparent 2px,
|
||||
var(--scanline) 2px,
|
||||
var(--scanline) 3px
|
||||
);
|
||||
opacity:.35;
|
||||
animation:tech-scan 12s linear infinite;
|
||||
}
|
||||
@keyframes tech-pulse{
|
||||
0%,100%{opacity:.55;transform:translateX(-50%) scale(1)}
|
||||
50%{opacity:.85;transform:translateX(-50%) scale(1.05)}
|
||||
}
|
||||
@keyframes tech-scan{
|
||||
0%{transform:translateY(0)}
|
||||
100%{transform:translateY(32px)}
|
||||
}
|
||||
@keyframes tech-shine{
|
||||
0%,100%{opacity:.45}
|
||||
50%{opacity:.9}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce){
|
||||
.tech-glow,.tech-glow-2,.tech-scanline,.card::after,.site-header::after{animation:none}
|
||||
}
|
||||
|
||||
.page-wrap{position:relative;z-index:1}
|
||||
|
||||
.site-header{
|
||||
border-bottom:1px solid var(--border-header);
|
||||
background:transparent;
|
||||
backdrop-filter:none;
|
||||
}
|
||||
.site-header::after{
|
||||
content:"";display:block;height:1px;margin-top:-1px;
|
||||
background:linear-gradient(90deg,transparent,var(--accent),var(--accent-2),transparent);
|
||||
opacity:.7;animation:tech-shine 4s ease-in-out infinite;
|
||||
}
|
||||
.site-title{
|
||||
letter-spacing:.04em;
|
||||
background:linear-gradient(135deg,var(--text-title) 0%,var(--accent) 45%,var(--accent-2) 100%);
|
||||
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
|
||||
background-clip:text;
|
||||
filter:drop-shadow(0 0 24px var(--title-glow));
|
||||
}
|
||||
.site-title-sub{
|
||||
display:block;font-size:.72rem;font-weight:500;
|
||||
letter-spacing:.22em;text-transform:uppercase;
|
||||
color:var(--text-muted);margin-top:.35rem;
|
||||
-webkit-text-fill-color:var(--text-muted);
|
||||
}
|
||||
|
||||
.site-nav a{
|
||||
border-radius:999px;
|
||||
letter-spacing:.02em;
|
||||
position:relative;overflow:hidden;
|
||||
transition:transform .2s,box-shadow .2s,border-color .2s,background .2s;
|
||||
}
|
||||
.site-nav a::before{
|
||||
content:"";position:absolute;inset:0;
|
||||
background:linear-gradient(120deg,transparent,rgba(255,255,255,.06),transparent);
|
||||
opacity:0;transition:opacity .25s;
|
||||
}
|
||||
.site-nav a:hover::before{opacity:1}
|
||||
.site-nav a:hover{
|
||||
transform:translateY(-1px);
|
||||
box-shadow:0 4px 20px var(--nav-hover-glow);
|
||||
}
|
||||
.site-nav a.active{
|
||||
background:linear-gradient(135deg,var(--nav-active),var(--accent-2));
|
||||
border-color:transparent;
|
||||
box-shadow:0 0 20px var(--nav-active-glow),inset 0 1px 0 rgba(255,255,255,.15);
|
||||
}
|
||||
|
||||
.theme-switch-btn:hover{
|
||||
color:var(--text-primary);
|
||||
}
|
||||
.theme-switch-btn.active{
|
||||
box-shadow:0 0 12px var(--btn-glow);
|
||||
}
|
||||
|
||||
.card{
|
||||
border-radius:14px;
|
||||
transition:transform .25s,box-shadow .25s,border-color .25s;
|
||||
}
|
||||
.card:hover{
|
||||
transform:translateY(-2px);
|
||||
border-color:var(--card-border-hover);
|
||||
box-shadow:var(--shadow-card-hover);
|
||||
}
|
||||
.card::after{
|
||||
animation:tech-shine 5s ease-in-out infinite;
|
||||
}
|
||||
.card h2{letter-spacing:.03em}
|
||||
.card h2:before{
|
||||
box-shadow:0 0 12px var(--accent),0 0 4px var(--accent-2);
|
||||
}
|
||||
|
||||
input:focus,select:focus,textarea:focus{
|
||||
box-shadow:0 0 0 3px var(--focus-ring),0 0 16px var(--focus-glow);
|
||||
}
|
||||
button.btn-primary{
|
||||
font-weight:600;letter-spacing:.04em;
|
||||
box-shadow:0 4px 20px var(--btn-glow);
|
||||
transition:transform .15s,box-shadow .2s,opacity .2s;
|
||||
}
|
||||
button.btn-primary:hover{
|
||||
transform:translateY(-1px);
|
||||
box-shadow:0 6px 28px var(--btn-glow-strong);
|
||||
opacity:1;
|
||||
}
|
||||
|
||||
.list-item{
|
||||
transition:border-color .2s,box-shadow .2s,transform .2s;
|
||||
}
|
||||
.list-item:hover{
|
||||
border-color:var(--card-border-hover);
|
||||
box-shadow:0 4px 16px var(--card-glow);
|
||||
}
|
||||
table tbody tr{transition:background .15s}
|
||||
table tbody tr:hover{background:var(--row-hover)}
|
||||
|
||||
.stat-item{
|
||||
backdrop-filter:blur(8px);
|
||||
transition:transform .2s,box-shadow .2s;
|
||||
}
|
||||
.stat-item:hover{
|
||||
transform:translateY(-2px);
|
||||
box-shadow:0 8px 24px var(--card-glow);
|
||||
}
|
||||
.stat-item .value{
|
||||
font-variant-numeric:tabular-nums;
|
||||
letter-spacing:.02em;
|
||||
}
|
||||
|
||||
.pos-card{
|
||||
position:relative;overflow:hidden;
|
||||
transition:border-color .2s,box-shadow .2s;
|
||||
}
|
||||
.pos-card::before{
|
||||
content:"";position:absolute;top:0;left:0;right:0;height:2px;
|
||||
background:linear-gradient(90deg,var(--accent),var(--accent-2));
|
||||
opacity:.5;
|
||||
}
|
||||
.pos-card:hover{
|
||||
border-color:var(--card-border-hover);
|
||||
box-shadow:0 6px 24px var(--card-glow);
|
||||
}
|
||||
|
||||
.badge{letter-spacing:.02em;border:1px solid transparent}
|
||||
.badge.dir{border-color:rgba(76,194,255,.25)}
|
||||
.badge.profit{border-color:rgba(76,217,127,.3)}
|
||||
.badge.loss{border-color:rgba(255,102,102,.3)}
|
||||
|
||||
.modal-box{
|
||||
border:1px solid var(--card-border-hover);
|
||||
box-shadow:var(--shadow-card-hover),0 0 60px var(--card-glow);
|
||||
}
|
||||
|
||||
.flash{
|
||||
box-shadow:0 0 24px var(--focus-glow);
|
||||
letter-spacing:.02em;
|
||||
}
|
||||
|
||||
.profile-spec{
|
||||
border:1px solid var(--card-border-hover);
|
||||
box-shadow:inset 0 0 40px var(--card-glow);
|
||||
}
|
||||
|
||||
.key-live .live-price-line,.live-price{
|
||||
text-shadow:0 0 12px var(--focus-glow);
|
||||
}
|
||||
|
||||
.preset-tabs a.active{
|
||||
box-shadow:0 0 12px var(--focus-glow);
|
||||
}
|
||||
/* Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt */
|
||||
/* 科技感增强层 — 与 base.html 变量配合 */
|
||||
|
||||
.tech-bg{
|
||||
position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden;
|
||||
}
|
||||
.tech-grid{
|
||||
position:absolute;inset:0;
|
||||
background-image:
|
||||
linear-gradient(var(--bg-grid) 1px,transparent 1px),
|
||||
linear-gradient(90deg,var(--bg-grid) 1px,transparent 1px);
|
||||
background-size:32px 32px;
|
||||
mask-image:radial-gradient(ellipse 85% 75% at 50% 35%,#000 20%,transparent 75%);
|
||||
}
|
||||
.tech-glow{
|
||||
position:absolute;width:70vmax;height:70vmax;
|
||||
top:-25%;left:50%;transform:translateX(-50%);
|
||||
background:radial-gradient(circle,var(--ambient-glow) 0%,transparent 65%);
|
||||
animation:tech-pulse 8s ease-in-out infinite;
|
||||
}
|
||||
.tech-glow-2{
|
||||
position:absolute;width:50vmax;height:50vmax;
|
||||
bottom:-20%;right:-10%;
|
||||
background:radial-gradient(circle,var(--ambient-glow-2) 0%,transparent 70%);
|
||||
animation:tech-pulse 10s ease-in-out infinite reverse;
|
||||
}
|
||||
.tech-scanline{
|
||||
position:absolute;inset:0;
|
||||
background:repeating-linear-gradient(
|
||||
0deg,
|
||||
transparent,
|
||||
transparent 2px,
|
||||
var(--scanline) 2px,
|
||||
var(--scanline) 3px
|
||||
);
|
||||
opacity:.35;
|
||||
animation:tech-scan 12s linear infinite;
|
||||
}
|
||||
@keyframes tech-pulse{
|
||||
0%,100%{opacity:.55;transform:translateX(-50%) scale(1)}
|
||||
50%{opacity:.85;transform:translateX(-50%) scale(1.05)}
|
||||
}
|
||||
@keyframes tech-scan{
|
||||
0%{transform:translateY(0)}
|
||||
100%{transform:translateY(32px)}
|
||||
}
|
||||
@keyframes tech-shine{
|
||||
0%,100%{opacity:.45}
|
||||
50%{opacity:.9}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce){
|
||||
.tech-glow,.tech-glow-2,.tech-scanline,.card::after,.site-header::after{animation:none}
|
||||
}
|
||||
|
||||
.page-wrap{position:relative;z-index:1}
|
||||
|
||||
.site-header{
|
||||
border-bottom:1px solid var(--border-header);
|
||||
background:transparent;
|
||||
backdrop-filter:none;
|
||||
}
|
||||
.site-header::after{
|
||||
content:"";display:block;height:1px;margin-top:-1px;
|
||||
background:linear-gradient(90deg,transparent,var(--accent),var(--accent-2),transparent);
|
||||
opacity:.7;animation:tech-shine 4s ease-in-out infinite;
|
||||
}
|
||||
.site-title{
|
||||
letter-spacing:.04em;
|
||||
background:linear-gradient(135deg,var(--text-title) 0%,var(--accent) 45%,var(--accent-2) 100%);
|
||||
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
|
||||
background-clip:text;
|
||||
filter:drop-shadow(0 0 24px var(--title-glow));
|
||||
}
|
||||
.site-title-sub{
|
||||
display:block;font-size:.72rem;font-weight:500;
|
||||
letter-spacing:.22em;text-transform:uppercase;
|
||||
color:var(--text-muted);margin-top:.35rem;
|
||||
-webkit-text-fill-color:var(--text-muted);
|
||||
}
|
||||
|
||||
.site-nav a{
|
||||
border-radius:999px;
|
||||
letter-spacing:.02em;
|
||||
position:relative;overflow:hidden;
|
||||
transition:transform .2s,box-shadow .2s,border-color .2s,background .2s;
|
||||
}
|
||||
.site-nav a::before{
|
||||
content:"";position:absolute;inset:0;
|
||||
background:linear-gradient(120deg,transparent,rgba(255,255,255,.06),transparent);
|
||||
opacity:0;transition:opacity .25s;
|
||||
}
|
||||
.site-nav a:hover::before{opacity:1}
|
||||
.site-nav a:hover{
|
||||
transform:translateY(-1px);
|
||||
box-shadow:0 4px 20px var(--nav-hover-glow);
|
||||
}
|
||||
.site-nav a.active{
|
||||
background:linear-gradient(135deg,var(--nav-active),var(--accent-2));
|
||||
border-color:transparent;
|
||||
box-shadow:0 0 20px var(--nav-active-glow),inset 0 1px 0 rgba(255,255,255,.15);
|
||||
}
|
||||
|
||||
.theme-switch-btn:hover{
|
||||
color:var(--text-primary);
|
||||
}
|
||||
.theme-switch-btn.active{
|
||||
box-shadow:0 0 12px var(--btn-glow);
|
||||
}
|
||||
|
||||
.card{
|
||||
border-radius:14px;
|
||||
transition:transform .25s,box-shadow .25s,border-color .25s;
|
||||
}
|
||||
.card:hover{
|
||||
transform:translateY(-2px);
|
||||
border-color:var(--card-border-hover);
|
||||
box-shadow:var(--shadow-card-hover);
|
||||
}
|
||||
.card::after{
|
||||
animation:tech-shine 5s ease-in-out infinite;
|
||||
}
|
||||
.card h2{letter-spacing:.03em}
|
||||
.card h2:before{
|
||||
box-shadow:0 0 12px var(--accent),0 0 4px var(--accent-2);
|
||||
}
|
||||
|
||||
input:focus,select:focus,textarea:focus{
|
||||
box-shadow:0 0 0 3px var(--focus-ring),0 0 16px var(--focus-glow);
|
||||
}
|
||||
button.btn-primary{
|
||||
font-weight:600;letter-spacing:.04em;
|
||||
box-shadow:0 4px 20px var(--btn-glow);
|
||||
transition:transform .15s,box-shadow .2s,opacity .2s;
|
||||
}
|
||||
button.btn-primary:hover{
|
||||
transform:translateY(-1px);
|
||||
box-shadow:0 6px 28px var(--btn-glow-strong);
|
||||
opacity:1;
|
||||
}
|
||||
|
||||
.list-item{
|
||||
transition:border-color .2s,box-shadow .2s,transform .2s;
|
||||
}
|
||||
.list-item:hover{
|
||||
border-color:var(--card-border-hover);
|
||||
box-shadow:0 4px 16px var(--card-glow);
|
||||
}
|
||||
table tbody tr{transition:background .15s}
|
||||
table tbody tr:hover{background:var(--row-hover)}
|
||||
|
||||
.stat-item{
|
||||
backdrop-filter:blur(8px);
|
||||
transition:transform .2s,box-shadow .2s;
|
||||
}
|
||||
.stat-item:hover{
|
||||
transform:translateY(-2px);
|
||||
box-shadow:0 8px 24px var(--card-glow);
|
||||
}
|
||||
.stat-item .value{
|
||||
font-variant-numeric:tabular-nums;
|
||||
letter-spacing:.02em;
|
||||
}
|
||||
|
||||
.pos-card{
|
||||
position:relative;overflow:hidden;
|
||||
transition:border-color .2s,box-shadow .2s;
|
||||
}
|
||||
.pos-card::before{
|
||||
content:"";position:absolute;top:0;left:0;right:0;height:2px;
|
||||
background:linear-gradient(90deg,var(--accent),var(--accent-2));
|
||||
opacity:.5;
|
||||
}
|
||||
.pos-card:hover{
|
||||
border-color:var(--card-border-hover);
|
||||
box-shadow:0 6px 24px var(--card-glow);
|
||||
}
|
||||
|
||||
.badge{letter-spacing:.02em;border:1px solid transparent}
|
||||
.badge.dir{border-color:rgba(76,194,255,.25)}
|
||||
.badge.profit{border-color:rgba(76,217,127,.3)}
|
||||
.badge.loss{border-color:rgba(255,102,102,.3)}
|
||||
|
||||
.modal-box{
|
||||
border:1px solid var(--card-border-hover);
|
||||
box-shadow:var(--shadow-card-hover),0 0 60px var(--card-glow);
|
||||
}
|
||||
|
||||
.flash{
|
||||
box-shadow:0 0 24px var(--focus-glow);
|
||||
letter-spacing:.02em;
|
||||
}
|
||||
|
||||
.profile-spec{
|
||||
border:1px solid var(--card-border-hover);
|
||||
box-shadow:inset 0 0 40px var(--card-glow);
|
||||
}
|
||||
|
||||
.key-live .live-price-line,.live-price{
|
||||
text-shadow:0 0 12px var(--focus-glow);
|
||||
}
|
||||
|
||||
.preset-tabs a.active{
|
||||
box-shadow:0 0 12px var(--focus-glow);
|
||||
}
|
||||
|
||||
+107
-106
@@ -1,106 +1,107 @@
|
||||
/* 持仓监控页 — 与 split-grid(关键位监控)同宽,全端自适应 */
|
||||
.trade-page{width:100%}
|
||||
.trade-split{margin-bottom:1.25rem}
|
||||
.trade-split .card{min-height:480px}
|
||||
.trade-top-bar{
|
||||
display:flex;flex-wrap:wrap;gap:.65rem 1rem;
|
||||
align-items:center;justify-content:space-between;
|
||||
margin-bottom:1.25rem;
|
||||
}
|
||||
.trade-top-bar-main{display:flex;flex-wrap:wrap;gap:.5rem .65rem;align-items:center;flex:1;min-width:0}
|
||||
.trade-top-bar-actions{display:flex;flex-wrap:wrap;gap:.5rem;align-items:center}
|
||||
.trade-top-hint{font-size:.72rem;white-space:nowrap}
|
||||
.btn-ctp-sm{padding:.4rem .9rem;font-size:.8rem;width:auto;white-space:nowrap}
|
||||
.trade-card{margin-bottom:0;height:100%;display:flex;flex-direction:column}
|
||||
.trade-card h2{margin-bottom:.35rem;flex-shrink:0}
|
||||
.trade-card .card-body{flex:1;min-height:0;display:flex;flex-direction:column}
|
||||
.trade-card-full{margin-bottom:1.5rem}
|
||||
.pos-hint{font-size:.75rem;margin:-.15rem 0 .5rem .25rem;color:var(--text-muted)}
|
||||
.trade-order-status{display:grid;gap:.55rem;margin:.5rem 0 .75rem;padding:.65rem .85rem;background:var(--card-inner);border:1px solid var(--card-border);border-radius:8px;font-size:.82rem}
|
||||
.trade-order-status-compact{margin-top:0}
|
||||
.trade-order-status .status-row{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem .65rem}
|
||||
.trade-form-rows{display:flex;flex-direction:column;gap:.75rem;margin-bottom:.85rem}
|
||||
.trade-form-line{display:grid;gap:.65rem;align-items:end}
|
||||
.trade-form-line.line-3{grid-template-columns:1.4fr 0.8fr 0.8fr}
|
||||
.trade-field label{display:block;font-size:.72rem;margin-bottom:.28rem;color:var(--text-label)}
|
||||
.trade-field select,.trade-field input{width:100%;box-sizing:border-box}
|
||||
.trade-field .lots-auto{color:var(--accent);font-weight:600;background:var(--card-inner);cursor:default}
|
||||
.lots-warn{font-size:.7rem;margin-top:.25rem;margin-bottom:0}
|
||||
.price-type-tabs{display:flex;gap:.35rem;margin-bottom:.35rem}
|
||||
.price-tab{border:1px solid var(--card-border);background:var(--card-inner);color:var(--text-muted);padding:.28rem .7rem;border-radius:6px;font-size:.75rem;cursor:pointer;flex:1;text-align:center;width:auto}
|
||||
.price-tab.active{border-color:var(--accent);color:var(--accent);font-weight:600;background:rgba(56,189,248,.08)}
|
||||
.market-hint{font-size:.7rem;margin-top:.25rem}
|
||||
.trade-action-row{display:flex;flex-direction:column;gap:.45rem;margin:.85rem 0 .55rem}
|
||||
.trade-action-row .btn-open{padding:.65rem .75rem;font-size:.9rem;width:100%}
|
||||
.trade-action-row .btn-open:disabled{opacity:.45;cursor:not-allowed;filter:grayscale(.25)}
|
||||
.trade-action-row .btn-open.btn-session-off{background:var(--text-muted);border-color:var(--text-muted)}
|
||||
.trailing-be-toggle{display:flex;align-items:center;gap:.4rem;font-size:.78rem;color:var(--text-label);margin-bottom:.45rem;cursor:pointer;user-select:none}
|
||||
.trailing-be-toggle input{width:auto;margin:0}
|
||||
.trade-rr-hint{font-size:.78rem;color:var(--text-accent);margin:0}
|
||||
.session-hint{font-size:.72rem;margin:.35rem 0 0;text-align:center}
|
||||
.trade-order-msg{font-size:.82rem;text-align:center;margin:0;padding:.35rem}
|
||||
.trade-order-msg.ok{color:var(--profit)}
|
||||
.trade-order-msg.err{color:var(--loss)}
|
||||
.trade-footer{background:var(--card-inner);border-radius:8px;padding:.65rem .85rem;font-size:.78rem;line-height:1.5;border:1px solid var(--card-border);margin-top:.5rem}
|
||||
.trade-footer strong{color:var(--accent)}
|
||||
.rec-blocked td{opacity:.55}
|
||||
.rec-ok td:first-child{font-weight:600}
|
||||
.rec-trend-break td:first-child .trend-name{font-weight:700}
|
||||
.trend-badge{font-size:.72rem;white-space:nowrap}
|
||||
.trend-badge.break{color:var(--accent);font-weight:700;border:1px solid var(--accent);background:rgba(56,189,248,.12)}
|
||||
.trend-hint{font-size:.72rem;color:var(--text-muted);margin:.35rem 0 .65rem;line-height:1.5}
|
||||
.rec-sort-bar{display:flex;flex-wrap:wrap;align-items:center;gap:.45rem .65rem;margin-bottom:.55rem;font-size:.78rem}
|
||||
.rec-sort-bar label{color:var(--text-muted);white-space:nowrap}
|
||||
.rec-sort-bar select{padding:.35rem .5rem;font-size:.78rem;min-width:7rem}
|
||||
.rec-stats{
|
||||
font-size:.78rem;color:var(--text-muted);margin-bottom:.45rem;line-height:1.5;
|
||||
}
|
||||
.rec-stats strong{color:var(--accent);font-weight:600}
|
||||
.rec-sort-dir-btn{
|
||||
border:1px solid var(--card-border);background:var(--card-inner);color:var(--text-muted);
|
||||
padding:.3rem .55rem;border-radius:6px;cursor:pointer;font-size:.78rem;min-width:2rem;
|
||||
}
|
||||
.rec-sort-dir-btn:hover{border-color:var(--accent);color:var(--accent)}
|
||||
.gap-badge{font-size:.72rem}
|
||||
.rec-change-up{color:var(--profit)}
|
||||
.rec-change-down{color:var(--loss)}
|
||||
#recommend .trade-table-wrap{max-height:min(70vh,520px)}
|
||||
#positions .card-body.card-scroll{flex:1;max-height:none;overflow-y:auto}
|
||||
.pos-pending-orders{margin-top:.55rem;padding-top:.55rem;border-top:1px dashed var(--table-border)}
|
||||
.pos-pending-orders .pending-title{font-size:.68rem;color:var(--text-muted);margin-bottom:.35rem}
|
||||
.pos-pending-item{display:flex;justify-content:space-between;align-items:center;gap:.5rem;font-size:.75rem;padding:.35rem .5rem;border-radius:6px;margin-bottom:.25rem;background:var(--list-item-bg)}
|
||||
.pos-pending-right{display:flex;align-items:center;gap:.45rem;flex-shrink:0}
|
||||
.pos-dismiss-btn{padding:.2rem .55rem;font-size:.68rem;border-radius:6px;border:1px solid var(--table-border);background:var(--card-inner);color:var(--text-muted);cursor:pointer;width:auto;min-height:auto;line-height:1.3}
|
||||
.pos-dismiss-btn:disabled{opacity:.55;cursor:wait}
|
||||
.pos-sl-btn{border-color:var(--accent);color:var(--accent)}
|
||||
.pos-pending-item.sl{border-left:3px solid var(--loss)}
|
||||
.pos-pending-item.tp{border-left:3px solid var(--profit)}
|
||||
.pos-pending-item.ctp{border-left:3px solid var(--accent)}
|
||||
.pos-card.is-pending{border:1px dashed var(--accent);opacity:.95}
|
||||
.pos-card.is-pending .badge.pending{background:rgba(56,189,248,.15);color:var(--accent)}
|
||||
.pos-card.is-pending .pos-metrics .cell.pnl-pending label{color:var(--accent)}
|
||||
.pos-close-btn{padding:.4rem .85rem;font-size:.78rem;border-radius:8px;border:1px solid var(--loss);background:var(--loss-bg);color:var(--loss);cursor:pointer;white-space:nowrap;width:auto;flex-shrink:0;min-height:36px}
|
||||
.pos-close-btn:disabled,.pos-close-btn.is-session-off{opacity:.45;cursor:not-allowed;border-color:var(--text-muted);background:var(--card-inner);color:var(--text-muted)}
|
||||
.pos-dismiss-btn:disabled,.pos-dismiss-btn.is-session-off{opacity:.45;cursor:not-allowed;color:var(--text-muted)}
|
||||
.pos-card-meta-line{font-size:.78rem;line-height:1.65;color:var(--text-muted);margin-bottom:.55rem}
|
||||
.pos-card-meta-line strong{color:var(--text)}
|
||||
.pos-card-actions{display:flex;gap:.35rem;flex-shrink:0;align-items:center}
|
||||
.pos-order-btn{padding:.4rem .85rem;font-size:.78rem;border-radius:8px;border:1px solid var(--accent);background:rgba(56,189,248,.1);color:var(--accent);cursor:pointer;white-space:nowrap;width:auto;flex-shrink:0;min-height:36px}
|
||||
.pos-order-btn:disabled,.pos-order-btn.pos-order-done{opacity:.55;cursor:default;border-color:var(--table-border);background:var(--card-inner);color:var(--text-muted)}
|
||||
.pos-order-btn:disabled:not(.pos-order-done){cursor:wait}
|
||||
|
||||
@media (min-width:768px) and (max-width:1100px){
|
||||
.trade-split .card{min-height:420px}
|
||||
.trade-form-line.line-3{grid-template-columns:1fr 1fr}
|
||||
.trade-form-line.line-3 .trade-field:first-child{grid-column:1/-1}
|
||||
}
|
||||
|
||||
@media (max-width:767px){
|
||||
.trade-top-bar{flex-direction:column;align-items:stretch}
|
||||
.trade-top-bar-actions{width:100%}
|
||||
.btn-ctp-sm{width:100%;min-height:44px}
|
||||
.trade-split .card{min-height:auto}
|
||||
.trade-form-line.line-3{grid-template-columns:1fr}
|
||||
.trade-card-full{margin-bottom:1rem}
|
||||
.trade-table-wrap{max-height:320px}
|
||||
}
|
||||
/* Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt */
|
||||
/* 持仓监控页 — 与 split-grid(关键位监控)同宽,全端自适应 */
|
||||
.trade-page{width:100%}
|
||||
.trade-split{margin-bottom:1.25rem}
|
||||
.trade-split .card{min-height:480px}
|
||||
.trade-top-bar{
|
||||
display:flex;flex-wrap:wrap;gap:.65rem 1rem;
|
||||
align-items:center;justify-content:space-between;
|
||||
margin-bottom:1.25rem;
|
||||
}
|
||||
.trade-top-bar-main{display:flex;flex-wrap:wrap;gap:.5rem .65rem;align-items:center;flex:1;min-width:0}
|
||||
.trade-top-bar-actions{display:flex;flex-wrap:wrap;gap:.5rem;align-items:center}
|
||||
.trade-top-hint{font-size:.72rem;white-space:nowrap}
|
||||
.btn-ctp-sm{padding:.4rem .9rem;font-size:.8rem;width:auto;white-space:nowrap}
|
||||
.trade-card{margin-bottom:0;height:100%;display:flex;flex-direction:column}
|
||||
.trade-card h2{margin-bottom:.35rem;flex-shrink:0}
|
||||
.trade-card .card-body{flex:1;min-height:0;display:flex;flex-direction:column}
|
||||
.trade-card-full{margin-bottom:1.5rem}
|
||||
.pos-hint{font-size:.75rem;margin:-.15rem 0 .5rem .25rem;color:var(--text-muted)}
|
||||
.trade-order-status{display:grid;gap:.55rem;margin:.5rem 0 .75rem;padding:.65rem .85rem;background:var(--card-inner);border:1px solid var(--card-border);border-radius:8px;font-size:.82rem}
|
||||
.trade-order-status-compact{margin-top:0}
|
||||
.trade-order-status .status-row{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem .65rem}
|
||||
.trade-form-rows{display:flex;flex-direction:column;gap:.75rem;margin-bottom:.85rem}
|
||||
.trade-form-line{display:grid;gap:.65rem;align-items:end}
|
||||
.trade-form-line.line-3{grid-template-columns:1.4fr 0.8fr 0.8fr}
|
||||
.trade-field label{display:block;font-size:.72rem;margin-bottom:.28rem;color:var(--text-label)}
|
||||
.trade-field select,.trade-field input{width:100%;box-sizing:border-box}
|
||||
.trade-field .lots-auto{color:var(--accent);font-weight:600;background:var(--card-inner);cursor:default}
|
||||
.lots-warn{font-size:.7rem;margin-top:.25rem;margin-bottom:0}
|
||||
.price-type-tabs{display:flex;gap:.35rem;margin-bottom:.35rem}
|
||||
.price-tab{border:1px solid var(--card-border);background:var(--card-inner);color:var(--text-muted);padding:.28rem .7rem;border-radius:6px;font-size:.75rem;cursor:pointer;flex:1;text-align:center;width:auto}
|
||||
.price-tab.active{border-color:var(--accent);color:var(--accent);font-weight:600;background:rgba(56,189,248,.08)}
|
||||
.market-hint{font-size:.7rem;margin-top:.25rem}
|
||||
.trade-action-row{display:flex;flex-direction:column;gap:.45rem;margin:.85rem 0 .55rem}
|
||||
.trade-action-row .btn-open{padding:.65rem .75rem;font-size:.9rem;width:100%}
|
||||
.trade-action-row .btn-open:disabled{opacity:.45;cursor:not-allowed;filter:grayscale(.25)}
|
||||
.trade-action-row .btn-open.btn-session-off{background:var(--text-muted);border-color:var(--text-muted)}
|
||||
.trailing-be-toggle{display:flex;align-items:center;gap:.4rem;font-size:.78rem;color:var(--text-label);margin-bottom:.45rem;cursor:pointer;user-select:none}
|
||||
.trailing-be-toggle input{width:auto;margin:0}
|
||||
.trade-rr-hint{font-size:.78rem;color:var(--text-accent);margin:0}
|
||||
.session-hint{font-size:.72rem;margin:.35rem 0 0;text-align:center}
|
||||
.trade-order-msg{font-size:.82rem;text-align:center;margin:0;padding:.35rem}
|
||||
.trade-order-msg.ok{color:var(--profit)}
|
||||
.trade-order-msg.err{color:var(--loss)}
|
||||
.trade-footer{background:var(--card-inner);border-radius:8px;padding:.65rem .85rem;font-size:.78rem;line-height:1.5;border:1px solid var(--card-border);margin-top:.5rem}
|
||||
.trade-footer strong{color:var(--accent)}
|
||||
.rec-blocked td{opacity:.55}
|
||||
.rec-ok td:first-child{font-weight:600}
|
||||
.rec-trend-break td:first-child .trend-name{font-weight:700}
|
||||
.trend-badge{font-size:.72rem;white-space:nowrap}
|
||||
.trend-badge.break{color:var(--accent);font-weight:700;border:1px solid var(--accent);background:rgba(56,189,248,.12)}
|
||||
.trend-hint{font-size:.72rem;color:var(--text-muted);margin:.35rem 0 .65rem;line-height:1.5}
|
||||
.rec-sort-bar{display:flex;flex-wrap:wrap;align-items:center;gap:.45rem .65rem;margin-bottom:.55rem;font-size:.78rem}
|
||||
.rec-sort-bar label{color:var(--text-muted);white-space:nowrap}
|
||||
.rec-sort-bar select{padding:.35rem .5rem;font-size:.78rem;min-width:7rem}
|
||||
.rec-stats{
|
||||
font-size:.78rem;color:var(--text-muted);margin-bottom:.45rem;line-height:1.5;
|
||||
}
|
||||
.rec-stats strong{color:var(--accent);font-weight:600}
|
||||
.rec-sort-dir-btn{
|
||||
border:1px solid var(--card-border);background:var(--card-inner);color:var(--text-muted);
|
||||
padding:.3rem .55rem;border-radius:6px;cursor:pointer;font-size:.78rem;min-width:2rem;
|
||||
}
|
||||
.rec-sort-dir-btn:hover{border-color:var(--accent);color:var(--accent)}
|
||||
.gap-badge{font-size:.72rem}
|
||||
.rec-change-up{color:var(--profit)}
|
||||
.rec-change-down{color:var(--loss)}
|
||||
#recommend .trade-table-wrap{max-height:min(70vh,520px)}
|
||||
#positions .card-body.card-scroll{flex:1;max-height:none;overflow-y:auto}
|
||||
.pos-pending-orders{margin-top:.55rem;padding-top:.55rem;border-top:1px dashed var(--table-border)}
|
||||
.pos-pending-orders .pending-title{font-size:.68rem;color:var(--text-muted);margin-bottom:.35rem}
|
||||
.pos-pending-item{display:flex;justify-content:space-between;align-items:center;gap:.5rem;font-size:.75rem;padding:.35rem .5rem;border-radius:6px;margin-bottom:.25rem;background:var(--list-item-bg)}
|
||||
.pos-pending-right{display:flex;align-items:center;gap:.45rem;flex-shrink:0}
|
||||
.pos-dismiss-btn{padding:.2rem .55rem;font-size:.68rem;border-radius:6px;border:1px solid var(--table-border);background:var(--card-inner);color:var(--text-muted);cursor:pointer;width:auto;min-height:auto;line-height:1.3}
|
||||
.pos-dismiss-btn:disabled{opacity:.55;cursor:wait}
|
||||
.pos-sl-btn{border-color:var(--accent);color:var(--accent)}
|
||||
.pos-pending-item.sl{border-left:3px solid var(--loss)}
|
||||
.pos-pending-item.tp{border-left:3px solid var(--profit)}
|
||||
.pos-pending-item.ctp{border-left:3px solid var(--accent)}
|
||||
.pos-card.is-pending{border:1px dashed var(--accent);opacity:.95}
|
||||
.pos-card.is-pending .badge.pending{background:rgba(56,189,248,.15);color:var(--accent)}
|
||||
.pos-card.is-pending .pos-metrics .cell.pnl-pending label{color:var(--accent)}
|
||||
.pos-close-btn{padding:.4rem .85rem;font-size:.78rem;border-radius:8px;border:1px solid var(--loss);background:var(--loss-bg);color:var(--loss);cursor:pointer;white-space:nowrap;width:auto;flex-shrink:0;min-height:36px}
|
||||
.pos-close-btn:disabled,.pos-close-btn.is-session-off{opacity:.45;cursor:not-allowed;border-color:var(--text-muted);background:var(--card-inner);color:var(--text-muted)}
|
||||
.pos-dismiss-btn:disabled,.pos-dismiss-btn.is-session-off{opacity:.45;cursor:not-allowed;color:var(--text-muted)}
|
||||
.pos-card-meta-line{font-size:.78rem;line-height:1.65;color:var(--text-muted);margin-bottom:.55rem}
|
||||
.pos-card-meta-line strong{color:var(--text)}
|
||||
.pos-card-actions{display:flex;gap:.35rem;flex-shrink:0;align-items:center}
|
||||
.pos-order-btn{padding:.4rem .85rem;font-size:.78rem;border-radius:8px;border:1px solid var(--accent);background:rgba(56,189,248,.1);color:var(--accent);cursor:pointer;white-space:nowrap;width:auto;flex-shrink:0;min-height:36px}
|
||||
.pos-order-btn:disabled,.pos-order-btn.pos-order-done{opacity:.55;cursor:default;border-color:var(--table-border);background:var(--card-inner);color:var(--text-muted)}
|
||||
.pos-order-btn:disabled:not(.pos-order-done){cursor:wait}
|
||||
|
||||
@media (min-width:768px) and (max-width:1100px){
|
||||
.trade-split .card{min-height:420px}
|
||||
.trade-form-line.line-3{grid-template-columns:1fr 1fr}
|
||||
.trade-form-line.line-3 .trade-field:first-child{grid-column:1/-1}
|
||||
}
|
||||
|
||||
@media (max-width:767px){
|
||||
.trade-top-bar{flex-direction:column;align-items:stretch}
|
||||
.trade-top-bar-actions{width:100%}
|
||||
.btn-ctp-sm{width:100%;min-height:44px}
|
||||
.trade-split .card{min-height:auto}
|
||||
.trade-form-line.line-3{grid-template-columns:1fr}
|
||||
.trade-card-full{margin-bottom:1rem}
|
||||
.trade-table-wrap{max-height:320px}
|
||||
}
|
||||
|
||||
+27
-23
@@ -1,23 +1,27 @@
|
||||
(function () {
|
||||
var form = document.getElementById('contract-search-form');
|
||||
if (!form) return;
|
||||
|
||||
var wrap = form.querySelector('.symbol-wrap');
|
||||
var hidden = wrap && wrap.querySelector('input[name="symbol"]');
|
||||
var visible = form.querySelector('#contract-symbol-input');
|
||||
|
||||
// 带 symbol 参数进入时,显示合约代码
|
||||
if (hidden && hidden.value && visible && !visible.value) {
|
||||
visible.value = hidden.value;
|
||||
}
|
||||
|
||||
form.addEventListener('submit', function () {
|
||||
if (!hidden || !visible) return;
|
||||
var v = visible.value.trim();
|
||||
// 若未从下拉选择,尝试用输入框内容(支持直接输入 rb2510)
|
||||
if (!hidden.value && v) {
|
||||
var m = v.match(/([A-Za-z]+\d{3,4})/);
|
||||
hidden.value = m ? m[1] : v;
|
||||
}
|
||||
});
|
||||
})();
|
||||
/* Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
* 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
* 详见 LICENSE.zh-CN.txt
|
||||
*/
|
||||
(function () {
|
||||
var form = document.getElementById('contract-search-form');
|
||||
if (!form) return;
|
||||
|
||||
var wrap = form.querySelector('.symbol-wrap');
|
||||
var hidden = wrap && wrap.querySelector('input[name="symbol"]');
|
||||
var visible = form.querySelector('#contract-symbol-input');
|
||||
|
||||
// 带 symbol 参数进入时,显示合约代码
|
||||
if (hidden && hidden.value && visible && !visible.value) {
|
||||
visible.value = hidden.value;
|
||||
}
|
||||
|
||||
form.addEventListener('submit', function () {
|
||||
if (!hidden || !visible) return;
|
||||
var v = visible.value.trim();
|
||||
// 若未从下拉选择,尝试用输入框内容(支持直接输入 rb2510)
|
||||
if (!hidden.value && v) {
|
||||
var m = v.match(/([A-Za-z]+\d{3,4})/);
|
||||
hidden.value = m ? m[1] : v;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
+127
-123
@@ -1,123 +1,127 @@
|
||||
(function () {
|
||||
var el = document.getElementById('equity-curve-chart');
|
||||
var raw = window.__EQUITY_CURVE__;
|
||||
if (!el || !raw || !raw.length || !window.LightweightCharts) return;
|
||||
|
||||
var chart = null;
|
||||
var series = null;
|
||||
var chartData = [];
|
||||
|
||||
function cssVar(name, fallback) {
|
||||
var v = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
||||
return v || fallback;
|
||||
}
|
||||
|
||||
function themeColors() {
|
||||
return {
|
||||
bg: 'transparent',
|
||||
text: cssVar('--text-muted', '#7a82a0'),
|
||||
grid: cssVar('--table-border', 'rgba(76,194,255,.1)'),
|
||||
border: cssVar('--card-border', 'rgba(76,194,255,.22)'),
|
||||
line: cssVar('--accent', '#4cc2ff'),
|
||||
};
|
||||
}
|
||||
|
||||
function parseTime(s) {
|
||||
if (!s) return null;
|
||||
var t = String(s).trim().replace(' ', 'T');
|
||||
if (t.length === 16) t += ':00';
|
||||
var d = new Date(t);
|
||||
if (isNaN(d.getTime())) return null;
|
||||
return Math.floor(d.getTime() / 1000);
|
||||
}
|
||||
|
||||
function buildData() {
|
||||
var data = [];
|
||||
var lastTs = 0;
|
||||
raw.forEach(function (p) {
|
||||
var ts = parseTime(p.time);
|
||||
if (ts == null) return;
|
||||
if (ts <= lastTs) ts = lastTs + 1;
|
||||
lastTs = ts;
|
||||
data.push({ time: ts, value: Number(p.value) });
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
function applyChartTheme() {
|
||||
if (!chart || !series) return;
|
||||
var c = themeColors();
|
||||
chart.applyOptions({
|
||||
layout: {
|
||||
background: { type: 'solid', color: c.bg },
|
||||
textColor: c.text,
|
||||
},
|
||||
grid: {
|
||||
vertLines: { color: c.grid },
|
||||
horzLines: { color: c.grid },
|
||||
},
|
||||
rightPriceScale: { borderColor: c.border },
|
||||
timeScale: { borderColor: c.border },
|
||||
});
|
||||
series.applyOptions({ color: c.line });
|
||||
}
|
||||
|
||||
function renderChart() {
|
||||
chartData = buildData();
|
||||
if (!chartData.length) {
|
||||
el.innerHTML = '<p class="text-muted" style="padding:1rem">暂无资金曲线数据</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
var c = themeColors();
|
||||
if (chart) {
|
||||
chart.remove();
|
||||
chart = null;
|
||||
series = null;
|
||||
}
|
||||
|
||||
chart = LightweightCharts.createChart(el, {
|
||||
width: el.clientWidth || 800,
|
||||
height: 220,
|
||||
layout: {
|
||||
background: { type: 'solid', color: c.bg },
|
||||
textColor: c.text,
|
||||
fontSize: 11,
|
||||
},
|
||||
grid: {
|
||||
vertLines: { color: c.grid },
|
||||
horzLines: { color: c.grid },
|
||||
},
|
||||
rightPriceScale: { borderColor: c.border },
|
||||
timeScale: { borderColor: c.border, timeVisible: true, secondsVisible: false },
|
||||
});
|
||||
series = chart.addLineSeries({
|
||||
color: c.line,
|
||||
lineWidth: 2,
|
||||
priceFormat: { type: 'price', precision: 2, minMove: 0.01 },
|
||||
});
|
||||
series.setData(chartData);
|
||||
chart.timeScale().fitContent();
|
||||
}
|
||||
|
||||
renderChart();
|
||||
|
||||
window.addEventListener('resize', function () {
|
||||
if (chart) chart.applyOptions({ width: el.clientWidth || 800 });
|
||||
});
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
if (e.target.closest('[data-theme-pick]')) {
|
||||
setTimeout(applyChartTheme, 50);
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof MutationObserver !== 'undefined') {
|
||||
var obs = new MutationObserver(function (mutations) {
|
||||
mutations.forEach(function (m) {
|
||||
if (m.attributeName === 'data-theme') applyChartTheme();
|
||||
});
|
||||
});
|
||||
obs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
||||
}
|
||||
})();
|
||||
/* Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
* 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
* 详见 LICENSE.zh-CN.txt
|
||||
*/
|
||||
(function () {
|
||||
var el = document.getElementById('equity-curve-chart');
|
||||
var raw = window.__EQUITY_CURVE__;
|
||||
if (!el || !raw || !raw.length || !window.LightweightCharts) return;
|
||||
|
||||
var chart = null;
|
||||
var series = null;
|
||||
var chartData = [];
|
||||
|
||||
function cssVar(name, fallback) {
|
||||
var v = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
||||
return v || fallback;
|
||||
}
|
||||
|
||||
function themeColors() {
|
||||
return {
|
||||
bg: 'transparent',
|
||||
text: cssVar('--text-muted', '#7a82a0'),
|
||||
grid: cssVar('--table-border', 'rgba(76,194,255,.1)'),
|
||||
border: cssVar('--card-border', 'rgba(76,194,255,.22)'),
|
||||
line: cssVar('--accent', '#4cc2ff'),
|
||||
};
|
||||
}
|
||||
|
||||
function parseTime(s) {
|
||||
if (!s) return null;
|
||||
var t = String(s).trim().replace(' ', 'T');
|
||||
if (t.length === 16) t += ':00';
|
||||
var d = new Date(t);
|
||||
if (isNaN(d.getTime())) return null;
|
||||
return Math.floor(d.getTime() / 1000);
|
||||
}
|
||||
|
||||
function buildData() {
|
||||
var data = [];
|
||||
var lastTs = 0;
|
||||
raw.forEach(function (p) {
|
||||
var ts = parseTime(p.time);
|
||||
if (ts == null) return;
|
||||
if (ts <= lastTs) ts = lastTs + 1;
|
||||
lastTs = ts;
|
||||
data.push({ time: ts, value: Number(p.value) });
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
function applyChartTheme() {
|
||||
if (!chart || !series) return;
|
||||
var c = themeColors();
|
||||
chart.applyOptions({
|
||||
layout: {
|
||||
background: { type: 'solid', color: c.bg },
|
||||
textColor: c.text,
|
||||
},
|
||||
grid: {
|
||||
vertLines: { color: c.grid },
|
||||
horzLines: { color: c.grid },
|
||||
},
|
||||
rightPriceScale: { borderColor: c.border },
|
||||
timeScale: { borderColor: c.border },
|
||||
});
|
||||
series.applyOptions({ color: c.line });
|
||||
}
|
||||
|
||||
function renderChart() {
|
||||
chartData = buildData();
|
||||
if (!chartData.length) {
|
||||
el.innerHTML = '<p class="text-muted" style="padding:1rem">暂无资金曲线数据</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
var c = themeColors();
|
||||
if (chart) {
|
||||
chart.remove();
|
||||
chart = null;
|
||||
series = null;
|
||||
}
|
||||
|
||||
chart = LightweightCharts.createChart(el, {
|
||||
width: el.clientWidth || 800,
|
||||
height: 220,
|
||||
layout: {
|
||||
background: { type: 'solid', color: c.bg },
|
||||
textColor: c.text,
|
||||
fontSize: 11,
|
||||
},
|
||||
grid: {
|
||||
vertLines: { color: c.grid },
|
||||
horzLines: { color: c.grid },
|
||||
},
|
||||
rightPriceScale: { borderColor: c.border },
|
||||
timeScale: { borderColor: c.border, timeVisible: true, secondsVisible: false },
|
||||
});
|
||||
series = chart.addLineSeries({
|
||||
color: c.line,
|
||||
lineWidth: 2,
|
||||
priceFormat: { type: 'price', precision: 2, minMove: 0.01 },
|
||||
});
|
||||
series.setData(chartData);
|
||||
chart.timeScale().fitContent();
|
||||
}
|
||||
|
||||
renderChart();
|
||||
|
||||
window.addEventListener('resize', function () {
|
||||
if (chart) chart.applyOptions({ width: el.clientWidth || 800 });
|
||||
});
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
if (e.target.closest('[data-theme-pick]')) {
|
||||
setTimeout(applyChartTheme, 50);
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof MutationObserver !== 'undefined') {
|
||||
var obs = new MutationObserver(function (mutations) {
|
||||
mutations.forEach(function (m) {
|
||||
if (m.attributeName === 'data-theme') applyChartTheme();
|
||||
});
|
||||
});
|
||||
obs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
||||
}
|
||||
})();
|
||||
|
||||
+38
-34
@@ -1,34 +1,38 @@
|
||||
(function () {
|
||||
var keyTimer = null;
|
||||
|
||||
function fmtDist(v) {
|
||||
if (v === null || v === undefined) return '--';
|
||||
return Number(v).toFixed(2);
|
||||
}
|
||||
|
||||
function pollKeyPrices() {
|
||||
var list = document.getElementById('key-monitor-list');
|
||||
if (!list || !list.querySelector('.key-item')) return;
|
||||
|
||||
fetch('/api/key_prices')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (rows) {
|
||||
rows.forEach(function (row) {
|
||||
var el = list.querySelector('.key-item[data-key-id="' + row.id + '"]');
|
||||
if (!el) return;
|
||||
var priceEl = el.querySelector('.live-price');
|
||||
var upEl = el.querySelector('.dist-up');
|
||||
var downEl = el.querySelector('.dist-down');
|
||||
if (priceEl) priceEl.textContent = row.price != null ? row.price : '--';
|
||||
if (upEl) upEl.textContent = fmtDist(row.dist_upper);
|
||||
if (downEl) downEl.textContent = fmtDist(row.dist_lower);
|
||||
});
|
||||
})
|
||||
.catch(function () { /* ignore */ });
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
pollKeyPrices();
|
||||
keyTimer = setInterval(pollKeyPrices, 1000);
|
||||
});
|
||||
})();
|
||||
/* Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
* 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
* 详见 LICENSE.zh-CN.txt
|
||||
*/
|
||||
(function () {
|
||||
var keyTimer = null;
|
||||
|
||||
function fmtDist(v) {
|
||||
if (v === null || v === undefined) return '--';
|
||||
return Number(v).toFixed(2);
|
||||
}
|
||||
|
||||
function pollKeyPrices() {
|
||||
var list = document.getElementById('key-monitor-list');
|
||||
if (!list || !list.querySelector('.key-item')) return;
|
||||
|
||||
fetch('/api/key_prices')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (rows) {
|
||||
rows.forEach(function (row) {
|
||||
var el = list.querySelector('.key-item[data-key-id="' + row.id + '"]');
|
||||
if (!el) return;
|
||||
var priceEl = el.querySelector('.live-price');
|
||||
var upEl = el.querySelector('.dist-up');
|
||||
var downEl = el.querySelector('.dist-down');
|
||||
if (priceEl) priceEl.textContent = row.price != null ? row.price : '--';
|
||||
if (upEl) upEl.textContent = fmtDist(row.dist_upper);
|
||||
if (downEl) downEl.textContent = fmtDist(row.dist_lower);
|
||||
});
|
||||
})
|
||||
.catch(function () { /* ignore */ });
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
pollKeyPrices();
|
||||
keyTimer = setInterval(pollKeyPrices, 1000);
|
||||
});
|
||||
})();
|
||||
|
||||
+660
-656
File diff suppressed because it is too large
Load Diff
+57
-53
@@ -1,53 +1,57 @@
|
||||
(function () {
|
||||
var toggle = document.getElementById('nav-toggle');
|
||||
var nav = document.getElementById('site-nav');
|
||||
var backdrop = document.getElementById('nav-backdrop');
|
||||
if (!toggle || !nav) return;
|
||||
|
||||
function openNav() {
|
||||
nav.classList.add('open');
|
||||
if (backdrop) {
|
||||
backdrop.hidden = false;
|
||||
backdrop.classList.add('show');
|
||||
}
|
||||
toggle.setAttribute('aria-expanded', 'true');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeNav() {
|
||||
nav.classList.remove('open');
|
||||
if (backdrop) {
|
||||
backdrop.classList.remove('show');
|
||||
backdrop.hidden = true;
|
||||
}
|
||||
toggle.setAttribute('aria-expanded', 'false');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
function isMobileNav() {
|
||||
return window.matchMedia('(max-width: 767px)').matches;
|
||||
}
|
||||
|
||||
toggle.addEventListener('click', function () {
|
||||
if (nav.classList.contains('open')) closeNav();
|
||||
else openNav();
|
||||
});
|
||||
|
||||
if (backdrop) {
|
||||
backdrop.addEventListener('click', closeNav);
|
||||
}
|
||||
|
||||
nav.querySelectorAll('a').forEach(function (link) {
|
||||
link.addEventListener('click', function () {
|
||||
if (isMobileNav()) closeNav();
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('resize', function () {
|
||||
if (!isMobileNav()) closeNav();
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Escape') closeNav();
|
||||
});
|
||||
})();
|
||||
/* Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
* 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
* 详见 LICENSE.zh-CN.txt
|
||||
*/
|
||||
(function () {
|
||||
var toggle = document.getElementById('nav-toggle');
|
||||
var nav = document.getElementById('site-nav');
|
||||
var backdrop = document.getElementById('nav-backdrop');
|
||||
if (!toggle || !nav) return;
|
||||
|
||||
function openNav() {
|
||||
nav.classList.add('open');
|
||||
if (backdrop) {
|
||||
backdrop.hidden = false;
|
||||
backdrop.classList.add('show');
|
||||
}
|
||||
toggle.setAttribute('aria-expanded', 'true');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeNav() {
|
||||
nav.classList.remove('open');
|
||||
if (backdrop) {
|
||||
backdrop.classList.remove('show');
|
||||
backdrop.hidden = true;
|
||||
}
|
||||
toggle.setAttribute('aria-expanded', 'false');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
function isMobileNav() {
|
||||
return window.matchMedia('(max-width: 767px)').matches;
|
||||
}
|
||||
|
||||
toggle.addEventListener('click', function () {
|
||||
if (nav.classList.contains('open')) closeNav();
|
||||
else openNav();
|
||||
});
|
||||
|
||||
if (backdrop) {
|
||||
backdrop.addEventListener('click', closeNav);
|
||||
}
|
||||
|
||||
nav.querySelectorAll('a').forEach(function (link) {
|
||||
link.addEventListener('click', function () {
|
||||
if (isMobileNav()) closeNav();
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('resize', function () {
|
||||
if (!isMobileNav()) closeNav();
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Escape') closeNav();
|
||||
});
|
||||
})();
|
||||
|
||||
+49
-45
@@ -1,45 +1,49 @@
|
||||
(function () {
|
||||
var timer = null;
|
||||
|
||||
function fmtDist(v) {
|
||||
if (v === null || v === undefined) return '--';
|
||||
return v.toFixed(2);
|
||||
}
|
||||
|
||||
function pollPrices() {
|
||||
var list = document.getElementById('plan-monitor-list');
|
||||
if (!list || !list.querySelector('.plan-item')) return;
|
||||
|
||||
fetch('/api/plan_prices')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (rows) {
|
||||
rows.forEach(function (row) {
|
||||
var el = list.querySelector('.plan-item[data-plan-id="' + row.id + '"]');
|
||||
if (!el) return;
|
||||
var priceEl = el.querySelector('.live-price');
|
||||
var distEl = el.querySelector('.live-dist');
|
||||
var upEl = el.querySelector('.dist-up');
|
||||
var downEl = el.querySelector('.dist-down');
|
||||
if (priceEl) {
|
||||
priceEl.textContent = row.price != null ? row.price : '--';
|
||||
}
|
||||
if (row.in_zone && distEl) {
|
||||
distEl.innerHTML = '<span class="text-profit" style="font-weight:600">在区间内</span>';
|
||||
} else if (distEl) {
|
||||
distEl.innerHTML =
|
||||
'距上<span class="dist-up">' + fmtDist(row.dist_upper) + '</span> ' +
|
||||
'距下<span class="dist-down">' + fmtDist(row.dist_lower) + '</span>';
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(function () { /* ignore */ });
|
||||
}
|
||||
|
||||
function startPolling() {
|
||||
if (timer) clearInterval(timer);
|
||||
pollPrices();
|
||||
timer = setInterval(pollPrices, 1000);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', startPolling);
|
||||
})();
|
||||
/* Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
* 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
* 详见 LICENSE.zh-CN.txt
|
||||
*/
|
||||
(function () {
|
||||
var timer = null;
|
||||
|
||||
function fmtDist(v) {
|
||||
if (v === null || v === undefined) return '--';
|
||||
return v.toFixed(2);
|
||||
}
|
||||
|
||||
function pollPrices() {
|
||||
var list = document.getElementById('plan-monitor-list');
|
||||
if (!list || !list.querySelector('.plan-item')) return;
|
||||
|
||||
fetch('/api/plan_prices')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (rows) {
|
||||
rows.forEach(function (row) {
|
||||
var el = list.querySelector('.plan-item[data-plan-id="' + row.id + '"]');
|
||||
if (!el) return;
|
||||
var priceEl = el.querySelector('.live-price');
|
||||
var distEl = el.querySelector('.live-dist');
|
||||
var upEl = el.querySelector('.dist-up');
|
||||
var downEl = el.querySelector('.dist-down');
|
||||
if (priceEl) {
|
||||
priceEl.textContent = row.price != null ? row.price : '--';
|
||||
}
|
||||
if (row.in_zone && distEl) {
|
||||
distEl.innerHTML = '<span class="text-profit" style="font-weight:600">在区间内</span>';
|
||||
} else if (distEl) {
|
||||
distEl.innerHTML =
|
||||
'距上<span class="dist-up">' + fmtDist(row.dist_upper) + '</span> ' +
|
||||
'距下<span class="dist-down">' + fmtDist(row.dist_lower) + '</span>';
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(function () { /* ignore */ });
|
||||
}
|
||||
|
||||
function startPolling() {
|
||||
if (timer) clearInterval(timer);
|
||||
pollPrices();
|
||||
timer = setInterval(pollPrices, 1000);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', startPolling);
|
||||
})();
|
||||
|
||||
+79
-75
@@ -1,75 +1,79 @@
|
||||
(function () {
|
||||
var posTimer = null;
|
||||
|
||||
function fmtNum(v, digits) {
|
||||
if (v === null || v === undefined) return '--';
|
||||
return Number(v).toFixed(digits === undefined ? 2 : digits);
|
||||
}
|
||||
|
||||
function buildPosCard(row) {
|
||||
var pnlClass = '';
|
||||
if (row.float_pnl > 0) pnlClass = 'pnl-pos';
|
||||
if (row.float_pnl < 0) pnlClass = 'pnl-neg';
|
||||
var pnlText = '--';
|
||||
if (row.float_pnl != null) {
|
||||
var sign = row.float_pnl >= 0 ? '+' : '';
|
||||
pnlText = sign + fmtNum(row.float_pnl) + '元';
|
||||
if (row.float_pct != null) {
|
||||
pnlText += ' (' + sign + fmtNum(row.float_pct) + '%)';
|
||||
}
|
||||
}
|
||||
var rr = row.rr_ratio != null ? row.rr_ratio + ':1' : '--';
|
||||
var openT = (row.open_time || '').replace('T', ' ').slice(0, 16);
|
||||
|
||||
return (
|
||||
'<div class="pos-card" data-pos-id="' + row.id + '">' +
|
||||
'<div class="pos-card-head">' +
|
||||
'<div><div class="title">' + row.symbol + ' <span class="badge dir">' + row.direction + '</span></div></div>' +
|
||||
'<form method="post" action="/close_position/' + row.id + '" style="display:inline" onsubmit="return confirm(\'确认平仓?\')">' +
|
||||
'<button type="submit" class="btn-del pos-del">平仓</button></form>' +
|
||||
'</div>' +
|
||||
'<div class="pos-card-meta">来源 <strong>手动输入</strong> · 风险 <strong>' +
|
||||
fmtNum(row.risk_pct) + '%≈' + fmtNum(row.risk_amount) + '元</strong></div>' +
|
||||
'<div class="pos-metrics">' +
|
||||
'<div class="cell"><label>成交价</label><div>' + fmtNum(row.entry_price) + '</div></div>' +
|
||||
'<div class="cell"><label>止损</label><div>' + fmtNum(row.stop_loss) + '</div></div>' +
|
||||
'<div class="cell"><label>止盈</label><div>' + fmtNum(row.take_profit) + '</div></div>' +
|
||||
'<div class="cell"><label>盈亏比</label><div>' + rr + '</div></div>' +
|
||||
'<div class="cell"><label>标记价</label><div>' + (row.mark_price != null ? fmtNum(row.mark_price) : '--') + '</div></div>' +
|
||||
'<div class="cell ' + pnlClass + '"><label>浮盈亏</label><div>' + pnlText + '</div></div>' +
|
||||
'<div class="cell"><label>预估手续费</label><div>' + fmtNum(row.est_fee) + '元</div></div>' +
|
||||
'<div class="cell ' + (row.est_pnl_net > 0 ? 'pnl-pos' : (row.est_pnl_net < 0 ? 'pnl-neg' : '')) + '">' +
|
||||
'<label>扣费后</label><div>' + (row.est_pnl_net != null ? fmtNum(row.est_pnl_net) + '元' : '--') + '</div></div>' +
|
||||
'</div>' +
|
||||
'<div class="pos-footer">' +
|
||||
'<span>保证金 ' + fmtNum(row.margin) + '元</span>' +
|
||||
'<span>仓位占比 ' + fmtNum(row.position_pct) + '%</span>' +
|
||||
'<span>开仓 ' + (openT || '--') + '</span>' +
|
||||
'<span>持仓 ' + (row.holding_duration || '--') + '</span>' +
|
||||
'<span>张数 ' + row.lots + '</span>' +
|
||||
'<span>手续费(估) ' + fmtNum(row.est_fee) + '元 (' + (row.est_fee_close_type || '') + ')</span>' +
|
||||
'</div></div>'
|
||||
);
|
||||
}
|
||||
|
||||
function pollPositions() {
|
||||
var list = document.getElementById('position-live-list');
|
||||
if (!list) return;
|
||||
|
||||
fetch('/api/position_live')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (rows) {
|
||||
if (!rows.length) {
|
||||
list.innerHTML = '<div class="empty-hint">暂无持仓,左侧录入后显示</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = rows.map(buildPosCard).join('');
|
||||
})
|
||||
.catch(function () { /* ignore */ });
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
pollPositions();
|
||||
posTimer = setInterval(pollPositions, 1000);
|
||||
});
|
||||
})();
|
||||
/* Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
* 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
* 详见 LICENSE.zh-CN.txt
|
||||
*/
|
||||
(function () {
|
||||
var posTimer = null;
|
||||
|
||||
function fmtNum(v, digits) {
|
||||
if (v === null || v === undefined) return '--';
|
||||
return Number(v).toFixed(digits === undefined ? 2 : digits);
|
||||
}
|
||||
|
||||
function buildPosCard(row) {
|
||||
var pnlClass = '';
|
||||
if (row.float_pnl > 0) pnlClass = 'pnl-pos';
|
||||
if (row.float_pnl < 0) pnlClass = 'pnl-neg';
|
||||
var pnlText = '--';
|
||||
if (row.float_pnl != null) {
|
||||
var sign = row.float_pnl >= 0 ? '+' : '';
|
||||
pnlText = sign + fmtNum(row.float_pnl) + '元';
|
||||
if (row.float_pct != null) {
|
||||
pnlText += ' (' + sign + fmtNum(row.float_pct) + '%)';
|
||||
}
|
||||
}
|
||||
var rr = row.rr_ratio != null ? row.rr_ratio + ':1' : '--';
|
||||
var openT = (row.open_time || '').replace('T', ' ').slice(0, 16);
|
||||
|
||||
return (
|
||||
'<div class="pos-card" data-pos-id="' + row.id + '">' +
|
||||
'<div class="pos-card-head">' +
|
||||
'<div><div class="title">' + row.symbol + ' <span class="badge dir">' + row.direction + '</span></div></div>' +
|
||||
'<form method="post" action="/close_position/' + row.id + '" style="display:inline" onsubmit="return confirm(\'确认平仓?\')">' +
|
||||
'<button type="submit" class="btn-del pos-del">平仓</button></form>' +
|
||||
'</div>' +
|
||||
'<div class="pos-card-meta">来源 <strong>手动输入</strong> · 风险 <strong>' +
|
||||
fmtNum(row.risk_pct) + '%≈' + fmtNum(row.risk_amount) + '元</strong></div>' +
|
||||
'<div class="pos-metrics">' +
|
||||
'<div class="cell"><label>成交价</label><div>' + fmtNum(row.entry_price) + '</div></div>' +
|
||||
'<div class="cell"><label>止损</label><div>' + fmtNum(row.stop_loss) + '</div></div>' +
|
||||
'<div class="cell"><label>止盈</label><div>' + fmtNum(row.take_profit) + '</div></div>' +
|
||||
'<div class="cell"><label>盈亏比</label><div>' + rr + '</div></div>' +
|
||||
'<div class="cell"><label>标记价</label><div>' + (row.mark_price != null ? fmtNum(row.mark_price) : '--') + '</div></div>' +
|
||||
'<div class="cell ' + pnlClass + '"><label>浮盈亏</label><div>' + pnlText + '</div></div>' +
|
||||
'<div class="cell"><label>预估手续费</label><div>' + fmtNum(row.est_fee) + '元</div></div>' +
|
||||
'<div class="cell ' + (row.est_pnl_net > 0 ? 'pnl-pos' : (row.est_pnl_net < 0 ? 'pnl-neg' : '')) + '">' +
|
||||
'<label>扣费后</label><div>' + (row.est_pnl_net != null ? fmtNum(row.est_pnl_net) + '元' : '--') + '</div></div>' +
|
||||
'</div>' +
|
||||
'<div class="pos-footer">' +
|
||||
'<span>保证金 ' + fmtNum(row.margin) + '元</span>' +
|
||||
'<span>仓位占比 ' + fmtNum(row.position_pct) + '%</span>' +
|
||||
'<span>开仓 ' + (openT || '--') + '</span>' +
|
||||
'<span>持仓 ' + (row.holding_duration || '--') + '</span>' +
|
||||
'<span>张数 ' + row.lots + '</span>' +
|
||||
'<span>手续费(估) ' + fmtNum(row.est_fee) + '元 (' + (row.est_fee_close_type || '') + ')</span>' +
|
||||
'</div></div>'
|
||||
);
|
||||
}
|
||||
|
||||
function pollPositions() {
|
||||
var list = document.getElementById('position-live-list');
|
||||
if (!list) return;
|
||||
|
||||
fetch('/api/position_live')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (rows) {
|
||||
if (!rows.length) {
|
||||
list.innerHTML = '<div class="empty-hint">暂无持仓,左侧录入后显示</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = rows.map(buildPosCard).join('');
|
||||
})
|
||||
.catch(function () { /* ignore */ });
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
pollPositions();
|
||||
posTimer = setInterval(pollPositions, 1000);
|
||||
});
|
||||
})();
|
||||
|
||||
+93
-89
@@ -1,89 +1,93 @@
|
||||
(function () {
|
||||
var deferredPrompt = null;
|
||||
var installBtn = document.getElementById('pwa-install-btn');
|
||||
var iosHint = document.getElementById('pwa-ios-hint');
|
||||
|
||||
function isStandalone() {
|
||||
return window.matchMedia('(display-mode: standalone)').matches
|
||||
|| window.navigator.standalone === true;
|
||||
}
|
||||
|
||||
function isIOS() {
|
||||
return /iPad|iPhone|iPod/.test(navigator.userAgent)
|
||||
&& !window.MSStream;
|
||||
}
|
||||
|
||||
function isTouchDevice() {
|
||||
return window.matchMedia('(hover: none) and (pointer: coarse)').matches
|
||||
|| window.matchMedia('(max-width: 1024px)').matches;
|
||||
}
|
||||
|
||||
function updateThemeColor() {
|
||||
var meta = document.getElementById('meta-theme-color');
|
||||
if (!meta) return;
|
||||
var theme = document.documentElement.getAttribute('data-theme');
|
||||
meta.setAttribute('content', theme === 'light' ? '#e8eef8' : '#050508');
|
||||
}
|
||||
|
||||
function showInstallBtn() {
|
||||
if (installBtn && !isStandalone()) {
|
||||
installBtn.hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
function showIosHint() {
|
||||
if (iosHint && isIOS() && !isStandalone()) {
|
||||
iosHint.classList.add('show');
|
||||
}
|
||||
}
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function () {
|
||||
navigator.serviceWorker.register('/sw.js', { scope: '/' }).catch(function () { /* ignore */ });
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('beforeinstallprompt', function (e) {
|
||||
e.preventDefault();
|
||||
deferredPrompt = e;
|
||||
showInstallBtn();
|
||||
});
|
||||
|
||||
if (installBtn) {
|
||||
installBtn.addEventListener('click', function () {
|
||||
if (!deferredPrompt) {
|
||||
if (isIOS()) showIosHint();
|
||||
return;
|
||||
}
|
||||
deferredPrompt.prompt();
|
||||
deferredPrompt.userChoice.then(function () {
|
||||
deferredPrompt = null;
|
||||
installBtn.hidden = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('appinstalled', function () {
|
||||
deferredPrompt = null;
|
||||
if (installBtn) installBtn.hidden = true;
|
||||
if (iosHint) iosHint.classList.remove('show');
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
updateThemeColor();
|
||||
showIosHint();
|
||||
if (isStandalone()) {
|
||||
if (installBtn) installBtn.hidden = true;
|
||||
if (iosHint) iosHint.classList.remove('show');
|
||||
return;
|
||||
}
|
||||
if (isTouchDevice() && installBtn && deferredPrompt) {
|
||||
showInstallBtn();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
var pick = e.target.closest('[data-theme-pick]');
|
||||
if (pick) setTimeout(updateThemeColor, 80);
|
||||
});
|
||||
})();
|
||||
/* Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
* 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
* 详见 LICENSE.zh-CN.txt
|
||||
*/
|
||||
(function () {
|
||||
var deferredPrompt = null;
|
||||
var installBtn = document.getElementById('pwa-install-btn');
|
||||
var iosHint = document.getElementById('pwa-ios-hint');
|
||||
|
||||
function isStandalone() {
|
||||
return window.matchMedia('(display-mode: standalone)').matches
|
||||
|| window.navigator.standalone === true;
|
||||
}
|
||||
|
||||
function isIOS() {
|
||||
return /iPad|iPhone|iPod/.test(navigator.userAgent)
|
||||
&& !window.MSStream;
|
||||
}
|
||||
|
||||
function isTouchDevice() {
|
||||
return window.matchMedia('(hover: none) and (pointer: coarse)').matches
|
||||
|| window.matchMedia('(max-width: 1024px)').matches;
|
||||
}
|
||||
|
||||
function updateThemeColor() {
|
||||
var meta = document.getElementById('meta-theme-color');
|
||||
if (!meta) return;
|
||||
var theme = document.documentElement.getAttribute('data-theme');
|
||||
meta.setAttribute('content', theme === 'light' ? '#e8eef8' : '#050508');
|
||||
}
|
||||
|
||||
function showInstallBtn() {
|
||||
if (installBtn && !isStandalone()) {
|
||||
installBtn.hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
function showIosHint() {
|
||||
if (iosHint && isIOS() && !isStandalone()) {
|
||||
iosHint.classList.add('show');
|
||||
}
|
||||
}
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function () {
|
||||
navigator.serviceWorker.register('/sw.js', { scope: '/' }).catch(function () { /* ignore */ });
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('beforeinstallprompt', function (e) {
|
||||
e.preventDefault();
|
||||
deferredPrompt = e;
|
||||
showInstallBtn();
|
||||
});
|
||||
|
||||
if (installBtn) {
|
||||
installBtn.addEventListener('click', function () {
|
||||
if (!deferredPrompt) {
|
||||
if (isIOS()) showIosHint();
|
||||
return;
|
||||
}
|
||||
deferredPrompt.prompt();
|
||||
deferredPrompt.userChoice.then(function () {
|
||||
deferredPrompt = null;
|
||||
installBtn.hidden = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('appinstalled', function () {
|
||||
deferredPrompt = null;
|
||||
if (installBtn) installBtn.hidden = true;
|
||||
if (iosHint) iosHint.classList.remove('show');
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
updateThemeColor();
|
||||
showIosHint();
|
||||
if (isStandalone()) {
|
||||
if (installBtn) installBtn.hidden = true;
|
||||
if (iosHint) iosHint.classList.remove('show');
|
||||
return;
|
||||
}
|
||||
if (isTouchDevice() && installBtn && deferredPrompt) {
|
||||
showInstallBtn();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
var pick = e.target.closest('[data-theme-pick]');
|
||||
if (pick) setTimeout(updateThemeColor, 80);
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/* Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
* 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
* 详见 LICENSE.zh-CN.txt
|
||||
*/
|
||||
(function () {
|
||||
function parseNum(v) {
|
||||
var n = parseFloat(v);
|
||||
|
||||
+159
-155
@@ -1,155 +1,159 @@
|
||||
(function () {
|
||||
var cache = null;
|
||||
|
||||
function fmtNum(v, suffix) {
|
||||
if (v === null || v === undefined || v === '') return '-';
|
||||
var n = Number(v);
|
||||
if (isNaN(n)) return String(v);
|
||||
var s = Number.isInteger(n) ? String(n) : n.toFixed(2);
|
||||
return suffix ? s + suffix : s;
|
||||
}
|
||||
|
||||
function fmtMoney(v) {
|
||||
if (v === null || v === undefined) return '-';
|
||||
return fmtNum(v) + ' 元';
|
||||
}
|
||||
|
||||
function fmtPct(v) {
|
||||
if (v === null || v === undefined) return '-';
|
||||
return fmtNum(v) + '%';
|
||||
}
|
||||
|
||||
function setSummary(s) {
|
||||
var map = {
|
||||
total_trades: function () { return fmtNum(s.total_trades); },
|
||||
win_rate: function () { return fmtPct(s.win_rate); },
|
||||
avg_profit: function () { return fmtMoney(s.avg_profit); },
|
||||
avg_loss: function () { return fmtMoney(s.avg_loss); },
|
||||
profit_loss_ratio: function () { return fmtNum(s.profit_loss_ratio); },
|
||||
consecutive_losses: function () { return fmtNum(s.consecutive_losses); },
|
||||
max_drawdown: function () {
|
||||
var amt = fmtMoney(s.max_drawdown);
|
||||
var pct = s.max_drawdown_pct ? ' (' + fmtPct(s.max_drawdown_pct) + ')' : '';
|
||||
return amt + pct;
|
||||
},
|
||||
max_loss_amount: function () { return fmtMoney(s.max_loss_amount); },
|
||||
max_loss_pct: function () { return fmtPct(s.max_loss_pct); },
|
||||
max_profit_amount: function () { return fmtMoney(s.max_profit_amount); },
|
||||
max_profit_pct: function () { return fmtPct(s.max_profit_pct); },
|
||||
total_fee: function () { return fmtMoney(s.total_fee); },
|
||||
emotion_count: function () { return fmtNum(s.emotion_count); },
|
||||
emotion_ratio: function () { return fmtPct(s.emotion_ratio); },
|
||||
};
|
||||
document.querySelectorAll('#stats-summary [data-k]').forEach(function (el) {
|
||||
var key = el.getAttribute('data-k');
|
||||
el.textContent = map[key] ? map[key]() : '-';
|
||||
});
|
||||
}
|
||||
|
||||
function fillViewSelect(views, selected) {
|
||||
var sel = document.getElementById('stats-view-select');
|
||||
if (!sel) return;
|
||||
sel.innerHTML = '';
|
||||
views.forEach(function (v) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = v.key;
|
||||
opt.textContent = v.label;
|
||||
if (v.key === selected) opt.selected = true;
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
function cellClass(key, val) {
|
||||
if (key === 'total_net' || key === 'max_profit' || key === 'avg_profit') {
|
||||
if (val > 0) return 'text-profit';
|
||||
if (val < 0) return 'text-loss';
|
||||
}
|
||||
if (key === 'max_loss' || key === 'avg_loss' || key === 'total_fee') {
|
||||
return 'text-loss';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function renderBreakdown(key) {
|
||||
if (!cache || !cache.breakdowns) return;
|
||||
var block = cache.breakdowns[key];
|
||||
var head = document.getElementById('stats-breakdown-head');
|
||||
var body = document.getElementById('stats-breakdown-body');
|
||||
if (!block || !head || !body) return;
|
||||
|
||||
head.innerHTML = '';
|
||||
block.columns.forEach(function (col) {
|
||||
var th = document.createElement('th');
|
||||
th.textContent = col.label;
|
||||
head.appendChild(th);
|
||||
});
|
||||
|
||||
body.innerHTML = '';
|
||||
if (!block.rows || !block.rows.length) {
|
||||
var tr = document.createElement('tr');
|
||||
var td = document.createElement('td');
|
||||
td.colSpan = block.columns.length;
|
||||
td.className = 'text-muted';
|
||||
td.textContent = '暂无数据';
|
||||
tr.appendChild(td);
|
||||
body.appendChild(tr);
|
||||
return;
|
||||
}
|
||||
|
||||
block.rows.forEach(function (row) {
|
||||
var tr = document.createElement('tr');
|
||||
block.columns.forEach(function (col) {
|
||||
var td = document.createElement('td');
|
||||
var val = row[col.key];
|
||||
if (col.key === 'win_rate') {
|
||||
td.textContent = fmtPct(val);
|
||||
} else if (col.key === 'label') {
|
||||
td.textContent = val || '-';
|
||||
} else if (typeof val === 'number') {
|
||||
td.textContent = fmtNum(val);
|
||||
td.className = cellClass(col.key, val);
|
||||
} else {
|
||||
td.textContent = val != null ? val : '-';
|
||||
}
|
||||
tr.appendChild(td);
|
||||
});
|
||||
body.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
function applyData(data) {
|
||||
cache = data;
|
||||
setSummary(data.summary || {});
|
||||
var views = data.views || [];
|
||||
var sel = document.getElementById('stats-view-select');
|
||||
var current = sel && sel.value ? sel.value : (views[0] && views[0].key);
|
||||
fillViewSelect(views, current);
|
||||
renderBreakdown(current);
|
||||
var updated = document.getElementById('stats-updated');
|
||||
if (updated) {
|
||||
updated.textContent = data.updated_at
|
||||
? '统计更新于 ' + data.updated_at.replace('T', ' ')
|
||||
: '统计已加载';
|
||||
}
|
||||
}
|
||||
|
||||
function loadStats() {
|
||||
fetch('/api/stats')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(applyData)
|
||||
.catch(function () {
|
||||
var updated = document.getElementById('stats-updated');
|
||||
if (updated) updated.textContent = '加载失败,请刷新页面';
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var viewSel = document.getElementById('stats-view-select');
|
||||
if (viewSel) {
|
||||
viewSel.addEventListener('change', function () {
|
||||
renderBreakdown(this.value);
|
||||
});
|
||||
}
|
||||
loadStats();
|
||||
});
|
||||
})();
|
||||
/* Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
* 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
* 详见 LICENSE.zh-CN.txt
|
||||
*/
|
||||
(function () {
|
||||
var cache = null;
|
||||
|
||||
function fmtNum(v, suffix) {
|
||||
if (v === null || v === undefined || v === '') return '-';
|
||||
var n = Number(v);
|
||||
if (isNaN(n)) return String(v);
|
||||
var s = Number.isInteger(n) ? String(n) : n.toFixed(2);
|
||||
return suffix ? s + suffix : s;
|
||||
}
|
||||
|
||||
function fmtMoney(v) {
|
||||
if (v === null || v === undefined) return '-';
|
||||
return fmtNum(v) + ' 元';
|
||||
}
|
||||
|
||||
function fmtPct(v) {
|
||||
if (v === null || v === undefined) return '-';
|
||||
return fmtNum(v) + '%';
|
||||
}
|
||||
|
||||
function setSummary(s) {
|
||||
var map = {
|
||||
total_trades: function () { return fmtNum(s.total_trades); },
|
||||
win_rate: function () { return fmtPct(s.win_rate); },
|
||||
avg_profit: function () { return fmtMoney(s.avg_profit); },
|
||||
avg_loss: function () { return fmtMoney(s.avg_loss); },
|
||||
profit_loss_ratio: function () { return fmtNum(s.profit_loss_ratio); },
|
||||
consecutive_losses: function () { return fmtNum(s.consecutive_losses); },
|
||||
max_drawdown: function () {
|
||||
var amt = fmtMoney(s.max_drawdown);
|
||||
var pct = s.max_drawdown_pct ? ' (' + fmtPct(s.max_drawdown_pct) + ')' : '';
|
||||
return amt + pct;
|
||||
},
|
||||
max_loss_amount: function () { return fmtMoney(s.max_loss_amount); },
|
||||
max_loss_pct: function () { return fmtPct(s.max_loss_pct); },
|
||||
max_profit_amount: function () { return fmtMoney(s.max_profit_amount); },
|
||||
max_profit_pct: function () { return fmtPct(s.max_profit_pct); },
|
||||
total_fee: function () { return fmtMoney(s.total_fee); },
|
||||
emotion_count: function () { return fmtNum(s.emotion_count); },
|
||||
emotion_ratio: function () { return fmtPct(s.emotion_ratio); },
|
||||
};
|
||||
document.querySelectorAll('#stats-summary [data-k]').forEach(function (el) {
|
||||
var key = el.getAttribute('data-k');
|
||||
el.textContent = map[key] ? map[key]() : '-';
|
||||
});
|
||||
}
|
||||
|
||||
function fillViewSelect(views, selected) {
|
||||
var sel = document.getElementById('stats-view-select');
|
||||
if (!sel) return;
|
||||
sel.innerHTML = '';
|
||||
views.forEach(function (v) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = v.key;
|
||||
opt.textContent = v.label;
|
||||
if (v.key === selected) opt.selected = true;
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
function cellClass(key, val) {
|
||||
if (key === 'total_net' || key === 'max_profit' || key === 'avg_profit') {
|
||||
if (val > 0) return 'text-profit';
|
||||
if (val < 0) return 'text-loss';
|
||||
}
|
||||
if (key === 'max_loss' || key === 'avg_loss' || key === 'total_fee') {
|
||||
return 'text-loss';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function renderBreakdown(key) {
|
||||
if (!cache || !cache.breakdowns) return;
|
||||
var block = cache.breakdowns[key];
|
||||
var head = document.getElementById('stats-breakdown-head');
|
||||
var body = document.getElementById('stats-breakdown-body');
|
||||
if (!block || !head || !body) return;
|
||||
|
||||
head.innerHTML = '';
|
||||
block.columns.forEach(function (col) {
|
||||
var th = document.createElement('th');
|
||||
th.textContent = col.label;
|
||||
head.appendChild(th);
|
||||
});
|
||||
|
||||
body.innerHTML = '';
|
||||
if (!block.rows || !block.rows.length) {
|
||||
var tr = document.createElement('tr');
|
||||
var td = document.createElement('td');
|
||||
td.colSpan = block.columns.length;
|
||||
td.className = 'text-muted';
|
||||
td.textContent = '暂无数据';
|
||||
tr.appendChild(td);
|
||||
body.appendChild(tr);
|
||||
return;
|
||||
}
|
||||
|
||||
block.rows.forEach(function (row) {
|
||||
var tr = document.createElement('tr');
|
||||
block.columns.forEach(function (col) {
|
||||
var td = document.createElement('td');
|
||||
var val = row[col.key];
|
||||
if (col.key === 'win_rate') {
|
||||
td.textContent = fmtPct(val);
|
||||
} else if (col.key === 'label') {
|
||||
td.textContent = val || '-';
|
||||
} else if (typeof val === 'number') {
|
||||
td.textContent = fmtNum(val);
|
||||
td.className = cellClass(col.key, val);
|
||||
} else {
|
||||
td.textContent = val != null ? val : '-';
|
||||
}
|
||||
tr.appendChild(td);
|
||||
});
|
||||
body.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
function applyData(data) {
|
||||
cache = data;
|
||||
setSummary(data.summary || {});
|
||||
var views = data.views || [];
|
||||
var sel = document.getElementById('stats-view-select');
|
||||
var current = sel && sel.value ? sel.value : (views[0] && views[0].key);
|
||||
fillViewSelect(views, current);
|
||||
renderBreakdown(current);
|
||||
var updated = document.getElementById('stats-updated');
|
||||
if (updated) {
|
||||
updated.textContent = data.updated_at
|
||||
? '统计更新于 ' + data.updated_at.replace('T', ' ')
|
||||
: '统计已加载';
|
||||
}
|
||||
}
|
||||
|
||||
function loadStats() {
|
||||
fetch('/api/stats')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(applyData)
|
||||
.catch(function () {
|
||||
var updated = document.getElementById('stats-updated');
|
||||
if (updated) updated.textContent = '加载失败,请刷新页面';
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var viewSel = document.getElementById('stats-view-select');
|
||||
if (viewSel) {
|
||||
viewSel.addEventListener('change', function () {
|
||||
renderBreakdown(this.value);
|
||||
});
|
||||
}
|
||||
loadStats();
|
||||
});
|
||||
})();
|
||||
|
||||
+140
-136
@@ -1,136 +1,140 @@
|
||||
(function () {
|
||||
var trendPayload = null;
|
||||
|
||||
function jsonPost(url, body) {
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body || {})
|
||||
}).then(function (r) { return r.json(); });
|
||||
}
|
||||
|
||||
function formData(form) {
|
||||
var fd = new FormData(form);
|
||||
var o = {};
|
||||
fd.forEach(function (v, k) { o[k] = v; });
|
||||
return o;
|
||||
}
|
||||
|
||||
function showPreview(el, text, ok) {
|
||||
if (!el) return;
|
||||
if (!text) {
|
||||
el.hidden = true;
|
||||
el.textContent = '';
|
||||
return;
|
||||
}
|
||||
el.hidden = false;
|
||||
el.textContent = text;
|
||||
el.style.color = ok === false ? 'var(--loss)' : '';
|
||||
}
|
||||
|
||||
function formatPlan(plan) {
|
||||
if (!plan) return '';
|
||||
var lines = [];
|
||||
if (plan.symbol) lines.push('品种:' + plan.symbol);
|
||||
if (plan.target_lots != null) lines.push('目标手数:' + plan.target_lots);
|
||||
if (plan.first_lots != null) lines.push('首仓:' + plan.first_lots + ' 手');
|
||||
if (plan.grid && plan.grid.length) {
|
||||
lines.push('补仓档位:' + plan.grid.map(function (g) { return g.price; }).join(' → '));
|
||||
}
|
||||
if (plan.message) lines.push(plan.message);
|
||||
return lines.length ? lines.join('\n') : JSON.stringify(plan, null, 2);
|
||||
}
|
||||
|
||||
function formatRoll(preview) {
|
||||
if (!preview) return '';
|
||||
var lines = [];
|
||||
if (preview.add_lots != null) lines.push('加仓手数:' + preview.add_lots);
|
||||
if (preview.new_stop_loss != null) lines.push('新止损:' + preview.new_stop_loss);
|
||||
if (preview.total_lots != null) lines.push('合计手数:' + preview.total_lots);
|
||||
if (preview.worst_loss != null) lines.push('最坏亏损:' + preview.worst_loss + ' 元');
|
||||
if (preview.message) lines.push(preview.message);
|
||||
return lines.length ? lines.join('\n') : JSON.stringify(preview, null, 2);
|
||||
}
|
||||
|
||||
var trendForm = document.getElementById('trend-form');
|
||||
var btnPreview = document.getElementById('btn-trend-preview');
|
||||
var btnExec = document.getElementById('btn-trend-exec');
|
||||
var previewEl = document.getElementById('trend-preview');
|
||||
|
||||
if (btnPreview && trendForm) {
|
||||
btnPreview.addEventListener('click', function () {
|
||||
btnPreview.disabled = true;
|
||||
jsonPost('/api/strategy/trend/preview', formData(trendForm)).then(function (d) {
|
||||
if (!d.ok) {
|
||||
showPreview(previewEl, d.error || '预览失败', false);
|
||||
btnExec.hidden = true;
|
||||
return;
|
||||
}
|
||||
trendPayload = formData(trendForm);
|
||||
showPreview(previewEl, formatPlan(d.plan), true);
|
||||
btnExec.hidden = false;
|
||||
}).finally(function () {
|
||||
btnPreview.disabled = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
if (btnExec) {
|
||||
btnExec.addEventListener('click', function () {
|
||||
if (!trendPayload) return;
|
||||
btnExec.disabled = true;
|
||||
btnExec.textContent = '执行中…';
|
||||
jsonPost('/api/strategy/trend/execute', trendPayload).then(function (d) {
|
||||
if (!d.ok) { alert(d.error); return; }
|
||||
location.reload();
|
||||
}).finally(function () {
|
||||
btnExec.disabled = false;
|
||||
btnExec.textContent = '确认执行首仓';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var rollForm = document.getElementById('roll-form');
|
||||
var btnRollP = document.getElementById('btn-roll-preview');
|
||||
var btnRollE = document.getElementById('btn-roll-exec');
|
||||
var rollPrev = document.getElementById('roll-preview');
|
||||
if (btnRollP && rollForm) {
|
||||
btnRollP.addEventListener('click', function () {
|
||||
btnRollP.disabled = true;
|
||||
jsonPost('/api/strategy/roll/preview', formData(rollForm)).then(function (d) {
|
||||
if (!d.ok) {
|
||||
showPreview(rollPrev, d.error, false);
|
||||
btnRollE.hidden = true;
|
||||
return;
|
||||
}
|
||||
showPreview(rollPrev, formatRoll(d.preview), true);
|
||||
btnRollE.hidden = false;
|
||||
}).finally(function () {
|
||||
btnRollP.disabled = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
if (btnRollE && rollForm) {
|
||||
btnRollE.addEventListener('click', function () {
|
||||
btnRollE.disabled = true;
|
||||
btnRollE.textContent = '执行中…';
|
||||
jsonPost('/api/strategy/roll/execute', formData(rollForm)).then(function (d) {
|
||||
if (!d.ok) { alert(d.error); return; }
|
||||
location.reload();
|
||||
}).finally(function () {
|
||||
btnRollE.disabled = false;
|
||||
btnRollE.textContent = '执行滚仓';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var btnStop = document.getElementById('btn-trend-stop');
|
||||
if (btnStop) {
|
||||
btnStop.addEventListener('click', function () {
|
||||
var pid = document.querySelector('#trend-stop-form input[name=plan_id]');
|
||||
jsonPost('/api/strategy/trend/stop', { plan_id: pid ? pid.value : 0 }).then(function (d) {
|
||||
if (!d.ok) { alert(d.error); return; }
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
/* Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
* 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
* 详见 LICENSE.zh-CN.txt
|
||||
*/
|
||||
(function () {
|
||||
var trendPayload = null;
|
||||
|
||||
function jsonPost(url, body) {
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body || {})
|
||||
}).then(function (r) { return r.json(); });
|
||||
}
|
||||
|
||||
function formData(form) {
|
||||
var fd = new FormData(form);
|
||||
var o = {};
|
||||
fd.forEach(function (v, k) { o[k] = v; });
|
||||
return o;
|
||||
}
|
||||
|
||||
function showPreview(el, text, ok) {
|
||||
if (!el) return;
|
||||
if (!text) {
|
||||
el.hidden = true;
|
||||
el.textContent = '';
|
||||
return;
|
||||
}
|
||||
el.hidden = false;
|
||||
el.textContent = text;
|
||||
el.style.color = ok === false ? 'var(--loss)' : '';
|
||||
}
|
||||
|
||||
function formatPlan(plan) {
|
||||
if (!plan) return '';
|
||||
var lines = [];
|
||||
if (plan.symbol) lines.push('品种:' + plan.symbol);
|
||||
if (plan.target_lots != null) lines.push('目标手数:' + plan.target_lots);
|
||||
if (plan.first_lots != null) lines.push('首仓:' + plan.first_lots + ' 手');
|
||||
if (plan.grid && plan.grid.length) {
|
||||
lines.push('补仓档位:' + plan.grid.map(function (g) { return g.price; }).join(' → '));
|
||||
}
|
||||
if (plan.message) lines.push(plan.message);
|
||||
return lines.length ? lines.join('\n') : JSON.stringify(plan, null, 2);
|
||||
}
|
||||
|
||||
function formatRoll(preview) {
|
||||
if (!preview) return '';
|
||||
var lines = [];
|
||||
if (preview.add_lots != null) lines.push('加仓手数:' + preview.add_lots);
|
||||
if (preview.new_stop_loss != null) lines.push('新止损:' + preview.new_stop_loss);
|
||||
if (preview.total_lots != null) lines.push('合计手数:' + preview.total_lots);
|
||||
if (preview.worst_loss != null) lines.push('最坏亏损:' + preview.worst_loss + ' 元');
|
||||
if (preview.message) lines.push(preview.message);
|
||||
return lines.length ? lines.join('\n') : JSON.stringify(preview, null, 2);
|
||||
}
|
||||
|
||||
var trendForm = document.getElementById('trend-form');
|
||||
var btnPreview = document.getElementById('btn-trend-preview');
|
||||
var btnExec = document.getElementById('btn-trend-exec');
|
||||
var previewEl = document.getElementById('trend-preview');
|
||||
|
||||
if (btnPreview && trendForm) {
|
||||
btnPreview.addEventListener('click', function () {
|
||||
btnPreview.disabled = true;
|
||||
jsonPost('/api/strategy/trend/preview', formData(trendForm)).then(function (d) {
|
||||
if (!d.ok) {
|
||||
showPreview(previewEl, d.error || '预览失败', false);
|
||||
btnExec.hidden = true;
|
||||
return;
|
||||
}
|
||||
trendPayload = formData(trendForm);
|
||||
showPreview(previewEl, formatPlan(d.plan), true);
|
||||
btnExec.hidden = false;
|
||||
}).finally(function () {
|
||||
btnPreview.disabled = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
if (btnExec) {
|
||||
btnExec.addEventListener('click', function () {
|
||||
if (!trendPayload) return;
|
||||
btnExec.disabled = true;
|
||||
btnExec.textContent = '执行中…';
|
||||
jsonPost('/api/strategy/trend/execute', trendPayload).then(function (d) {
|
||||
if (!d.ok) { alert(d.error); return; }
|
||||
location.reload();
|
||||
}).finally(function () {
|
||||
btnExec.disabled = false;
|
||||
btnExec.textContent = '确认执行首仓';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var rollForm = document.getElementById('roll-form');
|
||||
var btnRollP = document.getElementById('btn-roll-preview');
|
||||
var btnRollE = document.getElementById('btn-roll-exec');
|
||||
var rollPrev = document.getElementById('roll-preview');
|
||||
if (btnRollP && rollForm) {
|
||||
btnRollP.addEventListener('click', function () {
|
||||
btnRollP.disabled = true;
|
||||
jsonPost('/api/strategy/roll/preview', formData(rollForm)).then(function (d) {
|
||||
if (!d.ok) {
|
||||
showPreview(rollPrev, d.error, false);
|
||||
btnRollE.hidden = true;
|
||||
return;
|
||||
}
|
||||
showPreview(rollPrev, formatRoll(d.preview), true);
|
||||
btnRollE.hidden = false;
|
||||
}).finally(function () {
|
||||
btnRollP.disabled = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
if (btnRollE && rollForm) {
|
||||
btnRollE.addEventListener('click', function () {
|
||||
btnRollE.disabled = true;
|
||||
btnRollE.textContent = '执行中…';
|
||||
jsonPost('/api/strategy/roll/execute', formData(rollForm)).then(function (d) {
|
||||
if (!d.ok) { alert(d.error); return; }
|
||||
location.reload();
|
||||
}).finally(function () {
|
||||
btnRollE.disabled = false;
|
||||
btnRollE.textContent = '执行滚仓';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var btnStop = document.getElementById('btn-trend-stop');
|
||||
if (btnStop) {
|
||||
btnStop.addEventListener('click', function () {
|
||||
var pid = document.querySelector('#trend-stop-form input[name=plan_id]');
|
||||
jsonPost('/api/strategy/trend/stop', { plan_id: pid ? pid.value : 0 }).then(function (d) {
|
||||
if (!d.ok) { alert(d.error); return; }
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
+286
-282
@@ -1,282 +1,286 @@
|
||||
(function () {
|
||||
var recommendedGroupsCache = null;
|
||||
var recommendedGroupsPromise = null;
|
||||
|
||||
function loadRecommendedGroups() {
|
||||
if (recommendedGroupsCache) {
|
||||
return Promise.resolve(recommendedGroupsCache);
|
||||
}
|
||||
if (recommendedGroupsPromise) {
|
||||
return recommendedGroupsPromise;
|
||||
}
|
||||
recommendedGroupsPromise = fetch('/api/symbols/recommended')
|
||||
.then(function (r) {
|
||||
if (!r.ok) {
|
||||
throw new Error('HTTP ' + r.status);
|
||||
}
|
||||
return r.json();
|
||||
})
|
||||
.then(function (groups) {
|
||||
recommendedGroupsCache = Array.isArray(groups) ? groups : [];
|
||||
return recommendedGroupsCache;
|
||||
})
|
||||
.catch(function () {
|
||||
recommendedGroupsCache = null;
|
||||
throw new Error('load failed');
|
||||
})
|
||||
.finally(function () {
|
||||
recommendedGroupsPromise = null;
|
||||
});
|
||||
return recommendedGroupsPromise;
|
||||
}
|
||||
|
||||
function formatSub(item) {
|
||||
var sub = '同花顺 ' + item.ths_code +
|
||||
(item.market_code ? ' · ' + item.market_code : '') +
|
||||
' · ' + (item.exchange || '');
|
||||
if (item.max_lots != null && item.max_lots > 0) {
|
||||
sub += ' · 最大 ' + item.max_lots + ' 手';
|
||||
}
|
||||
return sub;
|
||||
}
|
||||
|
||||
function formatInputLabel(item) {
|
||||
return item.input_label || (item.name + ' ' + item.ths_code);
|
||||
}
|
||||
|
||||
function itemMatchesQuery(item, qLower) {
|
||||
if (!qLower) return true;
|
||||
var hay = (
|
||||
item.name + ' ' + item.ths_code + ' ' +
|
||||
(item.display || '') + ' ' + (item.contract || '') + ' ' +
|
||||
(item.exchange || '')
|
||||
).toLowerCase();
|
||||
return hay.indexOf(qLower) >= 0;
|
||||
}
|
||||
|
||||
function groupedHasMatch(groups, qLower) {
|
||||
if (!qLower) return true;
|
||||
return groups.some(function (group) {
|
||||
return group.items.some(function (item) {
|
||||
return itemMatchesQuery(item, qLower);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initSymbolInput(wrapper) {
|
||||
const input = wrapper.querySelector('.symbol-input');
|
||||
const hiddenThs = wrapper.querySelector('input[name="symbol"]')
|
||||
|| wrapper.querySelector('.symbol-ths-code');
|
||||
const hiddenName = wrapper.querySelector('input[name="symbol_name"]');
|
||||
const hiddenMarket = wrapper.querySelector('input[name="market_code"]');
|
||||
const hiddenSina = wrapper.querySelector('input[name="sina_code"]');
|
||||
const dropdown = wrapper.querySelector('.symbol-dropdown');
|
||||
const selectedEl = wrapper.querySelector('.symbol-selected');
|
||||
const isMarketPicker = wrapper.classList.contains('market-symbol-wrap');
|
||||
const useMainsPicker = isMarketPicker || wrapper.classList.contains('symbol-mains');
|
||||
let timer = null;
|
||||
let abortCtrl = null;
|
||||
const cache = new Map();
|
||||
let mainsCache = null;
|
||||
|
||||
function hideDropdown() {
|
||||
dropdown.classList.remove('show');
|
||||
}
|
||||
|
||||
function selectItem(item) {
|
||||
const label = formatInputLabel(item);
|
||||
input.value = label;
|
||||
if (hiddenThs) hiddenThs.value = item.ths_code;
|
||||
if (hiddenName) hiddenName.value = item.name;
|
||||
if (hiddenMarket) hiddenMarket.value = item.market_code || '';
|
||||
if (hiddenSina) hiddenSina.value = item.sina_code || '';
|
||||
if (selectedEl) selectedEl.textContent = formatSub(item);
|
||||
hideDropdown();
|
||||
input.dispatchEvent(new CustomEvent('symbol-selected', { detail: item, bubbles: true }));
|
||||
}
|
||||
|
||||
function buildOptionEl(item) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'symbol-option';
|
||||
if (item.near_expiry) {
|
||||
div.classList.add('near-expiry');
|
||||
}
|
||||
var label = item.display || (item.name + ' ' + item.ths_code);
|
||||
if (item.near_expiry) {
|
||||
label += ' <span class="near-expiry-tag">临期</span>';
|
||||
}
|
||||
div.innerHTML = label +
|
||||
'<div class="sub">' + formatSub(item) + '</div>';
|
||||
div.addEventListener('mousedown', function (e) {
|
||||
e.preventDefault();
|
||||
selectItem(item);
|
||||
});
|
||||
return div;
|
||||
}
|
||||
|
||||
function renderItems(items) {
|
||||
dropdown.innerHTML = '';
|
||||
if (!items.length) {
|
||||
dropdown.innerHTML = '<div class="symbol-option">无匹配,可输入同花顺代码如 ag2608</div>';
|
||||
} else {
|
||||
items.forEach(function (item) {
|
||||
dropdown.appendChild(buildOptionEl(item));
|
||||
});
|
||||
}
|
||||
dropdown.classList.add('show');
|
||||
}
|
||||
|
||||
function renderGrouped(groups, filterQ) {
|
||||
dropdown.innerHTML = '';
|
||||
const qLower = (filterQ || '').trim().toLowerCase();
|
||||
let any = false;
|
||||
groups.forEach(function (group) {
|
||||
const items = group.items.filter(function (item) {
|
||||
return itemMatchesQuery(item, qLower);
|
||||
});
|
||||
if (!items.length) return;
|
||||
any = true;
|
||||
const head = document.createElement('div');
|
||||
head.className = 'symbol-group-head';
|
||||
head.textContent = group.category;
|
||||
dropdown.appendChild(head);
|
||||
items.forEach(function (item) {
|
||||
dropdown.appendChild(buildOptionEl(item));
|
||||
});
|
||||
});
|
||||
if (!any) {
|
||||
dropdown.innerHTML = '<div class="symbol-option">无匹配品种,可输入合约代码如 ag2608</div>';
|
||||
}
|
||||
dropdown.classList.add('show');
|
||||
}
|
||||
|
||||
function showMarketMains(filterQ, onEmpty) {
|
||||
const q = (filterQ || '').trim();
|
||||
const qLower = q.toLowerCase();
|
||||
if (mainsCache) {
|
||||
if (!q || groupedHasMatch(mainsCache, qLower)) {
|
||||
renderGrouped(mainsCache, q);
|
||||
return;
|
||||
}
|
||||
if (typeof onEmpty === 'function') {
|
||||
onEmpty(q);
|
||||
return;
|
||||
}
|
||||
renderGrouped(mainsCache, q);
|
||||
return;
|
||||
}
|
||||
dropdown.innerHTML = '<div class="symbol-option">正在加载推荐品种…</div>';
|
||||
dropdown.classList.add('show');
|
||||
loadRecommendedGroups()
|
||||
.then(function (groups) {
|
||||
mainsCache = groups;
|
||||
if (!groups.length) {
|
||||
dropdown.innerHTML =
|
||||
'<div class="symbol-option">当前资金下暂无推荐品种,可输入合约代码搜索</div>';
|
||||
dropdown.classList.add('show');
|
||||
return;
|
||||
}
|
||||
showMarketMains(filterQ, onEmpty);
|
||||
})
|
||||
.catch(function () {
|
||||
dropdown.innerHTML =
|
||||
'<div class="symbol-option">推荐品种加载失败,请刷新页面或输入合约代码搜索</div>';
|
||||
dropdown.classList.add('show');
|
||||
});
|
||||
}
|
||||
|
||||
function search(q) {
|
||||
if (cache.has(q)) {
|
||||
renderItems(cache.get(q));
|
||||
return;
|
||||
}
|
||||
if (abortCtrl) {
|
||||
abortCtrl.abort();
|
||||
}
|
||||
abortCtrl = new AbortController();
|
||||
fetch('/api/symbols/search?q=' + encodeURIComponent(q), {
|
||||
signal: abortCtrl.signal,
|
||||
})
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (items) {
|
||||
cache.set(q, items);
|
||||
renderItems(items);
|
||||
})
|
||||
.catch(function (err) {
|
||||
if (err && err.name === 'AbortError') return;
|
||||
hideDropdown();
|
||||
});
|
||||
}
|
||||
|
||||
function handleQuery(q) {
|
||||
if (useMainsPicker) {
|
||||
showMarketMains(q, function (query) {
|
||||
search(query);
|
||||
});
|
||||
} else {
|
||||
search(q);
|
||||
}
|
||||
}
|
||||
|
||||
input.addEventListener('input', function () {
|
||||
if (hiddenThs) hiddenThs.value = '';
|
||||
if (hiddenName) hiddenName.value = '';
|
||||
if (hiddenMarket) hiddenMarket.value = '';
|
||||
if (hiddenSina) hiddenSina.value = '';
|
||||
if (selectedEl) selectedEl.textContent = '';
|
||||
const q = input.value.trim();
|
||||
if (!q) {
|
||||
if (useMainsPicker) {
|
||||
showMarketMains('');
|
||||
} else {
|
||||
hideDropdown();
|
||||
}
|
||||
return;
|
||||
}
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(function () {
|
||||
handleQuery(q);
|
||||
}, 120);
|
||||
});
|
||||
|
||||
input.addEventListener('blur', function () {
|
||||
setTimeout(hideDropdown, 150);
|
||||
});
|
||||
|
||||
input.addEventListener('focus', function () {
|
||||
const q = input.value.trim();
|
||||
if (useMainsPicker) {
|
||||
showMarketMains(q, function (query) {
|
||||
if (query) search(query);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (q && hiddenThs && !hiddenThs.value) {
|
||||
search(q);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('.symbol-wrap').forEach(initSymbolInput);
|
||||
|
||||
document.querySelectorAll('form').forEach(function (form) {
|
||||
if (!form.querySelector('.symbol-wrap')) return;
|
||||
if (form.id === 'market-form') return;
|
||||
form.addEventListener('submit', function (e) {
|
||||
const ths = form.querySelector('input[name="symbol"]')
|
||||
|| form.querySelector('.symbol-ths-code');
|
||||
const market = form.querySelector('input[name="market_code"]');
|
||||
if (ths && !ths.value.trim()) {
|
||||
e.preventDefault();
|
||||
alert('请从下拉列表选择品种');
|
||||
return;
|
||||
}
|
||||
if (market && !market.value.trim()) {
|
||||
e.preventDefault();
|
||||
alert('请从下拉列表选择品种(需含同花顺行情代码)');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
/* Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
* 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
* 详见 LICENSE.zh-CN.txt
|
||||
*/
|
||||
(function () {
|
||||
var recommendedGroupsCache = null;
|
||||
var recommendedGroupsPromise = null;
|
||||
|
||||
function loadRecommendedGroups() {
|
||||
if (recommendedGroupsCache) {
|
||||
return Promise.resolve(recommendedGroupsCache);
|
||||
}
|
||||
if (recommendedGroupsPromise) {
|
||||
return recommendedGroupsPromise;
|
||||
}
|
||||
recommendedGroupsPromise = fetch('/api/symbols/recommended')
|
||||
.then(function (r) {
|
||||
if (!r.ok) {
|
||||
throw new Error('HTTP ' + r.status);
|
||||
}
|
||||
return r.json();
|
||||
})
|
||||
.then(function (groups) {
|
||||
recommendedGroupsCache = Array.isArray(groups) ? groups : [];
|
||||
return recommendedGroupsCache;
|
||||
})
|
||||
.catch(function () {
|
||||
recommendedGroupsCache = null;
|
||||
throw new Error('load failed');
|
||||
})
|
||||
.finally(function () {
|
||||
recommendedGroupsPromise = null;
|
||||
});
|
||||
return recommendedGroupsPromise;
|
||||
}
|
||||
|
||||
function formatSub(item) {
|
||||
var sub = '同花顺 ' + item.ths_code +
|
||||
(item.market_code ? ' · ' + item.market_code : '') +
|
||||
' · ' + (item.exchange || '');
|
||||
if (item.max_lots != null && item.max_lots > 0) {
|
||||
sub += ' · 最大 ' + item.max_lots + ' 手';
|
||||
}
|
||||
return sub;
|
||||
}
|
||||
|
||||
function formatInputLabel(item) {
|
||||
return item.input_label || (item.name + ' ' + item.ths_code);
|
||||
}
|
||||
|
||||
function itemMatchesQuery(item, qLower) {
|
||||
if (!qLower) return true;
|
||||
var hay = (
|
||||
item.name + ' ' + item.ths_code + ' ' +
|
||||
(item.display || '') + ' ' + (item.contract || '') + ' ' +
|
||||
(item.exchange || '')
|
||||
).toLowerCase();
|
||||
return hay.indexOf(qLower) >= 0;
|
||||
}
|
||||
|
||||
function groupedHasMatch(groups, qLower) {
|
||||
if (!qLower) return true;
|
||||
return groups.some(function (group) {
|
||||
return group.items.some(function (item) {
|
||||
return itemMatchesQuery(item, qLower);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initSymbolInput(wrapper) {
|
||||
const input = wrapper.querySelector('.symbol-input');
|
||||
const hiddenThs = wrapper.querySelector('input[name="symbol"]')
|
||||
|| wrapper.querySelector('.symbol-ths-code');
|
||||
const hiddenName = wrapper.querySelector('input[name="symbol_name"]');
|
||||
const hiddenMarket = wrapper.querySelector('input[name="market_code"]');
|
||||
const hiddenSina = wrapper.querySelector('input[name="sina_code"]');
|
||||
const dropdown = wrapper.querySelector('.symbol-dropdown');
|
||||
const selectedEl = wrapper.querySelector('.symbol-selected');
|
||||
const isMarketPicker = wrapper.classList.contains('market-symbol-wrap');
|
||||
const useMainsPicker = isMarketPicker || wrapper.classList.contains('symbol-mains');
|
||||
let timer = null;
|
||||
let abortCtrl = null;
|
||||
const cache = new Map();
|
||||
let mainsCache = null;
|
||||
|
||||
function hideDropdown() {
|
||||
dropdown.classList.remove('show');
|
||||
}
|
||||
|
||||
function selectItem(item) {
|
||||
const label = formatInputLabel(item);
|
||||
input.value = label;
|
||||
if (hiddenThs) hiddenThs.value = item.ths_code;
|
||||
if (hiddenName) hiddenName.value = item.name;
|
||||
if (hiddenMarket) hiddenMarket.value = item.market_code || '';
|
||||
if (hiddenSina) hiddenSina.value = item.sina_code || '';
|
||||
if (selectedEl) selectedEl.textContent = formatSub(item);
|
||||
hideDropdown();
|
||||
input.dispatchEvent(new CustomEvent('symbol-selected', { detail: item, bubbles: true }));
|
||||
}
|
||||
|
||||
function buildOptionEl(item) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'symbol-option';
|
||||
if (item.near_expiry) {
|
||||
div.classList.add('near-expiry');
|
||||
}
|
||||
var label = item.display || (item.name + ' ' + item.ths_code);
|
||||
if (item.near_expiry) {
|
||||
label += ' <span class="near-expiry-tag">临期</span>';
|
||||
}
|
||||
div.innerHTML = label +
|
||||
'<div class="sub">' + formatSub(item) + '</div>';
|
||||
div.addEventListener('mousedown', function (e) {
|
||||
e.preventDefault();
|
||||
selectItem(item);
|
||||
});
|
||||
return div;
|
||||
}
|
||||
|
||||
function renderItems(items) {
|
||||
dropdown.innerHTML = '';
|
||||
if (!items.length) {
|
||||
dropdown.innerHTML = '<div class="symbol-option">无匹配,可输入同花顺代码如 ag2608</div>';
|
||||
} else {
|
||||
items.forEach(function (item) {
|
||||
dropdown.appendChild(buildOptionEl(item));
|
||||
});
|
||||
}
|
||||
dropdown.classList.add('show');
|
||||
}
|
||||
|
||||
function renderGrouped(groups, filterQ) {
|
||||
dropdown.innerHTML = '';
|
||||
const qLower = (filterQ || '').trim().toLowerCase();
|
||||
let any = false;
|
||||
groups.forEach(function (group) {
|
||||
const items = group.items.filter(function (item) {
|
||||
return itemMatchesQuery(item, qLower);
|
||||
});
|
||||
if (!items.length) return;
|
||||
any = true;
|
||||
const head = document.createElement('div');
|
||||
head.className = 'symbol-group-head';
|
||||
head.textContent = group.category;
|
||||
dropdown.appendChild(head);
|
||||
items.forEach(function (item) {
|
||||
dropdown.appendChild(buildOptionEl(item));
|
||||
});
|
||||
});
|
||||
if (!any) {
|
||||
dropdown.innerHTML = '<div class="symbol-option">无匹配品种,可输入合约代码如 ag2608</div>';
|
||||
}
|
||||
dropdown.classList.add('show');
|
||||
}
|
||||
|
||||
function showMarketMains(filterQ, onEmpty) {
|
||||
const q = (filterQ || '').trim();
|
||||
const qLower = q.toLowerCase();
|
||||
if (mainsCache) {
|
||||
if (!q || groupedHasMatch(mainsCache, qLower)) {
|
||||
renderGrouped(mainsCache, q);
|
||||
return;
|
||||
}
|
||||
if (typeof onEmpty === 'function') {
|
||||
onEmpty(q);
|
||||
return;
|
||||
}
|
||||
renderGrouped(mainsCache, q);
|
||||
return;
|
||||
}
|
||||
dropdown.innerHTML = '<div class="symbol-option">正在加载推荐品种…</div>';
|
||||
dropdown.classList.add('show');
|
||||
loadRecommendedGroups()
|
||||
.then(function (groups) {
|
||||
mainsCache = groups;
|
||||
if (!groups.length) {
|
||||
dropdown.innerHTML =
|
||||
'<div class="symbol-option">当前资金下暂无推荐品种,可输入合约代码搜索</div>';
|
||||
dropdown.classList.add('show');
|
||||
return;
|
||||
}
|
||||
showMarketMains(filterQ, onEmpty);
|
||||
})
|
||||
.catch(function () {
|
||||
dropdown.innerHTML =
|
||||
'<div class="symbol-option">推荐品种加载失败,请刷新页面或输入合约代码搜索</div>';
|
||||
dropdown.classList.add('show');
|
||||
});
|
||||
}
|
||||
|
||||
function search(q) {
|
||||
if (cache.has(q)) {
|
||||
renderItems(cache.get(q));
|
||||
return;
|
||||
}
|
||||
if (abortCtrl) {
|
||||
abortCtrl.abort();
|
||||
}
|
||||
abortCtrl = new AbortController();
|
||||
fetch('/api/symbols/search?q=' + encodeURIComponent(q), {
|
||||
signal: abortCtrl.signal,
|
||||
})
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (items) {
|
||||
cache.set(q, items);
|
||||
renderItems(items);
|
||||
})
|
||||
.catch(function (err) {
|
||||
if (err && err.name === 'AbortError') return;
|
||||
hideDropdown();
|
||||
});
|
||||
}
|
||||
|
||||
function handleQuery(q) {
|
||||
if (useMainsPicker) {
|
||||
showMarketMains(q, function (query) {
|
||||
search(query);
|
||||
});
|
||||
} else {
|
||||
search(q);
|
||||
}
|
||||
}
|
||||
|
||||
input.addEventListener('input', function () {
|
||||
if (hiddenThs) hiddenThs.value = '';
|
||||
if (hiddenName) hiddenName.value = '';
|
||||
if (hiddenMarket) hiddenMarket.value = '';
|
||||
if (hiddenSina) hiddenSina.value = '';
|
||||
if (selectedEl) selectedEl.textContent = '';
|
||||
const q = input.value.trim();
|
||||
if (!q) {
|
||||
if (useMainsPicker) {
|
||||
showMarketMains('');
|
||||
} else {
|
||||
hideDropdown();
|
||||
}
|
||||
return;
|
||||
}
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(function () {
|
||||
handleQuery(q);
|
||||
}, 120);
|
||||
});
|
||||
|
||||
input.addEventListener('blur', function () {
|
||||
setTimeout(hideDropdown, 150);
|
||||
});
|
||||
|
||||
input.addEventListener('focus', function () {
|
||||
const q = input.value.trim();
|
||||
if (useMainsPicker) {
|
||||
showMarketMains(q, function (query) {
|
||||
if (query) search(query);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (q && hiddenThs && !hiddenThs.value) {
|
||||
search(q);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('.symbol-wrap').forEach(initSymbolInput);
|
||||
|
||||
document.querySelectorAll('form').forEach(function (form) {
|
||||
if (!form.querySelector('.symbol-wrap')) return;
|
||||
if (form.id === 'market-form') return;
|
||||
form.addEventListener('submit', function (e) {
|
||||
const ths = form.querySelector('input[name="symbol"]')
|
||||
|| form.querySelector('.symbol-ths-code');
|
||||
const market = form.querySelector('input[name="market_code"]');
|
||||
if (ths && !ths.value.trim()) {
|
||||
e.preventDefault();
|
||||
alert('请从下拉列表选择品种');
|
||||
return;
|
||||
}
|
||||
if (market && !market.value.trim()) {
|
||||
e.preventDefault();
|
||||
alert('请从下拉列表选择品种(需含同花顺行情代码)');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
+57
-53
@@ -1,53 +1,57 @@
|
||||
(function () {
|
||||
var KEY = 'qihuo-theme';
|
||||
|
||||
function updateButtons(theme) {
|
||||
document.querySelectorAll('[data-theme-pick]').forEach(function (btn) {
|
||||
var pick = btn.getAttribute('data-theme-pick');
|
||||
var on = pick === theme;
|
||||
btn.classList.toggle('active', on);
|
||||
btn.setAttribute('aria-pressed', on ? 'true' : 'false');
|
||||
});
|
||||
}
|
||||
|
||||
function apply(theme) {
|
||||
if (theme !== 'light' && theme !== 'dark') {
|
||||
theme = 'dark';
|
||||
}
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
try {
|
||||
localStorage.setItem(KEY, theme);
|
||||
} catch (e) { /* ignore */ }
|
||||
updateButtons(theme);
|
||||
}
|
||||
|
||||
var saved = null;
|
||||
try {
|
||||
saved = localStorage.getItem(KEY);
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
if (saved === 'light' || saved === 'dark') {
|
||||
apply(saved);
|
||||
} else {
|
||||
var prefersLight = window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches;
|
||||
apply(prefersLight ? 'light' : 'dark');
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('[data-theme-pick]');
|
||||
if (!btn) return;
|
||||
e.preventDefault();
|
||||
apply(btn.getAttribute('data-theme-pick'));
|
||||
});
|
||||
|
||||
function syncButtons() {
|
||||
var cur = document.documentElement.getAttribute('data-theme') || 'dark';
|
||||
updateButtons(cur);
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', syncButtons);
|
||||
} else {
|
||||
syncButtons();
|
||||
}
|
||||
})();
|
||||
/* Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
* 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
* 详见 LICENSE.zh-CN.txt
|
||||
*/
|
||||
(function () {
|
||||
var KEY = 'qihuo-theme';
|
||||
|
||||
function updateButtons(theme) {
|
||||
document.querySelectorAll('[data-theme-pick]').forEach(function (btn) {
|
||||
var pick = btn.getAttribute('data-theme-pick');
|
||||
var on = pick === theme;
|
||||
btn.classList.toggle('active', on);
|
||||
btn.setAttribute('aria-pressed', on ? 'true' : 'false');
|
||||
});
|
||||
}
|
||||
|
||||
function apply(theme) {
|
||||
if (theme !== 'light' && theme !== 'dark') {
|
||||
theme = 'dark';
|
||||
}
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
try {
|
||||
localStorage.setItem(KEY, theme);
|
||||
} catch (e) { /* ignore */ }
|
||||
updateButtons(theme);
|
||||
}
|
||||
|
||||
var saved = null;
|
||||
try {
|
||||
saved = localStorage.getItem(KEY);
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
if (saved === 'light' || saved === 'dark') {
|
||||
apply(saved);
|
||||
} else {
|
||||
var prefersLight = window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches;
|
||||
apply(prefersLight ? 'light' : 'dark');
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('[data-theme-pick]');
|
||||
if (!btn) return;
|
||||
e.preventDefault();
|
||||
apply(btn.getAttribute('data-theme-pick'));
|
||||
});
|
||||
|
||||
function syncButtons() {
|
||||
var cur = document.documentElement.getAttribute('data-theme') || 'dark';
|
||||
updateButtons(cur);
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', syncButtons);
|
||||
} else {
|
||||
syncButtons();
|
||||
}
|
||||
})();
|
||||
|
||||
+1487
-1483
File diff suppressed because it is too large
Load Diff
+46
-42
@@ -1,42 +1,46 @@
|
||||
(function () {
|
||||
var switchEl = document.getElementById('trade-edit-switch');
|
||||
if (!switchEl) return;
|
||||
|
||||
function setEditMode(on) {
|
||||
document.querySelectorAll('.cell-edit-hide').forEach(function (el) {
|
||||
el.style.display = on ? 'none' : '';
|
||||
});
|
||||
document.querySelectorAll('.cell-edit-show').forEach(function (el) {
|
||||
if (el.type === 'hidden') return;
|
||||
el.style.display = on ? '' : 'none';
|
||||
});
|
||||
document.querySelectorAll('.trade-save-btn').forEach(function (btn) {
|
||||
btn.disabled = !on;
|
||||
});
|
||||
}
|
||||
|
||||
switchEl.addEventListener('change', function () {
|
||||
setEditMode(switchEl.checked);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.trade-save-btn').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var row = btn.closest('tr[data-trade-id]');
|
||||
if (!row) return;
|
||||
var id = row.getAttribute('data-trade-id');
|
||||
var form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '/update_trade/' + id;
|
||||
row.querySelectorAll('.cell-edit-show').forEach(function (el) {
|
||||
if (!el.name) return;
|
||||
var input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = el.name;
|
||||
input.value = el.value;
|
||||
form.appendChild(input);
|
||||
});
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
});
|
||||
});
|
||||
})();
|
||||
/* Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
* 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
* 详见 LICENSE.zh-CN.txt
|
||||
*/
|
||||
(function () {
|
||||
var switchEl = document.getElementById('trade-edit-switch');
|
||||
if (!switchEl) return;
|
||||
|
||||
function setEditMode(on) {
|
||||
document.querySelectorAll('.cell-edit-hide').forEach(function (el) {
|
||||
el.style.display = on ? 'none' : '';
|
||||
});
|
||||
document.querySelectorAll('.cell-edit-show').forEach(function (el) {
|
||||
if (el.type === 'hidden') return;
|
||||
el.style.display = on ? '' : 'none';
|
||||
});
|
||||
document.querySelectorAll('.trade-save-btn').forEach(function (btn) {
|
||||
btn.disabled = !on;
|
||||
});
|
||||
}
|
||||
|
||||
switchEl.addEventListener('change', function () {
|
||||
setEditMode(switchEl.checked);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.trade-save-btn').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var row = btn.closest('tr[data-trade-id]');
|
||||
if (!row) return;
|
||||
var id = row.getAttribute('data-trade-id');
|
||||
var form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '/update_trade/' + id;
|
||||
row.querySelectorAll('.cell-edit-show').forEach(function (el) {
|
||||
if (!el.name) return;
|
||||
var input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = el.name;
|
||||
input.value = el.value;
|
||||
form.appendChild(input);
|
||||
});
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
+68
-64
@@ -1,64 +1,68 @@
|
||||
var CACHE_VERSION = 'qihuo-v3';
|
||||
var STATIC_CACHE = CACHE_VERSION + '-static';
|
||||
var STATIC_ASSETS = [
|
||||
'/static/css/tech.css',
|
||||
'/static/css/responsive.css',
|
||||
'/static/css/trade.css',
|
||||
'/static/js/theme.js',
|
||||
'/static/js/nav.js',
|
||||
'/static/js/pwa.js',
|
||||
'/static/js/symbol.js',
|
||||
'/static/js/trade.js',
|
||||
'/static/icons/icon-192.png',
|
||||
'/static/icons/icon-512.png',
|
||||
'/static/icons/icon.svg',
|
||||
'/static/manifest.json',
|
||||
'/login'
|
||||
];
|
||||
|
||||
self.addEventListener('install', function (event) {
|
||||
event.waitUntil(
|
||||
caches.open(STATIC_CACHE).then(function (cache) {
|
||||
return cache.addAll(STATIC_ASSETS).catch(function () { /* ignore partial */ });
|
||||
}).then(function () { return self.skipWaiting(); })
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('activate', function (event) {
|
||||
event.waitUntil(
|
||||
caches.keys().then(function (keys) {
|
||||
return Promise.all(keys.filter(function (k) {
|
||||
return k.startsWith('qihuo-') && k !== STATIC_CACHE;
|
||||
}).map(function (k) { return caches.delete(k); }));
|
||||
}).then(function () { return self.clients.claim(); })
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', function (event) {
|
||||
var req = event.request;
|
||||
if (req.method !== 'GET') return;
|
||||
|
||||
var url = new URL(req.url);
|
||||
if (url.origin !== self.location.origin) return;
|
||||
|
||||
if (url.pathname.indexOf('/static/') === 0) {
|
||||
event.respondWith(
|
||||
caches.match(req).then(function (cached) {
|
||||
return cached || fetch(req).then(function (res) {
|
||||
var copy = res.clone();
|
||||
caches.open(STATIC_CACHE).then(function (cache) { cache.put(req, copy); });
|
||||
return res;
|
||||
});
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.mode === 'navigate' || (req.headers.get('accept') || '').indexOf('text/html') !== -1) {
|
||||
event.respondWith(
|
||||
fetch(req).catch(function () {
|
||||
return caches.match('/login');
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
/* Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
* 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
* 详见 LICENSE.zh-CN.txt
|
||||
*/
|
||||
var CACHE_VERSION = 'qihuo-v3';
|
||||
var STATIC_CACHE = CACHE_VERSION + '-static';
|
||||
var STATIC_ASSETS = [
|
||||
'/static/css/tech.css',
|
||||
'/static/css/responsive.css',
|
||||
'/static/css/trade.css',
|
||||
'/static/js/theme.js',
|
||||
'/static/js/nav.js',
|
||||
'/static/js/pwa.js',
|
||||
'/static/js/symbol.js',
|
||||
'/static/js/trade.js',
|
||||
'/static/icons/icon-192.png',
|
||||
'/static/icons/icon-512.png',
|
||||
'/static/icons/icon.svg',
|
||||
'/static/manifest.json',
|
||||
'/login'
|
||||
];
|
||||
|
||||
self.addEventListener('install', function (event) {
|
||||
event.waitUntil(
|
||||
caches.open(STATIC_CACHE).then(function (cache) {
|
||||
return cache.addAll(STATIC_ASSETS).catch(function () { /* ignore partial */ });
|
||||
}).then(function () { return self.skipWaiting(); })
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('activate', function (event) {
|
||||
event.waitUntil(
|
||||
caches.keys().then(function (keys) {
|
||||
return Promise.all(keys.filter(function (k) {
|
||||
return k.startsWith('qihuo-') && k !== STATIC_CACHE;
|
||||
}).map(function (k) { return caches.delete(k); }));
|
||||
}).then(function () { return self.clients.claim(); })
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', function (event) {
|
||||
var req = event.request;
|
||||
if (req.method !== 'GET') return;
|
||||
|
||||
var url = new URL(req.url);
|
||||
if (url.origin !== self.location.origin) return;
|
||||
|
||||
if (url.pathname.indexOf('/static/') === 0) {
|
||||
event.respondWith(
|
||||
caches.match(req).then(function (cached) {
|
||||
return cached || fetch(req).then(function (res) {
|
||||
var copy = res.clone();
|
||||
caches.open(STATIC_CACHE).then(function (cache) { cache.put(req, copy); });
|
||||
return res;
|
||||
});
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.mode === 'navigate' || (req.headers.get('accept') || '').indexOf('text/html') !== -1) {
|
||||
event.respondWith(
|
||||
fetch(req).catch(function () {
|
||||
return caches.match('/login');
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user