feat(web): 更新应用外壳路由视图和政策制度页面,增强前端路由嵌套和页面展示能力
This commit is contained in:
@@ -16,25 +16,28 @@
|
||||
'overview-main': activeView === 'overview',
|
||||
'workbench-main': activeView === 'workbench',
|
||||
'requests-main': activeView === 'requests',
|
||||
'approval-main': activeView === 'approval',
|
||||
'approval-main': activeView === 'approval',
|
||||
'policies-main': activeView === 'policies',
|
||||
'audit-main': activeView === 'audit',
|
||||
'audit-detail-main': activeView === 'audit' && auditDetailOpen,
|
||||
'logs-main': activeView === 'logs',
|
||||
'employees-main': activeView === 'employees',
|
||||
'settings-main': activeView === 'settings'
|
||||
}"
|
||||
>
|
||||
<TopBar
|
||||
v-if="activeView !== 'settings' && !(activeView === 'audit' && auditDetailOpen)"
|
||||
:current-view="topBarView"
|
||||
:search="search"
|
||||
:active-view="activeView"
|
||||
:ranges="ranges"
|
||||
:current-view="topBarView"
|
||||
:search="search"
|
||||
:active-view="activeView"
|
||||
:ranges="ranges"
|
||||
:active-range="activeRange"
|
||||
:employee-summary="employeeSummary"
|
||||
:knowledge-summary="knowledgeSummary"
|
||||
:logs-summary="logsSummary"
|
||||
:request-summary="requestSummary"
|
||||
:detail-mode="detailMode"
|
||||
:log-detail-mode="logDetailMode"
|
||||
:detail-alerts="detailAlerts"
|
||||
:custom-range="customRange"
|
||||
@update:search="search = $event"
|
||||
@@ -45,7 +48,7 @@
|
||||
/>
|
||||
|
||||
<FilterBar
|
||||
v-if="activeView !== 'overview' && activeView !== 'workbench' && activeView !== 'requests' && activeView !== 'approval' && activeView !== 'policies' && activeView !== 'audit' && activeView !== 'employees' && activeView !== 'settings'"
|
||||
v-if="activeView !== 'overview' && activeView !== 'workbench' && activeView !== 'requests' && activeView !== 'approval' && activeView !== 'policies' && activeView !== 'audit' && activeView !== 'logs' && activeView !== 'employees' && activeView !== 'settings'"
|
||||
:compact="activeView === 'overview'"
|
||||
:filters="filters"
|
||||
:ranges="ranges"
|
||||
@@ -59,10 +62,11 @@
|
||||
'requests-workarea': activeView === 'requests',
|
||||
'approval-workarea': activeView === 'approval',
|
||||
'policies-workarea': activeView === 'policies',
|
||||
'audit-workarea': activeView === 'audit',
|
||||
'employees-workarea': activeView === 'employees',
|
||||
'settings-workarea': activeView === 'settings'
|
||||
}"
|
||||
'audit-workarea': activeView === 'audit',
|
||||
'logs-workarea': activeView === 'logs',
|
||||
'employees-workarea': activeView === 'employees',
|
||||
'settings-workarea': activeView === 'settings'
|
||||
}"
|
||||
>
|
||||
<OverviewView
|
||||
v-if="activeView === 'overview'"
|
||||
@@ -99,11 +103,13 @@
|
||||
@create-request="openTravelCreate"
|
||||
/>
|
||||
|
||||
<ApprovalCenterView v-else-if="activeView === 'approval'" />
|
||||
<PoliciesView v-else-if="activeView === 'policies'" @summary-change="knowledgeSummary = $event" />
|
||||
<ApprovalCenterView v-else-if="activeView === 'approval'" />
|
||||
<PoliciesView v-else-if="activeView === 'policies'" @summary-change="knowledgeSummary = $event" />
|
||||
<AuditView v-else-if="activeView === 'audit'" @detail-open-change="auditDetailOpen = $event" />
|
||||
<EmployeeManagementView v-else-if="activeView === 'employees'" @overview-change="employeeSummary = $event" />
|
||||
<SettingsView v-else />
|
||||
<LogDetailView v-else-if="activeView === 'logs' && logDetailMode" />
|
||||
<LogsView v-else-if="activeView === 'logs'" @summary-change="logsSummary = $event" />
|
||||
<EmployeeManagementView v-else-if="activeView === 'employees'" @overview-change="employeeSummary = $event" />
|
||||
<SettingsView v-else />
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@@ -133,10 +139,12 @@ import TravelReimbursementCreateView from './TravelReimbursementCreateView.vue'
|
||||
import TravelRequestDetailView from './TravelRequestDetailView.vue'
|
||||
import RequestsView from './RequestsView.vue'
|
||||
import ApprovalCenterView from './ApprovalCenterView.vue'
|
||||
import PoliciesView from './PoliciesView.vue'
|
||||
import AuditView from './AuditView.vue'
|
||||
import EmployeeManagementView from './EmployeeManagementView.vue'
|
||||
import SettingsView from './SettingsView.vue'
|
||||
import PoliciesView from './PoliciesView.vue'
|
||||
import AuditView from './AuditView.vue'
|
||||
import LogsView from './LogsView.vue'
|
||||
import LogDetailView from './LogDetailView.vue'
|
||||
import EmployeeManagementView from './EmployeeManagementView.vue'
|
||||
import SettingsView from './SettingsView.vue'
|
||||
|
||||
import { useAppShell } from '../composables/useAppShell.js'
|
||||
import { useSystemState } from '../composables/useSystemState.js'
|
||||
@@ -144,6 +152,7 @@ import { filterNavItemsByAccess } from '../utils/accessControl.js'
|
||||
|
||||
const employeeSummary = ref(null)
|
||||
const knowledgeSummary = ref(null)
|
||||
const logsSummary = ref(null)
|
||||
const auditDetailOpen = ref(false)
|
||||
|
||||
const {
|
||||
@@ -154,6 +163,7 @@ const {
|
||||
customRange,
|
||||
detailAlerts,
|
||||
detailMode,
|
||||
logDetailMode,
|
||||
filteredRequests,
|
||||
filters,
|
||||
handleApprove,
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
v-if="isAdmin"
|
||||
class="more-btn ingest"
|
||||
type="button"
|
||||
:disabled="Boolean(ingestingId) || deletingId === doc.id"
|
||||
:disabled="Boolean(ingestingId) || deletingId === doc.id || Number(doc.stateCode || 0) === 2"
|
||||
:aria-label="resolveIngestActionTitle(doc)"
|
||||
:title="resolveIngestActionTitle(doc)"
|
||||
@click="handleManualIngest(doc)"
|
||||
@@ -356,6 +356,21 @@
|
||||
<div v-else-if="llmWikiError" class="preview-status error">{{ llmWikiError }}</div>
|
||||
<div v-else-if="llmWikiDocument" class="llm-wiki-grid">
|
||||
<section class="llm-wiki-section llm-wiki-summary-section">
|
||||
<div
|
||||
v-if="llmWikiDocument.quality_status !== 'formal'"
|
||||
class="llm-wiki-alert"
|
||||
:class="resolveLlmWikiQualityTone(llmWikiDocument)"
|
||||
>
|
||||
<strong>{{ resolveLlmWikiQualityLabel(llmWikiDocument) }}</strong>
|
||||
<p>{{ llmWikiDocument.quality_note || '当前展示内容不是正式 Hermes 归纳,请人工复核后再使用。' }}</p>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="llmWikiDocument.quality_note"
|
||||
class="llm-wiki-alert info"
|
||||
>
|
||||
<strong>{{ resolveLlmWikiQualityLabel(llmWikiDocument) }}</strong>
|
||||
<p>{{ llmWikiDocument.quality_note }}</p>
|
||||
</div>
|
||||
<div class="llm-wiki-section-head">
|
||||
<div>
|
||||
<h3>知识总结</h3>
|
||||
@@ -363,6 +378,12 @@
|
||||
</div>
|
||||
<span class="llm-wiki-count">{{ llmWikiDocument.knowledge_candidate_count }} 条知识</span>
|
||||
</div>
|
||||
<div class="llm-wiki-stat-grid">
|
||||
<span>正文分块 {{ llmWikiDocument.candidate_chunk_count }}</span>
|
||||
<span>过滤分块 {{ llmWikiDocument.filtered_chunk_count }}</span>
|
||||
<span>成功分组 {{ llmWikiDocument.successful_group_count }}/{{ llmWikiDocument.group_count }}</span>
|
||||
<span>正式知识 {{ llmWikiDocument.formal_knowledge_candidate_count }}</span>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="llmWikiSummaryDraft"
|
||||
class="llm-wiki-editor"
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user