去提问»
提问背景 Question:
难道不该用类似数组,然后在模板中循环,模拟之前的聊天过程? 如果没有聊天记录,就显示默认的内容。为什么要用json?

Go模板循环与JS渲染对比

发布时间:2026-08-19 16:10 👁️ 阅读:0

💡 这是一个非常专业且切中要害的问题!

您说得完全对,最符合直觉、也是最传统的做法,就是在 Go 的 HTML 模板中使用 {{range .History}} 循环直接输出 HTML。

然而,在您的特定场景下,不直接使用 Go 模板循环输出,而是推荐注入 JSON 并用 JS 渲染,核心原因在于:客户端 Markdown 解析与动态交互绑定(如复制代码块按钮)

我们可以对比以下两种方案的实现细节,您可以根据偏好自由选择:


方案一:使用 Go 模板直接循环(传统的后端渲染)

如果在 Go 后端进行 {{range}} 循环,您的代码会像这样:

<div id="chat-window">
  {{if .History}}
    {{range .History}}
      <div class="message {{.Sender}}">
        <!-- 🚨 问题:这里的内容是原始 Markdown,前端无法直接正常显示 -->
        {{.Text}}
      </div>
    {{end}}
  {{else}}
    <!-- 默认的欢迎气泡 -->
    <div class="message ai" style="border-left: 4px solid #38bdf8;">
      <!-- 欢迎词 Markdown -->
    </div>
  {{end}}
  <div id="bottom-spacer"></div>
</div>

⚠️ 采用此方案必须解决的两大痛点:

  1. Markdown 解析失效:Go 输出的纯文本含有 ###** 或代码块,浏览器无法自动解析。您必须在页面 DOMContentLoaded 时,用 JS 遍历所有 .message,提取里面的文本,通过 marked.parse 解析后再写回 innerHTML
  2. 代码复制按钮等交互失效:您在 appendMessage() 里为每个代码块动态创建了包裹层 <div class="code-block-wrapper">[复制] 按钮,并绑定了点击事件。如果直接用 Go 循环生成 HTML,这些动态生成的复制按钮和通道标识将统统丢失,除非您把这套复杂的 DOM 遍历与绑定逻辑在前端重写一遍。

方案二:后端注入 JSON,前端调用 JS 渲染(推荐方案)

由于您已经把“渲染 Markdown”、“配置代码块包装器”、“添加复制按钮”、“添加通道标识”这些极其繁琐的工作完美整合在前端的 appendMessage() 函数里,那么把历史记录作为 JSON 数组一次性喂给 JS,由 JS 来调用 appendMessage,能最完美地复用现有成果。

🛠️ Go 后端准备数据:

// 1. 定义消息结构体
type ChatMessage struct {
    Sender   string `json:"sender"`   // "user" 或 "ai"
    Text     string `json:"text"`     // 消息内容
    Provider string `json:"provider"` // 通道标识,没有可为空
}

// 2. 将历史列表序列化为 JSON 字符串
historyBytes, _ := json.Marshal(userHistoryList)
historyJSON := string(historyBytes) 

// 3. 传给前端模板的值为 historyJSON(例如: '[{"sender":"user","text":"你好"}]')

🛠️ HTML 模板中完美结合:

在您的 HTML 底部 <script> 中加入如下逻辑,既可以支持在无历史记录时显示默认欢迎内容,又能在有数据时无缝恢复历史会话:

document.addEventListener("DOMContentLoaded", () => {
  const chatWindow = document.getElementById("chat-window");
  const spacer = document.getElementById("bottom-spacer");

  // 1. 接收 Go 注入的 JSON 变量(注意:使用 Go 模板输出 JSON 原生对象,确保安全)
  // 若未登录或无数据,后台请赋予空数组 "[]"
  const historyData = {{.HistoryJSON}};

  if (historyData && historyData.length > 0) {
    // 2. 🌟 有历史数据:循环调用您现成的 appendMessage,自动完成 Markdown 解析与复制按钮绑定!
    historyData.forEach(item => {
      // 根据 item.sender 分发气泡
      appendMessage(item.sender, item.text, false, item.provider);
    });
    
    // 自动滚动到最新一条历史
    chatWindow.scrollTop = chatWindow.scrollHeight;
  } else {
    // 3. 🌟 无历史数据:显示您的默认欢迎词
    const welcomeText = `### 💡 欢迎使用【百问百答】AI 助手!

我是你的智能助手...`;
    const welcomeDiv = document.createElement("div");
    welcomeDiv.className = "message ai";
    welcomeDiv.style.borderLeft = "4px solid #38bdf8";
    welcomeDiv.innerHTML = marked.parse(welcomeText);
    
    chatWindow.insertBefore(welcomeDiv, spacer);
  }
});

⚖️ 总结:

  • 如果您不想让前端 JS 做任何初始化渲染:建议在 Go 后端引入 goldmark 库,先在后端将 Markdown 转为 HTML,然后再用 Go 模板的 {{range}} 输出 HTML,并在前端用 JS 监听并补齐“复制”按钮。
  • 如果希望前端样式最统一、代码最精简:使用方案二。直接利用前端封装好的 appendMessage() 方法去处理历史记录,能做到“新发的消息”与“历史消息”在视觉、Markdown排版、复制功能上100% 绝对一致