调整任务详情页左右预览布局
This commit is contained in:
@@ -219,3 +219,21 @@
|
||||
4. 调整导出时的表格回写方式,优先克隆模板原有表格结构并填充新数据,尽量保留表格外观和基础样式。
|
||||
5. 执行后端语法检查,确认导出服务改动稳定。
|
||||
- **执行结果**: 当前导出链路已改为“尽量复用模板原始段落/表格样式后写入 AI 内容”,比此前的新建空白内容块方式更接近原模板样式,也更容易把 AI 结果正确写回文档。
|
||||
|
||||
## 会话 ID: local-20260702202424
|
||||
- [2026-07-02 20:24:24]
|
||||
- **执行原因**: 用户希望用极简一句话概括当前项目功能状态,并说明明天的工作重点。
|
||||
- **执行过程**:
|
||||
1. 基于当前已完成能力,压缩总结项目现状。
|
||||
2. 提炼明日优先事项,聚焦导出准确性与样式保真。
|
||||
- **执行结果**: 已形成简短状态说明,可作为明日继续开发的工作摘要。
|
||||
|
||||
## 会话 ID: local-20260702202807
|
||||
- [2026-07-02 20:28:07]
|
||||
- **执行原因**: 用户希望将任务详情页改为“上 + 左右”结构,左侧显示段落,右侧显示生成结果预览。
|
||||
- **执行过程**:
|
||||
1. 重构任务详情页布局,将页面调整为顶部任务概览、下方左右分栏的结构。
|
||||
2. 左侧增加段落列表与段落状态展示,并保留附件映射信息。
|
||||
3. 右侧改为当前选中段落的结果预览区域,展示段落标题、状态、文件要求和生成内容。
|
||||
4. 执行前端类型检查,确认本轮布局调整稳定。
|
||||
- **执行结果**: 当前任务详情页已改为“上 + 左右”结构,用户可在左侧切换段落,在右侧查看对应生成结果预览。
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# 任务执行摘要
|
||||
|
||||
## 会话 ID: local-20260703092828
|
||||
- [2026-07-03 09:28:28]
|
||||
- **执行原因**: 用户要求提交当前已完成的预览页布局调整代码。
|
||||
- **执行过程**:
|
||||
1. 检查当前工作区改动,确认仅包含任务详情页左右布局调整和对应任务记录。
|
||||
2. 整理并暂存相关文件,排除未跟踪的原型目录。
|
||||
3. 准备使用中文提交信息完成本次代码提交。
|
||||
- **执行结果**: 当前改动已整理完成并准备提交,包含任务详情页“上 + 左右”结构调整。
|
||||
+120
-34
@@ -33,37 +33,55 @@
|
||||
<div class="status-message">{{ progressMessage || documentInfo.error || '任务已创建,等待执行。' }}</div>
|
||||
</a-card>
|
||||
|
||||
<a-card class="mapping-card" title="段落与附件映射">
|
||||
<div v-if="paragraphMappings.length" class="mapping-list">
|
||||
<div v-for="item in paragraphMappings" :key="item.paragraph_id" class="mapping-item">
|
||||
<div class="mapping-top">
|
||||
<span class="mapping-index">{{ item.sort_index }}</span>
|
||||
<div class="mapping-main">
|
||||
<div class="mapping-title">{{ item.title }}</div>
|
||||
<div v-if="item.file_note" class="mapping-note">{{ item.file_note }}</div>
|
||||
<div class="detail-layout">
|
||||
<aside class="detail-left">
|
||||
<a-card class="left-card" title="段落列表">
|
||||
<div v-if="paragraphMappings.length" class="mapping-list">
|
||||
<div
|
||||
v-for="item in paragraphMappings"
|
||||
:key="item.paragraph_id"
|
||||
:class="['mapping-item', { active: selectedParagraphId === item.paragraph_id }]"
|
||||
@click="selectParagraph(item.paragraph_id)"
|
||||
>
|
||||
<div class="mapping-top">
|
||||
<span class="mapping-index">{{ item.sort_index }}</span>
|
||||
<div class="mapping-main">
|
||||
<div class="mapping-title">{{ item.title }}</div>
|
||||
<div v-if="item.file_note" class="mapping-note">{{ item.file_note }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mapping-status">
|
||||
<a-badge :status="statusBadge(logStatusMap[item.paragraph_id] || 'pending')" :text="statusText(logStatusMap[item.paragraph_id] || 'pending')" />
|
||||
</div>
|
||||
<div v-if="item.selected_files?.length" class="mapping-files">
|
||||
<span v-for="file in item.selected_files" :key="file.file_path" class="mapping-file">{{ file.file_name }}</span>
|
||||
</div>
|
||||
<div v-else class="mapping-empty">未选择附件</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.selected_files?.length" class="mapping-files">
|
||||
<span v-for="file in item.selected_files" :key="file.file_path" class="mapping-file">{{ file.file_name }}</span>
|
||||
</div>
|
||||
<div v-else class="mapping-empty">未选择附件</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-empty v-else description="当前任务未记录附件映射" />
|
||||
</a-card>
|
||||
<a-empty v-else description="当前任务未记录段落信息" />
|
||||
</a-card>
|
||||
</aside>
|
||||
|
||||
<a-card class="preview-card" title="生成结果预览">
|
||||
<div v-if="logs.length" class="preview-wrap">
|
||||
<section v-for="item in logs" :key="item.paragraph_id" :id="`section-${item.paragraph_id}`" class="preview-section">
|
||||
<div class="preview-section-head">
|
||||
<h3>{{ item.title }}</h3>
|
||||
<a-badge :status="statusBadge(item.status)" :text="statusText(item.status)" />
|
||||
</div>
|
||||
<div class="preview-block" v-html="renderBlocks(item.content?.content || [])" />
|
||||
</section>
|
||||
</div>
|
||||
<a-empty v-else :description="isRunning ? '任务进行中,已完成段落会逐步出现在这里' : '暂无生成内容'" />
|
||||
</a-card>
|
||||
<section class="detail-right">
|
||||
<a-card class="preview-card" title="生成结果预览">
|
||||
<template v-if="selectedParagraph">
|
||||
<div class="preview-section">
|
||||
<div class="preview-section-head">
|
||||
<h3>{{ selectedParagraph.title }}</h3>
|
||||
<a-badge :status="statusBadge(selectedParagraph.status)" :text="statusText(selectedParagraph.status)" />
|
||||
</div>
|
||||
<div v-if="selectedMapping?.file_note" class="preview-note">文件要求:{{ selectedMapping.file_note }}</div>
|
||||
<div v-if="selectedMapping?.selected_files?.length" class="preview-files">
|
||||
<span v-for="file in selectedMapping.selected_files" :key="file.file_path" class="mapping-file">{{ file.file_name }}</span>
|
||||
</div>
|
||||
<div class="preview-block" v-html="renderBlocks(selectedParagraph.content?.content || [])" />
|
||||
</div>
|
||||
</template>
|
||||
<a-empty v-else :description="isRunning ? '任务进行中,当前段落结果尚未生成' : '当前没有可预览的段落结果'" />
|
||||
</a-card>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -96,11 +114,21 @@ const documentInfo = ref<any>({})
|
||||
const logs = ref<LogItem[]>([])
|
||||
const progressPercent = ref(0)
|
||||
const progressMessage = ref('')
|
||||
const selectedParagraphId = ref<number | null>(null)
|
||||
let progressSource: EventSource | null = null
|
||||
|
||||
const isRunning = computed(() => ['pending', 'generating'].includes(documentInfo.value.status))
|
||||
const isCompleted = computed(() => documentInfo.value.status === 'completed')
|
||||
const paragraphMappings = computed(() => documentInfo.value.request_payload?.paragraphs || [])
|
||||
const logStatusMap = computed(() =>
|
||||
Object.fromEntries(logs.value.map((item) => [item.paragraph_id, item.status]))
|
||||
)
|
||||
const selectedParagraph = computed(() =>
|
||||
logs.value.find((item) => item.paragraph_id === selectedParagraphId.value) || null
|
||||
)
|
||||
const selectedMapping = computed(() =>
|
||||
paragraphMappings.value.find((item: any) => item.paragraph_id === selectedParagraphId.value) || null
|
||||
)
|
||||
|
||||
function statusText(status: string) {
|
||||
const map: Record<string, string> = {
|
||||
@@ -145,6 +173,20 @@ async function loadDocument() {
|
||||
const response: any = await generateApi.getDocument(id)
|
||||
documentInfo.value = response.data || {}
|
||||
logs.value = response.data?.logs || []
|
||||
if (!selectedParagraphId.value) {
|
||||
selectedParagraphId.value = logs.value[0]?.paragraph_id || paragraphMappings.value[0]?.paragraph_id || null
|
||||
}
|
||||
if (
|
||||
selectedParagraphId.value &&
|
||||
!paragraphMappings.value.some((item: any) => item.paragraph_id === selectedParagraphId.value) &&
|
||||
!logs.value.some((item) => item.paragraph_id === selectedParagraphId.value)
|
||||
) {
|
||||
selectedParagraphId.value = logs.value[0]?.paragraph_id || paragraphMappings.value[0]?.paragraph_id || null
|
||||
}
|
||||
}
|
||||
|
||||
function selectParagraph(paragraphId: number) {
|
||||
selectedParagraphId.value = paragraphId
|
||||
}
|
||||
|
||||
function bindProgress() {
|
||||
@@ -254,12 +296,31 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.status-card,
|
||||
.mapping-card,
|
||||
.preview-card {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.detail-layout {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.detail-left {
|
||||
width: 360px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-right {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.left-card {
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.status-message {
|
||||
margin-top: 12px;
|
||||
font-size: 13px;
|
||||
@@ -276,6 +337,18 @@ onBeforeUnmount(() => {
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 14px;
|
||||
background: #fafbfc;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.mapping-item:hover {
|
||||
border-color: #c7d2fe;
|
||||
background: #f8faff;
|
||||
}
|
||||
|
||||
.mapping-item.active {
|
||||
border-color: #4f46e5;
|
||||
background: #eef2ff;
|
||||
}
|
||||
|
||||
.mapping-top {
|
||||
@@ -312,6 +385,10 @@ onBeforeUnmount(() => {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.mapping-status {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.mapping-files {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -334,16 +411,12 @@ onBeforeUnmount(() => {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.preview-wrap {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.preview-section {
|
||||
padding: 16px;
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
border: 1px solid #e5e7eb;
|
||||
min-height: 480px;
|
||||
}
|
||||
|
||||
.preview-section-head {
|
||||
@@ -358,6 +431,19 @@ onBeforeUnmount(() => {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.preview-note {
|
||||
margin-bottom: 12px;
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.preview-files {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
:deep(.result-text) {
|
||||
margin: 0 0 12px;
|
||||
line-height: 1.8;
|
||||
|
||||
Reference in New Issue
Block a user