去提问»
提问背景 Question:
那么以下代码,针对 description我要如何处理。 // 2. 百科详情页 Handler // 路由配置:r.GET("/baike/article/:id", ai.Baike_detail) func Baike_detail(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { articleIDStr := ps.ByName("id") articleID, err := strconv.Atoi(articleIDStr) if err != nil { http.NotFound(w, r) return } // 1. 查询文章主体 var a Article var contentRaw string var createdAtInt int64 row := fun.DB.QueryRow( "SELECT id, question, title, description, content, click_count, like_count, created_at FROM cms_article WHERE id = ? AND status = 1", articleID, ) err = row.Scan(&a.ID, &a.Question, &a.Title, &a.Description, &contentRaw, &a.ClickCount, &a.LikeCount, &createdAtInt) if err != nil { if err == sql.ErrNoRows { http.NotFound(w, r) } else { http.Error(w, "数据库错误: "+err.Error(), http.StatusInternalServerError) } return } //a.Content = template.HTML(contentRaw) // 转换为非转义 HTML,直接渲染 a.Content = ConvertMarkdown(contentRaw) //解析Markdown并转义template.HTML a.CreatedAt = time.Unix(createdAtInt, 0).Format("2006-01-02 15:04") // 2. 异步增加浏览量(避免阻塞主渲染) go func(id int) { _, _ = fun.DB.Exec("UPDATE cms_article SET click_count = click_count + 1 WHERE id = ?", id) }(articleID) // 3. 查询当前文章的 Tags var tags []Tag tagRows, err := fun.DB.Query(` SELECT t.id, t.name FROM cms_tag t INNER JOIN cms_article_tag at ON t.id = at.tag_id WHERE at.article_id = ?`, articleID, ) if err == nil { for tagRows.Next() { var t Tag if err := tagRows.Scan(&t.ID, &t.Name); err == nil { tags = append(tags, t) } } tagRows.Close() } data := BaikeDetailData{ Article: a, Tags: tags, } // 4. 渲染模板 path := filepath.Join(fun.AppPath, "/templates/ai/baike_detail.html") mb, err := fun.TManager.Get(path) if err != nil { http.Error(w, "详情模板渲染失败: "+err.Error(), http.StatusInternalServerError) return } err = mb.Execute(w, data) if err != nil { fun.WriteLog("渲染百科详情页模板报错: %s", err) } } /* func ConvertMarkdown(mdContent string) template.HTML { var buf bytes.Buffer if err := goldmark.Convert([]byte(mdContent), &buf); err != nil { return template.HTML(mdContent) // 如果解析失败,回退返回原始文本 } return template.HTML(buf.String()) } */ func ConvertMarkdown(mdContent string) template.HTML { // 1. 将字面量的 "" (反斜杠+n) 替换为真正的换行符 "" mdContent = strings.ReplaceAll(mdContent, "\\n", "\n") // 2. 兼容性处理:将字面量的 "\\r" 替换为真正的 "\r" mdContent = strings.ReplaceAll(mdContent, "\\r", "\n") // 3. 兼容性处理:将 Windows 换行符统一替换为 Linux 换行符(可选,防患未然) mdContent = strings.ReplaceAll(mdContent, "\r", "\n") var buf bytes.Buffer if err := goldmark.Convert([]byte(mdContent), &buf); err != nil { return template.HTML(mdContent) // 如果解析失败,回退返回原始文本 } return template.HTML(buf.String()) }

Go百科详情提问背景处理

发布时间:2026-08-18 14:55 👁️ 阅读:1

针对 description(提问背景),为了既能优雅地展示代码与排版(支持 Markdown),又能防止长内容/多代码撑爆页面,最稳妥、体验最好的处理方案是:

  1. 后端:使用您现有的 ConvertMarkdown 方法,将 description 解析为 template.HTML,确保代码块和格式正常渲染。
  2. 前端:不要在后端强行截断字符串(因为截断 HTML/Markdown 会导致标签未闭合、代码块错乱)。应当**在前端使用 CSS/JS 实现「超出高度折叠 + 点击展开」**的效果。

具体改造方案如下:

一、 后端代码调整

首先,确保您的 Article 结构体或传递给模板的数据结构中,Description(或者新增一个字段)能够接收 template.HTML 类型,避免前端模板渲染时被转义。

1. 修改结构体定义

建议在 Article 结构体中将 Description 定义为 template.HTML,或者在渲染结构体中新增一个 DescriptionHTML 字段:

type Article struct {
	ID          int
	Question    string
	Title       string
	Description template.HTML // 修改为 template.HTML 类型
	Content     template.HTML
	ClickCount  int
	LikeCount   int
	CreatedAt   string
}

2. 在 Handler 中解析 Markdown

Baike_detail 函数中,读取数据库后进行解析转换:

func Baike_detail(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	// ... 前面查询数据的代码保持不变 ...
	
	var a Article
	var contentRaw string
	var descriptionRaw string // 用临时变量接收数据库中的原始 String
	var createdAtInt int64

	row := fun.DB.QueryRow(
		"SELECT id, question, title, description, content, click_count, like_count, created_at FROM cms_article WHERE id = ? AND status = 1",
		articleID,
	)
	err = row.Scan(&a.ID, &a.Question, &a.Title, &descriptionRaw, &contentRaw, &a.ClickCount, &a.LikeCount, &createdAtInt)
	// ... 错误处理 ...

	// 核心修改点:解析提问背景和正文
	a.Description = ConvertMarkdown(descriptionRaw) // 解析提问背景的 Markdown
	a.Content = ConvertMarkdown(contentRaw)         // 解析正文
	a.CreatedAt = time.Unix(createdAtInt, 0).Format("2006-01-02 15:04")

	// ... 后续逻辑保持不变 ...
}

二、 前端 HTML/CSS 控制(防止内容过长)

baike_detail.html 中,对提问背景区域包裹一层容器,利用 CSS 限制最大显示高度,并提供“展开/收起”按钮。这样即使提问背景里有一百行代码,也不会影响首屏体验。

1. HTML 结构

<div class="question-bg-container">
    <div class="question-bg-title">提问背景:</div>
    <!-- 限制高度的包裹盒子 -->
    <div class="question-bg-content" id="qBgContent">
        {{.Article.Description}}
    </div>
    <!-- 展开收起按钮 -->
    <div class="toggle-btn" id="toggleBtn" style="display: none;">展开全部 ↓</div>
</div>

2. CSS 样式(关键:限高、遮罩渐变)

.question-bg-container {
    background-color: #f8f9fa;
    border: 1px solid #e9ecef;
    border-radius: 6px;
    padding: 15px;
    margin-bottom: 20px;
    position: relative;
}

.question-bg-title {
    font-weight: bold;
    color: #495057;
    margin-bottom: 8px;
}

.question-bg-content {
    max-height: 180px; /* 默认最大高度,超出折叠,可放下约5-8行代码 */
    overflow: hidden;
    transition: max-height 0.3s ease;
    position: relative;
}

/* 当内容超出时,底部的半透明遮罩,显得更平滑 */
.question-bg-content.collapsed::after {
    content: "";
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 50px;
    background: linear-gradient(transparent, #f8f9fa);
    pointer-events: none;
}

.toggle-btn {
    text-align: center;
    color: #007bff;
    cursor: pointer;
    margin-top: 10px;
    font-size: 14px;
    user-select: none;
}

3. JS 逻辑(动态判断是否需要折叠)

document.addEventListener("DOMContentLoaded", function() {
    const content = document.getElementById("qBgContent");
    const btn = document.getElementById("toggleBtn");
    
    // 设定一个阈值(与CSS中max-height一致)
    const maxHeight = 180; 
    
    // 如果实际内容高度大于限制高度,则显示“展开”按钮,并应用遮罩
    if (content.scrollHeight > maxHeight) {
        content.classList.add("collapsed");
        btn.style.display = "block";
    }
    
    btn.addEventListener("click", function() {
        if (content.classList.contains("collapsed")) {
            // 展开
            content.style.maxHeight = content.scrollHeight + "px";
            content.classList.remove("collapsed");
            btn.innerText = "收起全部 ↑";
        } else {
            // 收起
            content.style.maxHeight = maxHeight + "px";
            content.classList.add("collapsed");
            btn.innerText = "展开全部 ↓";
        }
    });
});

总结

  • 需要 Markdown 解析吗? 需要,因为提问背景里经常会有行内代码或代码块,解析后排版更好看。
  • 长度如何控制? 不要用后端 substr 截断,改用 前端 CSS max-height + JS scrollHeight 判断进行折叠。这种做法最稳妥,不会破坏代码块 HTML 闭合标签,且兼容性最好。