三个维度统一:提议卡去掉合议行、改基本面与择时行;候选栏换三枚按立场着色的维度标签

提议卡不再重复显示合计,也不再用「基看多」这类简写:公司深度行改为基本面行,
合议行改为择时行,立场词统一看多红、看空绿、中性灰;择时详情改成昨夜定性、
三路意见与一句系统结论。建仓候选栏两枚芯片换成基本面、技术面、择时三枚标签,
文字是维度名、颜色是立场,悬停看依据。候选处置快照的合议摘要补择时那一票。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
zlt 2026-09-15 10:36:03 +08:00
parent bb711ab5db
commit e14c9d9808
2 changed files with 75 additions and 23 deletions

View File

@ -740,6 +740,7 @@ def disposition_snapshot(now=None) -> dict:
res["by_code"][code]["consensus"] = {
"fund": rec.get("fund"), "overall": rec.get("overall"),
"tech": rec.get("tech"), "phase": rec.get("phase"),
"timing": rec.get("timing"), # 择时那一票 (2026-09-15: 候选栏三枚维度标签用)
"direction": rec.get("direction"), "route": rec.get("route")}
return res

View File

@ -442,6 +442,13 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}
.ptag.accent{background:var(--accent-weak);color:var(--accent);}
.ptag.warn{background:var(--warn-weak);color:var(--warn);}
.ptag.neutral{background:var(--surface-2);color:var(--muted);}
/* 维度标签 (2026-09-15): 文字是维度名 (基本面/技术面/择时), 颜色是立场 —— 看多红、看空绿、中性灰、无读数虚框。
候选栏与提议卡共用一套, 与抽屉里的立场词同色, 不再各写各的。 */
.dim{display:inline-flex;align-items:center;font-size:11px;font-weight:560;padding:1px 7px;border-radius:6px;flex:none;line-height:1.5;cursor:default;}
.dim.up{background:var(--up-weak);color:var(--up);}
.dim.down{background:var(--down-weak);color:var(--down);}
.dim.neutral{background:rgba(138,136,130,.14);color:var(--ink-2);}
.dim.na{background:transparent;color:var(--muted);border:1px dashed var(--hair);padding:0 6px;}
.prop-sheet{position:fixed;left:0;right:0;bottom:0;z-index:3000;max-height:86vh;overflow:auto;
background:var(--surface);border-radius:16px 16px 0 0;box-shadow:0 -12px 40px rgba(17,17,16,.22);}
.prop-metric{display:flex;flex-direction:column;gap:7px;margin:8px 0;}
@ -1158,20 +1165,22 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}
<span class="ptag" :class="propJudgeTag(p).cls">{{ propJudgeTag(p).text }}</span>
<span class="sub" v-if="(p.hard_numbers||{}).judge_conf!=null">把握 {{ (p.hard_numbers||{}).judge_conf }}</span><i class="car"></i>
</div>
<div class="prop-src-row" v-if="propCompanyTag(p)" @click="openPropDetail(p,'company')">
<span class="lbl">公司深度</span>
<span class="ptag" :class="propCompanyTag(p).cls">{{ propCompanyTag(p).text }}</span>
<span class="sub" v-if="cr(p) && cr(p).valuation">估值{{ cr(p).valuation }}</span><i class="car"></i>
<!-- 三个维度与抽屉、候选栏同一套 (2026-09-15): 基本面 / 技术面 / 择时, 立场词按看多红看空绿中性灰。
合计那一行不再重复显示 —— 结论已在建议档位里; 点择时可看三路意见与系统结论。 -->
<div class="prop-src-row" v-if="propFundTag(p)" @click="openPropDetail(p,'company')">
<span class="lbl">基本面</span>
<span class="ptag" :class="propFundTag(p).cls">{{ propFundTag(p).text }}</span>
<span class="sub">{{ propFundSub(p) }}</span><i class="car"></i>
</div>
<div class="prop-src-row" v-if="propTechTag(p)" @click="openPropDetail(p,'tech')">
<span class="lbl">技术面</span>
<span class="ptag" :class="propTechTag(p).cls">{{ propTechTag(p).text }}</span>
<span class="sub">{{ propTechSub(p) }}</span><i class="car"></i>
</div>
<div class="prop-src-row" v-if="propConsensusTag(p)" @click="openPropDetail(p,'consensus')">
<span class="lbl">合议</span>
<span class="ptag" :class="propConsensusTag(p).cls">{{ propConsensusTag(p).text }}</span>
<span class="sub">{{ propConsensusSub(p) }}</span><i class="car"></i>
<div class="prop-src-row" v-if="propTimingTag(p)" @click="openPropDetail(p,'consensus')">
<span class="lbl">择时</span>
<span class="ptag" :class="propTimingTag(p).cls">{{ propTimingTag(p).text }}</span>
<span class="sub">{{ propTimingSub(p) }}</span><i class="car"></i>
</div>
</div>
<div style="margin-top:6px">
@ -1283,10 +1292,11 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}
<span class="nm" style="text-decoration:underline dotted">{{ r.name || nm(r.ts_code) }}</span>
<span class="cat" v-if="r.theme">{{ r.theme }}</span>
<span class="sp"></span>
<!--源合议两枚芯片 (2026-09-14 页面收尾包): 质地档 + 技术面立场·相位。数据从候选处置的合议摘要来-->
<!--枚维度标签 (2026-09-15): 文字是维度名, 颜色是立场, 与提议卡、抽屉同一套; 悬停看立场与依据-->
<template v-if="conOf(r.ts_code)">
<b v-if="conOf(r.ts_code).overall" :class="conOf(r.ts_code).overall==='好'?'clr-up':(conOf(r.ts_code).overall==='差'?'clr-down':'muted')" :title="'基本面 ' + (conOf(r.ts_code).fund||'无读数')" style="font-size:11.5px">质地{{ conOf(r.ts_code).overall }}</b>
<b :class="techStanceCls(conOf(r.ts_code).tech)" :title="'技术面 ' + (conOf(r.ts_code).tech||'无读数') + ' · ' + (conOf(r.ts_code).phase||'—')" style="font-size:11.5px">{{ conOf(r.ts_code).tech || '技术—' }}</b>
<span class="dim" :class="dimCls(conOf(r.ts_code).fund)" :title="'基本面 ' + (conOf(r.ts_code).fund||'无读数') + (conOf(r.ts_code).overall ? ' · 质地' + conOf(r.ts_code).overall : '')">基本面</span>
<span class="dim" :class="dimCls(conOf(r.ts_code).tech)" :title="'技术面 ' + (conOf(r.ts_code).tech||'无读数') + (conOf(r.ts_code).phase ? ' · ' + conOf(r.ts_code).phase : '')">技术面</span>
<span class="dim" :class="dimCls(conOf(r.ts_code).timing)" :title="'择时 ' + (conOf(r.ts_code).timing||'无读数')">择时</span>
</template>
<span class="disp" :class="dispOf(r.ts_code).cls" style="padding:0 6px">{{ dispOf(r.ts_code).label }}</span>
</div>
@ -2075,19 +2085,21 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}
</template>
<template v-else-if="propDetailKind==='consensus'">
<div class="st muted" v-if="!propCon(propDetailP)">这只票没有三源合议(合议开关没开,或三方都没有读数)。</div>
<div class="st muted" v-if="!propCon(propDetailP) && !(propDetailP.hard_numbers||{}).timing">这只票没有择时读数与三路意见(开关没开,或三方都没有读数)。</div>
<template v-else>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:10px;flex-wrap:wrap">
<span class="ptag" :class="propConsensusTag(propDetailP).cls">{{ propConsensusTag(propDetailP).text }}</span>
<span class="ptag neutral" v-if="propCon(propDetailP).route">路由:{{ propCon(propDetailP).route }}</span>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:10px;flex-wrap:wrap" v-if="propTimingTag(propDetailP)">
<span class="ptag" :class="propTimingTag(propDetailP).cls">{{ propTimingTag(propDetailP).text }}</span>
<span class="st">{{ propTimingSub(propDetailP) || '决策系统昨夜没有这只票的结论' }}</span>
</div>
<div class="cs-sect"><div class="h">三路意见</div>
<div class="prop-metric">
<div class="m"><span class="nm">基本面</span><span class="ptag neutral">{{ (propCon(propDetailP).votes||{}).fund || '无读数' }}</span></div>
<div class="m"><span class="nm">技术面</span><span class="ptag neutral">{{ (propCon(propDetailP).votes||{}).tech || '无读数' }}</span></div>
<div class="m"><span class="nm">择时系统</span><span class="ptag neutral">{{ (propCon(propDetailP).votes||{}).timing || '无读数' }}</span></div>
<div class="m"><span class="nm">基本面</span><span class="ptag" :class="dimCls(((propCon(propDetailP)||{}).votes||{}).fund)">{{ ((propCon(propDetailP)||{}).votes||{}).fund || '无读数' }}</span></div>
<div class="m"><span class="nm">技术面</span><span class="ptag" :class="dimCls(((propCon(propDetailP)||{}).votes||{}).tech)">{{ ((propCon(propDetailP)||{}).votes||{}).tech || '无读数' }}</span></div>
<div class="m"><span class="nm">择时</span><span class="ptag" :class="dimCls(((propCon(propDetailP)||{}).votes||{}).timing)">{{ ((propCon(propDetailP)||{}).votes||{}).timing || '无读数' }}</span></div>
</div>
<div class="cs-sect" v-if="propCon(propDetailP).route_reason"><div class="h">为什么这么走</div><div class="st">{{ propCon(propDetailP).route_reason }}</div></div>
<div class="st muted" v-if="propCon(propDetailP).reason" style="margin-top:6px">{{ propCon(propDetailP).reason }}</div>
</div>
<div class="cs-sect" v-if="propCon(propDetailP) && propCon(propDetailP).route"><div class="h">系统结论</div>
<div class="st">{{ propConclusion(propDetailP) }}</div></div>
</template>
</template>
</div>
@ -3138,6 +3150,44 @@ createApp({
};
// 合议 (2026-09-11 工作包二): 三方多数方向与强弱, 读下单当时记进硬数字的那份 (解释这条提议
// 为什么按合议路由走)。方向按页面口径红涨绿跌; 无读数用中性灰。点开看三票与路由理由。
// 立场 → 颜色类 (2026-09-15, 三处共用): 看多红, 看空绿, 中性灰, 无读数虚框。
const dimCls = (st) => st === '看多' ? 'up' : (st === '看空' ? 'down' : (st === '中性' ? 'neutral' : 'na'));
// 基本面行: 立场取硬数字里的 fund_stance (合议六键), 没有就按质地档推; 副文案给质地与估值。
const propFundTag = (p) => {
const fs = ((p.hard_numbers || {}).fund_stance || {}).stance, c = cr(p);
const st = fs || (c && c.overall ? (c.overall === '好' ? '看多' : (c.overall === '差' ? '看空' : '中性')) : null);
if (!st && !c) return null;
return { text: st || '无读数', cls: dimCls(st) };
};
const propFundSub = (p) => {
const c = cr(p) || {}, fs = (p.hard_numbers || {}).fund_stance || {}, bits = [];
if (c.overall) bits.push('质地' + c.overall);
if (c.valuation) bits.push('估值' + c.valuation);
if (fs.mark) bits.push(fs.mark);
return bits.join(' · ');
};
// 择时行: 硬数字里的 timing (合议六键), 昨夜定性翻成中文。
const NIGHT_CN = { BUY: '建议买入', WATCH: '观望', SELL: '建议卖出', AVOID: '回避', DROPPED: '已剔除' };
const propTimingTag = (p) => {
const t = (p.hard_numbers || {}).timing;
if (!t) return null;
return { text: t.stance || '无读数', cls: dimCls(t.stance) };
};
const propTimingSub = (p) => {
const t = (p.hard_numbers || {}).timing || {}, bits = [];
if (t.nightly) bits.push('昨夜定性 ' + (NIGHT_CN[t.nightly] || t.nightly));
if (t.flip_at) bits.push('盘中 ' + t.flip_at + ' 判转多');
return bits.join(' · ');
};
// 系统结论 (择时详情用): 把路由翻成用户看得懂的一句话, 与抽屉判断卡同一套措辞。
const propConclusion = (p) => {
const c = propCon(p) || {};
if (c.route === '放行') return c.strength === '强' ? '三路意见一致看多,研判通过即建仓' : '多数意见看多,研判通过即建仓';
if (c.route === '观察') return '观察中:' + csPlainRoute(c.route_reason);
if (c.route === '交人') return '试探仓候选:' + csPlainRoute(c.route_reason) + ';研判通过后再定';
if (c.route === '跳过') return '不建仓:' + csPlainRoute(c.route_reason);
return c.route_reason || '';
};
const propCon = (p) => (p && p.hard_numbers && p.hard_numbers.consensus) || null;
const propConsensusTag = (p) => {
const c = propCon(p);
@ -3156,8 +3206,8 @@ createApp({
// 弹窗: 点某来源看它的完整依据。teleport 到 body, 层级同单票信号抽屉 (3000/2995) 盖住右栏。
const propDetailOpen = ref(false), propDetailKind = ref(''), propDetailP = ref(null);
const propTechFull = ref(null), propTechErr = ref('');
const PROP_SRC_LABEL = { plan: '选股系统', judge: '决策系统', company: '公司深度',
tech: '技术面', consensus: '三源合议' };
const PROP_SRC_LABEL = { plan: '选股系统', judge: '决策系统', company: '基本面',
tech: '技术面', consensus: '择时' };
function openPropDetail(p, kind) {
propDetailP.value = p; propDetailKind.value = kind; propDetailOpen.value = true;
propTechFull.value = null; propTechErr.value = '';
@ -4482,6 +4532,7 @@ createApp({
// 2026-09-12 提议卡重构: 来源结论标签 + 详情弹窗
cr, propPlanTag, propPlanSub, propJudgeTag, propCompanyTag, propTechTag, propTechSub,
propCon, propConsensusTag, propConsensusSub,
propFundTag, propFundSub, propTimingTag, propTimingSub, propConclusion, dimCls,
propDetailOpen, propDetailKind, propDetailP, propTechFull, propTechErr, PROP_SRC_LABEL,
openPropDetail, closePropDetail,
pHaltBuy, pResumeBuy, pHaltAll, pResumeAll, pReduce, pIncrease, pLiquidate, pSectorExit,