重构+优化: CR-04+06 AiChat 流式渲染改进 + CR-10 useRendered DRY 抽
- CR-04: splitBlocks 正则切块改 marked.lexer() tokens 遍历(type=code 的 .raw 整块,其余按双换行切),正确处理行首空格/4+反引号嵌套/未闭合围栏——lexer 与 parse 同 marked 实例同 gfm 规则,切块边界与渲染边界一致。 - CR-06: watch(mdReady) 翻 true 时主动 scheduleStreamParse 重算,修首屏 marked 慢+暂无 delta 致末段停留 escapeFallback 纯文本。 - CR-10: useMarkdown 抽 useRendered(computed 读 mdReady+renderMd + ensureLoaded 幂等预热),4 view(TaskDetail/ProjectDetail/Ideas/Knowledge)删手写 renderedDesc computed + onMounted loadMarkdown 重复,loadMarkdown 提模块级。DRY 行为零变化。 批4 wlnazlvdc,vue-tsc 0 err
This commit is contained in:
@@ -214,19 +214,9 @@ import { useProjectStore } from '../stores/project'
|
||||
import { formatDate } from '../utils/time'
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue'
|
||||
import { useConfirm } from '../composables/useConfirm'
|
||||
import { useMarkdown } from '../composables/useMarkdown'
|
||||
import { useRendered } from '../composables/useMarkdown'
|
||||
import type { IdeaRecord } from '../api/types'
|
||||
|
||||
// B-260615-25:灵感描述 Markdown 渲染(复用 AiChat/TaskDetail 同款渲染器,模块级单例)
|
||||
const { renderMd, loadMarkdown, mdReady } = useMarkdown()
|
||||
|
||||
// 描述渲染依赖 mdReady 响应式:loadMarkdown 完成前 escapeFallback 纯文本,完成后 mdReady 翻转
|
||||
// 触发 computed 重算(B-25 漏响应式致 v-html=renderMd 首次纯文本后不重算,代码块不渲染)
|
||||
const renderedDesc = computed(() => {
|
||||
void mdReady.value
|
||||
return renderMd(currentIdea.value?.description ?? '')
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const store = useProjectStore()
|
||||
@@ -295,6 +285,12 @@ const currentIdea = computed(() => {
|
||||
return store.ideas.find(i => i.id === selectedId.value) ?? null
|
||||
})
|
||||
|
||||
// B-260615-25:灵感描述 Markdown 渲染(复用 AiChat/TaskDetail 同款渲染器,模块级单例),
|
||||
// useRendered 封装 computed(读 mdReady 触发响应式 + renderMd)+ ensureLoaded(幂等预热)
|
||||
const { rendered: renderedDesc, ensureLoaded } = useRendered(
|
||||
() => currentIdea.value?.description ?? '',
|
||||
)
|
||||
|
||||
const currentStatus = computed(() => {
|
||||
return currentIdea.value?.status || 'draft'
|
||||
})
|
||||
@@ -450,7 +446,7 @@ async function onStatusChange(e: Event) {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loadMarkdown() // 后台预热 Markdown 渲染器(模块单例,与 AiChat/TaskDetail 共享),不阻塞
|
||||
ensureLoaded() // 后台预热 Markdown 渲染器(模块单例,与 AiChat/TaskDetail 共享),不阻塞
|
||||
await store.loadIdeas()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -235,22 +235,12 @@
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useKnowledgeStore, KNOWLEDGE_KINDS, parseTags } from '../stores/knowledge'
|
||||
import { useMarkdown } from '../composables/useMarkdown'
|
||||
import { useRendered } from '../composables/useMarkdown'
|
||||
import type { KnowledgeDetailPayload, KnowledgeEventRecord } from '../api/types'
|
||||
|
||||
const { t } = useI18n()
|
||||
const store = useKnowledgeStore()
|
||||
|
||||
// B-260615-25:知识内容 Markdown 渲染(复用 AiChat/TaskDetail 同款渲染器,模块级单例)
|
||||
const { renderMd, loadMarkdown, mdReady } = useMarkdown()
|
||||
|
||||
// 内容渲染依赖 mdReady 响应式:loadMarkdown 完成前 escapeFallback 纯文本,完成后 mdReady 翻转
|
||||
// 触发 computed 重算(B-25 漏响应式致 v-html=renderMd 首次纯文本后不重算,代码块不渲染)
|
||||
const renderedContent = computed(() => {
|
||||
void mdReady.value
|
||||
return renderMd(detail.value?.knowledge.content ?? '')
|
||||
})
|
||||
|
||||
// 顶层 Tab: library(知识库) | inbox(审核收件箱)
|
||||
const topTab = ref<'library' | 'inbox'>('library')
|
||||
|
||||
@@ -309,6 +299,11 @@ function getCategoryCount(key: string): number {
|
||||
// ===== 详情选中 + 加载 =====
|
||||
const selectedId = ref<string | null>(null)
|
||||
const detail = ref<KnowledgeDetailPayload | null>(null)
|
||||
// B-260615-25:知识内容 Markdown 渲染(复用 AiChat/TaskDetail 同款渲染器,模块级单例),
|
||||
// useRendered 封装 computed(读 mdReady 触发响应式 + renderMd)+ ensureLoaded(幂等预热)
|
||||
const { rendered: renderedContent, ensureLoaded } = useRendered(
|
||||
() => detail.value?.knowledge.content ?? '',
|
||||
)
|
||||
const refLimit = ref(10)
|
||||
|
||||
async function selectKnowledge(id: string) {
|
||||
@@ -512,7 +507,7 @@ function relativeTime(millisStr: string): string {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadMarkdown() // 后台预热 Markdown 渲染器(模块单例,与 AiChat/TaskDetail 共享),不阻塞
|
||||
ensureLoaded() // 后台预热 Markdown 渲染器(模块单例,与 AiChat/TaskDetail 共享),不阻塞
|
||||
store.loadList()
|
||||
store.loadCandidates()
|
||||
})
|
||||
|
||||
@@ -241,17 +241,7 @@ import { parseStack } from '../utils/project'
|
||||
import { projectStatusLabel, projectStageInfo, taskStatusLabel, taskStatusClass } from '../constants/project'
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue'
|
||||
import { useConfirm } from '../composables/useConfirm'
|
||||
import { useMarkdown } from '../composables/useMarkdown'
|
||||
|
||||
// B-260615-25:项目描述 Markdown 渲染(复用 AiChat/TaskDetail 同款渲染器,模块级单例)
|
||||
const { renderMd, loadMarkdown, mdReady } = useMarkdown()
|
||||
|
||||
// 描述渲染依赖 mdReady 响应式:loadMarkdown 完成前 escapeFallback 纯文本,完成后 mdReady 翻转
|
||||
// 触发 computed 重算(B-25 漏响应式致 v-html=renderMd 首次纯文本后不重算,代码块不渲染)
|
||||
const renderedDesc = computed(() => {
|
||||
void mdReady.value
|
||||
return renderMd(currentProject.value?.description ?? '')
|
||||
})
|
||||
import { useRendered } from '../composables/useMarkdown'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -280,6 +270,11 @@ const projectId = computed(() => route.params.id as string)
|
||||
const currentProject = computed(() =>
|
||||
store.projects.find(p => p.id === projectId.value)
|
||||
)
|
||||
// B-260615-25:项目描述 Markdown 渲染(复用 AiChat/TaskDetail 同款渲染器,模块级单例),
|
||||
// useRendered 封装 computed(读 mdReady 触发响应式 + renderMd)+ ensureLoaded(幂等预热)
|
||||
const { rendered: renderedDesc, ensureLoaded: ensureMdLoaded } = useRendered(
|
||||
() => currentProject.value?.description ?? '',
|
||||
)
|
||||
const stageKey = computed(() => currentProject.value?.status ?? 'planning')
|
||||
const statusLabel = computed(() => projectStatusLabel(stageKey.value))
|
||||
const currentStageIndex = computed(() => projectStageInfo(stageKey.value).stepIndex)
|
||||
@@ -462,7 +457,7 @@ watch(() => store.pendingApproval, (newApproval) => {
|
||||
|
||||
// ── 生命周期 ──
|
||||
onMounted(async () => {
|
||||
loadMarkdown() // 后台预热 Markdown 渲染器(模块单例,与 AiChat/TaskDetail 共享),不阻塞
|
||||
ensureMdLoaded() // 后台预热 Markdown 渲染器(模块单例,与 AiChat/TaskDetail 共享),不阻塞
|
||||
await store.loadProjects()
|
||||
await store.loadTasks() // 全量加载,详情页用 computed 过滤本项目(避免覆盖全局 tasks 单例)
|
||||
unlisten = await store.startEventListener()
|
||||
|
||||
@@ -99,7 +99,7 @@ import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { taskApi, projectApi } from '../api'
|
||||
import { formatDate } from '../utils/time'
|
||||
import { useMarkdown } from '../composables/useMarkdown'
|
||||
import { useRendered } from '../composables/useMarkdown'
|
||||
import {
|
||||
taskStatusLabel,
|
||||
taskStatusClass,
|
||||
@@ -108,16 +108,6 @@ import {
|
||||
} from '../constants/project'
|
||||
import type { TaskRecord, ProjectRecord } from '../api/types'
|
||||
|
||||
// B-24:任务描述 Markdown 渲染(复用 AiChat 同款渲染器,模块级单例)
|
||||
const { renderMd, loadMarkdown, mdReady } = useMarkdown()
|
||||
|
||||
// 描述渲染依赖 mdReady 响应式:loadMarkdown 完成前 escapeFallback 纯文本,完成后 mdReady 翻转
|
||||
// 触发 computed 重算(B-24 漏响应式致 v-html=renderMd 首次纯文本后不重算,代码块不渲染)
|
||||
const renderedDescription = computed(() => {
|
||||
void mdReady.value
|
||||
return renderMd(task.value?.description ?? '')
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const loading = ref(true)
|
||||
@@ -127,6 +117,12 @@ const projects = ref<ProjectRecord[]>([])
|
||||
|
||||
const taskId = computed(() => route.params.id as string)
|
||||
|
||||
// B-24:任务描述 Markdown 渲染(复用 AiChat 同款渲染器,模块级单例),useRendered 封装
|
||||
// computed(读 mdReady 触发响应式 + renderMd)+ ensureLoaded(幂等预热)
|
||||
const { rendered: renderedDescription, ensureLoaded } = useRendered(
|
||||
() => task.value?.description ?? '',
|
||||
)
|
||||
|
||||
const projectName = computed(() => {
|
||||
const p = projects.value.find(p => p.id === task.value?.project_id)
|
||||
return p?.name ?? task.value?.project_id ?? '—'
|
||||
@@ -159,7 +155,7 @@ function refresh() {
|
||||
watch(taskId, () => { load() })
|
||||
|
||||
onMounted(() => {
|
||||
loadMarkdown() // 后台预热 Markdown 渲染器(模块单例,与 AiChat 共享),不阻塞 load
|
||||
ensureLoaded() // 后台预热 Markdown 渲染器(模块单例,与 AiChat 共享),不阻塞 load
|
||||
load()
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user