引入 Element Plus 主题定制和主题皮肤 composable,将全局 样式拆分为组件级独立 CSS 文件(侧边栏、顶栏、工作台等), 统一色彩变量和间距规范,重构所有视图和组件样式以适配新 主题系统,优化图表和知识图谱组件视觉表现,提取审计和差 旅报销相关子组件。
196 lines
4.4 KiB
Vue
196 lines
4.4 KiB
Vue
<template>
|
|
<section class="ingest-run-info">
|
|
<div class="info-title">
|
|
<div>
|
|
<span>基本信息</span>
|
|
<h4>{{ model.folder || '未指定知识目录' }}</h4>
|
|
</div>
|
|
<strong :class="model.statusTone">{{ model.statusLabel }}</strong>
|
|
</div>
|
|
|
|
<div class="info-grid">
|
|
<div v-for="item in infoItems" :key="item.label" class="info-item">
|
|
<span>{{ item.label }}</span>
|
|
<strong>{{ item.value }}</strong>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed } from 'vue'
|
|
|
|
const props = defineProps({
|
|
run: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
model: {
|
|
type: Object,
|
|
required: true
|
|
}
|
|
})
|
|
|
|
const infoItems = computed(() => {
|
|
const routeJson = props.run?.route_json || {}
|
|
const currentDocument = props.model.documents?.find(
|
|
(item) => item.documentId === props.model.currentDocumentId
|
|
)
|
|
return [
|
|
{ label: 'Trace ID', value: props.run?.run_id || '-' },
|
|
{ label: '任务类型', value: resolveJobType(routeJson.job_type) },
|
|
{ label: '触发来源', value: resolveSource(props.run?.source) },
|
|
{ label: '当前阶段', value: props.model.phaseLabel || '-' },
|
|
{ label: '开始时间', value: formatDateTime(props.run?.started_at) },
|
|
{ label: '结束时间', value: formatDateTime(props.run?.finished_at) },
|
|
{ label: '执行耗时', value: formatElapsed(props.run?.started_at, props.run?.finished_at) },
|
|
{ label: '当前文件', value: currentDocument?.name || '-' }
|
|
]
|
|
})
|
|
|
|
function resolveJobType(value) {
|
|
const jobType = String(value || '').trim()
|
|
if (jobType === 'knowledge_index_sync') return 'LightRAG 知识归纳'
|
|
if (jobType === 'llm_wiki_sync') return 'LLM Wiki 知识归纳'
|
|
return jobType || '-'
|
|
}
|
|
|
|
function resolveSource(value) {
|
|
const source = String(value || '').trim()
|
|
if (source === 'manual') return '手动触发'
|
|
if (source === 'scheduled') return '定时任务'
|
|
if (source === 'system') return '系统任务'
|
|
return source || '-'
|
|
}
|
|
|
|
function formatDateTime(value) {
|
|
if (!value) return '-'
|
|
const date = new Date(value)
|
|
if (Number.isNaN(date.getTime())) return String(value)
|
|
return date.toLocaleString('zh-CN', {
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
hour12: false
|
|
})
|
|
}
|
|
|
|
function formatElapsed(startedAt, finishedAt) {
|
|
const started = new Date(startedAt || '')
|
|
const finished = finishedAt ? new Date(finishedAt) : new Date()
|
|
if (Number.isNaN(started.getTime()) || Number.isNaN(finished.getTime())) return '-'
|
|
const seconds = Math.max(0, Math.round((finished.getTime() - started.getTime()) / 1000))
|
|
if (seconds < 60) return `${seconds}s`
|
|
const minutes = Math.floor(seconds / 60)
|
|
const restSeconds = seconds % 60
|
|
if (minutes < 60) return `${minutes}m ${restSeconds}s`
|
|
const hours = Math.floor(minutes / 60)
|
|
return `${hours}h ${minutes % 60}m`
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.ingest-run-info {
|
|
display: grid;
|
|
gap: 12px;
|
|
padding: 14px;
|
|
border: 1px solid #dbe6ef;
|
|
border-radius: 8px;
|
|
background: #fff;
|
|
}
|
|
|
|
.info-title {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
}
|
|
|
|
.info-title span {
|
|
color: var(--theme-primary-active);
|
|
font-size: 12px;
|
|
font-weight: 850;
|
|
}
|
|
|
|
.info-title h4 {
|
|
margin: 4px 0 0;
|
|
color: #0f172a;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.info-title > strong {
|
|
align-self: flex-start;
|
|
min-height: 26px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 0 9px;
|
|
border-radius: 999px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.info-title > strong.success {
|
|
background: var(--success-soft);
|
|
color: var(--success-active);
|
|
}
|
|
|
|
.info-title > strong.warning {
|
|
background: #fef3c7;
|
|
color: #92400e;
|
|
}
|
|
|
|
.info-title > strong.danger {
|
|
background: #fee2e2;
|
|
color: #991b1b;
|
|
}
|
|
|
|
.info-title > strong.muted {
|
|
background: #eef2f7;
|
|
color: #475569;
|
|
}
|
|
|
|
.info-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
gap: 8px;
|
|
}
|
|
|
|
.info-item {
|
|
min-width: 0;
|
|
display: grid;
|
|
gap: 5px;
|
|
padding: 10px;
|
|
border-radius: 8px;
|
|
background: #f8fafc;
|
|
}
|
|
|
|
.info-item span {
|
|
color: #64748b;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.info-item strong {
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
color: #0f172a;
|
|
font-size: 13px;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
@media (max-width: 1100px) {
|
|
.info-grid {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
}
|
|
|
|
@media (max-width: 620px) {
|
|
.info-title {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.info-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|