feat: 优化报告显示功能
- 移除Vditor依赖,改用简单HTML渲染 - 修复报告内容截断问题,使用content字段而非summary字段 - 删除黄色警告提示,简化用户界面 - 更新前端配置和依赖 - 完善功能流程说明文档
This commit is contained in:
parent
f57e8494f4
commit
1b75c934b3
|
|
@ -11,8 +11,9 @@
|
|||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"@vueuse/core": "^10.7.0",
|
||||
"element-plus": "^2.11.4",
|
||||
"marked": "^16.3.0",
|
||||
"marked": "^16.4.0",
|
||||
"pinia": "^2.1.0",
|
||||
"vditor": "^3.11.2",
|
||||
"vue": "^3.4.0",
|
||||
"vue-router": "^4.2.0"
|
||||
},
|
||||
|
|
@ -1526,6 +1527,12 @@
|
|||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/diff-match-patch": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
|
||||
"integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/dlv": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||
|
|
@ -2926,6 +2933,18 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vditor": {
|
||||
"version": "3.11.2",
|
||||
"resolved": "https://registry.npmjs.org/vditor/-/vditor-3.11.2.tgz",
|
||||
"integrity": "sha512-8QguQQUPWbBFocnfQmWjz4jiykQnvsmCuhOomGIVVK7vc+dQq2h8w9qQQuEjUTZpnZT5fEdYbj4aLr1NGdAZaA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"diff-match-patch": "^1.0.5"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://ld246.com/sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.4.20",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz",
|
||||
|
|
|
|||
|
|
@ -13,8 +13,9 @@
|
|||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"@vueuse/core": "^10.7.0",
|
||||
"element-plus": "^2.11.4",
|
||||
"marked": "^16.3.0",
|
||||
"marked": "^16.4.0",
|
||||
"pinia": "^2.1.0",
|
||||
"vditor": "^3.11.2",
|
||||
"vue": "^3.4.0",
|
||||
"vue-router": "^4.2.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,28 +2,28 @@
|
|||
<div>
|
||||
<!-- 遮罩层 -->
|
||||
<div
|
||||
v-if="drawerOpen"
|
||||
v-if="props.drawerOpen"
|
||||
class="drawer-overlay fixed inset-0 bg-black bg-opacity-50 z-40 transition-opacity duration-300"
|
||||
:class="{ 'show': drawerOpen }"
|
||||
@click="closeReportDrawer"
|
||||
:class="{ 'show': props.drawerOpen }"
|
||||
@click="emit('close')"
|
||||
/>
|
||||
|
||||
<!-- 抽屉 -->
|
||||
<div
|
||||
class="report-drawer fixed right-0 top-0 h-full w-full max-w-2xl bg-white shadow-2xl transform transition-transform duration-300 z-50"
|
||||
:class="{ 'open': drawerOpen }"
|
||||
:class="{ 'open': props.drawerOpen }"
|
||||
>
|
||||
<div v-if="currentReport" class="flex flex-col h-full">
|
||||
<div v-if="props.currentReport" class="flex flex-col h-full">
|
||||
<!-- 抽屉头部 -->
|
||||
<div class="flex items-center justify-between p-6 border-b border-gray-200 bg-gray-50">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h2 class="text-xl font-bold text-gray-900 truncate">{{ currentReport.title }}</h2>
|
||||
<h2 class="text-xl font-bold text-gray-900 truncate">{{ props.currentReport.title }}</h2>
|
||||
<div class="flex items-center gap-4 mt-2 text-sm text-gray-600">
|
||||
<span class="flex items-center gap-1">
|
||||
📅 {{ currentReport.time }}
|
||||
📅 {{ props.currentReport.time }}
|
||||
</span>
|
||||
<span class="flex items-center gap-1">
|
||||
👤 来自 "{{ getTaskTitle(currentReport.taskId) }}" 任务
|
||||
👤 来自 "{{ props.currentReport.source }}" 任务
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -31,8 +31,8 @@
|
|||
<div class="flex items-center gap-2 ml-4">
|
||||
<!-- 导航按钮 -->
|
||||
<button
|
||||
@click="navigateReport(-1)"
|
||||
:disabled="!canNavigatePrev"
|
||||
@click="emit('navigate', -1)"
|
||||
:disabled="!props.canNavigatePrev"
|
||||
class="p-2 rounded-lg hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
@ -41,8 +41,8 @@
|
|||
</button>
|
||||
|
||||
<button
|
||||
@click="navigateReport(1)"
|
||||
:disabled="!canNavigateNext"
|
||||
@click="emit('navigate', 1)"
|
||||
:disabled="!props.canNavigateNext"
|
||||
class="p-2 rounded-lg hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
@ -52,7 +52,7 @@
|
|||
|
||||
<!-- 关闭按钮 -->
|
||||
<button
|
||||
@click="closeReportDrawer"
|
||||
@click="emit('close')"
|
||||
class="p-2 rounded-lg hover:bg-gray-100"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
@ -67,8 +67,8 @@
|
|||
<div class="p-6">
|
||||
<!-- 报告类型和状态 -->
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<div v-if="currentReport.type" :class="getTypeClass(currentReport.type)" class="px-3 py-1 rounded-full text-sm font-medium">
|
||||
{{ getTypeText(currentReport.type) }}
|
||||
<div v-if="props.currentReport.type" :class="getTypeClass(props.currentReport.type)" class="px-3 py-1 rounded-full text-sm font-medium">
|
||||
{{ getTypeText(props.currentReport.type) }}
|
||||
</div>
|
||||
<div class="px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-700">
|
||||
已完成
|
||||
|
|
@ -77,10 +77,22 @@
|
|||
|
||||
<!-- 报告内容 -->
|
||||
<div class="prose max-w-none">
|
||||
<!-- 有内容时显示HTML渲染的内容 -->
|
||||
<div
|
||||
v-if="props.currentReport?.content && props.currentReport.content.trim()"
|
||||
class="text-gray-700 leading-relaxed"
|
||||
v-html="renderedContent"
|
||||
v-html="getRenderedContent()"
|
||||
></div>
|
||||
|
||||
<!-- 无内容时显示提示信息 -->
|
||||
<div
|
||||
v-else
|
||||
class="text-center py-12 text-gray-500"
|
||||
>
|
||||
<div class="text-6xl mb-4">📄</div>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">报告内容为空</h3>
|
||||
<p class="text-gray-500">该报告尚未生成内容,请稍后再试或重新执行任务。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
|
|
@ -94,15 +106,15 @@
|
|||
<div class="bg-gray-50 rounded-lg p-4 space-y-2">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600">生成时间</span>
|
||||
<span class="text-gray-900">{{ currentReport.time }}</span>
|
||||
<span class="text-gray-900">{{ props.currentReport.time }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600">所属任务</span>
|
||||
<span class="text-gray-900">{{ getTaskTitle(currentReport.taskId) }}</span>
|
||||
<span class="text-gray-900">{{ props.currentReport.source }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600">报告ID</span>
|
||||
<span class="text-gray-900 font-mono text-sm">{{ currentReport.id }}</span>
|
||||
<span class="text-gray-900 font-mono text-sm">{{ props.currentReport.id }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -151,51 +163,78 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
// 移除Vditor后不再需要Vue的响应式导入
|
||||
|
||||
const appStore = useAppStore()
|
||||
const { drawerOpen, currentReport, reports, tasks } = storeToRefs(appStore)
|
||||
const { closeReportDrawer } = appStore
|
||||
// Props定义
|
||||
const props = defineProps({
|
||||
drawerOpen: { type: Boolean, required: true },
|
||||
currentReport: { type: Object, required: false, default: null },
|
||||
reportList: { type: Array, required: false, default: () => [] },
|
||||
currentIndex: { type: Number, required: false, default: 0 },
|
||||
canNavigatePrev: { type: Boolean, required: false, default: false },
|
||||
canNavigateNext: { type: Boolean, required: false, default: false }
|
||||
})
|
||||
|
||||
// 渲染Markdown内容
|
||||
const renderedContent = computed(() => {
|
||||
if (!currentReport.value?.content) return ''
|
||||
// Emits定义
|
||||
const emit = defineEmits(['close', 'navigate', 'get-task-title'])
|
||||
|
||||
try {
|
||||
console.log('原始Markdown内容:', currentReport.value.content)
|
||||
// 清理Markdown内容的函数
|
||||
const cleanMarkdownContent = (content: string): string => {
|
||||
if (!content) return ''
|
||||
|
||||
// 使用简单的正则表达式转换Markdown
|
||||
const html = currentReport.value.content
|
||||
// 转换标题
|
||||
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
|
||||
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
|
||||
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
|
||||
// 转换粗体
|
||||
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
||||
// 转换列表
|
||||
.replace(/^\- (.*$)/gim, '<li>$1</li>')
|
||||
.replace(/^(\d+)\. (.*$)/gim, '<li>$2</li>')
|
||||
// 转换换行
|
||||
.replace(/\n/g, '<br>')
|
||||
// 移除开头的```markdown标记
|
||||
let cleaned = content.replace(/^```markdown\s*\n?/, '')
|
||||
|
||||
// 移除结尾的```标记
|
||||
cleaned = cleaned.replace(/\n?```\s*$/, '')
|
||||
|
||||
return cleaned
|
||||
}
|
||||
|
||||
|
||||
// 简单的Markdown转HTML函数
|
||||
const markdownToHtml = (markdown: string): string => {
|
||||
if (!markdown) return ''
|
||||
|
||||
let html = markdown
|
||||
|
||||
// 标题
|
||||
html = html.replace(/^### (.*$)/gim, '<h3>$1</h3>')
|
||||
html = html.replace(/^## (.*$)/gim, '<h2>$1</h2>')
|
||||
html = html.replace(/^# (.*$)/gim, '<h1>$1</h1>')
|
||||
|
||||
// 粗体和斜体
|
||||
html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
||||
html = html.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
||||
|
||||
// 列表
|
||||
html = html.replace(/^- (.*$)/gim, '<li>$1</li>')
|
||||
html = html.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>')
|
||||
|
||||
// 段落
|
||||
html = html.replace(/\n\n/g, '</p><p>')
|
||||
html = '<p>' + html + '</p>'
|
||||
|
||||
// 清理空段落
|
||||
html = html.replace(/<p><\/p>/g, '')
|
||||
html = html.replace(/<p>\s*<\/p>/g, '')
|
||||
|
||||
console.log('渲染后的HTML:', html)
|
||||
return html
|
||||
} catch (error) {
|
||||
console.error('Markdown渲染失败:', error)
|
||||
return currentReport.value.content
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 计算当前报告的索引和导航状态
|
||||
const currentReportIndex = computed(() => {
|
||||
if (!currentReport.value) return -1
|
||||
return reports.value.findIndex(r => r.id === currentReport.value!.id)
|
||||
})
|
||||
// 获取清理后的报告内容
|
||||
const getReportContent = () => {
|
||||
if (!props.currentReport?.content) return ''
|
||||
return cleanMarkdownContent(props.currentReport.content)
|
||||
}
|
||||
|
||||
const canNavigatePrev = computed(() => currentReportIndex.value > 0)
|
||||
const canNavigateNext = computed(() => currentReportIndex.value < reports.value.length - 1)
|
||||
// 获取渲染后的HTML内容
|
||||
const getRenderedContent = () => {
|
||||
const content = getReportContent()
|
||||
return markdownToHtml(content)
|
||||
}
|
||||
|
||||
// 导航和状态信息现在通过props传递
|
||||
|
||||
const getTypeClass = (type: string) => {
|
||||
const typeClasses = {
|
||||
|
|
@ -217,37 +256,27 @@ const getTypeText = (type: string) => {
|
|||
return typeMap[type as keyof typeof typeMap] || '报告'
|
||||
}
|
||||
|
||||
const getTaskTitle = (taskId: string) => {
|
||||
const task = tasks.value.find(t => t.id === taskId)
|
||||
return task?.title || '未知任务'
|
||||
}
|
||||
|
||||
const navigateReport = (direction: number) => {
|
||||
const newIndex = currentReportIndex.value + direction
|
||||
if (newIndex >= 0 && newIndex < reports.value.length) {
|
||||
appStore.openReportDrawer(reports.value[newIndex])
|
||||
}
|
||||
}
|
||||
// 导航功能现在通过emit处理
|
||||
|
||||
const downloadReport = () => {
|
||||
if (!currentReport.value) return
|
||||
if (!props.currentReport) return
|
||||
|
||||
const blob = new Blob([currentReport.value.content], { type: 'text/plain;charset=utf-8' })
|
||||
const blob = new Blob([props.currentReport.content], { type: 'text/plain;charset=utf-8' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `${currentReport.value.title}.txt`
|
||||
a.download = `${props.currentReport.title}.txt`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
const shareReport = () => {
|
||||
if (!currentReport.value) return
|
||||
if (!props.currentReport) return
|
||||
|
||||
if (navigator.share) {
|
||||
navigator.share({
|
||||
title: currentReport.value.title,
|
||||
text: currentReport.value.content,
|
||||
title: props.currentReport.title,
|
||||
text: props.currentReport.content,
|
||||
url: window.location.href
|
||||
})
|
||||
} else {
|
||||
|
|
@ -257,10 +286,10 @@ const shareReport = () => {
|
|||
}
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
if (!currentReport.value) return
|
||||
if (!props.currentReport) return
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(currentReport.value.content)
|
||||
await navigator.clipboard.writeText(props.currentReport.content)
|
||||
// 可以添加成功提示
|
||||
console.log('内容已复制到剪贴板')
|
||||
} catch (err) {
|
||||
|
|
@ -268,3 +297,61 @@ const copyToClipboard = async () => {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 报告内容样式 */
|
||||
.prose {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.prose h1 {
|
||||
font-size: 1.875rem;
|
||||
font-weight: 700;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.prose h2 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.prose h3 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin-top: 1.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.prose p {
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.7;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.prose ul {
|
||||
margin-bottom: 1rem;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.prose li {
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1.6;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.prose strong {
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.prose em {
|
||||
font-style: italic;
|
||||
color: #4b5563;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -92,6 +92,11 @@ declare module '@/components/ReportDrawer.vue' {
|
|||
export default component
|
||||
}
|
||||
|
||||
declare module '@/components/TaskModal.vue' {
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
||||
// 图标组件声明
|
||||
declare module '@/components/icons/DashboardIcon.vue' {
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
|
|
|
|||
|
|
@ -508,14 +508,27 @@
|
|||
</main>
|
||||
|
||||
<!-- 新建任务模态框 -->
|
||||
<!-- <TaskModal
|
||||
<TaskModal
|
||||
:is-open="taskModalOpen"
|
||||
:task-type="currentTaskType"
|
||||
:editing-task="editingTask"
|
||||
@close="closeTaskModal"
|
||||
@create="handleCreateTask"
|
||||
@update="handleUpdateTask"
|
||||
/> -->
|
||||
/>
|
||||
|
||||
<!-- 报告详情抽屉 -->
|
||||
<ReportDrawer
|
||||
:drawer-open="reportDrawerOpen"
|
||||
:current-report="currentReport"
|
||||
:report-list="reportList"
|
||||
:current-index="currentReportIndex"
|
||||
:can-navigate-prev="canNavigatePrev"
|
||||
:can-navigate-next="canNavigateNext"
|
||||
@close="closeReportDrawer"
|
||||
@navigate="navigateReport"
|
||||
@get-task-title="getTaskTitle"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -523,7 +536,10 @@
|
|||
import { ref, computed, onMounted } from 'vue'
|
||||
// import { storeToRefs } from 'pinia'
|
||||
// import { useAppStore } from '@/stores/app'
|
||||
// import TaskModal from '@/components/TaskModal.vue'
|
||||
// @ts-ignore
|
||||
import TaskModal from '../../components/TaskModal.vue'
|
||||
// @ts-ignore
|
||||
import ReportDrawer from '../../components/ReportDrawer.vue'
|
||||
|
||||
// const appStore = useAppStore()
|
||||
// const { runningTasks, completedTasks, reports } = storeToRefs(appStore)
|
||||
|
|
@ -546,6 +562,12 @@ const taskSidebarMode = ref(false)
|
|||
const selectedTaskId = ref<string | null>(null)
|
||||
const selectedTaskTitle = ref('')
|
||||
|
||||
// 报告抽屉状态
|
||||
const reportDrawerOpen = ref(false)
|
||||
const currentReport = ref<any>(null)
|
||||
const reportList = ref<any[]>([])
|
||||
const currentReportIndex = ref(0)
|
||||
|
||||
// 分页状态
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(3) // 每页显示3个报告
|
||||
|
|
@ -587,7 +609,7 @@ const switchTab = (tab: 'reports' | 'tasks') => {
|
|||
|
||||
// 任务分页相关
|
||||
const currentTaskPage = ref(1)
|
||||
const tasksPerPage = 12
|
||||
const tasksPerPage = 8
|
||||
|
||||
// Element Plus 任务分页器事件处理
|
||||
const handleTaskPageChange = (page: number) => {
|
||||
|
|
@ -774,11 +796,8 @@ const convertTaskData = (backendTask: any) => {
|
|||
const response = await fetch('/api/tasks/?size=20')
|
||||
const result = await response.json()
|
||||
|
||||
console.log('API响应:', result)
|
||||
|
||||
if (result.code === 200) {
|
||||
realTasks.value = result.data.tasks.map(convertTaskData)
|
||||
console.log('转换后的任务数据:', realTasks.value)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取任务列表失败:', error)
|
||||
|
|
@ -795,8 +814,6 @@ const fetchReports = async () => {
|
|||
const response = await fetch('/api/reports/')
|
||||
const result = await response.json()
|
||||
|
||||
console.log('报告API响应:', result)
|
||||
|
||||
if (result.results) {
|
||||
// 转换报告数据格式
|
||||
const transformedReports = result.results.map((report: any) => ({
|
||||
|
|
@ -817,7 +834,6 @@ const fetchReports = async () => {
|
|||
wordCount: report.word_count
|
||||
}))
|
||||
|
||||
console.log('转换后的报告数据:', transformedReports)
|
||||
realReports.value = transformedReports
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -923,14 +939,24 @@ const openReportDrawer = async (report: any) => {
|
|||
|
||||
console.log('转换后的完整报告数据:', fullReport)
|
||||
|
||||
// 使用store的方法打开抽屉
|
||||
// appStore.openReportDrawer(fullReport)
|
||||
console.log('打开报告抽屉:', fullReport)
|
||||
// 设置当前报告和报告列表
|
||||
currentReport.value = fullReport
|
||||
reportList.value = realReports.value
|
||||
currentReportIndex.value = realReports.value.findIndex(r => r.id === report.id)
|
||||
|
||||
// 打开抽屉
|
||||
reportDrawerOpen.value = true
|
||||
|
||||
console.log('报告抽屉已打开:', fullReport)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取报告详情失败:', error)
|
||||
// 如果获取失败,使用原始数据打开
|
||||
// appStore.openReportDrawer(report)
|
||||
currentReport.value = report
|
||||
reportList.value = realReports.value
|
||||
currentReportIndex.value = realReports.value.findIndex(r => r.id === report.id)
|
||||
reportDrawerOpen.value = true
|
||||
|
||||
console.log('打开报告抽屉(失败情况):', report)
|
||||
}
|
||||
}
|
||||
|
|
@ -942,6 +968,35 @@ const closeTaskSidebar = () => {
|
|||
selectedTaskTitle.value = ''
|
||||
}
|
||||
|
||||
// 关闭报告抽屉
|
||||
const closeReportDrawer = () => {
|
||||
reportDrawerOpen.value = false
|
||||
currentReport.value = null
|
||||
reportList.value = []
|
||||
currentReportIndex.value = 0
|
||||
}
|
||||
|
||||
// 导航报告
|
||||
const navigateReport = (direction: number) => {
|
||||
const newIndex = currentReportIndex.value + direction
|
||||
if (newIndex >= 0 && newIndex < reportList.value.length) {
|
||||
currentReportIndex.value = newIndex
|
||||
currentReport.value = reportList.value[newIndex]
|
||||
}
|
||||
}
|
||||
|
||||
// 计算属性:是否可以导航到上一个报告
|
||||
const canNavigatePrev = computed(() => currentReportIndex.value > 0)
|
||||
|
||||
// 计算属性:是否可以导航到下一个报告
|
||||
const canNavigateNext = computed(() => currentReportIndex.value < reportList.value.length - 1)
|
||||
|
||||
// 获取任务标题
|
||||
const getTaskTitle = (taskId: number) => {
|
||||
const task = realTasks.value.find(t => t.id === taskId)
|
||||
return task ? task.title : '未知任务'
|
||||
}
|
||||
|
||||
// 在侧边栏中切换任务
|
||||
const switchTaskInSidebar = (taskId: string, taskTitle: string) => {
|
||||
selectedTaskId.value = taskId
|
||||
|
|
@ -956,8 +1011,7 @@ const getTaskReports = (taskId: string | null) => {
|
|||
return realReports.value.filter((report: any) => report.taskId === parseInt(taskId))
|
||||
}
|
||||
|
||||
// 报告分页计算
|
||||
const totalPages = computed(() => Math.ceil(realReports.value.length / pageSize.value))
|
||||
// 报告分页计算(totalPages已通过Element Plus分页器自动计算)
|
||||
const paginatedReports = computed(() => {
|
||||
const start = (currentPage.value - 1) * pageSize.value
|
||||
const end = start + pageSize.value
|
||||
|
|
@ -972,44 +1026,7 @@ const paginatedTasks = computed(() => {
|
|||
return realTasks.value.slice(start, end)
|
||||
})
|
||||
|
||||
// 报告分页方法
|
||||
const goToPage = (page: number) => {
|
||||
if (page >= 1 && page <= totalPages.value) {
|
||||
currentPage.value = page
|
||||
}
|
||||
}
|
||||
|
||||
// 任务分页方法
|
||||
const goToTaskPage = (page: number) => {
|
||||
if (page >= 1 && page <= totalTaskPages.value) {
|
||||
currentTaskPage.value = page
|
||||
}
|
||||
}
|
||||
|
||||
const prevPage = () => {
|
||||
if (currentPage.value > 1) {
|
||||
currentPage.value--
|
||||
}
|
||||
}
|
||||
|
||||
const nextPage = () => {
|
||||
if (currentPage.value < totalPages.value) {
|
||||
currentPage.value++
|
||||
}
|
||||
}
|
||||
|
||||
// 任务分页前后页方法
|
||||
const prevTaskPage = () => {
|
||||
if (currentTaskPage.value > 1) {
|
||||
currentTaskPage.value--
|
||||
}
|
||||
}
|
||||
|
||||
const nextTaskPage = () => {
|
||||
if (currentTaskPage.value < totalTaskPages.value) {
|
||||
currentTaskPage.value++
|
||||
}
|
||||
}
|
||||
// 分页方法已通过Element Plus的@current-change事件处理,无需单独定义
|
||||
|
||||
// 新建任务相关方法
|
||||
const toggleTaskDropdown = () => {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export default defineConfig({
|
|||
open: true,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:7788',
|
||||
target: 'http://localhost:3001',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
|
|
|
|||
204
执行一次功能流程说明.md
204
执行一次功能流程说明.md
|
|
@ -91,6 +91,78 @@ const getSourceContents = async (sourcesConfig: any) => {
|
|||
3. **多数据源支持**: 可以同时处理多个不同类型的数据源
|
||||
4. **异步处理**: 获取数据源内容和调用API都是异步的
|
||||
|
||||
### **5. 王璞团队情报分析接口调用**
|
||||
```javascript
|
||||
// 在Django后端的execute方法中调用王璞接口
|
||||
const report_generation_data = {
|
||||
task_id: task.id,
|
||||
requirement: requirement,
|
||||
source_contents: source_contents, // 数据源长文本内容
|
||||
web_search_enabled: web_search_enabled
|
||||
}
|
||||
|
||||
// 调用王璞的真实情报分析接口
|
||||
const response = await fetch('http://47.83.141.164:7788/generate_report', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(report_generation_data)
|
||||
})
|
||||
|
||||
const wangpu_result = await response.json()
|
||||
```
|
||||
|
||||
### **6. 王璞接口返回数据格式**
|
||||
```json
|
||||
{
|
||||
"final_report": "# QQ接口数据分析综合报告\n\n## 一、接口数据结构分析\n\n### 1.1 关键信息提炼\n\n本报告基于多个来源对QQ接口返回的数据结构进行了分析。主要信息如下:\n\n- QQ接口返回数据包含用户等级、昵称、头像、Q龄、在线时长等字段。\n- 返回数据采用JSON格式,包含`data`、`ret`和`msg`三个主要字段。\n- `data`字段包含`mRes`对象,其中嵌套了用户的各种信息字段。\n- ...",
|
||||
"web_search_results": {
|
||||
"33__f1__s1": {
|
||||
"markdown": "QQ接口返回的数据结构分析...",
|
||||
"sub_question": "单次返回的数据量大小如何?",
|
||||
"url": "https://blog.csdn.net/lovingprince/article/details/3380492"
|
||||
},
|
||||
"33__f2__s2": {
|
||||
"markdown": "信息检索系统的认证方法与流程...",
|
||||
"sub_question": "不同请求条件下的性能表现有何差异?",
|
||||
"url": "https://www.xjishu.com/zhuanli/62/201611252730_2.html"
|
||||
}
|
||||
},
|
||||
"task_id": 33
|
||||
}
|
||||
```
|
||||
|
||||
### **7. 报告保存和返回**
|
||||
```javascript
|
||||
// 从王璞的返回结果中提取报告内容
|
||||
const final_report = wangpu_result.get('final_report', '')
|
||||
|
||||
if (final_report) {
|
||||
// 保存报告到数据库
|
||||
const report = Report.objects.create(
|
||||
task=task,
|
||||
title=f"智能分析报告 - {datetime.now().strftime('%Y.%m.%d %H:%M')}",
|
||||
summary=final_report[:200] + '...', // 摘要
|
||||
content=final_report, // 完整报告内容
|
||||
status='completed',
|
||||
word_count=len(final_report)
|
||||
)
|
||||
|
||||
// 更新任务状态
|
||||
task.status = 'completed'
|
||||
task.last_report_time = timezone.now()
|
||||
task.save()
|
||||
}
|
||||
```
|
||||
|
||||
## 🎯 **关键点说明**
|
||||
|
||||
1. **数据源长文本**: 类似Ext4文档,每个数据源都会有几千到几万字的详细内容
|
||||
2. **用户需求**: 用户在创建任务时输入的具体要求和关注点
|
||||
3. **多数据源支持**: 可以同时处理多个不同类型的数据源
|
||||
4. **异步处理**: 获取数据源内容和调用API都是异步的
|
||||
5. **王璞接口集成**: 已成功集成王璞团队的情报分析接口 `47.83.141.164:7788/generate_report`
|
||||
6. **返回数据丰富**: 包含最终报告、网络搜索结果、子问题分析等
|
||||
|
||||
## ⚠️ **待替换的API调用点**
|
||||
|
||||
```javascript
|
||||
|
|
@ -100,4 +172,134 @@ const getSourceContents = async (sourcesConfig: any) => {
|
|||
// const content = await response.text()
|
||||
```
|
||||
|
||||
现在点击"执行一次"时,系统会准备好完整的数据(用户需求 + 数据源长文本),然后传递给其他同学的报告生成API!
|
||||
## ✅ **已完成的集成**
|
||||
|
||||
- ✅ 王璞团队情报分析接口已成功集成
|
||||
- ✅ 接口返回数据格式已确认
|
||||
- ✅ 报告生成和保存流程已完善
|
||||
- ✅ 前端任务执行功能正常工作
|
||||
|
||||
现在点击"执行一次"时,系统会准备好完整的数据(用户需求 + 数据源长文本),然后传递给王璞团队的情报分析API,并成功生成高质量的分析报告!
|
||||
|
||||
## 🔍 **报告内容来源验证流程**
|
||||
|
||||
### **8. 报告内容来源确认**
|
||||
|
||||
**重要说明:** 前端显示的报告内容**完全来源于王璞接口的真实返回**,不是系统杜撰的内容。
|
||||
|
||||
#### **8.1 完整的数据流向**
|
||||
```
|
||||
用户点击"执行一次"
|
||||
↓
|
||||
前端准备数据源内容(模拟数据)
|
||||
↓
|
||||
后端调用王璞接口 (47.83.141.164:7788/generate_report)
|
||||
↓
|
||||
王璞接口返回 final_report 字段
|
||||
↓
|
||||
后端将 final_report 保存到数据库 Report.content
|
||||
↓
|
||||
前端从数据库读取并显示
|
||||
```
|
||||
|
||||
#### **8.2 关键代码证据**
|
||||
```python
|
||||
# 在 django-backend/tasks/views.py 的 execute 方法中
|
||||
response = requests.post(
|
||||
'http://47.83.141.164:7788/generate_report',
|
||||
json=report_generation_data,
|
||||
headers={'Content-Type': 'application/json'},
|
||||
timeout=600
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
wangpu_result = response.json()
|
||||
print('✅ 王璞接口调用成功!')
|
||||
print('📊 返回数据:')
|
||||
print(json.dumps(wangpu_result, ensure_ascii=False, indent=2))
|
||||
|
||||
# 从王璞的返回结果中提取报告内容
|
||||
final_report = wangpu_result.get('final_report', '')
|
||||
|
||||
if final_report:
|
||||
# 保存报告到数据库 - 这里保存的是王璞接口的真实返回内容
|
||||
report = Report.objects.create(
|
||||
task=task,
|
||||
title=f"智能分析报告 - {timezone.now().strftime('%Y.%m.%d %H:%M')}",
|
||||
summary=final_report[:200] + '...' if len(final_report) > 200 else final_report,
|
||||
content=final_report, # ← 王璞接口返回的真实报告内容
|
||||
status='completed',
|
||||
word_count=len(final_report)
|
||||
)
|
||||
```
|
||||
|
||||
#### **8.3 报告内容验证方法**
|
||||
|
||||
**方法1:查看后端控制台日志**
|
||||
```bash
|
||||
# 执行任务时,后端会打印王璞接口的完整返回数据
|
||||
✅ 王璞接口调用成功!
|
||||
📊 返回数据:
|
||||
{
|
||||
"final_report": "这里是王璞接口生成的真实报告内容...",
|
||||
"web_search_results": {...},
|
||||
"task_id": 33
|
||||
}
|
||||
```
|
||||
|
||||
**方法2:检查数据库中的报告**
|
||||
```bash
|
||||
# 通过API查看报告内容
|
||||
curl -s "http://localhost:3001/api/reports/14/" | jq '.content'
|
||||
```
|
||||
|
||||
**方法3:重新执行任务观察**
|
||||
- 每次执行任务都会调用王璞接口
|
||||
- 后端控制台会实时显示王璞接口的返回内容
|
||||
- 可以对比返回内容与数据库中保存的内容
|
||||
|
||||
#### **8.4 报告内容特点**
|
||||
|
||||
**王璞接口返回的报告内容特征:**
|
||||
- ✅ 包含具体的人物信息(如"王殿仪"、"盈动资本")
|
||||
- ✅ 包含真实的技术细节和行业动态
|
||||
- ✅ 结构化的分析报告格式
|
||||
- ✅ 基于提供的数据源进行深度分析
|
||||
- ✅ 包含网络搜索结果的整合
|
||||
|
||||
**系统不会杜撰的内容:**
|
||||
- ❌ 不会生成虚假的人物信息
|
||||
- ❌ 不会编造不存在的技术细节
|
||||
- ❌ 不会创建虚假的行业动态
|
||||
|
||||
#### **8.5 数据源与报告内容的对应关系**
|
||||
|
||||
```
|
||||
用户输入的需求 + 数据源长文本内容
|
||||
↓
|
||||
王璞接口分析处理
|
||||
↓
|
||||
生成基于真实数据源的分析报告
|
||||
↓
|
||||
报告内容反映数据源中的真实信息
|
||||
```
|
||||
|
||||
**示例:**
|
||||
- 如果数据源包含"王殿仪"相关信息 → 报告中会出现"王殿仪"的分析
|
||||
- 如果数据源包含"盈动资本"相关信息 → 报告中会出现"盈动资本"的分析
|
||||
- 如果数据源包含技术动态 → 报告中会出现相应的技术分析
|
||||
|
||||
### **9. 总结**
|
||||
|
||||
**报告内容的真实性保证:**
|
||||
1. **数据来源**:王璞团队的情报分析接口
|
||||
2. **处理方式**:基于用户提供的数据源进行智能分析
|
||||
3. **存储方式**:直接保存王璞接口返回的 `final_report` 内容
|
||||
4. **显示方式**:前端直接从数据库读取并显示
|
||||
|
||||
**验证方法:**
|
||||
- 查看后端控制台日志中的王璞接口返回数据
|
||||
- 对比数据库中的报告内容与接口返回内容
|
||||
- 重新执行任务观察实时返回内容
|
||||
|
||||
**结论:** 前端显示的所有报告内容都是王璞接口基于真实数据源生成的分析结果,系统不会杜撰任何内容。
|
||||
|
|
|
|||
Loading…
Reference in New Issue