/** * 问卷(`` 行)**作答后**的内容改写 —— 纯函数,便于 harness 直接测。 * * 背景(2026-09-24 用户反馈):「如果下方已经新对话了,上方的问卷应该是只读状态」。 * `QuestionCard.vue` 本来就有只读渲染分支(显示已选项 / 已填文本),但它的判定是 * `isReadonly = 解析 content 得到 answers` —— 而**此前没有任何代码把答案写回 content**, * 于是只读分支永远走不到:提交过的问卷在新一轮对话出现后仍然可点可填。 * * 这里负责「把提交的答案写回那一行」:把内容里**最后一个** * `…` 块的内部替换成 `{answers:[…]}`(只读分支认的形状)。 * 写回后: * · 卡片立刻渲染成只读(内容变了 → 渲染层整体重解析,见 BusinessRecord 的 content watcher) * · `saveHistory()` 落盘后,刷新页面仍是已作答态 */ import type { QuestionCardAnswer } from "./question-cards-content"; export type { QuestionCardAnswer }; const OPEN_TAG = ""; const CLOSE_TAG = ""; /** 归一化一条答案(只保留只读渲染需要的字段,避免把别的状态写进内容) */ const normalizeAnswer = (answer: QuestionCardAnswer | null | undefined) => ({ header: String(answer?.header ?? ""), selected: Array.isArray(answer?.selected) ? answer.selected.map((item) => String(item ?? "").trim()).filter(Boolean) : [], freeText: String(answer?.freeText ?? ""), skipped: Boolean(answer?.skipped), }); /** 生成只读渲染所需的内容(`QuestionCard` 的 readonly 分支按这个形状读) */ export const buildAnsweredQuestionCardsContent = (answers: QuestionCardAnswer[]): string => JSON.stringify({ answers: (Array.isArray(answers) ? answers : []).map(normalizeAnswer) }); /** * 把内容里**最后一个** `…` 改成"已作答"的内容。 * * ⚠️ **保留原有字段(尤其 `questions`),只补/换 `answers`**: * 只读态要求"选完还是问卷本来的样子",而那份样式(`(单选)` 标签、选项 A/B、提交按钮…) * 全靠 `questions` 渲染出来 —— 上一版把整块替换成只有 `answers` 的 JSON, * 结果只读渲染只能退化成一个简陋的小框(用户 2026-09-24:「问卷长什么样选完后还是长什么样」)。 * * 找不到标记(或标记不成对)时**原样返回** —— 宁可不动,也不要破坏内容里的其它行。 */ export const applyQuestionCardsAnswers = ( content: string, answers: QuestionCardAnswer[] ): string => { const text = String(content ?? ""); const closeIdx = text.lastIndexOf(CLOSE_TAG); if (closeIdx < 0) return text; const openIdx = text.lastIndexOf(OPEN_TAG, closeIdx); if (openIdx < 0) return text; const inner = text.slice(openIdx + OPEN_TAG.length, closeIdx); let payload: Record = {}; try { const parsed = JSON.parse(inner); if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) { payload = parsed as Record; } } catch { // 原内容不是 JSON(理论上不会发生):只写 answers,至少让只读态能渲染出答案 } const next = JSON.stringify({ ...payload, answers: (Array.isArray(answers) ? answers : []).map(normalizeAnswer), }); return text.slice(0, openIdx + OPEN_TAG.length) + next + text.slice(closeIdx); }; /** 内容里是否已经有作答结果(用于断言 / 幂等判断) */ export const hasAnsweredQuestionCards = (content: string): boolean => { const text = String(content ?? ""); const closeIdx = text.lastIndexOf(CLOSE_TAG); const openIdx = closeIdx >= 0 ? text.lastIndexOf(OPEN_TAG, closeIdx) : -1; if (openIdx < 0) return false; const inner = text.slice(openIdx + OPEN_TAG.length, closeIdx); try { const parsed = JSON.parse(inner); return Array.isArray(parsed?.answers) && parsed.answers.length > 0; } catch { return false; } };