/* rmc.ts — 原料克重自动计算小程序(可独立使用,亦可嵌入表单 iframe) */ "use strict"; /* ---------- 摩尔质量(常见陶瓷组分元素,g/mol) ---------- */ const AM = { H: 1.008, Li: 6.94, B: 10.81, C: 12.011, N: 14.007, O: 15.999, F: 18.998, Na: 22.99, Mg: 24.305, Al: 26.982, Si: 28.085, P: 30.974, S: 32.06, Cl: 35.45, K: 39.098, Ca: 40.078, Ti: 47.867, V: 50.942, Cr: 51.996, Mn: 54.938, Fe: 55.845, Co: 58.933, Ni: 58.693, Cu: 63.546, Zn: 65.38, Ga: 69.723, Y: 88.906, Zr: 91.224, Nb: 92.906, Mo: 95.95, Sn: 118.71, Sb: 121.76, Ba: 137.327, La: 138.905, Ce: 140.116, Nd: 144.242, Sm: 150.36, Gd: 157.25, Dy: 162.5, Hf: 178.49, Ta: 180.95, W: 183.84, Pb: 207.2, Bi: 208.98, Sr: 87.62, }; /** 化学式解析:支持嵌套括号、首系数、· 分隔(如 CuSO4·5H2O) */ function parseFormula(input) { const s = String(input || "").replace(/\s+/g, "").replace(/·|•|\*/g, "."); if (!s) return null; const total = {}; for (const part of s.split(".")) { const r = parsePart(part, 1); if (r == null) return null; for (const [el, n] of Object.entries(r)) total[el] = (total[el] || 0) + n; } return Object.keys(total).length ? total : null; } function parsePart(input, mult) { let s = String(input || ""); const cm = /^(\d+(?:\.\d+)?)(.*)$/.exec(s); if (cm) { mult *= parseFloat(cm[1]); s = cm[2]; } const out = {}; let i = 0; while (i < s.length) { const em = /^([A-Z][a-z]?)(\d*(?:\.\d+)?)(.*)$/.exec(s.slice(i)); if (em && AM[em[1]]) { out[em[1]] = (out[em[1]] || 0) + mult * (em[2] === "" ? 1 : parseFloat(em[2])); i += em[1].length + em[2].length; continue; } if (s[i] === "(" || s[i] === "[") { const close = s[i] === "(" ? ")" : "]"; let depth = 0, j = i; for (; j < s.length; j++) { if (s[j] === s[i]) depth++; else if (s[j] === close && --depth === 0) break; } if (j >= s.length) return null; const inner = parsePart(s.slice(i + 1, j), 1); if (inner == null) return null; const nm = /^(\d+(?:\.\d+)?)(.*)$/.exec(s.slice(j + 1)); const k = nm ? parseFloat(nm[1]) : 1; for (const [el, n] of Object.entries(inner)) out[el] = (out[el] || 0) + mult * k * n; i = j + 1 + (nm ? nm[1].length : 0); continue; } return null; // 无法识别的字符 } return Object.keys(out).length ? out : null; } /** 化学式摩尔质量 */ function molarMass(formula) { const p = parseFormula(formula); if (!p) return null; let m = 0; for (const [el, n] of Object.entries(p)) m += (AM[el] || 0) * n; return m > 0 ? m : null; } const DEFAULT_FORMULAS = ["BaCO3", "TiO2", "SrCO3", "ZrO2", "Nb2O5", "MgO", "Al2O3", "SiO2"]; const embedded = window.parent !== window; let cfg = null; const siloState = Array.from({ length: 8 }, () => ({ name: "", formula: "", coeff: "", grams: "" })); function $(sel) { return document.querySelector(sel); } function esc(s) { return String(s).replace(/&/g, "&").replace(/"/g, """).replace(/ ``).join(""); $("#mode").innerHTML = embedded ? "(已嵌入:料仓与目标化学式自动同步表单)" : "(独立模式:化学式与重量请手工填写)"; const g = Number(gsel.value) || 1; $("#target").textContent = cfg?.targets?.[g - 1]?.[0] ?? ""; let h = "料仓原料名称化学式摩尔质量(g/mol)摩尔系数下料克重(g)"; siloState.forEach((s, i) => { const mm = molarMass(s.formula); h += `${i + 1}` + `${esc(s.name || "—")}` + `${embedded ? esc(s.formula || "—") : ``}` + `${mm ? mm.toFixed(3) : s.formula ? "解析失败" : "—"}` + `` + `${esc(s.grams)}`; }); $("#silos").innerHTML = h; if (!embedded) $("#backfill").style.display = "none"; } /** 计算:克重_i = 系数_i×M_i / Σ(系数_j×M_j) × 样品重量 */ function compute() { const mass = parseFloat($("#mass").value); if (!(mass > 0)) { $("#msg").innerHTML = `样品重量无效`; return null; } const rows = []; for (let i = 0; i < 8; i++) { const c = parseFloat(siloState[i].coeff); if (!(c > 0)) { siloState[i].grams = ""; continue; } const m = molarMass(siloState[i].formula); if (m == null) { $("#msg").innerHTML = `料仓${i + 1} 化学式无法解析:${esc(siloState[i].formula)}`; return null; } rows.push([i, c * m]); } if (!rows.length) { $("#msg").innerHTML = `请至少为一个料仓填写摩尔系数`; return null; } const tot = rows.reduce((p, [, w]) => p + w, 0); const grams = Array.from({ length: 8 }, () => ""); for (const [i, w] of rows) { grams[i] = (w / tot * mass).toFixed(5); siloState[i].grams = grams[i]; } render(); $("#msg").innerHTML = `合计 ${tot.toFixed(3)} g/mol → 各仓 ${mass.toFixed(2)} g 分配完成`; return grams; } function bind() { $("#calc")?.addEventListener("click", () => { compute(); }); $("#backfill")?.addEventListener("click", () => { const grams = compute(); if (grams && embedded) window.parent.postMessage({ type: "rmc-result", group: Number($("#group").value), grams }, window.location.origin); }); $("#group")?.addEventListener("change", () => { render(); }); $("#silos")?.addEventListener("input", (e) => { const t = e.target; if (t.name === "f") siloState[Number(t.dataset.i)].formula = t.value; if (t.dataset.c != null) siloState[Number(t.dataset.c)].coeff = t.value; render(); }); } /* ---------- 嵌入通信 ---------- */ window.addEventListener("message", (ev) => { if (ev.origin !== window.location.origin) return; const m = ev.data; if (m?.type === "rmc-config") { cfg = m; m.silos?.forEach((s, i) => { siloState[i].name = s.name || ""; if (s.formula) siloState[i].formula = s.formula; }); if (m.sampleMass) $("#mass").value = m.sampleMass; render(); } }); if (embedded) window.parent.postMessage({ type: "rmc-ready" }, window.location.origin); bind(); render();