fix(knowledge): render DB dynamic articles via markdown safe renderer
ci/woodpecker/push/main Pipeline failed
ci/woodpecker/push/main Pipeline failed
A: dynamic knowledge-articles content goes through marked instead of raw v-html (escape-first, no new deps). B: plain-text fallback normalize (bracket headings, circled numbers to ordered list, blank lines, key-number bold, idempotent). C: detail-content styles for h2/h3/pre/blockquote/paragraph spacing. D: dialog width 700 to 860, content padding, in-dialog scroll. Task: project-20260924-knowledge-dialog-render-fullstack
This commit is contained in:
@@ -41,8 +41,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<MyDialog v-model="showDetail" :title="detailItem?.title || '文章详情'" :width="700">
|
||||
<!-- 详情弹窗(2026-09-24:宽度 700→860,长内容在弹窗内滚动) -->
|
||||
<MyDialog v-model="showDetail" :title="detailItem?.title || '文章详情'" :width="860">
|
||||
<div v-if="detailItem" class="detail-content" v-html="detailHtml"></div>
|
||||
<template #footer>
|
||||
<el-button @click="showDetail = false">关闭</el-button>
|
||||
@@ -170,7 +170,7 @@ import { ref, computed } from "vue"
|
||||
import { ElMessage } from "element-plus"
|
||||
import { ethicsQuizApi } from "../api/index"
|
||||
import api from "../api/index"
|
||||
import { marked } from "marked"
|
||||
import { renderKnowledgeContent } from "../utils/knowledgeRender"
|
||||
|
||||
// ── Tab ──
|
||||
const activeTab = ref("knowledge")
|
||||
@@ -312,8 +312,9 @@ function catLabel(key: string) {
|
||||
|
||||
function openItem(item: any) {
|
||||
detailItem.value = item
|
||||
// md 单一来源的内容用页面同一渲染器渲染;DB 动态知识库文章保持既有 HTML 直出
|
||||
detailHtml.value = item?.kind === "md" ? (marked(item.content || "") as string) : (item?.content || "")
|
||||
// 统一走 knowledgeRender(2026-09-24):md 单一来源保持既有 marked 渲染(含内联样式,不转义);
|
||||
// DB 动态知识库文章先做纯文本规范化再渲染,且原始 HTML 只以纯文本出现、不当 HTML 执行。
|
||||
detailHtml.value = renderKnowledgeContent(item)
|
||||
showDetail.value = true
|
||||
}
|
||||
|
||||
@@ -448,15 +449,25 @@ function backToIntro() {
|
||||
.empty-search { text-align: center; color: #999; padding: 40px; }
|
||||
.kb-hint { text-align: center; color: #999; padding: 24px; font-size: 14px; }
|
||||
.kb-hint-error { color: #f56c6c; }
|
||||
.detail-content { line-height: 1.8; font-size: 14px; }
|
||||
.detail-content h4 { margin: 16px 0 8px; }
|
||||
.detail-content h5 { margin: 12px 0 6px; color: #666; }
|
||||
.detail-content ul, .detail-content ol { padding-left: 20px; }
|
||||
.detail-content li { margin-bottom: 4px; }
|
||||
.detail-content code { background: #f5f5f5; padding: 2px 6px; border-radius: 4px; font-size: 13px; }
|
||||
.detail-content table { width: 100%; border-collapse: collapse; margin: 12px 0; }
|
||||
.detail-content th, .detail-content td { border: 1px solid #e0e0e0; padding: 8px; text-align: left; }
|
||||
.detail-content th { background: #f5f5f5; font-weight: 600; }
|
||||
/* ── 详情正文(v-html 内容不带 scoped 属性 ⇒ 元素级样式一律走 :deep()) ── */
|
||||
.detail-content { line-height: 1.8; font-size: 14px; color: #303133; padding: 4px 2px 28px; overflow-wrap: break-word; word-break: break-word; }
|
||||
.detail-content :deep(h1) { font-size: 20px; line-height: 1.4; margin: 0 0 14px; padding-bottom: 8px; border-bottom: 1px solid #ebeef5; }
|
||||
.detail-content :deep(h2) { font-size: 18px; line-height: 1.4; margin: 22px 0 12px; padding-left: 10px; border-left: 3px solid #409eff; }
|
||||
.detail-content :deep(h3) { font-size: 16px; line-height: 1.4; margin: 20px 0 10px; padding-left: 9px; border-left: 3px solid #dcdfe6; }
|
||||
.detail-content :deep(h4) { font-size: 15px; line-height: 1.45; margin: 18px 0 8px; color: #409eff; }
|
||||
.detail-content :deep(h5) { font-size: 14px; margin: 14px 0 6px; color: #666; }
|
||||
.detail-content :deep(p) { margin: 10px 0; line-height: 1.8; }
|
||||
.detail-content :deep(ul), .detail-content :deep(ol) { padding-left: 22px; margin: 10px 0; }
|
||||
.detail-content :deep(li) { margin-bottom: 6px; line-height: 1.8; }
|
||||
.detail-content :deep(li > p) { margin: 4px 0; }
|
||||
.detail-content :deep(strong) { color: #409eff; font-weight: 600; }
|
||||
.detail-content :deep(code) { background: #f5f5f5; padding: 2px 6px; border-radius: 4px; font-size: 13px; font-family: "JetBrains Mono", Consolas, Monaco, "Courier New", monospace; }
|
||||
.detail-content :deep(pre) { background: #f6f8fa; border: 1px solid #e4e7ed; border-radius: 6px; padding: 12px 14px; margin: 12px 0; overflow-x: auto; }
|
||||
.detail-content :deep(pre code) { background: transparent; padding: 0; font-size: 13px; line-height: 1.7; white-space: pre; }
|
||||
.detail-content :deep(blockquote) { margin: 12px 0; padding: 8px 14px; border-left: 4px solid #409eff; background: #f5f7fa; border-radius: 0 6px 6px 0; color: #606266; }
|
||||
.detail-content :deep(blockquote > p) { margin: 4px 0; }
|
||||
.detail-content :deep(hr) { border: none; border-top: 1px dashed #dcdfe6; margin: 18px 0; }
|
||||
.detail-content :deep(img) { max-width: 100%; height: auto; }
|
||||
/* v-html 内容不带 scoped 属性(旧版靠 HTML 内联 border/cellpadding 出边框);
|
||||
md 单一来源的表格是纯 markdown,改用 :deep 让既有表格样式真正生效(2026-09-24) */
|
||||
.detail-content :deep(table) { width: 100%; border-collapse: collapse; margin: 12px 0; }
|
||||
|
||||
Reference in New Issue
Block a user