Files
Jyotisha/jyotish-app/export.js
T
732642856 2457e23271 feat: Jyotish Web App v4.3 完整印度占星前端应用
技术栈: Vite + 原生HTML/JS/CSS + swisseph-wasm
功能模块:
- 排盘引擎: 行星/宫位/Dasha/Yoga/相位/燃烧检测/130+城市
- 高级计算: Karaka/Arudha/16分盘/Ashtakavarga/Shadbala/Panchanga/Chara Dasha
- 73个Yoga识别 (Pancha Mahapurusha + 扩展A/B)
- 生时校正v2.0: K.N.Rao方法论, 多维评分
- Transit v2.0: 日期选择/全行星/AV评分/Sade Sati
- 深度分析7子模块 + 扩展分析5子模块
- 导出v2.0: JSON 100%覆盖Python 13模块 + SVG/PNG
- i18n中英双语 + 点读机95词条 + AI聊天面板
- 认证+订阅 (JWT/Apple Sign-In/IAP)
- 浅色极简主题 (fatetell.com风格)
2026-04-30 03:51:30 +08:00

248 lines
8.3 KiB
JavaScript

/**
* Jyotish Export System v2.0 — 100% 完整星盘数据导出
* 覆盖 Python engine full-reading 全部13模块:
* dasha, yoga, varga_full, aspects, jaimini, nakshatra_advanced,
* argala, tajika, shadbala, ashtakavarga, validation, audit, actionable_context
*
* 导出格式: JSON / SVG / PNG
*/
import { SIGNS, SIGN_LORDS, PLANET_CN } from './jyotish-engine.js';
// ============================================================================
// JSON 导出 — 完整星盘数据 (100% coverage)
// ============================================================================
export function exportJSON(chartData, extras = {}) {
const { planets, ascendant, birth_info } = chartData;
const payload = {
meta: {
app: 'Jyotish Web App',
version: '4.4',
export_date: new Date().toISOString(),
ayanamsa: 'Lahiri (Chitrapaksha)',
},
birth_info,
ascendant: _cleanAscendant(ascendant),
};
// ── 1. Planets (完整字段) ──
payload.planets = {};
for (const [name, p] of Object.entries(planets)) {
if (p.error) continue;
payload.planets[name] = {
sign: p.sign, sign_cn: p.sign_cn,
degree: p.degree, degree_in_sign: p.degree_in_sign,
house: p.house, status: p.status || '',
retrograde: p.retrograde || false,
speed: p.speed || 0,
combust: p.combust || false,
combust_degree: p.combust_degree || null,
nakshatra: p.nakshatra, nakshatra_pada: p.nakshatra_pada,
nakshatra_lord: p.nakshatra_lord,
};
}
// ── 2. Dasha ──
if (extras.dasha) payload.modules = payload.modules || {};
if (extras.dasha) payload.modules.dasha = extras.dasha;
// ── 3. Yoga ──
if (extras.yogas) {
payload.modules = payload.modules || {};
const ascSign = ascendant?.sign || '';
const kendraLords = _getKendraLords(ascSign);
const trikonaLords = _getTrikonaLords(ascSign);
payload.modules.yoga = {
ascendant: ascSign,
planets_analyzed: Object.keys(payload.planets).length,
kendra_lords: kendraLords,
trikona_lords: trikonaLords,
yogas_detected: extras.yogas.length,
yogas: extras.yogas.map(y => ({
name: y.name || '',
name_cn: y.name_cn || '',
combination: y.combination || '',
effects: y.effects || [],
strength: y.strength || '',
})),
};
}
// ── 4. Varga Full ──
if (extras.vargas) {
payload.modules = payload.modules || {};
payload.modules.varga_full = extras.vargas;
}
// ── 5. Aspects ──
if (extras.aspects) {
payload.modules = payload.modules || {};
payload.modules.aspects = extras.aspects;
}
// ── 6. Jaimini ──
if (extras.jaimini) {
payload.modules = payload.modules || {};
payload.modules.jaimini = extras.jaimini;
}
// ── 7. Nakshatra Advanced ──
if (extras.nakshatraAdvanced) {
payload.modules = payload.modules || {};
payload.modules.nakshatra_advanced = extras.nakshatraAdvanced;
}
// ── 8. Argala ──
if (extras.argala) {
payload.modules = payload.modules || {};
payload.modules.argala = extras.argala;
}
// ── 9. Tajika ──
if (extras.tajika) {
payload.modules = payload.modules || {};
payload.modules.tajika = extras.tajika;
}
// ── 10. Shadbala ──
if (extras.shadbala) {
payload.modules = payload.modules || {};
payload.modules.shadbala = extras.shadbala;
}
// ── 11. Ashtakavarga ──
if (extras.ashtakavarga) {
payload.modules = payload.modules || {};
payload.modules.ashtakavarga = extras.ashtakavarga;
}
// ── 12. Panchanga ──
if (extras.panchanga) {
payload.modules = payload.modules || {};
payload.modules.panchanga = extras.panchanga;
}
// ── 13. Validation ──
if (extras.validation) {
payload.modules = payload.modules || {};
payload.modules.validation = extras.validation;
}
// ── 14. Audit ──
if (extras.audit) {
payload.modules = payload.modules || {};
payload.modules.audit = extras.audit;
}
// ── 15. Actionable Context ──
if (extras.actionableContext) {
payload.modules = payload.modules || {};
payload.modules.actionable_context = extras.actionableContext;
}
downloadFile(
JSON.stringify(payload, null, 2),
`jyotish-chart-${birth_info?.date || 'export'}.json`,
'application/json'
);
}
// ============================================================================
// SVG 导出
// ============================================================================
export function exportSVG(chartElement, filename = 'jyotish-chart') {
if (!chartElement) return;
const svg = chartElement.querySelector('svg');
if (!svg) return;
const clone = svg.cloneNode(true);
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
clone.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
const styleEl = document.createElementNS('http://www.w3.org/2000/svg', 'style');
styleEl.textContent = `text{font-family:'Noto Sans SC','Segoe UI',sans-serif;}.chart-planet-text{font-size:10px;fill:#333;}.chart-sign-text{font-size:9px;fill:#666;}.chart-asc-marker{font-size:11px;fill:#6d28d9;font-weight:700;}rect{stroke:#ccc;stroke-width:0.5;}`;
clone.insertBefore(styleEl, clone.firstChild);
downloadFile(new XMLSerializer().serializeToString(clone), `${filename}.svg`, 'image/svg+xml');
}
// ============================================================================
// PNG 导出
// ============================================================================
export async function exportPNG(chartElement, filename = 'jyotish-chart', scale = 2) {
if (!chartElement) return;
const svg = chartElement.querySelector('svg');
if (!svg) return;
const clone = svg.cloneNode(true);
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
const w = parseFloat(svg.getAttribute('width')) || 400;
const h = parseFloat(svg.getAttribute('height')) || 400;
clone.setAttribute('width', w);
clone.setAttribute('height', h);
const styleEl = document.createElementNS('http://www.w3.org/2000/svg', 'style');
styleEl.textContent = `text{font-family:'Noto Sans SC','Segoe UI',sans-serif;fill:#333;}.chart-planet-text{font-size:10px;}.chart-sign-text{font-size:9px;fill:#666;}.chart-asc-marker{font-size:11px;fill:#6d28d9;font-weight:700;}rect{stroke:#ccc;stroke-width:0.5;}`;
clone.insertBefore(styleEl, clone.firstChild);
const svgStr = new XMLSerializer().serializeToString(clone);
const img = new Image();
const canvas = document.createElement('canvas');
canvas.width = w * scale; canvas.height = h * scale;
const ctx = canvas.getContext('2d');
ctx.scale(scale, scale);
return new Promise((resolve) => {
img.onload = () => {
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, w, h);
ctx.drawImage(img, 0, 0, w, h);
canvas.toBlob(blob => {
if (blob) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = `${filename}.png`; a.click();
URL.revokeObjectURL(url);
}
resolve();
}, 'image/png');
};
img.onerror = () => resolve();
img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgStr)));
});
}
// ============================================================================
// 工具函数
// ============================================================================
function downloadFile(content, filename, mimeType) {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = filename; a.click();
URL.revokeObjectURL(url);
}
function _cleanAscendant(asc) {
if (!asc) return {};
return {
sign: asc.sign, sign_cn: asc.sign_cn,
degree: asc.degree, degree_in_sign: asc.degree_in_sign,
lord: asc.lord || SIGN_LORDS[asc.sign],
};
}
function _getKendraLords(ascSign) {
const ai = SIGNS.indexOf(ascSign);
if (ai < 0) return [];
const kendra = [1, 4, 7, 10];
return [...new Set(kendra.map(h => SIGN_LORDS[SIGNS[(ai + h - 1) % 12]]))];
}
function _getTrikonaLords(ascSign) {
const ai = SIGNS.indexOf(ascSign);
if (ai < 0) return [];
const trikona = [5, 9];
return [...new Set(trikona.map(h => SIGN_LORDS[SIGNS[(ai + h - 1) % 12]]))];
}