Files
OJ2/apps/web/src/shared/components/Markdown.vue
T
xuyueandClaude Opus 5.5 6f47ad8358 perf(Markdown): 题面、教程、AI 提示每次打开都去 unpkg.com 拉四个库,机房网络下又慢又不稳
md-editor-v3 的 MdPreview 不管正文里有没有代码、公式,都会从 unpkg.com 加载
highlight.js、katex、mermaid、echarts(mermaid 还是另一份 11.x)。而我们只用到最基础的
Markdown:题面 95% 是富文本存的 HTML,教程是标题、表格、图片,AI 输出是标题、列表、
表格、少量代码块,库里一处公式、mermaid 都没有。

- 显示:新组件 shared/components/Markdown.vue,直接用 markdown-it,代码高亮走本地的
  highlight.js(和练一练共用 shared/highlight.ts,补了 C++ / SQL)。9 处 MdPreview 全换掉。
  正文默认 16px,教程也统一成 16px。
- 编辑:后台教程编辑器换成项目已有的 CodeMirror,加一排格式按钮;图片可以按按钮选、
  粘贴、拖进来,都是先上传再插入。md-editor-v3 从依赖里删掉。
- AI 生成的内容(AI 提示、AI 分析、班级报告)不再放行原生 HTML:学生能在代码里诱导
  AI 吐出 <img onerror>,老师在后台一打开就执行。库里 1196 条 AI 输出没有一个 HTML
  标签,关掉不影响显示。题面、教程照旧放行。
- 长英文改成在单词之间折行,原来 break-all 会把 "I live" 断成 "I l / ive"。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:02:54 +08:00

164 lines
3.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { useThemeVars } from "naive-ui"
import { renderMarkdown } from "shared/markdown"
import "shared/highlight.css"
/**
* 显示一段 Markdown / HTML 正文:题面、教程、AI 的提示和报告。
* `html` 只给老师写的内容(题面、教程)打开,AI 生成的别开 —— 理由见 shared/markdown.ts。
* 默认 16px / 1.75,和原来 md-editor 一样;各页可以用自己的 class 盖掉。
*/
const { content, html = false } = defineProps<{
content: string | null | undefined
html?: boolean
}>()
const theme = useThemeVars()
const rendered = computed(() => renderMarkdown(content ?? "", html))
</script>
<template>
<div class="oj-markdown" v-html="rendered" />
</template>
<!-- 不加 scoped:v-html 进来的节点不带 scoped 的属性,靠 .oj-markdown 前缀收住范围 -->
<style>
.oj-markdown {
font-size: 16px;
line-height: 1.75;
/* 长英文、长网址在单词之间折;实在折不开再断词。原来 md-editor 是 break-all,
题面里「Hello, I am …」这种一句话会从单词中间断开 */
overflow-wrap: break-word;
}
.oj-markdown > :first-child {
margin-top: 0;
}
.oj-markdown > :last-child {
margin-bottom: 0;
}
.oj-markdown p {
margin: 0.7em 0;
}
.oj-markdown h1,
.oj-markdown h2,
.oj-markdown h3,
.oj-markdown h4,
.oj-markdown h5,
.oj-markdown h6 {
margin: 1.2em 0 0.5em;
font-weight: 600;
line-height: 1.4;
}
.oj-markdown h1 {
font-size: 1.5em;
}
.oj-markdown h2 {
font-size: 1.3em;
}
.oj-markdown h3 {
font-size: 1.15em;
}
.oj-markdown h4,
.oj-markdown h5,
.oj-markdown h6 {
font-size: 1em;
}
.oj-markdown ul,
.oj-markdown ol {
margin: 0.5em 0;
padding-left: 1.6em;
}
.oj-markdown li + li {
margin-top: 0.25em;
}
.oj-markdown li > p {
margin: 0.25em 0;
}
.oj-markdown a {
color: v-bind("theme.primaryColor");
text-decoration: none;
}
.oj-markdown a:hover {
text-decoration: underline;
}
.oj-markdown img {
max-width: 100%;
height: auto;
vertical-align: middle;
}
.oj-markdown hr {
margin: 1.2em 0;
border: 0;
border-top: 1px solid v-bind("theme.dividerColor");
}
.oj-markdown blockquote {
margin: 0.8em 0;
padding: 0.1em 1em;
border-left: 4px solid v-bind("theme.dividerColor");
color: v-bind("theme.textColor3");
}
.oj-markdown code {
padding: 0.1em 0.45em;
border-radius: 4px;
background-color: rgba(128, 128, 128, 0.12);
font-family: Consolas, Monaco, monospace;
/* 等宽字体比同字号的中文矮,再缩就和旁边的字差一截 */
font-size: 0.95em;
}
.oj-markdown pre {
margin: 0.8em 0;
padding: 12px 14px;
border-radius: 6px;
background-color: rgba(128, 128, 128, 0.08);
overflow-x: auto;
line-height: 1.6;
}
.oj-markdown pre code {
padding: 0;
border-radius: 0;
background: none;
font-size: 14px;
white-space: pre;
}
/* 宽表自己横着滚,不把整页撑出横向滚动条 */
.oj-markdown table {
display: block;
max-width: 100%;
overflow-x: auto;
margin: 0.8em 0;
border-collapse: collapse;
}
.oj-markdown th,
.oj-markdown td {
padding: 6px 12px;
border: 1px solid v-bind("theme.dividerColor");
text-align: left;
}
.oj-markdown th {
font-weight: 600;
background-color: rgba(128, 128, 128, 0.08);
}
</style>