docs(factor): New/Result两页终端风设计稿v1(可交互,任务#12方案B) [nas]
照 tears-compare-mockup 流程先出稿拍板再实现。三tab: ①提交页New:因子多选从Element下拉改「搜索+分组点选」面板(240因子下拉翻页难) +三类别色chip(101青/158紫/builtin琥珀)+已选托盘chips可删/清空(同对比托盘交互) +标的池mono textarea实时计数徽标+跨行业30只预设(与详情页一键直达同样本便于复现) +区间禁未来+跨度显示+提交校验前置(未满足禁用+原因文案) ②结果页Result外壳:5格metric-strip(最优ICIR红/最优因子青)+IC明细终端表格 (右对齐tabular-nums,IC/ICIR/t正红负绿,|t|≥2加粗,全局最优行青标+左青条) +tears区块虚线占位示意(新版TearsPanel已上线本次不动) ③实现说明:范围/交互变化/拆分预估(New半天+Result1-2小时,纯前端零后端) 种子合成数据mulberry32;charset声明防file://乱码;本机Chrome实测两tab+交互OK
This commit is contained in:
@@ -0,0 +1,460 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>因子分析两页终端风设计稿</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap">
|
||||
<style>
|
||||
:root{
|
||||
--bg:#05080d;--bg-card:#0a0f17;--bg-hover:#0e1622;--bg-overlay:#0d1320;--panel:#070b12;
|
||||
--text:#d4e4f0;--text-2:#7a8a9a;--text-3:#4a5868;
|
||||
--brand:#00e5ff;--cyan-dim:#00b8cc;--cyan-soft:rgba(0,229,255,0.14);
|
||||
--purple-dim:#b48cff;--amber:#ffb000;--amber-soft:rgba(255,176,0,0.14);
|
||||
--up:#ff4d5e;--down:#2ee68a;--danger:#ff4d5e;
|
||||
--lamp-ok:#2ee68a;--lamp-warn:#ffb000;--lamp-idle:#4a5868;
|
||||
--border:#16202e;--border-2:#0f1822;
|
||||
--grid-line:rgba(0,229,255,0.035);--grid-size:32px;
|
||||
--r-sm:2px;--r-md:3px;--r-lg:4px;
|
||||
--mono:'JetBrains Mono',ui-monospace,'SFMono-Regular',Consolas,'Liberation Mono',Menlo,monospace;
|
||||
--sans:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Hiragino Sans GB','Microsoft YaHei',Roboto,sans-serif;
|
||||
--ease:cubic-bezier(0.4,0,0.2,1);
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0}
|
||||
@media (prefers-reduced-motion:no-preference){html{scroll-behavior:smooth}}
|
||||
body{background:var(--bg);color:var(--text);font-family:var(--sans);font-size:13px;line-height:1.55;-webkit-font-smoothing:antialiased}
|
||||
body::before{content:'';position:fixed;inset:0;pointer-events:none;z-index:0;
|
||||
background-image:linear-gradient(var(--grid-line) 1px,transparent 1px),linear-gradient(90deg,var(--grid-line) 1px,transparent 1px);
|
||||
background-size:var(--grid-size) var(--grid-size)}
|
||||
.wrap{position:relative;z-index:1;max-width:1240px;margin:0 auto;padding:18px 22px 90px}
|
||||
.mono{font-family:var(--mono)}
|
||||
button{font-family:inherit}
|
||||
button:focus-visible{outline:1px solid var(--brand);outline-offset:2px}
|
||||
input,textarea{font-family:inherit}
|
||||
input:focus-visible,textarea:focus-visible{outline:1px solid rgba(0,229,255,.5);outline-offset:0}
|
||||
|
||||
/* ===== 顶栏 ===== */
|
||||
.topbar{display:flex;align-items:center;justify-content:space-between;gap:14px;border:1px solid var(--border);background:var(--bg-card);border-radius:var(--r-lg);padding:10px 14px;margin-bottom:14px;flex-wrap:wrap}
|
||||
.topbar .t{font-family:var(--mono);font-size:12.5px;letter-spacing:.05em;font-weight:700}
|
||||
.topbar .t .dim{color:var(--text-3);font-weight:400}
|
||||
.tabs{display:flex;gap:6px;flex-wrap:wrap}
|
||||
.tabs button{font-family:var(--mono);font-size:11.5px;padding:5px 14px;border-radius:var(--r-sm);border:1px solid var(--border);background:transparent;color:var(--text-2);cursor:pointer;transition:all .15s var(--ease)}
|
||||
.tabs button:hover{color:var(--text);border-color:var(--text-3)}
|
||||
.tabs button.on{color:var(--brand);background:var(--cyan-soft);border-color:rgba(0,229,255,.35)}
|
||||
.tab{display:none}
|
||||
.tab.on{display:block}
|
||||
|
||||
/* ===== 设计标注 ===== */
|
||||
.dcap{display:flex;align-items:center;gap:10px;margin:16px 2px 10px;flex-wrap:wrap}
|
||||
.dcap .no{font-family:var(--mono);font-size:11px;color:var(--amber);border:1px solid rgba(255,176,0,.4);background:var(--amber-soft);border-radius:var(--r-sm);padding:2px 8px;letter-spacing:.08em;white-space:nowrap}
|
||||
.dcap .txt{color:var(--text-2);font-size:12px}
|
||||
.dcap .txt b{color:var(--text)}
|
||||
.mock{border:1px solid var(--border);background:var(--bg);border-radius:var(--r-lg);padding:20px 22px 26px;box-shadow:0 8px 32px rgba(0,0,0,.6)}
|
||||
.mock.flash{box-shadow:0 0 0 1px rgba(0,229,255,.55),0 0 24px rgba(0,229,255,.15)}
|
||||
.new-tag{font-family:var(--mono);font-size:9px;color:var(--amber);border:1px dashed rgba(255,176,0,.45);border-radius:var(--r-sm);padding:1px 6px;letter-spacing:.06em;margin-left:8px;vertical-align:1px}
|
||||
|
||||
/* ===== 页面骨架 ===== */
|
||||
.backlink{color:var(--brand);font-size:12px;margin-bottom:12px;display:inline-block;font-family:var(--mono);letter-spacing:.03em}
|
||||
.page-head{display:flex;justify-content:space-between;align-items:flex-end;gap:14px;margin-bottom:16px;flex-wrap:wrap}
|
||||
.page-title{font-size:18px;font-weight:700;letter-spacing:.04em;margin:0}
|
||||
.page-sub{color:var(--text-2);font-size:12px;margin:2px 0 0;font-family:var(--mono);letter-spacing:.02em}
|
||||
|
||||
/* ===== 面板 ===== */
|
||||
.pane{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-lg);margin-bottom:12px;min-width:0}
|
||||
.pane>.ph{display:flex;align-items:center;gap:10px;padding:10px 14px;border-bottom:1px solid var(--border-2);flex-wrap:wrap}
|
||||
.pane>.ph .pt{font-size:12.5px;font-weight:700;letter-spacing:.06em}
|
||||
.pane>.ph .tag{font-family:var(--mono);font-size:9.5px;color:var(--text-3);letter-spacing:.1em}
|
||||
.pane>.pb{padding:14px}
|
||||
.formrow{display:flex;align-items:flex-start;gap:14px}
|
||||
.flab{width:88px;flex-shrink:0;padding-top:7px;text-align:right;color:var(--text-2);font-size:12.5px}
|
||||
.fbody{flex:1;min-width:0}
|
||||
|
||||
/* ===== 因子选择器 ===== */
|
||||
.fx-search{display:flex;gap:8px;margin-bottom:10px;flex-wrap:wrap;align-items:center}
|
||||
.fx-search input{flex:1;min-width:200px;background:var(--panel);border:1px solid var(--border);border-radius:var(--r-sm);color:var(--text);font-family:var(--mono);font-size:12px;padding:6px 10px}
|
||||
.fx-search input::placeholder{color:var(--text-3)}
|
||||
.fx-count{font-family:var(--mono);font-size:10.5px;color:var(--text-3);white-space:nowrap}
|
||||
.fx-groups{display:grid;grid-template-columns:1fr 1fr;gap:12px}
|
||||
@media(max-width:980px){.fx-groups{grid-template-columns:1fr}}
|
||||
.fx-group>.gh{display:flex;align-items:center;gap:8px;margin-bottom:8px}
|
||||
.fx-group>.gh .gt{font-family:var(--mono);font-size:11px;letter-spacing:.08em;font-weight:600}
|
||||
.fx-group>.gh .gc{font-family:var(--mono);font-size:10px;color:var(--text-3)}
|
||||
.cat-chip{display:inline-block;padding:1px 8px;border-radius:var(--r-sm);font-family:var(--mono);font-size:10.5px;font-weight:600;line-height:17px;border:1px solid transparent}
|
||||
.cat-alpha101{color:var(--brand);background:rgba(0,229,255,.1);border-color:rgba(0,229,255,.3)}
|
||||
.cat-alpha158{color:var(--purple-dim);background:rgba(180,140,255,.1);border-color:rgba(180,140,255,.3)}
|
||||
.cat-builtin{color:var(--amber);background:rgba(255,176,0,.1);border-color:rgba(255,176,0,.3)}
|
||||
.fx-grid{display:flex;flex-wrap:wrap;gap:5px}
|
||||
.fxp{font-family:var(--mono);font-size:11px;padding:2px 9px;border-radius:var(--r-sm);border:1px solid var(--border);background:var(--panel);color:var(--text-2);cursor:pointer;transition:all .12s var(--ease);user-select:none}
|
||||
.fxp:hover{color:var(--text);border-color:var(--text-3)}
|
||||
.fxp.sel-alpha101{color:var(--brand);border-color:rgba(0,229,255,.45);background:var(--cyan-soft)}
|
||||
.fxp.sel-alpha158{color:var(--purple-dim);border-color:rgba(180,140,255,.45);background:rgba(180,140,255,.12)}
|
||||
.fxp.sel-builtin{color:var(--amber);border-color:rgba(255,176,0,.45);background:var(--amber-soft)}
|
||||
.fxp.hidden{display:none}
|
||||
.preset{font-family:var(--mono);font-size:10.5px;padding:3px 10px;border-radius:var(--r-sm);border:1px dashed var(--border);background:transparent;color:var(--text-2);cursor:pointer}
|
||||
.preset:hover{color:var(--brand);border-color:rgba(0,229,255,.4)}
|
||||
|
||||
/* ===== 已选托盘 ===== */
|
||||
.picked{display:flex;align-items:center;gap:8px;margin-top:12px;border:1px dashed rgba(0,229,255,.3);background:rgba(0,229,255,.04);border-radius:var(--r-md);padding:8px 12px;flex-wrap:wrap;min-height:38px}
|
||||
.picked .pk{font-family:var(--mono);font-size:10.5px;color:var(--brand);letter-spacing:.1em;white-space:nowrap}
|
||||
.picked .chips{display:flex;gap:5px;flex-wrap:wrap;flex:1}
|
||||
.pchip{display:inline-flex;align-items:center;gap:6px;font-family:var(--mono);font-size:11px;border:1px solid var(--border);border-radius:var(--r-sm);padding:2px 8px;background:var(--panel)}
|
||||
.pchip .x{cursor:pointer;color:var(--text-3)}
|
||||
.pchip .x:hover{color:var(--danger)}
|
||||
.picked .clear{font-family:var(--mono);font-size:10.5px;color:var(--text-3);cursor:pointer;white-space:nowrap}
|
||||
.picked .clear:hover{color:var(--danger)}
|
||||
|
||||
/* ===== 标的池 / 日期 ===== */
|
||||
.sym-area{width:100%;min-height:128px;background:var(--panel);border:1px solid var(--border);border-radius:var(--r-sm);color:var(--text);font-family:var(--mono);font-size:12px;line-height:1.8;padding:8px 10px;resize:vertical}
|
||||
.sym-area::placeholder{color:var(--text-3)}
|
||||
.sym-meta{display:flex;align-items:center;gap:10px;margin-top:8px;flex-wrap:wrap}
|
||||
.sym-badge{font-family:var(--mono);font-size:10.5px;color:var(--brand);border:1px solid rgba(0,229,255,.3);background:var(--cyan-soft);border-radius:var(--r-sm);padding:2px 8px}
|
||||
.hint-inline{font-family:var(--mono);font-size:10.5px;color:var(--text-3)}
|
||||
.dates{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
|
||||
.dates input[type=date]{background:var(--panel);border:1px solid var(--border);border-radius:var(--r-sm);color:var(--text);font-family:var(--mono);font-size:12px;padding:6px 10px;color-scheme:dark}
|
||||
.dates .sep{color:var(--text-3);font-family:var(--mono);font-size:11px}
|
||||
.span-note{font-family:var(--mono);font-size:10.5px;color:var(--text-3)}
|
||||
|
||||
/* ===== 提交条 ===== */
|
||||
.submit-bar{display:flex;align-items:center;gap:14px;margin-top:4px;flex-wrap:wrap}
|
||||
.btn{font-size:12.5px;font-weight:600;border-radius:var(--r-md);padding:8px 18px;border:1px solid var(--border);color:var(--text-2);background:var(--bg-card);transition:all .15s var(--ease);cursor:pointer}
|
||||
.btn:hover{color:var(--text);border-color:var(--text-3)}
|
||||
.btn.primary{color:#032027;background:var(--brand);border-color:var(--brand);box-shadow:0 0 0 1px rgba(0,229,255,.22),0 0 18px rgba(0,229,255,.08);font-weight:700}
|
||||
.btn.primary:hover{background:#33ecff}
|
||||
.btn:disabled{opacity:.45;cursor:not-allowed;box-shadow:none}
|
||||
.btn.primary:disabled:hover{background:var(--brand)}
|
||||
.valid-note{font-family:var(--mono);font-size:11px;color:var(--amber)}
|
||||
|
||||
/* ===== 结果页指标条 ===== */
|
||||
.metric-strip{display:grid;grid-template-columns:repeat(5,1fr);gap:1px;background:var(--border);border:1px solid var(--border);border-radius:var(--r-lg);overflow:hidden;margin-bottom:12px}
|
||||
.metric{background:var(--bg-card);padding:10px 12px;min-width:0}
|
||||
.metric .k{color:var(--text-3);font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;white-space:nowrap}
|
||||
.metric .v{font-family:var(--mono);font-variant-numeric:tabular-nums;font-size:17px;font-weight:600;margin-top:2px;white-space:nowrap}
|
||||
@media(max-width:1080px){.metric-strip{grid-template-columns:repeat(3,1fr)}}
|
||||
|
||||
/* ===== 表格 ===== */
|
||||
.tblwrap{overflow-x:auto}
|
||||
.tbl{width:100%;border-collapse:collapse;font-size:12px}
|
||||
.tbl th{font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--text-3);text-align:right;padding:8px 10px;border-bottom:1px solid var(--border);background:var(--bg-hover);white-space:nowrap}
|
||||
.tbl th:first-child{text-align:left}
|
||||
.tbl td{padding:8px 10px;border-bottom:1px solid var(--border-2);white-space:nowrap;text-align:right;font-variant-numeric:tabular-nums}
|
||||
.tbl td:first-child{text-align:left}
|
||||
.tbl tr:hover td{background:var(--bg-hover)}
|
||||
.dim{color:var(--text-3)}
|
||||
.cy{color:var(--brand)}
|
||||
.pos{color:var(--up)}
|
||||
.neg{color:var(--down)}
|
||||
.fname-s{color:var(--brand);font-family:var(--mono)}
|
||||
.sig{font-weight:700}
|
||||
.best-row td{background:rgba(0,229,255,.05)}
|
||||
.best-row td:first-child{box-shadow:inset 2px 0 0 var(--brand)}
|
||||
|
||||
/* ===== tears 区块示意 ===== */
|
||||
.tears-tabs{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}
|
||||
.tears-tabs .tt{font-family:var(--mono);font-size:11px;padding:3px 12px;border-radius:var(--r-sm);border:1px solid var(--border);color:var(--text-2);cursor:pointer}
|
||||
.tears-tabs .tt.on{color:var(--brand);background:var(--cyan-soft);border-color:rgba(0,229,255,.35)}
|
||||
.tears-ph{border:1px dashed var(--border);border-radius:var(--r-md);background:var(--panel);padding:26px 18px;text-align:center}
|
||||
.tears-ph .big{font-family:var(--mono);font-size:13px;color:var(--brand);letter-spacing:.04em}
|
||||
.tears-ph .sm{color:var(--text-3);font-size:11.5px;margin-top:6px}
|
||||
|
||||
/* ===== 说明页 ===== */
|
||||
.nh{font-size:13.5px;font-weight:700;letter-spacing:.03em;margin:20px 0 8px;color:var(--text)}
|
||||
.nh:first-child{margin-top:0}
|
||||
.ask{margin:6px 0 0;padding-left:20px;color:var(--text-2)}
|
||||
.ask li{margin:6px 0}
|
||||
.ask b{color:var(--text)}
|
||||
.foot{color:var(--text-3);font-family:var(--mono);font-size:10.5px;text-align:center;margin-top:26px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="topbar">
|
||||
<div class="t">因子分析 · 两页终端风重构 <span class="dim">/ factor-new-result-mockup · 2026-08-30</span></div>
|
||||
<nav class="tabs">
|
||||
<button class="on" data-tab="new">① 提交页 New</button>
|
||||
<button data-tab="result">② 结果页 Result 外壳</button>
|
||||
<button data-tab="notes">③ 实现说明</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- ================= ① 提交页 ================= -->
|
||||
<section id="tab-new" class="tab on">
|
||||
<div class="dcap"><span class="no">N1</span><span class="txt"><b>提交页</b>:自选因子 × 自选标的池 × 自选区间的自由实验台。终端风替换 Element 表单——因子改为「搜索 + 分组点选」面板(240 个因子下拉翻页不便)。</span></div>
|
||||
<div class="mock flash">
|
||||
<span class="backlink">← 投研 / 因子分析</span>
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h2 class="page-title">因子分析 <span class="new-tag">终端风重构</span></h2>
|
||||
<p class="page-sub">IC 横截面 · 分层 tears 报告 · 自由实验台(排行榜答不了的换池 / 分年 / 多因子并排归这里)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 因子选择 -->
|
||||
<div class="pane">
|
||||
<div class="ph"><span class="pt">因子选择</span><span class="tag">FACTOR PICKER · 支持多选</span></div>
|
||||
<div class="pb">
|
||||
<div class="fx-search">
|
||||
<input id="fxq" type="text" placeholder="搜索因子名 / 类别,如 alpha16、KMID、alpha158…">
|
||||
<span class="fx-count" id="fxcount"></span>
|
||||
<button class="preset" id="fxtop">预选:榜单 top 因子</button>
|
||||
</div>
|
||||
<div class="fx-groups" id="fxgroups"><!-- JS 渲染 --></div>
|
||||
<div class="picked" id="picked">
|
||||
<span class="pk">已选 <span id="pkn">0</span></span>
|
||||
<div class="chips" id="pkchips"><span class="hint-inline">点击上方因子加入,≥1 个可提交</span></div>
|
||||
<span class="clear" id="pkclear">清空</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 标的池 + 区间 -->
|
||||
<div class="pane">
|
||||
<div class="ph"><span class="pt">标的池与区间</span><span class="tag">UNIVERSE & SPAN</span></div>
|
||||
<div class="pb">
|
||||
<div class="formrow">
|
||||
<div class="flab">标的池</div>
|
||||
<div class="fbody">
|
||||
<textarea class="sym-area" id="symarea" spellcheck="false" placeholder="每行一个标的代码(≥2,IC 横截面需多标的);支持空格 / 逗号 / 换行混合分隔">600519
|
||||
000858
|
||||
600036
|
||||
601318
|
||||
000001
|
||||
600000
|
||||
300750
|
||||
002594</textarea>
|
||||
<div class="sym-meta">
|
||||
<span class="sym-badge" id="symbadge">8 只</span>
|
||||
<span class="hint-inline">IC 横截面统计需要 ≥2 只;截面排名类因子(cs_rank 族)建议 300+ 只才不失真</span>
|
||||
<button class="preset" id="sympool">载入:跨行业 30 只样本</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="formrow" style="margin-top:14px">
|
||||
<div class="flab">分析区间</div>
|
||||
<div class="fbody">
|
||||
<div class="dates">
|
||||
<input type="date" id="dstart" value="2024-01-02">
|
||||
<span class="sep">→</span>
|
||||
<input type="date" id="dend" value="2026-06-30">
|
||||
<span class="span-note" id="spannote"></span>
|
||||
</div>
|
||||
<p class="hint-inline" style="margin:8px 0 0">区间前半作因子计算预热,后半(test 段)进入 IC 与分层统计</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提交 -->
|
||||
<div class="submit-bar">
|
||||
<button class="btn primary" id="submitbtn">提交分析 →</button>
|
||||
<span class="valid-note" id="validnote"></span>
|
||||
<span class="hint-inline">提交后进入进度页,完成自动跳转结果页</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ================= ② 结果页 ================= -->
|
||||
<section id="tab-result" class="tab">
|
||||
<div class="dcap"><span class="no">R1</span><span class="txt"><b>结果页外壳</b>:指标条 + IC 统计明细终端风替换;分层 tears 区块(TearsPanel)已是新版终端风,<b>本次不动</b>,仅示意位置。</span></div>
|
||||
<div class="mock">
|
||||
<span class="backlink">← 投研 / 因子分析</span>
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h2 class="page-title">因子分析结果 <span class="dim mono" style="font-size:13px">factor_e3a91c07</span></h2>
|
||||
<p class="page-sub">IC 横截面统计 · 分层 tears 报告 · 3 因子 × 8 标的 × 2024-01-02 → 2026-06-30</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-strip">
|
||||
<div class="metric"><div class="k">因子数</div><div class="v">3</div></div>
|
||||
<div class="metric"><div class="k">最优 ICIR</div><div class="v pos">0.412</div></div>
|
||||
<div class="metric"><div class="k">最优因子</div><div class="v cy" style="font-size:15px">alpha54</div></div>
|
||||
<div class="metric"><div class="k">平均 |IC|</div><div class="v">0.0431</div></div>
|
||||
<div class="metric"><div class="k">显著数 |t|≥2</div><div class="v">4 / 9</div></div>
|
||||
</div>
|
||||
|
||||
<div class="pane">
|
||||
<div class="ph"><span class="pt">IC 统计明细</span><span class="tag">按因子分组 · 最优 ICIR 行青标 · IC/ICIR/t 正红负绿</span></div>
|
||||
<div class="pb tblwrap">
|
||||
<table class="tbl">
|
||||
<thead><tr><th>因子</th><th>周期</th><th>IC 均值</th><th>IC 标准差</th><th>ICIR</th><th>t 统计</th><th>样本数</th></tr></thead>
|
||||
<tbody id="icrows"><!-- JS 渲染 --></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pane">
|
||||
<div class="ph"><span class="pt">分层 tears 报告</span><span class="tag">TEARS · 已上线新版,本次重构不动</span></div>
|
||||
<div class="pb">
|
||||
<div class="tears-tabs">
|
||||
<span class="tt on">alpha54</span><span class="tt">alpha16</span><span class="tt">KMID</span>
|
||||
</div>
|
||||
<div class="tears-ph">
|
||||
<div class="big">TearsPanel(新版终端风 · 已上线)</div>
|
||||
<div class="sm">指标条 + 月度 IC 柱 / 累计线 + 热力图 + 分组净值 + 多空净值 + IC 衰减,1 / 5 / 10D 联动 —— 此区域保持现状,不在本次两页重构范围内</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ================= ③ 实现说明 ================= -->
|
||||
<section id="tab-notes" class="tab">
|
||||
<div class="mock">
|
||||
<h3 class="nh">范围</h3>
|
||||
<ul class="ask">
|
||||
<li><b>New.vue(提交页)</b> + <b>Result.vue 外壳(指标卡 + IC 明细表)</b> 两处终端风重构;<b>TearsPanel 不动</b>(已是新版终端风)。</li>
|
||||
<li>要素零变化:因子多选按类分组 / 标的池 textarea 混合分隔 / 起止日期禁选未来 / 提交校验(≥1 因子 + ≥2 标的)/ query.factor 预填。</li>
|
||||
<li>纯前端改造,零后端改动;沿用 tokens.css,与排行榜 / 详情 / 对比 / tears 同一套视觉语言。</li>
|
||||
</ul>
|
||||
<h3 class="nh">提交页交互变化</h3>
|
||||
<ul class="ask">
|
||||
<li><b>因子选择从「多选下拉」改为「搜索 + 分组点选」面板</b>:240 个因子在下拉里翻页困难;点选即加入已选托盘,托盘 chips 可单删 / 清空,与排行榜「加入对比」托盘交互一致。</li>
|
||||
<li>保留类别三色:alpha101 青 / alpha158 紫 / builtin 琥珀(同排行榜类别 chip)。</li>
|
||||
<li>标的池:mono 字体 textarea + 实时计数徽标 + 快捷预设(跨行业 30 只,与详情页一键直达同一样本,方便复现)。</li>
|
||||
<li>提交按钮在未满足校验时禁用并给出原因(替代提交后弹 warning)。</li>
|
||||
</ul>
|
||||
<h3 class="nh">结果页外壳变化</h3>
|
||||
<ul class="ask">
|
||||
<li>五格指标条换 metric-strip 形态(同 tears 页指标条),最优 ICIR 红涨色、最优因子青色。</li>
|
||||
<li>IC 明细表换终端表格:数值右对齐 tabular-nums,IC / ICIR / t 正红负绿,|t|≥2 加粗,全局最优 ICIR 行青色左标。</li>
|
||||
</ul>
|
||||
<h3 class="nh">实现拆分(预估)</h3>
|
||||
<ul class="ask">
|
||||
<li><b>New.vue</b>:因子面板组件 FactorPicker.vue(搜索 + 分组网格 + 已选托盘)+ 表单区终端化,约半天。</li>
|
||||
<li><b>Result.vue</b>:指标条 + 表格模板替换(数据逻辑不动),约 1–2 小时。</li>
|
||||
<li>npm run build + push NAS 验收。</li>
|
||||
</ul>
|
||||
<p class="foot">设计稿数据为种子合成(mulberry32),仅供形态确认 · docs/factor_research/factor-new-result-mockup.html</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// —— 种子随机(形态稳定) ——
|
||||
function mulberry32(a){return function(){a|=0;a=a+0x6D2B79F5|0;let t=Math.imul(a^a>>>15,1|a);t=t+Math.imul(t^t>>>7,61|t)^t;return((t^t>>>14)>>>0)/4294967296}}
|
||||
const rnd=mulberry32(20260830);
|
||||
|
||||
// —— 因子种子数据(真实名抽样) ——
|
||||
const CATS=[
|
||||
{key:'alpha101',label:'alpha101',color:'cat-alpha101',names:['alpha1','alpha2','alpha3','alpha6','alpha9','alpha12','alpha16','alpha18','alpha32','alpha42','alpha44','alpha54','alpha65','alpha83','alpha101']},
|
||||
{key:'alpha158',label:'alpha158',color:'cat-alpha158',names:['KMID','KLEN','KUP','KLOW','KMID2','ROC','ROC20','MA','MA20','STD','STD20','BETA','RSQR','RESI','RESI5','MAX','MIN','QTLU','QTLD','RANK','CORR','CNT','VMA','VSTD','WVMA']},
|
||||
{key:'builtin',label:'builtin',color:'cat-builtin',names:['bb','cc','dontchg','environment','newhigh','sigma','tq','tq20','vol','year_high']}
|
||||
];
|
||||
let picked=new Set(['alpha16']);
|
||||
const DEFAULT_TOP=['alpha16','alpha54','RESI5','alpha42','alpha32'];
|
||||
|
||||
// —— 渲染因子分组 ——
|
||||
const groupsEl=document.getElementById('fxgroups');
|
||||
CATS.forEach(cat=>{
|
||||
const g=document.createElement('div');g.className='fx-group';
|
||||
g.innerHTML=`<div class="gh"><span class="cat-chip ${cat.color}">${cat.label}</span><span class="gc">${cat.names.length} 个</span></div>`;
|
||||
const grid=document.createElement('div');grid.className='fx-grid';
|
||||
cat.names.forEach(n=>{
|
||||
const b=document.createElement('span');b.className='fxp';b.textContent=n;b.dataset.cat=cat.key;b.dataset.name=n;
|
||||
if(picked.has(n))b.classList.add('sel-'+cat.key);
|
||||
b.onclick=()=>{picked.has(n)?picked.delete(n):picked.add(n);renderPicked();refreshCounts();};
|
||||
grid.appendChild(b);
|
||||
});
|
||||
g.appendChild(grid);groupsEl.appendChild(g);
|
||||
});
|
||||
|
||||
// —— 搜索过滤 ——
|
||||
const q=document.getElementById('fxq');
|
||||
q.addEventListener('input',()=>{
|
||||
const v=q.value.trim().toLowerCase();
|
||||
document.querySelectorAll('.fxp').forEach(el=>{
|
||||
el.classList.toggle('hidden',v&&!el.dataset.name.toLowerCase().includes(v)&&!el.dataset.cat.includes(v));
|
||||
});
|
||||
refreshCounts();
|
||||
});
|
||||
document.getElementById('fxtop').onclick=()=>{picked=new Set(DEFAULT_TOP);renderPicked();refreshCounts();};
|
||||
|
||||
// —— 已选托盘 ——
|
||||
function catOf(n){for(const c of CATS)if(c.names.includes(n))return c;return CATS[2];}
|
||||
function renderPicked(){
|
||||
const chips=document.getElementById('pkchips');
|
||||
document.getElementById('pkn').textContent=picked.size;
|
||||
if(!picked.size){chips.innerHTML='<span class="hint-inline">点击上方因子加入,≥1 个可提交</span>';}
|
||||
else{
|
||||
chips.innerHTML='';
|
||||
[...picked].forEach(n=>{
|
||||
const c=catOf(n);
|
||||
const s=document.createElement('span');s.className='pchip';
|
||||
s.innerHTML=`<span class="cat-chip ${c.color}">${c.label}</span>${n}<span class="x" title="移除">✕</span>`;
|
||||
s.querySelector('.x').onclick=()=>{picked.delete(n);renderPicked();refreshCounts();};
|
||||
chips.appendChild(s);
|
||||
});
|
||||
}
|
||||
validate();
|
||||
}
|
||||
document.getElementById('pkclear').onclick=()=>{picked.clear();renderPicked();refreshCounts();};
|
||||
|
||||
// —— 计数 / 校验 ——
|
||||
function visibleCount(){return [...document.querySelectorAll('.fxp')].filter(e=>!e.classList.contains('hidden')).length;}
|
||||
function refreshCounts(){
|
||||
document.getElementById('fxcount').textContent=`显示 ${visibleCount()} / 51`;
|
||||
document.querySelectorAll('.fxp').forEach(el=>{
|
||||
const c=el.dataset.cat,sel=picked.has(el.dataset.name);
|
||||
el.classList.toggle('sel-alpha101',sel&&c==='alpha101');
|
||||
el.classList.toggle('sel-alpha158',sel&&c==='alpha158');
|
||||
el.classList.toggle('sel-builtin',sel&&c==='builtin');
|
||||
});
|
||||
}
|
||||
const symArea=document.getElementById('symarea');
|
||||
function symCount(){return symArea.value.split(/[\s,,]+/).map(s=>s.trim()).filter(Boolean).length;}
|
||||
function validate(){
|
||||
const sc=symCount();
|
||||
document.getElementById('symbadge').textContent=`${sc} 只`;
|
||||
const btn=document.getElementById('submitbtn'),note=document.getElementById('validnote');
|
||||
if(!picked.size){btn.disabled=true;note.textContent='待办:至少选 1 个因子';}
|
||||
else if(sc<2){btn.disabled=true;note.textContent='待办:标的池 ≥2 只';}
|
||||
else{btn.disabled=false;note.textContent='';}
|
||||
}
|
||||
symArea.addEventListener('input',validate);
|
||||
document.getElementById('sympool').onclick=()=>{
|
||||
symArea.value=['600519','000858','600036','601318','000001','600000','300750','002594','600276','000538','600887','000651','000333','600690','000725','002415','600030','601166','600028','601857','600585','000625','601633','002352','600104','601398','600941','300059','002230','601888'].join('\n');
|
||||
validate();
|
||||
};
|
||||
// 区间跨度
|
||||
function span(){try{const a=new Date(dstart.value),b=new Date(dend.value);const d=Math.round((b-a)/864e5);document.getElementById('spannote').textContent=isFinite(d)?`跨度 ${d} 天 ≈ ${(d/365).toFixed(1)} 年`:'';}catch(e){}}
|
||||
const dstart=document.getElementById('dstart'),dend=document.getElementById('dend');
|
||||
dstart.oninput=span;dend.oninput=span;
|
||||
// 提交(示意)
|
||||
document.getElementById('submitbtn').onclick=()=>{alert('示意:提交 → 跳转进度页(/factor/progress/{task_id}),完成自动进结果页。真实实现走现有 submitFactor API。');};
|
||||
|
||||
// —— 结果页 IC 表(种子合成) ——
|
||||
const factors=['alpha54','alpha16','KMID'];
|
||||
const rows=[];
|
||||
factors.forEach(f=>{
|
||||
[1,5,10].forEach(p=>{
|
||||
const m=(rnd()-0.42)*0.12, sd=0.06+rnd()*0.05;
|
||||
const n=280+Math.floor(rnd()*24), icir=m/sd, t=icir*Math.sqrt(n);
|
||||
rows.push({f,p,m,sd,icir,t,n});
|
||||
});
|
||||
});
|
||||
const best=Math.max(...rows.map(r=>r.icir));
|
||||
document.getElementById('icrows').innerHTML=rows.map(r=>{
|
||||
const cls=v=>v>=0?'pos':'neg';
|
||||
const sig=Math.abs(r.t)>=2?' sig':'';
|
||||
const bestRow=r.icir===best?' class="best-row"':'';
|
||||
return `<tr${bestRow}><td><span class="fname-s">${r.f}</span>${r.icir===best?' <span class="cy dim" style="font-size:10px">◈ 最优</span>':''}</td><td class="dim mono">${r.p}D</td><td class="${cls(r.m)}">${r.m.toFixed(4)}</td><td>${r.sd.toFixed(4)}</td><td class="${cls(r.icir)}${sig}">${r.icir.toFixed(4)}</td><td class="${cls(r.t)}${sig}">${r.t.toFixed(2)}</td><td class="dim">${r.n}</td></tr>`;
|
||||
}).join('');
|
||||
|
||||
// —— tab 切换 ——
|
||||
document.querySelectorAll('.tabs button').forEach(b=>{
|
||||
b.onclick=()=>{
|
||||
document.querySelectorAll('.tabs button').forEach(x=>x.classList.remove('on'));
|
||||
document.querySelectorAll('.tab').forEach(x=>x.classList.remove('on'));
|
||||
b.classList.add('on');document.getElementById('tab-'+b.dataset.tab).classList.add('on');
|
||||
};
|
||||
});
|
||||
document.querySelectorAll('.tears-tabs .tt').forEach(b=>{
|
||||
b.onclick=()=>{document.querySelectorAll('.tears-tabs .tt').forEach(x=>x.classList.remove('on'));b.classList.add('on');};
|
||||
});
|
||||
|
||||
// init
|
||||
renderPicked();refreshCounts();validate();span();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user