diff --git a/static/css/base.css b/static/css/base.css
new file mode 100644
index 0000000..419c829
--- /dev/null
+++ b/static/css/base.css
@@ -0,0 +1,468 @@
+/* Copyright (c) 2025-2026 马建军. All rights reserved. */
+html{background:#050508;color-scheme:dark}
+html[data-theme="light"]{background:#e8eef8;color-scheme:light}
+html:not([data-theme="light"]){
+--bg-page:#050508;
+--bg-grid:rgba(76,194,255,.045);
+--border-header:rgba(76,194,255,.12);
+--header-bg:transparent;
+--text-primary:#e8eaf6;
+--text-title:#ffffff;
+--text-muted:#7a82a0;
+--text-label:#a8b4ff;
+--accent:#4cc2ff;
+--accent-2:#9d6bff;
+--ambient-glow:rgba(76,194,255,.14);
+--ambient-glow-2:rgba(123,66,255,.1);
+--scanline:rgba(76,194,255,.03);
+--title-glow:rgba(76,194,255,.35);
+--card-bg:rgba(10,12,22,.82);
+--card-border:rgba(76,194,255,.22);
+--card-border-hover:rgba(76,194,255,.45);
+--card-glow:rgba(76,194,255,.12);
+--card-inner:rgba(16,20,36,.9);
+--input-bg:rgba(12,14,26,.95);
+--input-border:rgba(76,194,255,.18);
+--nav-bg:rgba(14,16,28,.9);
+--nav-border:rgba(76,194,255,.15);
+--nav-hover:rgba(30,40,68,.85);
+--nav-hover-glow:rgba(76,194,255,.15);
+--nav-active:#2563eb;
+--nav-active-border:transparent;
+--nav-active-glow:rgba(76,194,255,.45);
+--focus-ring:rgba(76,194,255,.25);
+--focus-glow:rgba(76,194,255,.2);
+--btn-glow:rgba(76,194,255,.35);
+--btn-glow-strong:rgba(123,66,255,.4);
+--row-hover:rgba(76,194,255,.06);
+--list-item-bg:rgba(14,18,32,.8);
+--table-border:rgba(76,194,255,.1);
+--profit:#4cd97f;
+--profit-bg:rgba(76,217,127,.12);
+--loss:#ff6b7a;
+--loss-bg:rgba(255,107,122,.12);
+--dir-bg:rgba(30,45,80,.6);
+--planned-bg:rgba(234,193,71,.12);
+--planned-text:#eac147;
+--expired-bg:rgba(40,44,60,.8);
+--expired-text:#8a8a9e;
+--flash-bg:rgba(30,45,80,.7);
+--flash-text:#4cc2ff;
+--modal-mask:rgba(2,4,12,.82);
+--danger:#ff6b7a;
+--shadow-card:0 8px 32px rgba(0,0,0,.45),0 0 1px rgba(76,194,255,.3),inset 0 1px 0 rgba(255,255,255,.04);
+--shadow-card-hover:0 16px 48px rgba(0,0,0,.5),0 0 24px var(--card-glow);
+--calc-bg:rgba(20,24,42,.9);
+--toggle-bg:rgba(14,16,28,.9);
+--toggle-border:rgba(76,194,255,.2);
+}
+[data-theme="light"]{
+--bg-page:#e8eef8;
+--bg-grid:rgba(37,99,235,.07);
+--border-header:rgba(37,99,235,.12);
+--header-bg:transparent;
+--text-primary:#1a2233;
+--text-title:#0a1628;
+--text-muted:#5c6578;
+--text-label:#1d4ed8;
+--accent:#2563eb;
+--accent-2:#7c3aed;
+--ambient-glow:rgba(37,99,235,.12);
+--ambient-glow-2:rgba(124,58,237,.08);
+--scanline:rgba(37,99,235,.04);
+--title-glow:rgba(37,99,235,.2);
+--card-bg:rgba(255,255,255,.92);
+--card-border:rgba(37,99,235,.2);
+--card-border-hover:rgba(37,99,235,.4);
+--card-glow:rgba(37,99,235,.1);
+--card-inner:#f4f7fc;
+--input-bg:#ffffff;
+--input-border:#b8c5d6;
+--nav-bg:rgba(255,255,255,.95);
+--nav-border:rgba(37,99,235,.18);
+--nav-hover:#eef4ff;
+--nav-hover-glow:rgba(37,99,235,.12);
+--nav-active:#2563eb;
+--nav-active-border:transparent;
+--nav-active-glow:rgba(37,99,235,.35);
+--focus-ring:rgba(37,99,235,.2);
+--focus-glow:rgba(37,99,235,.15);
+--btn-glow:rgba(37,99,235,.25);
+--btn-glow-strong:rgba(37,99,235,.35);
+--row-hover:rgba(37,99,235,.05);
+--list-item-bg:#f1f5f9;
+--table-border:#e2e8f0;
+--profit:#15803d;
+--profit-bg:#dcfce7;
+--loss:#dc2626;
+--loss-bg:#fee2e2;
+--dir-bg:#dbeafe;
+--planned-bg:#fef9c3;
+--planned-text:#a16207;
+--expired-bg:#f1f5f9;
+--expired-text:#64748b;
+--flash-bg:#dbeafe;
+--flash-text:#1d4ed8;
+--modal-mask:rgba(15,23,42,.45);
+--danger:#dc2626;
+--shadow-card:0 8px 28px rgba(15,23,42,.08),0 0 1px rgba(37,99,235,.15),inset 0 1px 0 rgba(255,255,255,.95);
+--shadow-card-hover:0 16px 40px rgba(15,23,42,.12),0 0 20px var(--card-glow);
+--calc-bg:#eef2ff;
+--toggle-bg:#ffffff;
+--toggle-border:#c5d0dc;
+}
+*{margin:0;padding:0;box-sizing:border-box}
+body{
+font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;
+background:var(--bg-page);
+color:var(--text-primary);
+min-height:100vh;
+}
+.page-wrap{max-width:1800px;margin:0 auto;min-height:100vh}
+.site-header{text-align:center;padding:1.5rem 1rem 1.25rem;border-bottom:1px solid var(--border-header);position:relative}
+.site-title{font-size:1.75rem;font-weight:700;color:var(--text-title);margin-bottom:1.65rem;line-height:1.3}
+.header-tools{position:absolute;top:1rem;left:1.5rem;display:flex;gap:.5rem;align-items:center;z-index:20}
+.theme-switch{
+display:inline-flex;align-items:center;
+border-radius:999px;border:1px solid var(--toggle-border);
+background:var(--toggle-bg);padding:3px;gap:2px;
+}
+.theme-switch-btn{
+padding:.38rem .75rem;border:none;border-radius:999px;
+background:transparent;color:var(--text-muted);
+font-size:.75rem;cursor:pointer;transition:.2s;
+white-space:nowrap;width:auto;flex-shrink:0;
+}
+.theme-switch-btn:hover{color:var(--text-primary)}
+.theme-switch-btn.active{
+background:linear-gradient(135deg,var(--accent),var(--accent-2));
+color:#fff;box-shadow:0 0 12px var(--btn-glow);
+}
+.site-nav{display:flex;justify-content:center;gap:.45rem;flex-wrap:wrap}
+.site-nav a{
+padding:.55rem 1.15rem;border-radius:8px;
+border:1px solid transparent;
+background:transparent;
+color:var(--text-primary);
+text-decoration:none;font-size:.88rem;
+transition:.2s;white-space:nowrap;
+}
+.site-nav a:hover{background:var(--nav-hover);border-color:var(--nav-border);color:var(--text-title)}
+.site-nav a.active{background:var(--nav-active);border-color:var(--nav-active-border);color:#fff}
+.user-bar{position:absolute;top:1rem;right:1.5rem;font-size:.8rem;color:var(--text-muted);white-space:nowrap}
+.user-bar a{color:var(--danger);text-decoration:none;margin-left:.5rem}
+.main{padding:1.5rem}
+.text-muted{color:var(--text-muted)}
+.text-label{color:var(--text-label)}
+.text-accent{color:var(--accent)}
+.text-profit{color:var(--profit)}
+.text-loss{color:var(--loss)}
+.section-label{font-size:.9rem;color:var(--text-label);margin:.75rem 0 .5rem}
+.empty-hint{color:var(--text-muted);padding:.75rem;font-size:.85rem}
+.flash{padding:1rem;background:var(--flash-bg);color:var(--flash-text);border-radius:10px;margin-bottom:1.5rem;text-align:center;border:1px solid var(--card-border)}
+.card{
+background:var(--card-bg);
+border-radius:16px;padding:1.5rem;
+border:1px solid var(--card-border);
+margin-bottom:1.5rem;
+position:relative;overflow:hidden;
+box-shadow:var(--shadow-card);
+backdrop-filter:blur(10px);
+transition:background .25s,border-color .25s,box-shadow .25s;
+}
+.card::before{
+content:"";position:absolute;inset:0;
+background:linear-gradient(135deg,var(--card-glow) 0%,transparent 55%);
+pointer-events:none;
+}
+.card::after{
+content:"";position:absolute;top:0;left:12%;right:12%;height:1px;
+background:linear-gradient(90deg,transparent,var(--accent),var(--accent-2),transparent);
+opacity:.55;pointer-events:none;
+}
+.card > *{position:relative;z-index:1}
+.card h2{
+font-size:1.15rem;margin-bottom:1rem;color:var(--text-label);
+display:flex;align-items:center;gap:.5rem;
+}
+.card h2:before{
+content:"";width:4px;height:16px;
+background:linear-gradient(180deg,var(--accent),var(--accent-2));
+border-radius:2px;box-shadow:0 0 8px var(--card-glow);
+}
+.form-row{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem;align-items:center}
+.form-compact{display:flex;flex-direction:column;gap:.5rem;margin-bottom:1rem}
+.form-compact .form-line{display:grid;gap:.5rem;align-items:center}
+.form-compact .line-2{grid-template-columns:repeat(2,1fr)}
+.form-compact .line-3{grid-template-columns:repeat(3,1fr)}
+.form-compact .line-4{grid-template-columns:repeat(4,1fr)}
+.form-compact .line-5{grid-template-columns:repeat(5,1fr)}
+.form-compact.line-tight{gap:.35rem;margin-bottom:.5rem}
+.form-compact .line-btn{display:flex;gap:.5rem;align-items:center}
+.form-compact input,.form-compact select{padding:.55rem .7rem;font-size:.85rem;border-radius:8px}
+.form-compact .symbol-selected{font-size:.7rem;margin-top:2px}
+.form-compact button.btn-primary{padding:.55rem 1.5rem;font-size:.85rem;white-space:nowrap}
+.form-compact-review input.calc-readonly{color:var(--accent);background:var(--calc-bg);font-size:.82rem}
+.form-compact-review textarea{min-height:44px;font-size:.85rem;padding:.45rem .65rem}
+.form-compact-review .section-hint{font-size:.72rem;color:var(--text-muted);margin:.25rem 0 .15rem}
+.form-compact-review .tag-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.15rem .5rem;font-size:.75rem}
+.form-compact-review .tag-grid label{display:flex;align-items:center;gap:.3rem;cursor:pointer;color:var(--text-muted);white-space:nowrap}
+.form-compact-review .tag-grid input{width:auto;flex-shrink:0}
+.form-compact-review input[type="file"]{font-size:.72rem;padding:.4rem .5rem}
+.form-compact-review .mini-field span{font-size:.65rem;color:var(--text-muted);display:block;line-height:1;margin-bottom:2px}
+.form-compact-review .kline-row label{display:flex;align-items:center;gap:.35rem;color:var(--text-muted);white-space:nowrap}
+.form-compact-review .kline-row{display:grid;grid-template-columns:auto 1fr 1fr 1fr auto;gap:.5rem;align-items:end;font-size:.78rem}
+.split-grid.records-split .card{min-height:auto}
+.split-grid.records-split .card h2{font-size:1rem;margin-bottom:.5rem}
+.split-grid.records-split .card{padding:1rem 1.25rem}
+.page-title-sm{font-size:1.25rem;margin-bottom:.75rem}
+.form-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:.75rem;margin-bottom:1rem}
+.form-grid .full{grid-column:1/-1}
+.field label{display:block;font-size:.8rem;color:var(--text-label);margin-bottom:.35rem}
+input,select,textarea,button{
+padding:.7rem 1rem;border-radius:10px;
+border:1px solid var(--input-border);
+background:var(--input-bg);color:var(--text-primary);
+font-size:.9rem;outline:none;width:100%;
+transition:border-color .2s,background .2s;
+}
+textarea{min-height:80px;resize:vertical}
+input:focus,select:focus,textarea:focus{border-color:var(--accent)}
+button.btn-primary{background:linear-gradient(90deg,var(--accent),var(--accent-2));border:none;cursor:pointer;color:#fff;width:auto}
+button.btn-primary:hover{opacity:.9}
+.list{display:flex;flex-direction:column;gap:.75rem}
+.list-item{
+display:flex;justify-content:space-between;align-items:center;
+padding:1rem;background:var(--list-item-bg);
+border-radius:10px;gap:1rem;flex-wrap:wrap;
+border:1px solid var(--card-border);
+}
+.btn-del{padding:.4rem .8rem;background:var(--loss-bg);color:var(--loss);border-radius:8px;text-decoration:none;font-size:.85rem}
+table{width:100%;border-collapse:collapse}
+th,td{padding:.85rem;text-align:left;border-bottom:1px solid var(--table-border);font-size:.9rem;color:var(--text-primary)}
+th{color:var(--text-label);white-space:nowrap}
+.badge{padding:.25rem .5rem;border-radius:6px;font-size:.75rem}
+.badge.profit{background:var(--profit-bg);color:var(--profit)}
+.badge.loss{background:var(--loss-bg);color:var(--loss)}
+.badge.dir{background:var(--dir-bg);color:var(--accent)}
+.badge.planned{background:var(--planned-bg);color:var(--planned-text)}
+.badge.active{background:var(--profit-bg);color:var(--profit)}
+.badge.expired{background:var(--expired-bg);color:var(--expired-text)}
+.stat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:1rem;margin-bottom:1.5rem}
+.stat-item{
+background:var(--card-inner);padding:1rem;border-radius:12px;text-align:center;
+border:1px solid var(--card-border);position:relative;overflow:hidden;
+}
+.stat-item::before{
+content:"";position:absolute;top:0;left:0;right:0;height:2px;
+background:linear-gradient(90deg,var(--accent),var(--accent-2));opacity:.5;
+}
+.stat-item .label{font-size:.8rem;color:var(--text-muted)}
+.stat-item .value{font-size:1.4rem;font-weight:600;color:var(--text-title);margin-top:.25rem}
+.symbol-wrap{position:relative;z-index:1}
+.symbol-wrap:has(.symbol-dropdown.show){z-index:50}
+.symbol-dropdown{
+position:absolute;top:100%;left:0;right:0;
+background:var(--input-bg);border:1px solid var(--input-border);
+border-radius:10px;margin-top:4px;z-index:200;
+max-height:240px;overflow-y:auto;display:none;
+box-shadow:var(--shadow-card-hover);
+}
+.card:has(.symbol-dropdown.show){overflow:visible}
+.symbol-dropdown.show{display:block}
+.symbol-option{padding:.65rem 1rem;cursor:pointer;font-size:.85rem;border-bottom:1px solid var(--table-border)}
+.symbol-option:hover{background:var(--list-item-bg)}
+.symbol-option .sub{font-size:.75rem;color:var(--text-muted);margin-top:2px}
+.symbol-option.near-expiry{color:#ff6b7a}
+html[data-theme="light"] .symbol-option.near-expiry{color:#dc2626}
+.symbol-option.near-expiry .sub{color:inherit;opacity:.85}
+.near-expiry-tag{
+font-size:.68rem;padding:.1rem .35rem;border-radius:4px;
+background:rgba(255,107,122,.15);color:inherit;font-weight:600;
+}
+html[data-theme="light"] .near-expiry-tag{background:rgba(220,38,38,.12)}
+.symbol-group-head{
+padding:.4rem .85rem;font-size:.72rem;font-weight:600;
+color:var(--text-muted);background:var(--card-inner);
+border-bottom:1px solid var(--table-border);position:sticky;top:0;
+}
+.symbol-selected{font-size:.75rem;color:var(--accent);margin-top:4px}
+.check-row{display:flex;flex-wrap:wrap;gap:1rem;margin:.75rem 0}
+.check-row label{display:flex;align-items:center;gap:.4rem;font-size:.85rem;color:var(--text-muted);cursor:pointer}
+.check-row input{width:auto}
+.hint{font-size:.78rem;color:var(--text-muted);line-height:1.5;margin-top:.5rem}
+.filter-row{display:flex;gap:.75rem;flex-wrap:wrap;align-items:flex-end;margin-bottom:1rem}
+.filter-row .field{width:auto;min-width:140px}
+.filter-row button{width:auto}
+.split-grid{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;align-items:stretch;margin-bottom:1.5rem}
+.split-grid .card{margin-bottom:0;height:100%;min-height:480px;display:flex;flex-direction:column}
+.split-grid .card-body{flex:1;overflow:auto}
+.card-scroll{max-height:420px;overflow-y:auto}
+.preset-tabs{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem}
+.preset-tabs a{
+padding:.45rem .85rem;border-radius:8px;
+border:1px solid var(--input-border);
+color:var(--text-muted);text-decoration:none;font-size:.85rem;
+}
+.preset-tabs a.active,.preset-tabs a:hover{background:var(--dir-bg);color:var(--accent);border-color:var(--accent)}
+.btn-link{color:var(--accent);cursor:pointer;font-size:.85rem;background:none;border:none;padding:0}
+.btn-link:hover{text-decoration:underline}
+.modal-mask{position:fixed;inset:0;background:var(--modal-mask);z-index:1000;display:none;align-items:center;justify-content:center;padding:1rem}
+.modal-mask.show{display:flex}
+.modal-box{
+background:var(--card-bg);border:1px solid var(--card-border);
+border-radius:16px;max-width:900px;width:100%;
+max-height:90vh;overflow:auto;padding:1.5rem;
+box-shadow:var(--shadow-card);
+}
+.modal-box h3{margin-bottom:1rem;color:var(--text-label)}
+.modal-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:.75rem;font-size:.9rem}
+.modal-grid .item label{color:var(--text-muted);font-size:.75rem;display:block}
+.modal-grid .item div{margin-top:.2rem;color:var(--text-primary)}
+.form-compact .line-plan-1{grid-template-columns:minmax(0,1fr) minmax(72px,0.45fr) minmax(0,1.6fr)}
+.form-compact .line-plan-2{grid-template-columns:repeat(4,minmax(0,1fr)) auto;align-items:center}
+.form-compact .field-short select{padding:.55rem .5rem}
+.badge.emotion{background:var(--loss-bg);color:var(--loss);font-weight:600;border:1px solid var(--loss)}
+tr.row-emotion td{color:var(--loss)}
+tr.row-emotion td .badge.emotion{background:var(--loss);color:#fff;border-color:var(--loss)}
+.modal-box.review-modal-wide{max-width:960px}
+.modal-box.review-modal-fullscreen{
+width:calc(100vw - 1.5rem);max-width:none;
+height:calc(100vh - 1.5rem);max-height:none;
+border-radius:12px;display:flex;flex-direction:column;
+}
+.modal-box.review-modal-fullscreen h3{flex-shrink:0}
+#review-modal-body.review-modal-body{flex:1;overflow:auto;min-height:0}
+.review-detail-table{margin-bottom:1rem;overflow-x:auto}
+.review-detail-headers,.review-detail-values{
+display:grid;
+grid-template-columns:repeat(16,minmax(52px,1fr));
+gap:.35rem .4rem;align-items:start;
+}
+.review-detail-headers span{
+font-size:.68rem;color:var(--text-muted);
+white-space:nowrap;line-height:1.3;
+}
+.review-detail-values span{
+font-size:.82rem;color:var(--text-primary);
+line-height:1.35;word-break:break-all;
+}
+.review-detail-values span.emotion-val{color:var(--loss);font-weight:600}
+.review-detail-fields{padding:.25rem 0}
+.review-detail-section{margin-bottom:.85rem}
+.review-detail-section h4{font-size:.78rem;color:var(--text-label);margin-bottom:.45rem;font-weight:600}
+.review-detail-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.45rem .65rem}
+.review-detail-item label{display:block;font-size:.7rem;color:var(--text-muted);margin-bottom:.15rem}
+.review-detail-item div{font-size:.85rem;color:var(--text-primary);line-height:1.35}
+.review-detail-item.wide{grid-column:span 2}
+.review-detail-item.full{grid-column:1/-1}
+.review-detail-item.emotion div{color:var(--loss);font-weight:600}
+.review-detail-image{flex-shrink:0;padding-top:.75rem;border-top:1px solid var(--table-border)}
+.review-detail-image img{width:100%;border-radius:10px;border:1px solid var(--card-border)}
+.review-detail-image .no-img{color:var(--text-muted);font-size:.85rem;padding:2rem;text-align:center;background:var(--card-inner);border-radius:10px}
+.key-live{display:flex;align-items:center;justify-content:space-between;gap:.75rem;flex:1;min-width:160px}
+.key-live .live-price-line{font-size:.85rem;font-weight:600;color:var(--accent);white-space:nowrap}
+.key-live .live-dist{font-size:.72rem;color:var(--text-muted);white-space:nowrap}
+.key-live .live-dist span{color:var(--text-primary)}
+.list-item.key-item{gap:.65rem}
+.pos-card{background:var(--card-inner);border:1px solid var(--card-border);border-radius:12px;padding:1rem;margin-bottom:.75rem}
+.pos-card-head{display:flex;justify-content:space-between;align-items:flex-start;gap:.5rem;margin-bottom:.65rem}
+.pos-card-head .title{font-size:1rem;font-weight:600;color:var(--text-title)}
+.pos-card-meta{font-size:.75rem;color:var(--text-muted);margin-bottom:.65rem}
+.pos-card-meta strong{color:var(--text-primary)}
+.pos-metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:.5rem .65rem;margin-bottom:.65rem}
+.pos-metrics .cell label{display:block;font-size:.68rem;color:var(--text-muted);margin-bottom:.15rem}
+.pos-metrics .cell div{font-size:.88rem;color:var(--text-primary)}
+.pos-metrics .cell.pnl-pos div{color:var(--profit)}
+.pos-metrics .cell.pnl-neg div{color:var(--loss)}
+.pos-footer{font-size:.72rem;color:var(--text-muted);display:flex;flex-wrap:wrap;gap:.35rem 1rem;padding-top:.65rem;border-top:1px solid var(--table-border)}
+.pos-footer span{color:var(--text-primary)}
+.pos-del{font-size:.75rem;padding:.35rem .65rem}
+.trade-toolbar{display:flex;align-items:center;gap:1rem;margin-bottom:1rem;flex-wrap:wrap}
+.trade-switch-label{
+display:flex;align-items:center;gap:.4rem;
+font-size:.78rem;color:var(--text-muted);
+white-space:normal;margin-bottom:.65rem;cursor:pointer;
+line-height:1.45;max-width:100%;
+}
+.trade-switch-label span{line-height:1.45;color:var(--text-muted)}
+.trade-switch-label input{flex-shrink:0;width:auto}
+.trade-table-wrap{
+overflow:auto;
+max-height:420px;
+width:100%;
+-webkit-overflow-scrolling:touch;
+border-radius:10px;
+border:1px solid var(--table-border);
+background:var(--card-inner);
+}
+.trade-table{font-size:.8rem;width:max-content;min-width:100%;table-layout:auto}
+.trade-table th{font-size:.75rem;padding:.55rem .45rem;white-space:nowrap;background:var(--card-inner)}
+.trade-table td{padding:.45rem .4rem;vertical-align:middle;white-space:nowrap;background:var(--card-inner)}
+.trade-table th:last-child,
+.trade-table td:last-child{
+position:sticky;right:0;z-index:3;
+box-shadow:-6px 0 10px rgba(0,0,0,.08);
+}
+.trade-table thead th:last-child{z-index:4}
+.trade-table input,.trade-table select{
+padding:.35rem .45rem;font-size:.78rem;border-radius:6px;width:100%;min-width:0;
+}
+.trade-table .cell-readonly{color:var(--text-primary)}
+.records-equity-card .card-body{padding-top:0;background:transparent}
+.records-equity-card #equity-curve-chart{background:transparent;border-radius:0}
+.records-trade-card{overflow:visible}
+.records-trade-card .card-body{overflow:visible;background:transparent}
+.records-trade-card .trade-table-wrap{
+--rec-row-h:2.35rem;
+--rec-head-h:2.1rem;
+overflow:auto;
+height:calc(var(--rec-row-h) * 10 + var(--rec-head-h));
+max-height:calc(var(--rec-row-h) * 10 + var(--rec-head-h));
+width:100%;
+-webkit-overflow-scrolling:touch;
+border:none;
+border-radius:0;
+background:transparent;
+}
+.records-trade-card .trade-table th,
+.records-trade-card .trade-table td{
+background:transparent;
+}
+.records-trade-card .trade-table thead th{
+position:sticky;
+top:0;
+z-index:2;
+background:transparent;
+backdrop-filter:blur(12px);
+-webkit-backdrop-filter:blur(12px);
+box-shadow:0 1px 0 var(--table-border);
+}
+.records-trade-card .trade-table th:last-child,
+.records-trade-card .trade-table td:last-child{
+box-shadow:none;
+background:transparent;
+}
+.records-trade-card .trade-table thead th:last-child{
+z-index:5;
+}
+.trade-actions{display:flex;gap:.35rem;flex-wrap:nowrap;align-items:center;min-width:230px;white-space:nowrap}
+.trade-actions a,.trade-actions button{flex-shrink:0;white-space:nowrap;font-size:.72rem;padding:.3rem .55rem;border-radius:6px;text-decoration:none;border:none;cursor:pointer;width:auto;min-width:0}
+.btn-fill{background:var(--dir-bg);color:var(--accent)}
+.btn-verify{background:var(--nav-active);color:#fff}
+.btn-verify:disabled{opacity:.45;cursor:not-allowed}
+.badge.result-manual{background:var(--dir-bg);color:var(--accent)}
+.badge.result-external{background:var(--expired-bg);color:var(--expired-text)}
+.profile-page .profile-head{display:flex;align-items:center;gap:.65rem;flex-wrap:wrap;margin:1rem 0 .75rem;font-size:.9rem}
+.profile-page .profile-source{font-size:.72rem;color:var(--text-muted)}
+.profile-spec{max-width:820px;border:1px solid var(--card-border);border-radius:10px;background:var(--card-inner);padding:.25rem .85rem}
+.profile-row{display:grid;grid-template-columns:minmax(120px,28%) 1fr;gap:.5rem 1rem;padding:.6rem 0;border-bottom:1px solid var(--table-border);align-items:start}
+.profile-row:last-child{border-bottom:none}
+.profile-label{color:var(--text-muted);font-size:.84rem;line-height:1.4}
+.profile-value{color:var(--text-primary);font-size:.86rem;line-height:1.5;word-break:break-word}
+.profile-hint{color:var(--planned-text);font-size:.74rem;margin-top:.25rem;line-height:1.35}
+.calc-readonly{background:var(--calc-bg);color:var(--accent)}
+@media(max-width:1100px){
+.split-grid{grid-template-columns:1fr}
+.split-grid .card{min-height:auto}
+}
diff --git a/static/css/tech.css b/static/css/tech.css
index 7f32a7d..945902f 100644
--- a/static/css/tech.css
+++ b/static/css/tech.css
@@ -1,25 +1,8 @@
/* Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt */
/* 科技感增强层 — 与 base.html 变量配合 */
-@view-transition {
- navigation: auto;
-}
-
-::view-transition-old(app-main),
-::view-transition-new(app-main) {
- animation-duration: .18s;
-}
-
-::view-transition-old(app-tech-bg),
-::view-transition-new(app-tech-bg),
-::view-transition-old(site-header),
-::view-transition-new(site-header) {
- animation: none;
-}
-
.tech-bg{
position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden;
- view-transition-name:app-tech-bg;
}
.tech-grid{
position:absolute;inset:0;
@@ -70,13 +53,14 @@
.tech-glow,.tech-glow-2,.tech-scanline,.card::after,.site-header::after{animation:none}
}
+.main.nav-loading{opacity:.75;pointer-events:none;transition:opacity .12s}
+
.page-wrap{position:relative;z-index:1}
.site-header{
border-bottom:1px solid var(--border-header);
background:transparent;
backdrop-filter:none;
- view-transition-name:site-header;
}
.site-header::after{
content:"";display:block;height:1px;margin-top:-1px;
diff --git a/static/js/keys.js b/static/js/keys.js
index c99bf87..0db6a41 100644
--- a/static/js/keys.js
+++ b/static/js/keys.js
@@ -31,8 +31,20 @@
.catch(function () { /* ignore */ });
}
- document.addEventListener('DOMContentLoaded', function () {
+ function stopPolling() {
+ if (keyTimer) {
+ clearInterval(keyTimer);
+ keyTimer = null;
+ }
+ }
+
+ function startPolling() {
+ stopPolling();
pollKeyPrices();
keyTimer = setInterval(pollKeyPrices, 1000);
- });
+ }
+
+ if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(startPolling);
+ else document.addEventListener('DOMContentLoaded', startPolling);
+ if (window.qihuoOnPageLeave) window.qihuoOnPageLeave(stopPolling);
})();
diff --git a/static/js/market.js b/static/js/market.js
index 653bd7d..f8c4870 100644
--- a/static/js/market.js
+++ b/static/js/market.js
@@ -689,7 +689,12 @@
}
}
- document.addEventListener('DOMContentLoaded', function () {
+ function cleanupMarketPage() {
+ stopKlineStream();
+ destroyChart();
+ }
+
+ function bootMarketPage() {
if (!window.LightweightCharts) {
if (emptyEl) emptyEl.textContent = '图表库加载失败,请刷新页面';
return;
@@ -699,15 +704,6 @@
bindAutoButton();
bindChartOptions();
- document.addEventListener('click', function (e) {
- if (e.target.closest('[data-theme-pick]') && lastData) {
- setTimeout(function () {
- destroyChart();
- renderChart(lastData, { forceFull: true });
- }, 80);
- }
- });
-
var active = document.querySelector('.period-tab.active');
if (active) currentPeriod = active.getAttribute('data-period') || '15m';
@@ -732,10 +728,22 @@
} else {
updateRefreshHint(false);
}
+ }
- window.addEventListener('beforeunload', function () {
- stopKlineStream();
- destroyChart();
+ if (!window.__QIHUO_MARKET_THEME__) {
+ window.__QIHUO_MARKET_THEME__ = true;
+ document.addEventListener('click', function (e) {
+ if (e.target.closest('[data-theme-pick]') && lastData) {
+ setTimeout(function () {
+ destroyChart();
+ renderChart(lastData, { forceFull: true });
+ }, 80);
+ }
});
- });
+ }
+
+ if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(bootMarketPage);
+ else document.addEventListener('DOMContentLoaded', bootMarketPage);
+ if (window.qihuoOnPageLeave) window.qihuoOnPageLeave(cleanupMarketPage);
+ window.addEventListener('pagehide', cleanupMarketPage);
})();
diff --git a/static/js/nav.js b/static/js/nav.js
index d643240..e9ebf4c 100644
--- a/static/js/nav.js
+++ b/static/js/nav.js
@@ -42,6 +42,10 @@
}
function prefetchNav(href) {
+ if (window.qihuoPrefetchPage) {
+ window.qihuoPrefetchPage(href);
+ return;
+ }
if (!href || href.indexOf(window.location.origin) !== 0) return;
if (href === window.location.href) return;
var links = document.head.querySelectorAll('link[rel="prefetch"]');
diff --git a/static/js/page.js b/static/js/page.js
new file mode 100644
index 0000000..187e27d
--- /dev/null
+++ b/static/js/page.js
@@ -0,0 +1,17 @@
+/* Copyright (c) 2025-2026 马建军. All rights reserved.
+ * 专有软件 — 未经授权禁止复制、传播、转售。
+ * 详见 LICENSE.zh-CN.txt
+ */
+(function (global) {
+ global.qihuoOnPageLoad = function (fn) {
+ global.addEventListener('qihuo:page-load', fn);
+ };
+
+ global.qihuoOnPageLeave = function (fn) {
+ global.addEventListener('qihuo:page-leave', fn);
+ };
+
+ global.qihuoEmitPageLoad = function () {
+ global.dispatchEvent(new Event('qihuo:page-load'));
+ };
+})();
diff --git a/static/js/plans.js b/static/js/plans.js
index cd7d2be..7767e29 100644
--- a/static/js/plans.js
+++ b/static/js/plans.js
@@ -39,11 +39,20 @@
.catch(function () { /* ignore */ });
}
+ function stopPolling() {
+ if (timer) {
+ clearInterval(timer);
+ timer = null;
+ }
+ }
+
function startPolling() {
- if (timer) clearInterval(timer);
+ stopPolling();
pollPrices();
timer = setInterval(pollPrices, 1000);
}
- document.addEventListener('DOMContentLoaded', startPolling);
+ if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(startPolling);
+ else document.addEventListener('DOMContentLoaded', startPolling);
+ if (window.qihuoOnPageLeave) window.qihuoOnPageLeave(stopPolling);
})();
diff --git a/static/js/positions.js b/static/js/positions.js
index 1c7acfb..724f1eb 100644
--- a/static/js/positions.js
+++ b/static/js/positions.js
@@ -72,8 +72,20 @@
.catch(function () { /* ignore */ });
}
- document.addEventListener('DOMContentLoaded', function () {
+ function stopPolling() {
+ if (posTimer) {
+ clearInterval(posTimer);
+ posTimer = null;
+ }
+ }
+
+ function startPolling() {
+ stopPolling();
pollPositions();
posTimer = setInterval(pollPositions, 1000);
- });
+ }
+
+ if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(startPolling);
+ else document.addEventListener('DOMContentLoaded', startPolling);
+ if (window.qihuoOnPageLeave) window.qihuoOnPageLeave(stopPolling);
})();
diff --git a/static/js/review.js b/static/js/review.js
index 1ef08ed..495d308 100644
--- a/static/js/review.js
+++ b/static/js/review.js
@@ -163,11 +163,14 @@
});
}
- document.addEventListener('DOMContentLoaded', function () {
+ function bootReviewPage() {
bindForm();
bindModal();
recalc();
- });
+ }
+
+ if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(bootReviewPage);
+ else document.addEventListener('DOMContentLoaded', bootReviewPage);
window.recalc = recalc;
})();
diff --git a/static/js/stats.js b/static/js/stats.js
index 132f3b8..0c89767 100644
--- a/static/js/stats.js
+++ b/static/js/stats.js
@@ -147,13 +147,17 @@
});
}
- document.addEventListener('DOMContentLoaded', function () {
+ function bootStatsPage() {
var viewSel = document.getElementById('stats-view-select');
- if (viewSel) {
+ if (viewSel && !viewSel.dataset.statsBound) {
+ viewSel.dataset.statsBound = '1';
viewSel.addEventListener('change', function () {
renderBreakdown(this.value);
});
}
loadStats();
- });
+ }
+
+ if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(bootStatsPage);
+ else document.addEventListener('DOMContentLoaded', bootStatsPage);
})();
diff --git a/static/js/symbol.js b/static/js/symbol.js
index 78242d5..6a123b9 100644
--- a/static/js/symbol.js
+++ b/static/js/symbol.js
@@ -261,12 +261,14 @@
});
}
- document.addEventListener('DOMContentLoaded', function () {
+ function initSymbolForms() {
document.querySelectorAll('.symbol-wrap').forEach(initSymbolInput);
document.querySelectorAll('form').forEach(function (form) {
if (!form.querySelector('.symbol-wrap')) return;
if (form.id === 'market-form') return;
+ if (form.dataset.symbolGuard) return;
+ form.dataset.symbolGuard = '1';
form.addEventListener('submit', function (e) {
const ths = form.querySelector('input[name="symbol"]')
|| form.querySelector('.symbol-ths-code');
@@ -282,5 +284,8 @@
}
});
});
- });
+ }
+
+ if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(initSymbolForms);
+ else document.addEventListener('DOMContentLoaded', initSymbolForms);
})();
diff --git a/static/js/trade.js b/static/js/trade.js
index e861b95..26cb005 100644
--- a/static/js/trade.js
+++ b/static/js/trade.js
@@ -49,14 +49,6 @@
var productCategories = window.PRODUCT_CATEGORIES || [];
var POS_CACHE_KEY = 'qihuo_trading_live_v5';
- function runWhenReady(fn) {
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', fn);
- } else {
- fn();
- }
- }
-
function fmtNum(v, digits) {
if (v === null || v === undefined) return '--';
return Number(v).toFixed(digits === undefined ? 2 : digits);
@@ -1595,7 +1587,27 @@
.catch(function () {});
}
- runWhenReady(function () {
+ function cleanupTradePage() {
+ if (positionSource) {
+ positionSource.close();
+ positionSource = null;
+ }
+ if (recommendSource) {
+ recommendSource.close();
+ recommendSource = null;
+ }
+ if (quoteTimer) {
+ clearTimeout(quoteTimer);
+ quoteTimer = null;
+ }
+ if (calcTimer) {
+ clearTimeout(calcTimer);
+ calcTimer = null;
+ }
+ }
+
+ function bootTradePage() {
+ if (!list && !orderList) return;
updateCtpConnectButtonState();
setPriceType('limit');
if (isFixedMode() && lotsCalc) {
@@ -1624,14 +1636,20 @@
.then(function (r) { return r.json(); })
.then(function (data) { if (data.ok) renderRecommendations(data); })
.catch(function () {});
- document.addEventListener('visibilitychange', function () {
- if (document.visibilityState === 'visible' && !positionSource) {
- connectPositionStream();
- }
- });
updateSessionUi();
updateRRDisplay();
scheduleQuote();
scheduleAutoCalc();
+ }
+
+ document.addEventListener('visibilitychange', function () {
+ if (document.visibilityState === 'visible' && list && !positionSource) {
+ connectPositionStream();
+ }
});
+
+ if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(bootTradePage);
+ else bootTradePage();
+ if (window.qihuoOnPageLeave) window.qihuoOnPageLeave(cleanupTradePage);
+ window.addEventListener('pagehide', cleanupTradePage);
})();
diff --git a/static/js/turbonav.js b/static/js/turbonav.js
new file mode 100644
index 0000000..d0e49ea
--- /dev/null
+++ b/static/js/turbonav.js
@@ -0,0 +1,230 @@
+/* Copyright (c) 2025-2026 马建军. All rights reserved.
+ * 专有软件 — 未经授权禁止复制、传播、转售。
+ * 详见 LICENSE.zh-CN.txt
+ */
+(function () {
+ var PERMANENT_CSS = ['/static/css/base.css', '/static/css/tech.css', '/static/css/responsive.css'];
+ var CORE_SCRIPT_MARKERS = ['theme.js', 'symbol.js', 'page.js', 'nav.js', 'pwa.js', 'turbonav.js'];
+ var pageCache = new Map();
+ var MAX_CACHE = 10;
+ var inflight = null;
+ var currentUrl = normalizeUrl(window.location.href);
+
+ function normalizeUrl(href) {
+ var u = new URL(href, window.location.origin);
+ u.hash = '';
+ return u.href;
+ }
+
+ function isPermanentStylesheet(el) {
+ var href = el.getAttribute('href') || '';
+ return PERMANENT_CSS.some(function (p) { return href.indexOf(p) !== -1; });
+ }
+
+ function isCoreScript(el) {
+ var src = el.getAttribute('src') || '';
+ if (!src) return false;
+ return CORE_SCRIPT_MARKERS.some(function (m) { return src.indexOf(m) !== -1; });
+ }
+
+ function shouldTurboClick(e, link) {
+ if (!link || !link.href) return false;
+ if (e.defaultPrevented) return false;
+ if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return false;
+ if (link.target && link.target !== '_self') return false;
+ if (link.hasAttribute('download')) return false;
+ if (link.origin !== window.location.origin) return false;
+ if (normalizeUrl(link.href) === currentUrl) return false;
+ return true;
+ }
+
+ function parseHtml(html) {
+ return new DOMParser().parseFromString(html, 'text/html');
+ }
+
+ function fetchPage(url, signal) {
+ var key = normalizeUrl(url);
+ if (pageCache.has(key)) {
+ return Promise.resolve(pageCache.get(key));
+ }
+ return fetch(key, {
+ credentials: 'same-origin',
+ headers: { Accept: 'text/html' },
+ signal: signal
+ }).then(function (res) {
+ if (!res.ok) throw new Error('HTTP ' + res.status);
+ return res.text();
+ }).then(function (html) {
+ var doc = parseHtml(html);
+ pageCache.set(key, doc);
+ if (pageCache.size > MAX_CACHE) {
+ var first = pageCache.keys().next().value;
+ pageCache.delete(first);
+ }
+ return doc;
+ });
+ }
+
+ function collectPageCss(doc) {
+ var out = [];
+ doc.querySelectorAll('head link[rel="stylesheet"], head style').forEach(function (el) {
+ if (el.tagName === 'LINK' && isPermanentStylesheet(el)) return;
+ out.push(el);
+ });
+ return out;
+ }
+
+ function collectPageScripts(doc) {
+ var out = [];
+ var pastCore = false;
+ doc.body.querySelectorAll(':scope > script').forEach(function (el) {
+ if (isCoreScript(el)) {
+ if ((el.getAttribute('src') || '').indexOf('pwa.js') !== -1) pastCore = true;
+ return;
+ }
+ if (!pastCore) return;
+ out.push(el);
+ });
+ return out;
+ }
+
+ function removePageAssets() {
+ document.querySelectorAll('head link[rel="stylesheet"]').forEach(function (el) {
+ if (!isPermanentStylesheet(el)) el.remove();
+ });
+ document.querySelectorAll('head style').forEach(function (el) { el.remove(); });
+ document.querySelectorAll('body script[data-page-js]').forEach(function (el) { el.remove(); });
+ }
+
+ function applyPageCss(items) {
+ items.forEach(function (srcEl) {
+ var el = srcEl.cloneNode(true);
+ if (el.tagName === 'LINK') {
+ el.setAttribute('data-page-css', '');
+ } else {
+ el.setAttribute('data-page-css', '');
+ }
+ document.head.appendChild(el);
+ });
+ }
+
+ function runPageScripts(items) {
+ return items.reduce(function (chain, srcEl) {
+ return chain.then(function () {
+ return new Promise(function (resolve) {
+ var s = document.createElement('script');
+ s.setAttribute('data-page-js', '');
+ var src = srcEl.getAttribute('src');
+ if (src) {
+ var bust = (src.indexOf('?') >= 0 ? '&' : '?') + '_turbo=' + Date.now();
+ s.src = src + bust;
+ s.async = false;
+ s.onload = function () { resolve(); };
+ s.onerror = function () { resolve(); };
+ document.body.appendChild(s);
+ } else {
+ s.textContent = srcEl.textContent;
+ document.body.appendChild(s);
+ resolve();
+ }
+ });
+ });
+ }, Promise.resolve());
+ }
+
+ function syncNavActive(doc, url) {
+ var nav = document.getElementById('site-nav');
+ var fetchedNav = doc.getElementById('site-nav');
+ if (!nav || !fetchedNav) return;
+ nav.querySelectorAll('a.active').forEach(function (a) { a.classList.remove('active'); });
+ fetchedNav.querySelectorAll('a[href].active').forEach(function (fa) {
+ var href = fa.getAttribute('href');
+ if (!href) return;
+ var local = nav.querySelector('a[href="' + href + '"]')
+ || nav.querySelector('a[href="' + new URL(href, window.location.origin).pathname + '"]');
+ if (local) local.classList.add('active');
+ });
+ }
+
+ function setLoading(on) {
+ var main = document.querySelector('.main');
+ if (main) main.classList.toggle('nav-loading', on);
+ }
+
+ function applyDocument(doc, url, fromPopstate) {
+ var main = document.querySelector('.main');
+ var fetchedMain = doc.querySelector('.main');
+ if (!main || !fetchedMain) {
+ window.location.href = url;
+ return Promise.resolve();
+ }
+
+ window.dispatchEvent(new Event('qihuo:page-leave'));
+ removePageAssets();
+ applyPageCss(collectPageCss(doc));
+ main.innerHTML = fetchedMain.innerHTML;
+ document.title = doc.title || document.title;
+ syncNavActive(doc, url);
+
+ return runPageScripts(collectPageScripts(doc)).then(function () {
+ currentUrl = normalizeUrl(url);
+ if (!fromPopstate) {
+ history.pushState({ turbo: true }, '', currentUrl);
+ }
+ setLoading(false);
+ window.scrollTo(0, 0);
+ if (window.qihuoEmitPageLoad) window.qihuoEmitPageLoad();
+ });
+ }
+
+ function navigateTo(url, opts) {
+ opts = opts || {};
+ var target = normalizeUrl(url);
+ if (target === currentUrl && !opts.force) return Promise.resolve();
+
+ if (inflight) inflight.abort();
+ var ctrl = new AbortController();
+ inflight = ctrl;
+ setLoading(true);
+
+ return fetchPage(target, ctrl.signal).then(function (doc) {
+ if (ctrl.signal.aborted) return;
+ inflight = null;
+ applyDocument(doc, target, !!opts.fromPopstate);
+ }).catch(function (err) {
+ if (ctrl.signal.aborted) return;
+ inflight = null;
+ setLoading(false);
+ window.location.href = target;
+ });
+ }
+
+ function prefetchPage(href) {
+ var target = normalizeUrl(href);
+ if (target === currentUrl || pageCache.has(target)) return;
+ fetchPage(target).catch(function () { /* ignore */ });
+ }
+
+ window.qihuoNavigate = navigateTo;
+ window.qihuoPrefetchPage = prefetchPage;
+
+ document.addEventListener('click', function (e) {
+ var link = e.target.closest('#site-nav a[href]');
+ if (!link || !shouldTurboClick(e, link)) return;
+ e.preventDefault();
+ var navEl = document.getElementById('site-nav');
+ if (navEl) {
+ navEl.querySelectorAll('a.active').forEach(function (a) { a.classList.remove('active'); });
+ link.classList.add('active');
+ }
+ navigateTo(link.href);
+ }, true);
+
+ window.addEventListener('popstate', function () {
+ var target = normalizeUrl(window.location.href);
+ if (target === currentUrl) return;
+ navigateTo(target, { fromPopstate: true, force: true });
+ });
+
+ if (window.qihuoEmitPageLoad) window.qihuoEmitPageLoad();
+})();
diff --git a/static/sw.js b/static/sw.js
index 8d35917..4bdf277 100644
--- a/static/sw.js
+++ b/static/sw.js
@@ -2,14 +2,17 @@
* 专有软件 — 未经授权禁止复制、传播、转售。
* 详见 LICENSE.zh-CN.txt
*/
-var CACHE_VERSION = 'qihuo-v4';
+var CACHE_VERSION = 'qihuo-v6';
var STATIC_CACHE = CACHE_VERSION + '-static';
var STATIC_ASSETS = [
+ '/static/css/base.css',
'/static/css/tech.css',
'/static/css/responsive.css',
'/static/css/trade.css',
'/static/js/theme.js',
+ '/static/js/page.js',
'/static/js/nav.js',
+ '/static/js/turbonav.js',
'/static/js/pwa.js',
'/static/js/symbol.js',
'/static/js/trade.js',
diff --git a/templates/base.html b/templates/base.html
index c7d70c6..03b0ffd 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -25,475 +25,7 @@
} catch (e) { /* ignore */ }
-
+
{% block extra_css %}{% endblock %}
@@ -541,8 +73,10 @@
+
{% block extra_js %}{% endblock %}
+