时间线 · {{ csTimeline.length }} 条
@@ -2326,6 +2362,26 @@ createApp({
const planStatus = computed(() => planRaw.value.status || {});
const planRows = computed(() => planRaw.value.rows || []);
const planCand = computed(() => planRaw.value.candidates_raw || null);
+ // 技术面模块状态 (2026-09-11): 顶栏新鲜度芯片。只读 /api/tech/status, 交易员也看得到。
+ const techStatus = ref({});
+ const techChipText = computed(() => {
+ const t = techStatus.value || {};
+ if (t.enabled === false) return '关';
+ const dd = t.data_date;
+ if (!dd) return '无';
+ const s = String(dd);
+ return s.length === 8 ? s.slice(4, 6) + '-' + s.slice(6, 8) : s;
+ });
+ const techChipCls = computed(() => {
+ const t = techStatus.value || {};
+ if (t.enabled === false) return 'idle';
+ if (t.error || !t.data_date) return 'warn';
+ return (t.map || {}).fresh ? 'ok' : 'warn';
+ });
+ const loadTech = async () => {
+ const d = await call('get', '/api/tech/status');
+ techStatus.value = (d && d.ok) ? (d.data || {}) : { error: (d || {}).error || '读不到' };
+ };
// 榜单变化 (PMS 自算 —— 上游 changes 字段恒为 null)。另起一个接口, 不塞进 /api/upstream/plan:
// 它要读库, 而计划预览要读上游, 两者的故障域不同, 混在一起会互相拖垮。
const chgRaw = ref({}), chgLoading = ref(false), chgTab = ref('held');
@@ -3286,6 +3342,17 @@ createApp({
// 补全失败就保留快照并挂一条黄条, 不清空 —— 有一半总比没有强。
const csOpen = ref(false), csCode = ref(''), csLoading = ref(false),
csErr = ref(''), csData = ref(null), csFromSnap = ref(false);
+ // 研究面·技术面 (2026-09-11): 单票抽屉顶部一节, 点票时顺带调 /api/research 拉回。
+ const techFeed = ref(null), techFeedErr = ref('');
+ const techStanceCls = (s) => s === '看多' ? 'clr-up' : (s === '看空' ? 'clr-down' : 'muted');
+ async function csLoadResearch() {
+ const code = csCode.value;
+ techFeed.value = null; techFeedErr.value = '';
+ const d = await call('get', '/api/research/' + encodeURIComponent(code));
+ if (csCode.value !== code) return; // 期间换了票, 丢弃这份回包
+ if (d && d.ok === false) { techFeedErr.value = d.error || '读不到研究面'; return; }
+ techFeed.value = (d && d.data) ? d.data : d;
+ }
function _csSnapRows(code) {
const k = code6(code), out = [];
const push = (src, label, r, extra) => out.push(Object.assign(
@@ -3319,6 +3386,7 @@ createApp({
csOpen.value = true;
csLoadFull();
csLoadLedger();
+ csLoadResearch();
}
function closeCodeSignals() { csOpen.value = false; }
async function csLoadFull() {
@@ -3500,7 +3568,7 @@ createApp({
// (通道告警对交易员改由 dispatch-mode 里的 channel 兜底, 见 wsc 的注释)
(isAdmin.value ? loadWs() : Promise.resolve()),
loadStrategies(), loadOpLog(), loadOpenScan(), loadUpstream(),
- loadMacro()]);
+ loadMacro(), loadTech()]);
await loadNames();
lastRefresh.value = new Date().toTimeString().slice(0, 8);
loading.value = false;
@@ -4003,6 +4071,7 @@ createApp({
cancelCmd, replan, decide, ops, loadSchema, openReport, cancelIns,
planDrawer, planLoading, planStatus, planRows, planCand, planResult,
loadPlan, openPlan, refreshPlan,
+ techStatus, techChipText, techChipCls,
chgRaw, chgSt, chgLoading, chgTab, chg, chgHeld, chgLog, chgRevised, chgWatch,
loadChanges,
mode, tx, nm, fullName, nameMap, attention, heldPositions, traderParams,
@@ -4031,6 +4100,7 @@ createApp({
csOpen, csCode, csLoading, csErr, csData, csFromSnap, csTimeline, csSrcFails,
csTruncated, csSrcLabel, csLedger, csLedgerErr, csHeld, csInPlan,
openCodeSignals, closeCodeSignals, csLoadFull,
+ techFeed, techFeedErr, techStanceCls,
macro, macroGateOn, macroUnavailable, mzLabel, mzColor, mzBarStyle,
loadMacro, macroScan, macroAdopt,
mzSig, mzAdvicePending, mzChipCls, mzChipText, macroChipClick,