候选行等待态不占位、名称省略不换行;持仓表研究面列改为综合分析三枚维度标签

建仓候选栏右侧窄,处置芯片被挤成竖排:候选观察、技术面待开口这类等待态不再显示芯片,
原因进悬停;只有拟建仓、系统已放弃这类有动作意义的才显示。行内元素不换行,名称与主题超长省略。
持仓表与管理视图持仓总览的「研究面」列改名「综合分析」,显示基本面、技术面、择时三枚标签,
与候选栏、提议卡、抽屉同一套维度与颜色;持仓接口每行挂三票 dims。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
zlt 2026-09-15 10:47:50 +08:00
parent 7cf1d5be40
commit 8ff4ea3d8f
2 changed files with 46 additions and 22 deletions

View File

@ -388,6 +388,24 @@ def api_positions():
tech_service.attach(v["positions"])
except Exception as e: # noqa: BLE001
logger.warning("[持仓] 技术面立场挂不上: %s", e)
# 综合分析三票 (2026-09-15): 每行挂 dims = {fund, tech, timing, fund_fact, tech_phase, nightly},
# 供持仓表「综合分析」列三枚维度标签用。与扫描同一套装配 (consensus_service.decorate_positions:
# 基本面从逻辑状态映射的买方评析算, 技术面用早上映射, 择时用昨夜定性); 只是显示, 取不到不塌。
try:
from app.services import consensus_service
_lmap = logic_state_service.state_map() or {}
_crs = {code: (st or {}).get("company_review") for code, st in _lmap.items()}
consensus_service.decorate_positions(v["positions"], company_reviews=_crs)
for r in v["positions"]:
b = r.pop("consensus_blocks", None) or {}
fund, tech, tm = b.get("fund") or {}, b.get("tech") or {}, b.get("timing") or {}
# 技术面优先用 tech_service.attach 挂的那份 (同一映射, 已带相位与理由)
t = r.get("tech") or tech
r["dims"] = {"fund": fund.get("stance"), "fund_fact": fund.get("fact") or fund.get("no_read_why"),
"tech": t.get("stance"), "tech_phase": t.get("phase"),
"timing": tm.get("stance"), "nightly": tm.get("nightly")}
except Exception as e: # noqa: BLE001
logger.warning("[持仓] 综合分析三票挂不上: %s", e)
return {"ok": True, **v, "stock_params": sp}
return ok(_view)

View File

@ -344,8 +344,9 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}
border-bottom:1px dashed rgba(128,128,128,.16);font-size:12.5px;}
.sigrow2:last-child{border-bottom:none;}
.sigrow2 .t{width:36px;flex:none;font-size:11px;color:var(--muted);}
.sigrow2 .nm{font-weight:620;white-space:nowrap;}
.sigrow2 .cat{color:var(--muted);font-size:11.5px;white-space:nowrap;}
.sigrow2 .nm{font-weight:620;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;}
.sigrow2 .cat{color:var(--muted);font-size:11.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:34%;}
.sigrow2 .dim,.sigrow2 .disp{flex:none;white-space:nowrap;}
.sigrow2 .sp{flex:1;}
.sigrow2.old{opacity:.5;}
.sigrow2.clk{cursor:pointer;border-radius:4px;}
@ -449,6 +450,7 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}
.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;}
.dims{display:flex;gap:4px;flex-wrap:wrap;align-items:center;}
.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;}
@ -976,15 +978,13 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}
<div class="muted">{{ pct(s.row.cushion_pct) }}</div>
<div class="muted" v-if="s.row.neg_cushion_days>0">连亏 {{ s.row.neg_cushion_days }} 日</div></template>
</el-table-column>
<el-table-column label="研究面" width="92">
<el-table-column label="综合分析" width="168">
<template #default="s">
<template v-if="s.row.tech">
<b :class="techStanceCls(s.row.tech.stance)" style="cursor:pointer;text-decoration:underline dotted"
@click.stop="openCodeSignals(s.row.ts_code)"
:title="'技术面:' + s.row.tech.stance + '' + (s.row.tech.phase||'') + '\n' + (s.row.tech.reason||'') + '\n点一下看研究面'">{{ s.row.tech.stance }}</b>
<div class="muted" style="font-size:11px" v-if="s.row.tech.phase">{{ s.row.tech.phase }}</div>
</template>
<span class="muted" v-else>无读数</span>
<div class="dims" style="cursor:pointer" title="查看个股详情" @click.stop="openCodeSignals(s.row.ts_code)">
<span class="dim" :class="dimCls(dimsOf(s.row).fund)" :title="dimTip('基本面', dimsOf(s.row).fund, dimsOf(s.row).fund_fact)">基本面</span>
<span class="dim" :class="dimCls(dimsOf(s.row).tech)" :title="dimTip('技术面', dimsOf(s.row).tech, dimsOf(s.row).tech_phase)">技术面</span>
<span class="dim" :class="dimCls(dimsOf(s.row).timing)" :title="dimTip('择时', dimsOf(s.row).timing, dimsOf(s.row).nightly)">择时</span>
</div>
</template>
</el-table-column>
<el-table-column label="当日信号" width="108">
@ -1287,7 +1287,8 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}
<div class="muted" v-if="planStatus.ok===false">上游的选股计划现在读不到。</div>
<div class="muted" v-else-if="!watchCandidates.length">当前没有待建仓的候选,都已处理。</div>
<div v-else v-for="r in watchCandidates" :key="r.ts_code" class="sigrow2 clk"
title="查看个股详情" @click="openCodeSignals(r.ts_code)">
:title="'查看个股详情' + (dispOf(r.ts_code).label ? ' · ' + dispOf(r.ts_code).label : '') + (dispOf(r.ts_code).why ? '' + dispOf(r.ts_code).why : '')"
@click="openCodeSignals(r.ts_code)">
<span class="muted mono t">第{{ r.rank }}</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>
@ -1298,7 +1299,9 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}
<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>
<!-- 等待态 (候选观察/技术面待开口/盘中确认中/择时待转多) 不占行内位置 (2026-09-15): 这一栏本来就是
还在评估的票, 原因进悬停; 只有拟建仓、系统已放弃这类有动作意义的才显示芯片。 -->
<span class="disp" v-if="dispOf(r.ts_code).cls !== 'wait'" :class="dispOf(r.ts_code).cls" style="padding:0 6px">{{ dispOf(r.ts_code).label }}</span>
</div>
<div class="muted" style="margin-top:6px;font-size:11.5px">
受益层级<span class="help" data-tip="这只票离主题多近:直接受益还是间接受益。上游的选股计划给的。">?</span>
@ -1778,16 +1781,14 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}
class="muted">今天没有读数</span>
</template>
</el-table-column>
<!-- 研究面列 (2026-09-14 页面收尾包): 与「持仓」那一列同款, 点技术面立场看研究面抽屉-->
<el-table-column label="研究面" width="92">
<!-- 综合分析列 (2026-09-15): 与「持仓」那一列同款三枚维度标签, 点开看个股详情-->
<el-table-column label="综合分析" width="168">
<template #default="s">
<template v-if="s.row.tech">
<b :class="techStanceCls(s.row.tech.stance)" style="cursor:pointer;text-decoration:underline dotted"
@click.stop="openCodeSignals(s.row.ts_code)"
:title="'技术面:' + s.row.tech.stance + '' + (s.row.tech.phase||'') + '\n' + (s.row.tech.reason||'') + '\n点一下看研究面'">{{ s.row.tech.stance }}</b>
<div class="muted" style="font-size:11px" v-if="s.row.tech.phase">{{ s.row.tech.phase }}</div>
</template>
<span class="muted" v-else>无读数</span>
<div class="dims" style="cursor:pointer" title="查看个股详情" @click.stop="openCodeSignals(s.row.ts_code)">
<span class="dim" :class="dimCls(dimsOf(s.row).fund)" :title="dimTip('基本面', dimsOf(s.row).fund, dimsOf(s.row).fund_fact)">基本面</span>
<span class="dim" :class="dimCls(dimsOf(s.row).tech)" :title="dimTip('技术面', dimsOf(s.row).tech, dimsOf(s.row).tech_phase)">技术面</span>
<span class="dim" :class="dimCls(dimsOf(s.row).timing)" :title="dimTip('择时', dimsOf(s.row).timing, dimsOf(s.row).nightly)">择时</span>
</div>
</template>
</el-table-column>
<el-table-column label="做T" width="80">
@ -3152,6 +3153,11 @@ createApp({
// 为什么按合议路由走)。方向按页面口径红涨绿跌; 无读数用中性灰。点开看三票与路由理由。
// 立场 → 颜色类 (2026-09-15, 三处共用): 看多红, 看空绿, 中性灰, 无读数虚框。
const dimCls = (st) => st === '看多' ? 'up' : (st === '看空' ? 'down' : (st === '中性' ? 'neutral' : 'na'));
// 持仓表「综合分析」列: 后端每行挂的 dims (fund/tech/timing 三票与各自一句依据); 没挂就退回技术面那一块。
const dimsOf = (row) => (row && row.dims) || { tech: (row && row.tech && row.tech.stance) || null,
tech_phase: (row && row.tech && row.tech.phase) || null };
const NIGHT_CN2 = { BUY: '建议买入', WATCH: '观望', SELL: '建议卖出', AVOID: '回避', DROPPED: '已剔除' };
const dimTip = (name, st, extra) => name + ' ' + (st || '无读数') + (extra ? ' · ' + (NIGHT_CN2[extra] || extra) : '');
// 基本面行: 立场取硬数字里的 fund_stance (合议六键), 没有就按质地档推; 副文案给质地与估值。
const propFundTag = (p) => {
const fs = ((p.hard_numbers || {}).fund_stance || {}).stance, c = cr(p);
@ -4532,7 +4538,7 @@ createApp({
// 2026-09-12 提议卡重构: 来源结论标签 + 详情弹窗
cr, propPlanTag, propPlanSub, propJudgeTag, propCompanyTag, propTechTag, propTechSub,
propCon, propConsensusTag, propConsensusSub,
propFundTag, propFundSub, propTimingTag, propTimingSub, propConclusion, dimCls,
propFundTag, propFundSub, propTimingTag, propTimingSub, propConclusion, dimCls, dimsOf, dimTip,
propDetailOpen, propDetailKind, propDetailP, propTechFull, propTechErr, PROP_SRC_LABEL,
openPropDetail, closePropDetail,
pHaltBuy, pResumeBuy, pHaltAll, pResumeAll, pReduce, pIncrease, pLiquidate, pSectorExit,