/** * 生时校正 UI 渲染层 v2.0 * 引擎逻辑在 rectification-engine.js */ import { SIGNS, PLANET_CN, SIGN_LORDS } from './jyotish-engine.js'; import { EVENT_CATEGORIES, VARGA_SENSITIVITY, runRectification, getHouseLord, fmtTime, dateToJD } from './rectification-engine.js'; import { t, getLang, signName, planetName } from './i18n.js'; function fmtOffset(m) { return m === 0 ? t('rect.baseline') : `${m > 0 ? '+' : ''}${m}min`; } let rectEvents = []; export function renderRectificationTab(container) { const lang = getLang(); container.innerHTML = `

${t('rect.title')}

${t('rect.subtitle')}

${t('rect.method.info')}

${t('rect.config')}

${t('rect.sensitivity.info')} ${Object.entries(VARGA_SENSITIVITY).map(([k, v]) => `` ).join('')}
${t('rect.varga.col')}${t('rect.time.window')}
${k} (${lang === 'en' ? v.en : v.cn})~${v.min} ${t('rect.min.unit')}

${t('rect.events')} 0 ${t('rect.event.count')}

${t('rect.events.hint')}

`; bindEvents(container); } function bindEvents(container) { const q = s => container.querySelector(s); q('#rect-add-btn').addEventListener('click', () => { const dEl = q('#rect-event-date'), cEl = q('#rect-event-cat'), descEl = q('#rect-event-desc'); if (!dEl.value) { dEl.focus(); return; } rectEvents.push({ date: dEl.value, category: cEl.value, desc: descEl.value || EVENT_CATEGORIES[cEl.value].cn }); dEl.value = ''; descEl.value = ''; renderEventList(container); }); q('#rect-run-btn').addEventListener('click', function () { handleRun(container, this); }); } function renderEventList(container) { const listEl = container.querySelector('#rect-event-list'); const countEl = container.querySelector('#rect-event-count'); const lang = getLang(); countEl.textContent = `${rectEvents.length} ${t('rect.event.count')}`; if (!rectEvents.length) { listEl.innerHTML = `

${t('rect.no.events')}

`; return; } listEl.innerHTML = rectEvents.map((evt, i) => { const cat = EVENT_CATEGORIES[evt.category]; return `
${cat.icon} ${evt.date} ${lang === 'en' ? cat.en : cat.cn} (${cat.varga}) ${evt.desc}
`; }).join(''); listEl.querySelectorAll('.rect-evt-del').forEach(b => { b.addEventListener('click', () => { rectEvents.splice(+b.dataset.idx, 1); renderEventList(container); }); }); } async function handleRun(container, runBtn) { if (!rectEvents.length) { alert(t('rect.alert.no.events')); return; } if (!window.__jyotishBirth) { alert(t('rect.alert.no.chart')); return; } const q = s => container.querySelector(s); const progressEl = q('#rect-progress'), fillEl = q('#rect-progress-fill'); const textEl = q('#rect-progress-text'), resultsEl = q('#rect-results'); const rangeMin = +q('#rect-range').value, stepMin = +q('#rect-step').value; progressEl.classList.remove('hidden'); resultsEl.classList.add('hidden'); runBtn.querySelector('.btn-text').classList.add('hidden'); runBtn.querySelector('.btn-loading').classList.remove('hidden'); runBtn.disabled = true; try { const result = await runRectification(window.__jyotishBirth, rectEvents, { rangeMin, stepMin, onProgress(cur, total) { const pct = Math.round(cur / total * 100); fillEl.style.width = pct + '%'; textEl.textContent = `${t('rect.calculating')} ${cur}/${total} (${pct}%)`; }, }); if (result.error) { textEl.textContent = result.error; return; } renderResults(container, result); } catch (e) { textEl.textContent = `Error: ${e.message}`; console.error('[Rect]', e); } finally { runBtn.querySelector('.btn-text').classList.remove('hidden'); runBtn.querySelector('.btn-loading').classList.add('hidden'); runBtn.disabled = false; setTimeout(() => progressEl.classList.add('hidden'), 2000); } } function renderResults(container, result) { const el = container.querySelector('#rect-results'); el.classList.remove('hidden'); const lang = getLang(); const { bestMatch: bm, confidence: conf, baseChartInfo: base, results: all } = result; const confClr = { '高': '#22c55e', '中': '#f59e0b', '低': '#ef4444', '不确定': '#9ca3af' }; // 分盘变化 const vcHtml = bm.vargaChanges.length > 0 ? bm.vargaChanges.map(c => `${c.varga}${signName(c.from)}${signName(c.to)}`).join('') : `${t('rect.no.change')}`; // 评分条 const weights = { dasha:'40%', varga:'35%', house:'15%', nak:'10%' }; const scoreBars = Object.keys(weights).map(k => { const s = bm.scores[k]; return `
${t('rect.scoring.'+k)} (${weights[k]})
${s.pct}%
`; }).join(''); // 事件详情 const evtRows = bm.eventScores.map(es => { const cat = EVENT_CATEGORIES[es.event.category]; const rel = getRelevanceText(es.dasha, es.event.category, bm.ascSign); return ` ${cat.icon} ${lang==='en'?cat.en:cat.cn} (${cat.varga}) ${es.event.date} ${es.dasha ? planetName(es.dasha.mahadasha) : '—'} ${es.dasha?.antardasha ? planetName(es.dasha.antardasha) : '—'} ${es.dashaScore.toFixed(1)} ${es.vargaScore.toFixed(1)} ${rel}`; }).join(''); el.innerHTML = `

${t('rect.result')}

${t('rect.original.time')}${base.time}
${t('rect.rec.time')}${bm.time} (${fmtOffset(bm.offsetMin)})
${t('rect.confidence')}${conf.level} (${conf.bestPct}%)
${conf.recommendation.map(r => `

${r}

`).join('')}

${t('rect.scoring.title')}

${scoreBars}
${t('rect.total.score')} ${bm.totalScore}%

${t('rect.varga.changes')}

${vcHtml}
${t('rect.varga.col')}${t('rect.from.col')}${t('rect.to.col')}

${t('rect.top.candidates')}

${all.slice(0,10).map((r,i) => { const d9=r.vargaLagnas?.D9?.sign||r.ascSign, d10=r.vargaLagnas?.D10?.sign||r.ascSign; const d9chg=r.vargaChanges?.some(v=>v.varga==='D9')?' ⚠️':''; const d10chg=r.vargaChanges?.some(v=>v.varga==='D10')?' ⚠️':''; return ``; }).join('')}
#${t('rect.time')}${t('rect.offset')}${t('rect.asc')}D9D10${t('rect.score')}${t('rect.match')}
${i+1}${r.time}${fmtOffset(r.offsetMin)} ${signName(r.ascSign)}${signName(d9)}${d9chg}${signName(d10)}${d10chg} ${r.totalScore}%
${r.totalScore}%

${t('rect.event.detail')}

${evtRows}
${t('rect.event.col')}${t('rect.date.col')}${t('rect.maha.col')}${t('rect.antar.col')}${t('rect.score.dasha')}${t('rect.score.varga')}${t('rect.rel.col')}
`; el.querySelectorAll('.rect-top-results tr[data-offset]').forEach(tr => { tr.style.cursor = 'pointer'; tr.addEventListener('click', () => { const off = parseFloat(tr.dataset.offset); const r = all.find(x => x.offsetMin === off); if (r) showOffsetDetail(el, r, base); }); }); } function getRelevanceText(di, category, ascSign) { if (!di) return '—'; const cat = EVENT_CATEGORIES[category]; if (!cat) return '—'; const ai = SIGNS.indexOf(ascSign), hl = cat.houses.map(h => getHouseLord(ai, h)); const mdR = cat.planets.includes(di.mahadasha) || hl.includes(di.mahadasha); const adR = di.antardasha && (cat.planets.includes(di.antardasha) || hl.includes(di.antardasha)); if (mdR && adR) return t('rect.strong.match'); if (mdR) return t('rect.maha.match'); if (adR) return t('rect.antar.match'); return t('rect.no.match'); } function showOffsetDetail(container, r, base) { let det = container.querySelector('.rect-offset-detail'); if (!det) { det = document.createElement('div'); det.className = 'rect-offset-detail card'; container.appendChild(det); } const vlHtml = Object.entries(r.vargaLagnas || {}).map(([k, v]) => `${k}: ${v ? signName(v.sign) : '—'}` ).join(''); const hcHtml = r.houseChanges.length > 0 ? r.houseChanges.map(c => `${planetName(c.planet)}: H${c.from}→H${c.to}`).join('') : t('rect.no.change'); det.innerHTML = `

${t('rect.offset.detail').replace('{0}',fmtOffset(r.offsetMin)).replace('{1}',r.time)}

${t('rect.asc.label')}${signName(r.ascSign)} ${r.ascDeg?.toFixed(2)}°
${t('rect.moon.nak')}${r.moonNak?.nakName||'—'} Pada ${r.moonNak?.pada||'?'}
${t('rect.total.score')}${r.totalScore}%
${t('rect.varga.changes')}
${vlHtml}
${t('rect.house.changes')}
${hcHtml}
`; det.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } export function initRectification() { rectEvents = []; }