feat(web): 更新应用外壳路由视图和政策制度页面,增强前端路由嵌套和页面展示能力

This commit is contained in:
caoxiaozhu
2026-05-15 09:37:42 +00:00
parent 02f54ea208
commit 72ea05ae0d
3 changed files with 150 additions and 48 deletions

View File

@@ -31,6 +31,8 @@ import { resolveKnowledgePreviewLayoutState } from './knowledgePreviewLayout.js'
import { resolveInitialKnowledgeFolder } from './knowledgeFolderSelection.js'
import { buildOnlyOfficePreviewConfig } from './onlyOfficePreviewConfig.js'
const KNOWLEDGE_POLL_INTERVAL_MS = 5000
function triggerFileDownload(blob, filename) {
const url = URL.createObjectURL(blob)
const anchor = document.createElement('a')
@@ -42,6 +44,7 @@ function triggerFileDownload(blob, filename) {
let bodyOverflowSnapshot = ''
let bodyOverscrollBehaviorSnapshot = ''
let libraryPollTimer = 0
function setBodyScrollLocked(isLocked) {
if (typeof document === 'undefined') {
@@ -270,20 +273,26 @@ export default {
}
}
async function loadLibrary(options = {}) {
loading.value = true
try {
const payload = await fetchKnowledgeLibrary()
folders.value = payload.folders || []
documents.value = payload.documents || []
emit('summary-change', { totalDocuments: documents.value.length })
async function loadLibrary(options = {}) {
loading.value = true
try {
const payload = await fetchKnowledgeLibrary()
folders.value = payload.folders || []
documents.value = payload.documents || []
emit('summary-change', { totalDocuments: documents.value.length })
activeFolder.value = resolveInitialKnowledgeFolder(folders.value, activeFolder.value)
if (options.preserveSelection && selectedDocument.value?.id) {
const exists = documents.value.some((doc) => doc.id === selectedDocument.value.id)
const nextDocument = documents.value.find((doc) => doc.id === selectedDocument.value.id)
const exists = Boolean(nextDocument)
if (!exists) {
closePreview()
} else {
selectedDocument.value = {
...selectedDocument.value,
...nextDocument
}
}
}
if (options.preserveSelection && llmWikiDocument.value?.document_id) {
@@ -292,11 +301,12 @@ export default {
closeLlmWikiSummary()
}
}
syncLibraryPolling()
} catch (error) {
emit('summary-change', { totalDocuments: 0 })
toast(error.message || '知识库加载失败。')
} finally {
loading.value = false
emit('summary-change', { totalDocuments: 0 })
toast(error.message || '知识库加载失败。')
} finally {
loading.value = false
}
}
@@ -351,10 +361,37 @@ export default {
...patch
}
}
syncLibraryPolling()
}
function hasSyncingDocuments() {
return documents.value.some((doc) => Number(doc?.stateCode || 0) === 2)
}
function stopLibraryPolling() {
if (libraryPollTimer) {
window.clearInterval(libraryPollTimer)
libraryPollTimer = 0
}
}
function startLibraryPolling() {
stopLibraryPolling()
libraryPollTimer = window.setInterval(() => {
loadLibrary({ preserveSelection: true })
}, KNOWLEDGE_POLL_INTERVAL_MS)
}
function syncLibraryPolling() {
if (hasSyncingDocuments()) {
startLibraryPolling()
return
}
stopLibraryPolling()
}
function resolveIngestActionLabel(document) {
if (ingestingId.value === document.id) {
if (ingestingId.value === document.id || Number(document?.stateCode || 0) === 2) {
return '归纳中'
}
return Number(document?.stateCode || 0) === 3 ? '重新归纳' : '归纳'
@@ -372,27 +409,64 @@ export default {
}
function canViewLlmWiki(document) {
return isAdmin.value && Number(document?.stateCode || 0) === 3
return isAdmin.value && Boolean(document?.llmWikiAvailable)
}
function resolveViewLlmWikiTitle(document) {
if (!isAdmin.value) {
return '仅管理员可查看 LLM Wiki 归纳内容'
}
if (document?.llmWikiAvailable && Number(document?.stateCode || 0) === 4) {
return '查看本次降级归纳结果,仅供人工排查,不能视为正式知识'
}
if (document?.llmWikiAvailable && document?.llmWikiQualityStatus === 'partial_degraded') {
return '查看当前归纳结果,存在部分降级分组,请人工复核'
}
if (document?.llmWikiAvailable) {
return '查看并编辑当前文档的 LLM Wiki 归纳内容'
}
if (Number(document?.stateCode || 0) === 2) {
return 'Hermes 正在归纳当前文档,完成后可查看 LLM Wiki 知识总结'
}
if (Number(document?.stateCode || 0) === 4) {
return '当前文档上次归纳失败,请重新归纳后再查看'
return '当前文档上次归纳失败,且没有可查看的 LLM Wiki 产物'
}
if (Number(document?.stateCode || 0) !== 3) {
return '文档尚未完成归纳,暂无可查看的 LLM Wiki 知识总结'
}
return '查看并编辑当前文档的 LLM Wiki 归纳内容'
return '查看当前文档的 LLM Wiki 归纳内容'
}
function resolveLlmWikiQualityLabel(document) {
const qualityStatus = String(document?.quality_status || '').trim()
if (qualityStatus === 'partial_degraded') {
return '部分降级'
}
if (qualityStatus === 'fallback_only') {
return '降级兜底'
}
if (qualityStatus === 'runtime_only') {
return '非 Hermes 结果'
}
if (qualityStatus === 'failed') {
return '归纳失败'
}
return '正式归纳'
}
function resolveLlmWikiQualityTone(document) {
const qualityStatus = String(document?.quality_status || '').trim()
if (qualityStatus === 'formal') {
return 'success'
}
if (qualityStatus === 'partial_degraded') {
return 'warning'
}
return 'danger'
}
async function handleManualIngest(document) {
if (!isAdmin.value || ingestingId.value || !document?.id) {
if (!isAdmin.value || ingestingId.value || !document?.id || Number(document?.stateCode || 0) === 2) {
return
}
@@ -406,19 +480,13 @@ export default {
try {
const payload = await syncKnowledgeDocumentToLlmWiki({
folder: document.folder,
documentId: document.id
documentId: document.id,
force: true
})
await loadLibrary({ preserveSelection: true })
if (selectedDocument.value?.id === document.id) {
await selectDocument(document.id)
}
toast(payload.summary || 'Hermes 已完成文档归纳。')
toast(payload.summary || 'Hermes 已进入后台归纳。')
} catch (error) {
patchDocumentState(document.id, {
stateCode: 4,
state: '归纳失败',
stateTone: 'danger'
})
await loadLibrary({ preserveSelection: true })
toast(error.message || 'Hermes 归纳文档失败。')
} finally {
ingestingId.value = ''
@@ -654,6 +722,7 @@ export default {
revokePreviewBlob()
destroyOnlyOfficeEditor()
setBodyScrollLocked(false)
stopLibraryPolling()
window.removeEventListener('keydown', handleWindowKeydown)
})
@@ -711,6 +780,8 @@ export default {
selectedDocument,
resolveIngestActionLabel,
resolveIngestActionTitle,
resolveLlmWikiQualityLabel,
resolveLlmWikiQualityTone,
resolveViewLlmWikiTitle,
saveLlmWikiSummary,
totalCount,