个股详情重做:系统判断卡加三路意见行,慢变量与指标细节收进详情,去掉内部设计词

抽屉第一段改成主页面的风格:一张判断卡给结论芯片加一句白话与三票概览,
基本面、技术面、择时各一行(沿用提议卡来源行样式),点开才看质地明细、失效条件、
指标读数。页面不再出现合议、路由、交人这些设计词。信号说明里的裸英文码不当句子显示,
处置改成芯片,日期带横线。地址栏 #detail=代码 可直达该票详情。
研究面接口合议改用现读的技术面,修掉「显示看空却按无读数合议」的自相矛盾。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
zlt 2026-09-15 09:13:26 +08:00
parent 985abc48a2
commit e3fb0c6562
2 changed files with 166 additions and 52 deletions

View File

@ -787,10 +787,15 @@ def api_research(ts_code: str):
# 四块意见: assemble 用 company_review 算基本面、用映射算技术面、用昨夜定性算择时, 再合议。 # 四块意见: assemble 用 company_review 算基本面、用映射算技术面、用昨夜定性算择时, 再合议。
nightly = consensus_service.nightly_map([code]).get(code) nightly = consensus_service.nightly_map([code]).get(code)
tech_states = consensus_service.state_map() tech_states = consensus_service.state_map()
blocks = consensus_service.assemble({"ts_code": code, "company_review": cr},
nightly=nightly, tech_states=tech_states)
# 技术面详细读数 (点击时现读, 带最新一行与近日翻向次数); 缺则退回映射里的紧凑块。 # 技术面详细读数 (点击时现读, 带最新一行与近日翻向次数); 缺则退回映射里的紧凑块。
tfeed = tech_service.research_feed(code) tfeed = tech_service.research_feed(code)
# 合议也用现读的这份 (2026-09-15): 早上映射只覆盖相关票, 不在映射里的票合议会按技术面无读数算,
# 而抽屉又显示现读的立场, 两处对不上 (实见 300750: 技术面看空却判「只有基本面一路看多」)。
# 扫描那条路仍用映射, 这里只管显示自洽。
if tfeed.get("tech"):
tech_states = {**(tech_states or {}), code: tfeed["tech"]}
blocks = consensus_service.assemble({"ts_code": code, "company_review": cr},
nightly=nightly, tech_states=tech_states)
tech = tfeed.get("tech") or blocks["tech"] tech = tfeed.get("tech") or blocks["tech"]
tm = blocks["timing"] tm = blocks["timing"]
timing = {"stance": tm.get("stance"), "nightly": tm.get("nightly"), timing = {"stance": tm.get("stance"), "nightly": tm.get("nightly"),

View File

@ -395,6 +395,17 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}
.cs-anchors a:hover{background:rgba(64,120,220,.10);color:var(--fg,#222);} .cs-anchors a:hover{background:rgba(64,120,220,.10);color:var(--fg,#222);}
.cs-part{margin:0 0 16px;scroll-margin-top:44px;} .cs-part{margin:0 0 16px;scroll-margin-top:44px;}
.cs-part>.h{font-size:13px;font-weight:700;margin:0 0 8px;padding-bottom:5px;border-bottom:1px solid var(--hair);} .cs-part>.h{font-size:13px;font-weight:700;margin:0 0 8px;padding-bottom:5px;border-bottom:1px solid var(--hair);}
/* 系统判断卡与三路意见行 (2026-09-15): 沿用主页面的 .disp 芯片与提议卡 .prop-src-row 来源行。 */
.cs-verdict{display:flex;align-items:flex-start;gap:12px;background:var(--surface-2);border:1px solid var(--hair);border-radius:11px;padding:11px 13px;margin:0 0 8px;}
.cs-verdict .disp{flex:none;margin-top:1px;}
.cs-verdict .txt{flex:1;min-width:0;}
.cs-verdict .main{font-size:13px;color:var(--ink);line-height:1.5;}
.cs-verdict .votes{font-size:12px;color:var(--muted);margin-top:3px;}
.cs-ops{margin:0;border-top:none;}
.cs-ops .prop-src-row .sub{white-space:normal;}
.cs-detail{padding:2px 2px 8px 60px;font-size:12px;color:var(--ink-2);line-height:1.55;border-bottom:1px solid rgba(0,0,0,.04);}
.cs-detail .slow{margin-top:4px;color:var(--muted);}
.cs-detail .k{display:inline-block;font-size:11px;padding:0 5px;border-radius:5px;background:var(--chip);color:var(--muted);margin-right:6px;}
.cs-row{display:flex;align-items:baseline;gap:7px;padding:4px 0;font-size:12.5px; .cs-row{display:flex;align-items:baseline;gap:7px;padding:4px 0;font-size:12.5px;
border-bottom:1px solid rgba(0,0,0,.04);flex-wrap:wrap;} border-bottom:1px solid rgba(0,0,0,.04);flex-wrap:wrap;}
.cs-row .t{font-size:11.5px;color:var(--muted);width:40px;flex:none;} .cs-row .t{font-size:11.5px;color:var(--muted);width:40px;flex:none;}
@ -2105,55 +2116,56 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}
</div> </div>
<div class="cs-b"> <div class="cs-b">
<div class="cs-anchors"> <div class="cs-anchors">
<a @click="scrollTo('csp-research')">研究面</a> <a @click="scrollTo('csp-research')">判断</a>
<a @click="scrollTo('csp-signals')">今日信号</a> <a @click="scrollTo('csp-signals')">信号</a>
<a @click="scrollTo('csp-actions')">系统动作</a> <a @click="scrollTo('csp-actions')">动作</a>
</div> </div>
<!-- 一、研究面 · 三源合议 (2026-09-14 §4.4) --> <!-- 一、系统判断 (2026-09-15 重做): 先给结论与一句白话, 三路意见各一行, 慢变量与指标细节收进详情。
页面不出现内部设计词 (合议/路由/交人), 判定用的键一个没动。 -->
<div class="cs-part" id="csp-research"> <div class="cs-part" id="csp-research">
<div class="h">研究面 · 三源合议<span class="muted" style="font-weight:400" v-if="techFeed && techFeed.tech && techFeed.tech.data_date"> · 技术面读数日 {{ techFeed.tech.data_date }}</span></div> <div class="h">系统判断<span class="muted" style="font-weight:400" v-if="techFeed && techFeed.tech && techFeed.tech.data_date"> · 数据到 {{ csYmd(techFeed.tech.data_date) }}</span></div>
<div class="cs-warn" v-if="techFeedErr">研究面读取失败:{{ techFeedErr }}<el-button size="small" style="margin-left:6px" @click="csLoadResearch">重试</el-button></div> <div class="cs-warn" v-if="techFeedErr">判断依据读取失败:{{ techFeedErr }}<el-button size="small" style="margin-left:6px" @click="csLoadResearch">重试</el-button></div>
<div class="muted" v-else-if="!techFeed">该股票暂无研究面读数</div> <div class="muted" v-else-if="!techFeed">该股票暂无判断依据</div>
<template v-else> <template v-else>
<!-- 四枚芯片: 基本面 / 技术面 / 择时 / 合议。按立场红涨绿跌, 无读数灰; 悬停看一句理由。 --> <div class="cs-verdict" v-if="csVerdict">
<div class="cs-row" style="gap:10px;flex-wrap:wrap"> <span class="disp" :class="csVerdict.cls">{{ csVerdict.label }}</span>
<b :class="techStanceCls((techFeed.fund||{}).stance)" :title="(techFeed.fund||{}).fact || (techFeed.fund||{}).why || ('来源:' + ((techFeed.sources||{}).fund||'—'))">基本面 {{ (techFeed.fund||{}).stance || '无读数' }}<span class="muted" v-if="(techFeed.fund||{}).mark"> · {{ techFeed.fund.mark }}</span></b> <div class="txt">
<b :class="techStanceCls((techFeed.tech||{}).stance)" :title="(techFeed.tech||{}).reason || ('来源:' + ((techFeed.sources||{}).tech||'—'))">技术面 {{ (techFeed.tech||{}).stance || '无读数' }}<span class="muted" v-if="(techFeed.tech||{}).phase"> · {{ techFeed.tech.phase }}</span></b> <div class="main">{{ csVerdict.text }}</div>
<b :class="techStanceCls((techFeed.timing||{}).stance)" :title="'昨夜定性 ' + ((techFeed.timing||{}).nightly || '—') + '' + ((techFeed.timing||{}).trade_date || '—') + ''">择时 {{ (techFeed.timing||{}).stance || '无读数' }}</b> <div class="votes">{{ csVerdict.votes }}</div>
<b :class="techStanceCls((techFeed.consensus||{}).direction)" :title="(techFeed.consensus||{}).reason || ''">合议 {{ (techFeed.consensus||{}).direction || '—' }}<span class="muted" v-if="(techFeed.consensus||{}).strength"> · {{ techFeed.consensus.strength }}</span></b>
</div>
<div class="cs-row" v-if="(techFeed.consensus||{}).route">
<span class="s">路由</span><span>{{ techFeed.consensus.route }}</span>
<span class="muted" v-if="techFeed.consensus.route_reason">{{ techFeed.consensus.route_reason }}</span>
</div>
<div class="cs-row" v-if="techFeed.company">
<span class="s">质地</span><span>{{ techFeed.company.line }}</span>
<a v-if="techFeed.company.report_url" :href="techFeed.company.report_url" target="_blank" rel="noopener">评析报告</a>
<span class="ab-stale" v-if="techFeed.company.doubt_hard">硬疑点</span>
</div>
<div class="cs-row" v-if="(techFeed.company||{}).invalidation">
<span class="s muted">失效条件</span><span class="muted">{{ techFeed.company.invalidation }}</span>
</div>
<template v-if="techFeed.tech">
<div class="cs-row">
<span class="s">技术面</span>
<b :class="techStanceCls(techFeed.tech.stance)">{{ techFeed.tech.stance }}<span v-if="techFeed.tech.strength"> · {{ techFeed.tech.strength }}</span></b>
<span v-if="techFeed.tech.phase">{{ techFeed.tech.phase }}</span>
<span class="ab-stale" v-if="techFeed.tech.choppy">震荡市</span>
<div style="flex-basis:100%;font-size:12px;line-height:1.45" class="muted" v-if="techFeed.tech.reason">{{ techFeed.tech.reason }}</div>
</div> </div>
<div class="cs-row" v-if="techFeed.tech.latest"> </div>
<span class="s">布林</span><span>{{ (techFeed.tech.latest.boll||{}).state || '—' }}</span> <div class="prop-src cs-ops">
<span class="muted" v-if="(techFeed.tech.latest.boll||{}).squeeze">收口</span> <div class="prop-src-row" @click="csToggle('fund')">
<span class="s">多空布林线</span><span>{{ (techFeed.tech.latest.bbi||{}).state || '—' }}</span> <span class="lbl">基本面</span>
<span class="s">SAR</span> <span class="ptag" :class="csFundRow.tag.cls">{{ csFundRow.tag.text }}</span>
<b :class="techFeed.tech.sar_side==='多'?'clr-up':'clr-down'">{{ techFeed.tech.sar_side || '—' }}</b> <span class="sub">{{ csFundRow.sub }}</span><i class="car">{{ csDetail.fund ? '⌄' : '' }}</i>
<span class="muted" v-if="techFeed.tech.sar_value!=null">止损位 <span class="mono">{{ techFeed.tech.sar_value }}</span></span>
<span class="muted" v-if="techFeed.tech.sar_flip_days!=null">翻向 {{ techFeed.tech.sar_flip_days }} 天</span>
</div> </div>
</template> <div class="cs-detail" v-if="csDetail.fund">
<div class="muted" v-else>{{ (techFeed.sources||{}).tech || '这只票没有技术面读数' }}</div> <div v-for="(l,i) in csFundRow.lines" :key="'f'+i">{{ l }}</div>
<div v-if="csFundRow.invalidation" class="slow"><span class="k">失效条件</span>{{ csFundRow.invalidation }}</div>
<div v-if="csFundRow.report_url"><a :href="csFundRow.report_url" target="_blank" rel="noopener">评析报告</a></div>
<div v-if="!csFundRow.lines.length && !csFundRow.invalidation" class="muted">{{ csFundRow.empty }}</div>
</div>
<div class="prop-src-row" @click="csToggle('tech')">
<span class="lbl">技术面</span>
<span class="ptag" :class="csTechRow.tag.cls">{{ csTechRow.tag.text }}</span>
<span class="sub">{{ csTechRow.sub }}</span><i class="car">{{ csDetail.tech ? '⌄' : '' }}</i>
</div>
<div class="cs-detail" v-if="csDetail.tech">
<div v-for="(l,i) in csTechRow.lines" :key="'t'+i">{{ l }}</div>
<div v-if="!csTechRow.lines.length" class="muted">{{ csTechRow.empty }}</div>
</div>
<div class="prop-src-row" @click="csToggle('timing')">
<span class="lbl">择时</span>
<span class="ptag" :class="csTimingRow.tag.cls">{{ csTimingRow.tag.text }}</span>
<span class="sub">{{ csTimingRow.sub }}</span><i class="car">{{ csDetail.timing ? '⌄' : '' }}</i>
</div>
<div class="cs-detail" v-if="csDetail.timing">
<div v-for="(l,i) in csTimingRow.lines" :key="'m'+i">{{ l }}</div>
<div v-if="!csTimingRow.lines.length" class="muted">{{ csTimingRow.empty }}</div>
</div>
</div>
</template> </template>
</div> </div>
@ -2185,7 +2197,7 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}
<span class="cs-dup" v-if="r.dup>1">同样的又来了 {{ r.dup }} 次</span> <span class="cs-dup" v-if="r.dup>1">同样的又来了 {{ r.dup }} 次</span>
<span class="cs-dup" v-if="r.ts_from==='stream_id'">写入时刻</span> <span class="cs-dup" v-if="r.ts_from==='stream_id'">写入时刻</span>
<span class="ab-stale" v-if="r.stale">已过时</span> <span class="ab-stale" v-if="r.stale">已过时</span>
<div style="flex-basis:100%;font-size:12px;line-height:1.45" class="muted" v-if="r.reason">{{ r.reason }}</div> <div style="flex-basis:100%;font-size:12px;line-height:1.45" class="muted" v-if="csPlainReason(r.reason)">{{ csPlainReason(r.reason) }}</div>
</div> </div>
<div class="cs-row" style="margin-top:8px" v-if="csSnapMetrics.length || csSnapMr.watch || csSnapMr.avoid"> <div class="cs-row" style="margin-top:8px" v-if="csSnapMetrics.length || csSnapMr.watch || csSnapMr.avoid">
<span class="s muted">当前快照</span><span class="muted" style="font-size:11.5px">这两路上游只给当前值,不带时间</span> <span class="s muted">当前快照</span><span class="muted" style="font-size:11.5px">这两路上游只给当前值,不带时间</span>
@ -2218,10 +2230,14 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}
<span class="muted" v-if="r.arbiter">{{ tx('arbiter', r.arbiter) || r.arbiter }}</span> <span class="muted" v-if="r.arbiter">{{ tx('arbiter', r.arbiter) || r.arbiter }}</span>
<div style="flex-basis:100%;font-size:12px;line-height:1.45" class="muted" v-if="r.reason">{{ r.reason }}</div> <div style="flex-basis:100%;font-size:12px;line-height:1.45" class="muted" v-if="r.reason">{{ r.reason }}</div>
</div> </div>
<div class="cs-row" style="margin-top:8px"><span class="s">处置</span> <div class="cs-row" style="margin-top:10px;gap:8px"><span class="s">处置</span>
<span>{{ csDisp.held ? '在持仓里' : '没有持仓' }}</span> <span class="disp" :class="csDisp.held ? 'done' : 'wait'">{{ csDisp.held ? '持仓中' : '未持仓' }}</span>
<span class="muted">·</span> <span class="disp" :class="csDisp.inPlan ? 'done' : 'wait'">{{ csDisp.inPlan ? '在当日选股计划' : '不在当日选股计划' }}</span>
<span>{{ csDisp.inPlan ? '在今天的选股计划里' : '不在今天的选股计划里' }}</span></div> <template v-if="csDisp.inPlan && !csDisp.held && csDisp.cand">
<span class="disp" :class="csDisp.cand.cls">{{ csDisp.cand.label }}</span>
<span class="muted" style="font-size:12px" v-if="csDisp.cand.why">{{ csDisp.cand.why }}</span>
</template>
</div>
</div> </div>
</div> </div>
</aside> </aside>
@ -3637,6 +3653,7 @@ createApp({
return out; return out;
} }
function openCodeSignals(code) { function openCodeSignals(code) {
Object.assign(csDetail, { fund: false, tech: false, timing: false });
if (!code) return; if (!code) return;
csCode.value = String(code); csCode.value = String(code);
csErr.value = ''; csErr.value = '';
@ -3698,7 +3715,93 @@ createApp({
const csSnapMetrics = computed(() => (csData.value && csData.value.snapshot && csData.value.snapshot.metrics) || []); const csSnapMetrics = computed(() => (csData.value && csData.value.snapshot && csData.value.snapshot.metrics) || []);
const csSnapMr = computed(() => (csData.value && csData.value.snapshot && csData.value.snapshot.mr) || {}); const csSnapMr = computed(() => (csData.value && csData.value.snapshot && csData.value.snapshot.mr) || {});
const csSignalCount = computed(() => (csTimeline.value || []).length); const csSignalCount = computed(() => (csTimeline.value || []).length);
const csDisp = computed(() => ({ held: csHeld(csCode.value), inPlan: csInPlan(csCode.value) })); const csDisp = computed(() => ({ held: csHeld(csCode.value), inPlan: csInPlan(csCode.value),
cand: csCode.value ? dispOf(csCode.value) : null }));
// ---- 系统判断 (2026-09-15 抽屉重做): 结论一句白话 + 三路意见各一行, 内部设计词不上页面 ----
const csDetail = reactive({ fund: false, tech: false, timing: false });
function csToggle(k) { csDetail[k] = !csDetail[k]; }
const csStanceTag = (st) => ({ text: st || '无读数', cls: st === '看多' ? 'up' : (st === '看空' ? 'down' : 'neutral') });
const csYmd = (v) => { const x = String(v || ''); return /^\d{8}$/.test(x) ? x.slice(0, 4) + '-' + x.slice(4, 6) + '-' + x.slice(6) : x; };
const csVoteLine = (f) => [['基本面', f.fund], ['技术面', f.tech], ['择时', f.timing]]
.map(([k, b]) => k + ((b && b.stance) || '无读数')).join('、');
// 后端的路由原因是给复盘看的, 这里翻成用户看得懂的一句话。
function csPlainRoute(r) {
r = String(r || '');
if (r.includes('没有买方评析')) return '没有买方评析,不建仓';
if (r.startsWith('方向看空')) return '多数意见看空,不建仓';
if (r.includes('只有一方表态')) return '只有一路意见看多,其余没有明确意见,最多按试探仓小仓建';
if (r.includes('基本面看空') && r.includes('技术面看多')) return '基本面偏弱但技术面走强,最多按试探仓小仓建';
if (r.includes('等择时转多')) return '基本面与技术面看多,等择时系统转多';
if (r.includes('择时')) return '择时系统有异议,等其转多';
if (r.includes('等技术面开口')) return '布林带仍在收口,等开口后再评估';
if (r.includes('等技术面转向')) return '技术面偏空,等转向后再评估';
return r;
}
const csVerdict = computed(() => {
const f = techFeed.value; if (!f) return null;
const con = f.consensus || {}, tech = f.tech || {}, fund = f.fund || {};
const votes = csVoteLine(f);
if (csHeld(csCode.value)) {
if (tech.phase === '转空') return { cls: 'deny', label: '离场纪律', text: '技术面刚转空,按纪律减持或清仓', votes };
if (con.direction === '看空') return { cls: 'deny', label: '增持暂停', text: '多数意见看空,只减不加', votes };
if (tech.stance === '看空') return { cls: 'queue', label: '增持受限', text: '技术面偏空,暂停回踩补足与盈利加仓,减持照常', votes };
if (fund.stance === '看空') return { cls: 'queue', label: '补仓受限', text: '基本面偏弱,暂停补仓', votes };
return { cls: 'done', label: '持有', text: '意见未偏空,按纪律可增持', votes };
}
const route = con.route;
if (!route) return { cls: 'wait', label: '暂无判断', text: '意见不全,等数据到齐', votes };
if (route === '放行') return { cls: 'done', label: '符合建仓条件', text: con.strength === '强' ? '三路意见一致看多,研判通过即建仓' : '多数意见看多,研判通过即建仓', votes };
if (route === '观察') return { cls: 'wait', label: '观察中', text: csPlainRoute(con.route_reason), votes };
if (route === '交人') return { cls: 'queue', label: '试探仓候选', text: csPlainRoute(con.route_reason) + ';研判通过后再定', votes };
if (route === '跳过') return { cls: 'deny', label: '不建仓', text: csPlainRoute(con.route_reason), votes };
return { cls: 'wait', label: route, text: con.route_reason || '', votes };
});
const csFundRow = computed(() => {
const f = techFeed.value || {}, fund = f.fund || {}, c = f.company || null;
const tag = csStanceTag(fund.stance);
const bits = [];
if (c && c.overall) bits.push('质地' + c.overall);
if (c && c.valuation) bits.push('估值' + c.valuation);
if (fund.mark) bits.push(fund.mark);
if (c && c.doubt_hard) bits.push('有硬疑点');
const sub = bits.length ? bits.join(' · ') : (fund.fact || fund.why || ((f.sources || {}).fund) || '暂无买方评析');
const lines = [];
if (c && c.line) lines.push(c.line);
if (c && c.period) lines.push('报告期 ' + c.period);
return { tag, sub, lines, invalidation: c ? c.invalidation : null, report_url: c ? c.report_url : null,
empty: (f.sources || {}).company || '暂无买方评析' };
});
const csTechRow = computed(() => {
const f = techFeed.value || {}, t = f.tech || {};
const tag = csStanceTag(t.stance);
const head = [t.phase, t.strength ? t.strength : null].filter(Boolean).join('·');
const sub = t.stance ? (head ? head + '' : '') + (t.reason || '') : ((f.sources || {}).tech || '暂无技术面读数');
const lines = [];
const L = t.latest || null;
if (L) {
const b = L.boll || {}, bb = L.bbi || {}, sr = L.sar || {};
lines.push('布林线 ' + (b.state || '—') + (b.squeeze ? '(收口)' : '') + (b.bw_pct != null ? ',带宽 ' + b.bw_pct : ''));
lines.push('多空布林线 ' + (bb.state || '—'));
lines.push('SAR ' + (sr.side || t.sar_side || '—') + (sr.value != null ? ',止损位 ' + sr.value : (t.sar_value != null ? ',止损位 ' + t.sar_value : '')) + (sr.flip_days != null ? ',翻向 ' + sr.flip_days + ' 天' : ''));
} else if (t.sar_side) {
lines.push('SAR ' + t.sar_side + (t.sar_value != null ? ',止损位 ' + t.sar_value : '') + (t.sar_flip_days != null ? ',翻向 ' + t.sar_flip_days + ' 天' : ''));
}
if (t.choppy) lines.push('近二十日翻向频繁,属震荡市');
if (t.data_date) lines.push('读数日 ' + csYmd(t.data_date));
return { tag, sub, lines, empty: (f.sources || {}).tech || '暂无技术面读数' };
});
const csTimingRow = computed(() => {
const f = techFeed.value || {}, m = f.timing || {};
const tag = csStanceTag(m.stance);
const NIGHT = { BUY: '建议买入', WATCH: '观望', SELL: '建议卖出', AVOID: '回避', DROPPED: '已剔除' };
const bits = [];
if (m.nightly) bits.push('昨夜定性 ' + (NIGHT[m.nightly] || m.nightly) + (m.trade_date ? '' + csYmd(m.trade_date) + '' : ''));
if (m.flip_at) bits.push('盘中 ' + m.flip_at + ' 判转多');
const sub = bits.length ? bits.join(' · ') : ((f.sources || {}).timing || '决策系统昨夜没有这只票的结论');
return { tag, sub, lines: bits, empty: (f.sources || {}).timing || '暂无择时读数' };
});
// 信号说明里的裸英文码 (如 money_flow_in_intensity) 不当句子显示。
const csPlainReason = (r) => { const s = String(r || '').trim(); return (!s || /^[A-Za-z0-9_.:\-]+$/.test(s)) ? '' : s; };
// 「今天的信号」列与持仓角标都只数今天的 (2026-08-28): 昨天的风控卖出不再冒充今天 // 「今天的信号」列与持仓角标都只数今天的 (2026-08-28): 昨天的风控卖出不再冒充今天
const sigBriefHeldCount = computed(() => sigBrief.value.filter(x => x.held && x.today !== false).length); const sigBriefHeldCount = computed(() => sigBrief.value.filter(x => x.held && x.today !== false).length);
@ -4127,7 +4230,12 @@ createApp({
onUnmounted(() => { if (refreshTimer) clearInterval(refreshTimer); }); onUnmounted(() => { if (refreshTimer) clearInterval(refreshTimer); });
onMounted(async () => { onMounted(async () => {
await checkMe(); await checkMe();
if (authed.value) { loadAll(); loadPlan(); startAutoRefresh(); } if (authed.value) {
loadAll(); loadPlan(); startAutoRefresh();
// 地址栏带 #detail=代码 就直接打开该票的个股详情 (2026-09-15): 分享链接与预览都用得上。
const h = String(location.hash || '');
if (h.startsWith('#detail=')) openCodeSignals(decodeURIComponent(h.slice(8)));
}
}); // 首屏: 先验登录, 已登录才拉数据与启动自动刷新 }); // 首屏: 先验登录, 已登录才拉数据与启动自动刷新
// ═══════════ 交易员工作台改版 (2026-08-26) ═══════════ // ═══════════ 交易员工作台改版 (2026-08-26) ═══════════
// 一、融合消息栏: 把四类消息合成一条按时间倒序的流。 // 一、融合消息栏: 把四类消息合成一条按时间倒序的流。
@ -4391,6 +4499,7 @@ createApp({
csOpen, csCode, csLoading, csErr, csData, csFromSnap, csTimeline, csSrcFails, csOpen, csCode, csLoading, csErr, csData, csFromSnap, csTimeline, csSrcFails,
csTruncated, csSrcLabel, csLedger, csLedgerErr, csHeld, csInPlan, csTruncated, csSrcLabel, csLedger, csLedgerErr, csHeld, csInPlan,
csSnapMetrics, csSnapMr, csSignalCount, csDisp, csLoadResearch, csLoadLedger, csSnapMetrics, csSnapMr, csSignalCount, csDisp, csLoadResearch, csLoadLedger,
csDetail, csToggle, csVerdict, csFundRow, csTechRow, csTimingRow, csPlainReason, csYmd,
openCodeSignals, closeCodeSignals, csLoadFull, openCodeSignals, closeCodeSignals, csLoadFull,
techFeed, techFeedErr, techStanceCls, techFeed, techFeedErr, techStanceCls,
macro, macroGateOn, macroUnavailable, mzLabel, mzColor, mzBarStyle, macro, macroGateOn, macroUnavailable, mzLabel, mzColor, mzBarStyle,