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

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

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"] = { res["by_code"][code]["consensus"] = {
"fund": rec.get("fund"), "overall": rec.get("overall"), "fund": rec.get("fund"), "overall": rec.get("overall"),
"tech": rec.get("tech"), "phase": rec.get("phase"), "tech": rec.get("tech"), "phase": rec.get("phase"),
"timing": rec.get("timing"), # 择时那一票 (2026-09-15: 候选栏三枚维度标签用)
"direction": rec.get("direction"), "route": rec.get("route")} "direction": rec.get("direction"), "route": rec.get("route")}
return res 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.accent{background:var(--accent-weak);color:var(--accent);}
.ptag.warn{background:var(--warn-weak);color:var(--warn);} .ptag.warn{background:var(--warn-weak);color:var(--warn);}
.ptag.neutral{background:var(--surface-2);color:var(--muted);} .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; .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);} 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;} .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="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> <span class="sub" v-if="(p.hard_numbers||{}).judge_conf!=null">把握 {{ (p.hard_numbers||{}).judge_conf }}</span><i class="car"></i>
</div> </div>
<div class="prop-src-row" v-if="propCompanyTag(p)" @click="openPropDetail(p,'company')"> <!-- 三个维度与抽屉、候选栏同一套 (2026-09-15): 基本面 / 技术面 / 择时, 立场词按看多红看空绿中性灰。
<span class="lbl">公司深度</span> 合计那一行不再重复显示 —— 结论已在建议档位里; 点择时可看三路意见与系统结论。 -->
<span class="ptag" :class="propCompanyTag(p).cls">{{ propCompanyTag(p).text }}</span> <div class="prop-src-row" v-if="propFundTag(p)" @click="openPropDetail(p,'company')">
<span class="sub" v-if="cr(p) && cr(p).valuation">估值{{ cr(p).valuation }}</span><i class="car"></i> <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>
<div class="prop-src-row" v-if="propTechTag(p)" @click="openPropDetail(p,'tech')"> <div class="prop-src-row" v-if="propTechTag(p)" @click="openPropDetail(p,'tech')">
<span class="lbl">技术面</span> <span class="lbl">技术面</span>
<span class="ptag" :class="propTechTag(p).cls">{{ propTechTag(p).text }}</span> <span class="ptag" :class="propTechTag(p).cls">{{ propTechTag(p).text }}</span>
<span class="sub">{{ propTechSub(p) }}</span><i class="car"></i> <span class="sub">{{ propTechSub(p) }}</span><i class="car"></i>
</div> </div>
<div class="prop-src-row" v-if="propConsensusTag(p)" @click="openPropDetail(p,'consensus')"> <div class="prop-src-row" v-if="propTimingTag(p)" @click="openPropDetail(p,'consensus')">
<span class="lbl">合议</span> <span class="lbl">择时</span>
<span class="ptag" :class="propConsensusTag(p).cls">{{ propConsensusTag(p).text }}</span> <span class="ptag" :class="propTimingTag(p).cls">{{ propTimingTag(p).text }}</span>
<span class="sub">{{ propConsensusSub(p) }}</span><i class="car"></i> <span class="sub">{{ propTimingSub(p) }}</span><i class="car"></i>
</div> </div>
</div> </div>
<div style="margin-top:6px"> <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="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="cat" v-if="r.theme">{{ r.theme }}</span>
<span class="sp"></span> <span class="sp"></span>
<!--源合议两枚芯片 (2026-09-14 页面收尾包): 质地档 + 技术面立场·相位。数据从候选处置的合议摘要来--> <!--枚维度标签 (2026-09-15): 文字是维度名, 颜色是立场, 与提议卡、抽屉同一套; 悬停看立场与依据-->
<template v-if="conOf(r.ts_code)"> <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> <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>
<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).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> </template>
<span class="disp" :class="dispOf(r.ts_code).cls" style="padding:0 6px">{{ dispOf(r.ts_code).label }}</span> <span class="disp" :class="dispOf(r.ts_code).cls" style="padding:0 6px">{{ dispOf(r.ts_code).label }}</span>
</div> </div>
@ -2075,19 +2085,21 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}
</template> </template>
<template v-else-if="propDetailKind==='consensus'"> <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> <template v-else>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:10px;flex-wrap:wrap"> <div style="display:flex;align-items:center;gap:8px;margin-bottom:10px;flex-wrap:wrap" v-if="propTimingTag(propDetailP)">
<span class="ptag" :class="propConsensusTag(propDetailP).cls">{{ propConsensusTag(propDetailP).text }}</span> <span class="ptag" :class="propTimingTag(propDetailP).cls">{{ propTimingTag(propDetailP).text }}</span>
<span class="ptag neutral" v-if="propCon(propDetailP).route">路由:{{ propCon(propDetailP).route }}</span> <span class="st">{{ propTimingSub(propDetailP) || '决策系统昨夜没有这只票的结论' }}</span>
</div> </div>
<div class="cs-sect"><div class="h">三路意见</div>
<div class="prop-metric"> <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" :class="dimCls(((propCon(propDetailP)||{}).votes||{}).fund)">{{ ((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" :class="dimCls(((propCon(propDetailP)||{}).votes||{}).tech)">{{ ((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||{}).timing)">{{ ((propCon(propDetailP)||{}).votes||{}).timing || '无读数' }}</span></div>
</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>
<div class="st muted" v-if="propCon(propDetailP).reason" style="margin-top:6px">{{ propCon(propDetailP).reason }}</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>
</template> </template>
</div> </div>
@ -3138,6 +3150,44 @@ createApp({
}; };
// 合议 (2026-09-11 工作包二): 三方多数方向与强弱, 读下单当时记进硬数字的那份 (解释这条提议 // 合议 (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 propCon = (p) => (p && p.hard_numbers && p.hard_numbers.consensus) || null;
const propConsensusTag = (p) => { const propConsensusTag = (p) => {
const c = propCon(p); const c = propCon(p);
@ -3156,8 +3206,8 @@ createApp({
// 弹窗: 点某来源看它的完整依据。teleport 到 body, 层级同单票信号抽屉 (3000/2995) 盖住右栏。 // 弹窗: 点某来源看它的完整依据。teleport 到 body, 层级同单票信号抽屉 (3000/2995) 盖住右栏。
const propDetailOpen = ref(false), propDetailKind = ref(''), propDetailP = ref(null); const propDetailOpen = ref(false), propDetailKind = ref(''), propDetailP = ref(null);
const propTechFull = ref(null), propTechErr = ref(''); const propTechFull = ref(null), propTechErr = ref('');
const PROP_SRC_LABEL = { plan: '选股系统', judge: '决策系统', company: '公司深度', const PROP_SRC_LABEL = { plan: '选股系统', judge: '决策系统', company: '基本面',
tech: '技术面', consensus: '三源合议' }; tech: '技术面', consensus: '择时' };
function openPropDetail(p, kind) { function openPropDetail(p, kind) {
propDetailP.value = p; propDetailKind.value = kind; propDetailOpen.value = true; propDetailP.value = p; propDetailKind.value = kind; propDetailOpen.value = true;
propTechFull.value = null; propTechErr.value = ''; propTechFull.value = null; propTechErr.value = '';
@ -4482,6 +4532,7 @@ createApp({
// 2026-09-12 提议卡重构: 来源结论标签 + 详情弹窗 // 2026-09-12 提议卡重构: 来源结论标签 + 详情弹窗
cr, propPlanTag, propPlanSub, propJudgeTag, propCompanyTag, propTechTag, propTechSub, cr, propPlanTag, propPlanSub, propJudgeTag, propCompanyTag, propTechTag, propTechSub,
propCon, propConsensusTag, propConsensusSub, propCon, propConsensusTag, propConsensusSub,
propFundTag, propFundSub, propTimingTag, propTimingSub, propConclusion, dimCls,
propDetailOpen, propDetailKind, propDetailP, propTechFull, propTechErr, PROP_SRC_LABEL, propDetailOpen, propDetailKind, propDetailP, propTechFull, propTechErr, PROP_SRC_LABEL,
openPropDetail, closePropDetail, openPropDetail, closePropDetail,
pHaltBuy, pResumeBuy, pHaltAll, pResumeAll, pReduce, pIncrease, pLiquidate, pSectorExit, pHaltBuy, pResumeBuy, pHaltAll, pResumeAll, pReduce, pIncrease, pLiquidate, pSectorExit,