feat: persist heaven readings in unified dialog
This commit is contained in:
+200
-18
@@ -67,6 +67,12 @@ const state = {
|
||||
personalField: null,
|
||||
heavenPanel: "trend",
|
||||
heavenInterpretations: { trend: "", fortune: "", heart: "" },
|
||||
heavenReadingMode: "trend",
|
||||
heavenReadingTab: "current",
|
||||
heavenReadingHistory: { trend: [], fortune: [], heart: [] },
|
||||
heavenReadingSelectedId: 0,
|
||||
heavenReadingLoading: false,
|
||||
heavenReadingError: "",
|
||||
heartStage: "intro",
|
||||
heartTimer: null,
|
||||
heartSeconds: 30,
|
||||
@@ -98,6 +104,7 @@ const elements = {
|
||||
tradeLogDialog: document.querySelector("#tradeLogDialog"),
|
||||
alertsDialog: document.querySelector("#alertsDialog"),
|
||||
assistantDialog: document.querySelector("#assistantDialog"),
|
||||
heavenReadingDialog: document.querySelector("#heavenReadingDialog"),
|
||||
globalSearchDialog: document.querySelector("#globalSearchDialog"),
|
||||
globalSearchInput: document.querySelector("#globalSearchInput"),
|
||||
globalSearchResults: document.querySelector("#globalSearchResults"),
|
||||
@@ -554,6 +561,8 @@ function bindEvents() {
|
||||
});
|
||||
document.querySelector("#interpretTrendButton").addEventListener("click", () => interpretHeaven("trend"));
|
||||
document.querySelector("#interpretFortuneButton").addEventListener("click", () => interpretHeaven("fortune"));
|
||||
document.querySelector("#historyTrendButton").addEventListener("click", () => openHeavenHistory("trend"));
|
||||
document.querySelector("#historyFortuneButton").addEventListener("click", () => openHeavenHistory("fortune"));
|
||||
document.querySelector("#qiObservationDate").addEventListener("change", () => {
|
||||
state.personalField = null;
|
||||
state.heavenManualData = null;
|
||||
@@ -572,10 +581,18 @@ function bindEvents() {
|
||||
document.querySelector("#startBreathingButton").addEventListener("click", startHeartBreathing);
|
||||
document.querySelector("#beginCastingButton").addEventListener("click", beginHeartCasting);
|
||||
document.querySelector("#heartSoundToggle").addEventListener("click", toggleHeartSound);
|
||||
document.querySelector("#historyHeartButton").addEventListener("click", () => openHeavenHistory("heart"));
|
||||
initializeHeartCoinHold();
|
||||
initializeHeartLineInspection();
|
||||
document.querySelector("#interpretHeartButton").addEventListener("click", () => interpretHeaven("heart"));
|
||||
document.querySelector("#viewHeartReadingButton").addEventListener("click", () => openHeavenReading("heart"));
|
||||
document.querySelector("#restartHeartButton").addEventListener("click", resetHeartRitual);
|
||||
document.querySelector("#closeHeavenReadingDialog").addEventListener("click", () => elements.heavenReadingDialog.close());
|
||||
document.querySelectorAll("[data-heaven-reading-tab]").forEach((button) => {
|
||||
button.addEventListener("click", () => selectHeavenReadingTab(button.dataset.heavenReadingTab));
|
||||
});
|
||||
document.querySelector("#heavenReadingHistoryList").addEventListener("click", handleHeavenHistorySelection);
|
||||
document.querySelector("#heavenReadingHistoryDetail").addEventListener("click", handleHeavenHistoryAction);
|
||||
document.querySelectorAll("[data-heart-return]").forEach((button) => {
|
||||
button.addEventListener("click", resetHeartRitual);
|
||||
});
|
||||
@@ -2225,6 +2242,7 @@ async function loadHeavenSetup(force = false, sector = "", stockCode = "") {
|
||||
payload.requestedDate = requestedDate;
|
||||
payload.requestedKey = requestedKey;
|
||||
state.heavenSetup = payload;
|
||||
state.heavenInterpretations.fortune = payload.daily_fortune_reading || "";
|
||||
state.heavenManualData = Object.keys(payload.chart?.manual_data || {}).length
|
||||
? payload.chart.manual_data
|
||||
: null;
|
||||
@@ -3095,12 +3113,159 @@ function hexagramLineGraphic(value) {
|
||||
`;
|
||||
}
|
||||
|
||||
const HEAVEN_READING_META = {
|
||||
trend: { panel: "观势", action: "解势", done: "查看解势", status: "势已成", loading: "正在合参卦势" },
|
||||
fortune: { panel: "观气", action: "解运", done: "已解运", status: "气已定", loading: "正在推演气机" },
|
||||
heart: { panel: "观心", action: "我已察念,开始解卦", done: "查看解卦", status: "卦已解", loading: "正在整理卦辞" },
|
||||
};
|
||||
|
||||
function heavenReadingMeta(mode = state.heavenReadingMode) {
|
||||
return HEAVEN_READING_META[mode] || HEAVEN_READING_META.trend;
|
||||
}
|
||||
|
||||
function openHeavenReading(mode, options = {}) {
|
||||
state.heavenReadingMode = mode;
|
||||
state.heavenReadingTab = "current";
|
||||
state.heavenReadingError = options.error || "";
|
||||
state.heavenReadingLoading = Object.hasOwn(options, "loading")
|
||||
? Boolean(options.loading)
|
||||
: false;
|
||||
renderHeavenReadingDialog();
|
||||
if (!elements.heavenReadingDialog.open) elements.heavenReadingDialog.showModal();
|
||||
requestAnimationFrame(() => document.querySelector("#closeHeavenReadingDialog").focus());
|
||||
}
|
||||
|
||||
async function openHeavenHistory(mode) {
|
||||
state.heavenReadingMode = mode;
|
||||
state.heavenReadingTab = "history";
|
||||
state.heavenReadingSelectedId = 0;
|
||||
renderHeavenReadingDialog();
|
||||
if (!elements.heavenReadingDialog.open) elements.heavenReadingDialog.showModal();
|
||||
await loadHeavenReadingHistory(mode);
|
||||
}
|
||||
|
||||
function selectHeavenReadingTab(tab) {
|
||||
state.heavenReadingTab = tab === "history" ? "history" : "current";
|
||||
renderHeavenReadingDialog();
|
||||
if (state.heavenReadingTab === "history") loadHeavenReadingHistory(state.heavenReadingMode);
|
||||
}
|
||||
|
||||
async function loadHeavenReadingHistory(mode) {
|
||||
const list = document.querySelector("#heavenReadingHistoryList");
|
||||
list.innerHTML = '<div class="empty-state">正在读取历史记录</div>';
|
||||
try {
|
||||
const query = new URLSearchParams({ mode, limit: "100" });
|
||||
const payload = await apiRequest(`/api/heaven/readings?${query}`);
|
||||
state.heavenReadingHistory[mode] = payload.items || [];
|
||||
if (!state.heavenReadingHistory[mode].some((item) => number(item.id) === state.heavenReadingSelectedId)) {
|
||||
state.heavenReadingSelectedId = number(state.heavenReadingHistory[mode][0]?.id);
|
||||
}
|
||||
renderHeavenReadingHistory();
|
||||
} catch (error) {
|
||||
list.innerHTML = `<div class="empty-state">${escapeHtml(error.message || "历史记录加载失败")}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderHeavenReadingDialog() {
|
||||
const meta = heavenReadingMeta();
|
||||
setText("heavenReadingEyebrow", `问天 · ${meta.panel}`);
|
||||
setText("heavenReadingDialogTitle", state.heavenReadingTab === "history" ? "历史记录" : meta.status);
|
||||
document.querySelectorAll("[data-heaven-reading-tab]").forEach((button) => {
|
||||
const active = button.dataset.heavenReadingTab === state.heavenReadingTab;
|
||||
button.classList.toggle("active", active);
|
||||
button.setAttribute("aria-selected", String(active));
|
||||
});
|
||||
document.querySelector("#heavenReadingCurrent").hidden = state.heavenReadingTab !== "current";
|
||||
document.querySelector("#heavenReadingHistory").hidden = state.heavenReadingTab !== "history";
|
||||
if (state.heavenReadingTab === "current") renderHeavenReadingCurrent();
|
||||
else renderHeavenReadingHistory();
|
||||
refreshIcons();
|
||||
}
|
||||
|
||||
function renderHeavenReadingCurrent() {
|
||||
const reading = state.heavenInterpretations[state.heavenReadingMode];
|
||||
const loading = document.querySelector("#heavenReadingLoading");
|
||||
const empty = document.querySelector("#heavenReadingEmpty");
|
||||
const result = document.querySelector("#heavenReadingResult");
|
||||
const error = document.querySelector("#heavenReadingError");
|
||||
loading.hidden = !state.heavenReadingLoading;
|
||||
setText("heavenReadingLoadingTitle", heavenReadingMeta().loading);
|
||||
error.hidden = !state.heavenReadingError;
|
||||
error.textContent = state.heavenReadingError;
|
||||
result.hidden = state.heavenReadingLoading || !reading;
|
||||
empty.hidden = state.heavenReadingLoading || Boolean(reading) || Boolean(state.heavenReadingError);
|
||||
if (!reading || state.heavenReadingLoading) return;
|
||||
setText("heavenReadingResultStatus", heavenReadingMeta().status);
|
||||
setText("heavenReadingSubject", reading.subject || `${heavenReadingMeta().panel}解读`);
|
||||
setText("heavenReadingSubjectDetail", reading.subject_detail || displayCompactDate(reading.context_date || ""));
|
||||
setText("heavenReadingCreatedAt", reading.created_at ? formatTimestamp(reading.created_at) : "刚刚完成");
|
||||
document.querySelector("#heavenReadingAnswer").innerHTML = formatMentorAnswer(reading.answer || "");
|
||||
}
|
||||
|
||||
function renderHeavenReadingHistory() {
|
||||
const mode = state.heavenReadingMode;
|
||||
const items = state.heavenReadingHistory[mode] || [];
|
||||
setText("heavenReadingHistoryTitle", `${heavenReadingMeta(mode).panel}记录`);
|
||||
setText("heavenReadingHistoryCount", `${items.length} 条`);
|
||||
const list = document.querySelector("#heavenReadingHistoryList");
|
||||
list.innerHTML = items.map((item) => `
|
||||
<button class="heaven-reading-history-item ${number(item.id) === state.heavenReadingSelectedId ? "active" : ""}" type="button" data-heaven-reading-id="${number(item.id)}">
|
||||
<span>${escapeHtml(item.subject)}</span>
|
||||
<small>${escapeHtml(item.subject_detail || displayCompactDate(item.context_date))}</small>
|
||||
<time>${formatTimestamp(item.created_at)}</time>
|
||||
</button>
|
||||
`).join("") || '<div class="empty-state">暂无历史解读</div>';
|
||||
const selected = items.find((item) => number(item.id) === state.heavenReadingSelectedId);
|
||||
const detail = document.querySelector("#heavenReadingHistoryDetail");
|
||||
detail.innerHTML = selected ? `
|
||||
<header><div><span>${escapeHtml(heavenReadingMeta(mode).status)}</span><h3>${escapeHtml(selected.subject)}</h3></div><time>${formatTimestamp(selected.created_at)}</time></header>
|
||||
<p>${escapeHtml(selected.subject_detail || displayCompactDate(selected.context_date))}</p>
|
||||
<div class="heaven-reading-answer">${formatMentorAnswer(selected.answer || "")}</div>
|
||||
<footer><button class="button" type="button" data-delete-heaven-reading="${number(selected.id)}"><i data-lucide="trash-2"></i><span>删除记录</span></button></footer>
|
||||
` : '<div class="empty-state">选择一条记录查看完整解读</div>';
|
||||
refreshIcons();
|
||||
}
|
||||
|
||||
function handleHeavenHistorySelection(event) {
|
||||
const button = event.target.closest("[data-heaven-reading-id]");
|
||||
if (!button) return;
|
||||
state.heavenReadingSelectedId = number(button.dataset.heavenReadingId);
|
||||
renderHeavenReadingHistory();
|
||||
}
|
||||
|
||||
async function handleHeavenHistoryAction(event) {
|
||||
const button = event.target.closest("[data-delete-heaven-reading]");
|
||||
if (!button || !window.confirm("确定删除这条解读记录吗?")) return;
|
||||
const id = number(button.dataset.deleteHeavenReading);
|
||||
try {
|
||||
await apiRequest(`/api/heaven/readings/${id}`, "DELETE");
|
||||
const mode = state.heavenReadingMode;
|
||||
state.heavenReadingHistory[mode] = (state.heavenReadingHistory[mode] || []).filter((item) => number(item.id) !== id);
|
||||
if (number(state.heavenInterpretations[mode]?.id) === id) {
|
||||
state.heavenInterpretations[mode] = "";
|
||||
if (mode === "fortune" && state.heavenSetup) state.heavenSetup.daily_fortune_reading = null;
|
||||
updateHeavenInterpretationControls();
|
||||
}
|
||||
state.heavenReadingSelectedId = number(state.heavenReadingHistory[mode][0]?.id);
|
||||
renderHeavenReadingHistory();
|
||||
} catch (error) {
|
||||
showToast(error.message || "解读记录删除失败");
|
||||
}
|
||||
}
|
||||
|
||||
async function interpretHeaven(mode) {
|
||||
const existing = state.heavenInterpretations[mode];
|
||||
if (existing) {
|
||||
openHeavenReading(mode, { loading: false });
|
||||
return;
|
||||
}
|
||||
const button = document.querySelector(mode === "trend" ? "#interpretTrendButton" : mode === "fortune" ? "#interpretFortuneButton" : "#interpretHeartButton");
|
||||
if (button.disabled) return;
|
||||
button.disabled = true;
|
||||
const originalText = button.textContent;
|
||||
button.textContent = mode === "trend" ? "正在观势" : mode === "fortune" ? "正在察运" : "正在解卦";
|
||||
state.heavenReadingMode = mode;
|
||||
state.heavenReadingLoading = true;
|
||||
state.heavenReadingError = "";
|
||||
openHeavenReading(mode, { loading: true });
|
||||
updateHeavenInterpretationControls();
|
||||
hideHeavenNotice();
|
||||
try {
|
||||
const payload = {
|
||||
@@ -3112,10 +3277,15 @@ async function interpretHeaven(mode) {
|
||||
if (mode === "trend" && state.heavenManualData) payload.manual_data = state.heavenManualData;
|
||||
if (mode === "heart") payload.lines = state.heartLines;
|
||||
const result = await apiRequest("/api/heaven/interpret", "POST", payload);
|
||||
state.heavenInterpretations[mode] = {
|
||||
state.heavenInterpretations[mode] = result.reading || {
|
||||
answer: result.answer,
|
||||
meta: mode === "trend" ? "观势已成" : mode === "fortune" ? "观气已成" : "解卦已成",
|
||||
subject: `${heavenReadingMeta(mode).panel}解读`,
|
||||
context_date: payload.trade_date,
|
||||
created_at: new Date().toISOString(),
|
||||
};
|
||||
state.heavenReadingHistory[mode] = [];
|
||||
state.heavenReadingLoading = false;
|
||||
renderHeavenReadingDialog();
|
||||
if (result.notice) showHeavenNotice(result.notice);
|
||||
if (mode === "heart") {
|
||||
if (await transitionHeartStage("interpretation")) await playHeartReadSequence();
|
||||
@@ -3123,23 +3293,33 @@ async function interpretHeaven(mode) {
|
||||
renderHeavenInterpretation(mode, state.heavenInterpretations[mode]);
|
||||
}
|
||||
} catch (error) {
|
||||
showHeavenNotice(error.message || "问天解读失败");
|
||||
showToast(error.message || "问天解读失败");
|
||||
state.heavenReadingLoading = false;
|
||||
state.heavenReadingError = error.message || "问天解读失败";
|
||||
renderHeavenReadingDialog();
|
||||
showHeavenNotice(state.heavenReadingError);
|
||||
showToast(state.heavenReadingError);
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
button.textContent = originalText;
|
||||
state.heavenReadingLoading = false;
|
||||
updateHeavenInterpretationControls();
|
||||
}
|
||||
}
|
||||
|
||||
function renderHeavenInterpretation(mode, result) {
|
||||
const container = document.querySelector(`#${mode}Interpretation`);
|
||||
if (!result) {
|
||||
container.hidden = true;
|
||||
container.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
container.hidden = false;
|
||||
container.innerHTML = `<div>${formatMentorAnswer(result.answer)}</div><small>${escapeHtml(result.meta)}</small>`;
|
||||
function updateHeavenInterpretationControls() {
|
||||
const loading = state.heavenReadingLoading;
|
||||
const trendButton = document.querySelector("#interpretTrendButton");
|
||||
const fortuneButton = document.querySelector("#interpretFortuneButton");
|
||||
const heartButton = document.querySelector("#interpretHeartButton");
|
||||
trendButton.disabled = loading || !state.heavenSetup?.chart?.available;
|
||||
fortuneButton.disabled = loading || !state.heavenSetup?.field;
|
||||
heartButton.disabled = loading || state.heartLines.length !== 6;
|
||||
trendButton.textContent = loading && state.heavenReadingMode === "trend" ? "正在观势" : state.heavenInterpretations.trend ? HEAVEN_READING_META.trend.done : HEAVEN_READING_META.trend.action;
|
||||
fortuneButton.textContent = loading && state.heavenReadingMode === "fortune" ? "正在察运" : state.heavenInterpretations.fortune ? HEAVEN_READING_META.fortune.done : HEAVEN_READING_META.fortune.action;
|
||||
heartButton.textContent = loading && state.heavenReadingMode === "heart" ? "正在解卦" : state.heavenInterpretations.heart ? HEAVEN_READING_META.heart.done : HEAVEN_READING_META.heart.action;
|
||||
document.querySelector("#viewHeartReadingButton").disabled = !state.heavenInterpretations.heart;
|
||||
}
|
||||
|
||||
function renderHeavenInterpretation() {
|
||||
updateHeavenInterpretationControls();
|
||||
}
|
||||
|
||||
function initializeHeartAtmosphere() {
|
||||
@@ -3455,6 +3635,7 @@ async function finalizeHeartHexagram() {
|
||||
const payload = await apiRequest("/api/heaven/hexagram", "POST", { lines: state.heartLines });
|
||||
if (stageToken !== state.heartStageToken || state.heartStage !== "casting") return;
|
||||
state.heartHexagram = payload.hexagram;
|
||||
updateHeavenInterpretationControls();
|
||||
document.querySelector(".heart-hexagram-shell")?.classList.add("is-complete");
|
||||
setText("castingPrompt", "卦成了");
|
||||
heartSound.chime(660);
|
||||
@@ -3658,6 +3839,7 @@ async function resetHeartRitual() {
|
||||
state.heartThrows = [];
|
||||
state.heartHexagram = null;
|
||||
state.heavenInterpretations.heart = "";
|
||||
updateHeavenInterpretationControls();
|
||||
state.heartRevealToken += 1;
|
||||
heartCastingBusy = false;
|
||||
resetHeartCoins();
|
||||
|
||||
Reference in New Issue
Block a user