补齐模型管理与基础生成预览链路

This commit is contained in:
zwt13703
2026-07-02 15:00:11 +08:00
parent b15a3a1f18
commit a2bad58591
10 changed files with 850 additions and 52 deletions
+194 -17
View File
@@ -1,19 +1,196 @@
<template><div style="padding:24px"><a-card><template #title><span style="display:flex;align-items:center;gap:8px"><file-text-outlined /> 文档编辑</span></template><template #extra><a-button-group><a-button @click="undo"><undo-outlined />撤销</a-button><a-button @click="redo"><redo-outlined />重做</a-button></a-button-group><a-button type="primary" style="margin-left:12px" @click="exportDocx">导出 Word</a-button><a-button style="margin-left:8px" @click="exportPdf">导出 PDF</a-button></template><div style="display:flex;gap:16px"><div style="flex:1;min-width:0"><div style="display:flex;gap:4px;padding:8px;border:1px solid #d9d9d9;border-bottom:none;border-radius:6px 6px 0 0;flex-wrap:wrap"><a-button size="small"><b>B</b></a-button><a-button size="small"><i>I</i></a-button><a-button size="small"><u>U</u></a-button><a-divider type="vertical" /><a-button size="small"><font-size-outlined /></a-button><a-button size="small"><ordered-list-outlined /></a-button><a-button size="small"><table-outlined /></a-button></div><div ref="editorRef" contenteditable="true" style="border:1px solid #d9d9d9;border-radius:0 0 6px 6px;padding:24px;min-height:500px;outline:none;font-size:14px;line-height:1.8" v-html="editorHtml" @input="onEdit"></div></div><div style="width:200px;flex-shrink:0"><a-card title="文档结构" size="small"><div v-for="s in structure" :key="s.id" :class="['struct-item',{active:s.active}]" @click="scrollTo(s.id)"><file-text-outlined /> {{s.title}}</div></a-card></div></div></a-card></div></template>
<template>
<div style="padding:24px">
<a-card>
<template #title>
<span style="display:flex;align-items:center;gap:8px">
<file-text-outlined />
文档预览
</span>
</template>
<template #extra>
<a-button-group>
<a-button @click="undo"><undo-outlined />撤销</a-button>
<a-button @click="redo"><redo-outlined />重做</a-button>
</a-button-group>
<a-button type="primary" style="margin-left:12px" @click="exportDocx">导出 Word</a-button>
<a-button style="margin-left:8px" @click="exportPdf">导出 PDF</a-button>
</template>
<div style="display:flex;gap:16px">
<div style="flex:1;min-width:0">
<div style="display:flex;gap:4px;padding:8px;border:1px solid #d9d9d9;border-bottom:none;border-radius:6px 6px 0 0;flex-wrap:wrap">
<a-button size="small"><b>B</b></a-button>
<a-button size="small"><i>I</i></a-button>
<a-button size="small"><u>U</u></a-button>
<a-divider type="vertical" />
<a-button size="small"><font-size-outlined /></a-button>
<a-button size="small"><ordered-list-outlined /></a-button>
<a-button size="small"><table-outlined /></a-button>
</div>
<div
ref="editorRef"
contenteditable="true"
style="border:1px solid #d9d9d9;border-radius:0 0 6px 6px;padding:24px;min-height:500px;outline:none;font-size:14px;line-height:1.8;background:#fff"
v-html="editorHtml"
@input="onEdit"
/>
</div>
<div style="width:220px;flex-shrink:0">
<a-card title="文档结构" size="small">
<div
v-for="s in structure"
:key="s.id"
class="struct-item"
@click="scrollTo(s.anchor)"
>
<file-text-outlined />
{{ s.title }}
</div>
</a-card>
</div>
</div>
</a-card>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import { generateApi } from "@/api/generate";
import { message } from "ant-design-vue";
import { FileTextOutlined, UndoOutlined, RedoOutlined, FontSizeOutlined, OrderedListOutlined, TableOutlined } from "@ant-design/icons-vue";
const route = useRoute();
const editorRef = ref<HTMLElement|null>(null);
const editorHtml = ref(`<h2 style="text-align:center">2025年第一季度经济活动分析报告</h2><p style="text-align:center;color:#666">某某集团有限公司</p><h3>一、主要经营指标完成情况</h3><div style="background:#f0f0ff;padding:12px;border-left:3px solid #5b5bd6;border-radius:4px;margin:8px 0"><p>本季度营业收入完成<strong>12.35亿元</strong>,同比上升<strong>8.7%</strong>。</p></div><h3>主要经营指标完成情况表</h3><table border="1" style="width:100%;border-collapse:collapse"><tr><th>指标</th><th>完成值</th><th>同比</th></tr><tr><td>营业收入</td><td>12.35亿</td><td>+8.7%</td></tr></table><h3>二、成本费用分析</h3><p>本季度总成本9.87亿元,同比上升6.2%。</p>`);
const structure = ref([{id:'s1',title:'经营指标',active:true},{id:'s2',title:'指标表',active:false},{id:'s3',title:'成本费用',active:false}]);
function scrollTo(id:string){}
function onEdit(){}
function undo(){document.execCommand('undo')}
function redo(){document.execCommand('redo')}
function exportDocx(){const id=route.params.id;window.open(generateApi.exportDocx(Number(id)))}
function exportPdf(){const id=route.params.id;window.open(generateApi.exportPdf(Number(id)))}
import { computed, ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { generateApi } from '@/api/generate'
import { message } from 'ant-design-vue'
import {
FileTextOutlined,
UndoOutlined,
RedoOutlined,
FontSizeOutlined,
OrderedListOutlined,
TableOutlined,
} from '@ant-design/icons-vue'
interface ContentBlock {
type: string
text?: string
title?: string
headers?: string[]
rows?: string[][]
}
interface LogItem {
paragraph_id: number
title: string
sort_index: number
content: {
content: ContentBlock[]
}
}
const route = useRoute()
const editorRef = ref<HTMLElement | null>(null)
const logs = ref<LogItem[]>([])
const editorHtml = ref('<p>加载中...</p>')
const structure = computed(() =>
logs.value.map((item) => ({
id: item.paragraph_id,
title: item.title || `段落 ${item.sort_index}`,
anchor: `section-${item.paragraph_id}`,
})),
)
function renderTable(block: ContentBlock) {
const headers = block.headers || []
const rows = block.rows || []
const thead = headers.length
? `<thead><tr>${headers.map((header) => `<th>${header}</th>`).join('')}</tr></thead>`
: ''
const tbody = `<tbody>${rows
.map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join('')}</tr>`)
.join('')}</tbody>`
return `<table border="1" style="width:100%;border-collapse:collapse;margin:12px 0">${thead}${tbody}</table>`
}
function renderBlocks(blocks: ContentBlock[]) {
return blocks
.map((block) => {
if (block.type === 'table') {
return renderTable(block)
}
return `<p style="margin:10px 0">${block.text || ''}</p>`
})
.join('')
}
async function loadDocument() {
const id = Number(route.params.id)
const response: any = await generateApi.getDocument(id)
logs.value = response.data?.logs || []
if (!logs.value.length) {
editorHtml.value = '<p>暂无生成内容。</p>'
return
}
editorHtml.value = logs.value
.map(
(item) => `
<section id="section-${item.paragraph_id}" style="margin-bottom:24px">
<h3 style="margin-bottom:12px">${item.title}</h3>
<div style="background:#f8faff;padding:16px;border-left:3px solid #5b5bd6;border-radius:4px">
${renderBlocks(item.content?.content || [])}
</div>
</section>
`,
)
.join('')
}
function scrollTo(anchor: string) {
const element = document.getElementById(anchor)
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}
function onEdit() {}
function undo() {
document.execCommand('undo')
}
function redo() {
document.execCommand('redo')
}
function exportDocx() {
const id = Number(route.params.id)
window.open(generateApi.exportDocx(id))
}
function exportPdf() {
const id = Number(route.params.id)
window.open(generateApi.exportPdf(id))
}
onMounted(async () => {
try {
await loadDocument()
} catch (error: any) {
message.error(error.message || '加载文档失败')
editorHtml.value = '<p>文档加载失败。</p>'
}
})
</script>
<style scoped>.struct-item{padding:8px;cursor:pointer;border-radius:4px;font-size:13px}.struct-item:hover{background:#f5f5f5}.struct-item.active{background:#f0f0ff;color:#5b5bd6}</style>
<style scoped>
.struct-item {
padding: 8px;
cursor: pointer;
border-radius: 4px;
font-size: 13px;
display: flex;
gap: 6px;
align-items: center;
}
.struct-item:hover {
background: #f5f5f5;
}
</style>