这是一个基于现代前端技术栈构建的智能聊天助手项目,核心功能包括Markdown内容渲染、状态管理和数据持久化。项目采用Vue3作为前端框架,结合Pinia进行状态管理,并针对技术文档展示场景进行了深度优化。
作为开发者,我们经常需要处理技术文档的展示问题。传统方案要么功能单一,要么性能不佳。这个项目通过定制Markdown渲染管线、优化数据存储策略,实现了高性能、高安全性的技术内容展示方案。
项目选用markdown-it作为核心渲染引擎,这是一个轻量级但功能强大的Markdown解析器。基础配置如下:
javascript复制const md = new MarkdownIt({
html: true, // 允许HTML标签通过
breaks: true, // 转换换行符为<br>
linkify: true // 自动识别文本中的URL
})
这里有几个关键决策点:
html: true允许混合使用Markdown和HTML,为后续富文本展示提供可能breaks: true确保用户输入的换行符能正确显示linkify: true自动识别文本中的URL,提升用户体验通过集成highlight.js实现语法高亮,核心逻辑封装在highlight回调中:
javascript复制highlight: function(str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
const highlighted = hljs.highlight(str, {
language: lang,
ignoreIllegals: true
}).value
return `...${highlighted}...` // 包装自定义DOM结构
} catch (e) {
// 异常处理
}
}
// 默认回退处理
return `<pre class="hljs"><code>${md.utils.escapeHtml(str)}</code></pre>`
}
这里有几个值得注意的技术点:
ignoreIllegals: true提高对不规范代码的容错性项目为代码块设计了包含功能按钮的容器:
javascript复制return `
<div class="code-block">
<div class="code-header">
<span class="code-lang">${lang}</span>
<div class="code-actions">
<button class="code-action-btn" data-action="copy">
<img src="${copyIcon}" alt="copy" />
</button>
<button class="code-action-btn" data-action="theme">
<img src="${darkIcon}" alt="theme" />
</button>
</div>
</div>
<pre class="hljs"><code>${highlighted}</code></pre>
</div>`
这个设计解决了几个实际问题:
当配置html: true时,用户可能输入恶意HTML/JS代码:
markdown复制你好<script>恶意代码</script>
这会导致:
javascript复制export const renderMarkdown = (content) => {
if (!content) return ''
const rawHtml = md.render(content)
return DOMPurify.sanitize(rawHtml)
}
DOMPurify会:
<script>等危险标签onclick)采用Pinia进行模块化状态管理,典型store结构:
javascript复制const useChatStore = defineStore('chat', {
state: () => ({
conversations: [],
currentConversationId: null
}),
persist: {
key: 'llm-chat-state',
storage: indexedDBStorage,
paths: ['conversations', 'currentConversationId']
}
})
从localStorage迁移到IndexedDB的考量:
| 方案 | 容量 | IO类型 | 适用场景 |
|---|---|---|---|
| localStorage | 5MB | 同步 | 小数据量 |
| IndexedDB | 250MB+ | 异步 | 大数据量 |
迁移原因:
javascript复制class ChatDB {
private dbPromise: Promise<IDBPDatabase>
constructor() {
this.dbPromise = openDB(DB_NAME, DB_VERSION, {
upgrade(db) {
if (!db.objectStoreNames.contains(STORE_NAME)) {
db.createObjectStore(STORE_NAME)
}
},
})
}
async get(key: string): Promise<any> {
const db = await this.dbPromise
return db.get(STORE_NAME, key)
}
// ...其他方法
}
关键设计:
解决Pinia插件同步接口与IndexedDB异步特性的矛盾:
javascript复制export const indexedDBStorage = {
getItem(key) {
// 从内存缓存同步读取
const val = memoryCache[key]
return val ? JSON.stringify(val) : null
},
async setItem(key, value) {
// 异步写入IndexedDB
const parsedValue = JSON.parse(value)
memoryCache[key] = parsedValue
await chatDB.set(key, parsedValue)
}
}
问题1:从localStorage迁移后,部分用户数据丢失
原因:旧key与新key不一致
解决:实现数据迁移逻辑
javascript复制async function initIndexedDBStorage() {
let stored = await chatDB.get(CHAT_STORE_KEY)
if (!stored) {
const legacy = await chatDB.get('llm-chat') // 旧key
if (legacy) {
await chatDB.set(CHAT_STORE_KEY, legacy) // 迁移到新key
}
}
}
问题2:代码复制功能在移动端失效
原因:触摸事件与点击事件冲突
解决:改用Pointer Events API
调试工具:
性能分析:
测试工具:
这个项目从技术选型到具体实现都体现了一些核心原则:安全性优先、性能敏感、良好的扩展性。特别是在处理用户生成内容时,安全措施必须贯穿从输入到展示的整个链路。IndexedDB的采用则展示了如何根据实际需求选择合适的持久化方案。