Files
doc-forge/web/src/views/TemplateEditor.vue
T
2026-07-02 15:00:11 +08:00

195 lines
6.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div style="display:flex;height:calc(100vh - 112px)">
<div style="width:220px;border-right:1px solid #f0f0f0;overflow-y:auto;padding:12px">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
<h4 style="margin:0">段落列表</h4>
<a-button type="primary" size="small" @click="saveTemplate">保存</a-button>
</div>
<div
v-for="(p, i) in paragraphs"
:key="p.id"
:class="['para-item', { active: selectedId === p.id }]"
@click="selectPara(p.id)"
>
<span class="idx">{{ i + 1 }}</span>
<span class="title">{{ p.title || '未命名' }}</span>
<a-tag :color="p.edit_mode === 'ai' ? 'blue' : 'orange'" style="margin-left:auto">
{{ p.edit_mode === 'ai' ? 'AI' : '手动' }}
</a-tag>
</div>
</div>
<div style="flex:1;overflow-y:auto;padding:24px;background:#f0f1f3;display:flex;justify-content:center;align-items:flex-start">
<div style="width:794px;background:#fff;padding:80px 72px;min-height:500px;box-shadow:0 2px 8px rgba(0,0,0,.08)" v-html="previewHtml" />
</div>
<div style="width:380px;border-left:1px solid #f0f0f0;overflow-y:auto;padding:16px">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
<h4 style="margin:0">段落配置</h4>
<a-button @click="saveTemplate">保存配置</a-button>
</div>
<div v-if="selectedPara">
<a-form layout="vertical">
<a-form-item label="编辑方式">
<a-select v-model:value="selectedPara.edit_mode">
<a-select-option value="ai">AI 生成</a-select-option>
<a-select-option value="manual">人工编辑</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="selectedPara.edit_mode === 'ai'" label="生成模型">
<a-select v-model:value="selectedPara.model_id" allowClear placeholder="使用默认模型">
<a-select-option v-for="m in models" :key="m.id" :value="m.id">{{ m.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="输出格式">
<a-select v-model:value="selectedPara.output_format">
<a-select-option value="text">正式报告段落</a-select-option>
<a-select-option value="table">表格形式</a-select-option>
<a-select-option value="mixed">混合内容</a-select-option>
<a-select-option value="chart">图表形式</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="需要提示词">
<a-switch v-model:checked="selectedPara.need_prompt" />
</a-form-item>
<a-form-item v-if="selectedPara.need_prompt" label="预设提示词">
<a-textarea v-model:value="selectedPara.prompt_text" :rows="4" placeholder="在此输入提示词..." />
</a-form-item>
<a-form-item label="需要参考文件">
<a-switch v-model:checked="selectedPara.need_file" />
</a-form-item>
<a-form-item v-if="selectedPara.need_file" label="备注说明">
<a-textarea v-model:value="selectedPara.file_note" :rows="2" placeholder="提示用户上传什么文件" />
</a-form-item>
</a-form>
<a-button v-if="selectedPara.edit_mode === 'ai'" type="primary" block @click="testPara">立即测试</a-button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { Modal, message } from 'ant-design-vue'
import { useTemplateStore } from '@/stores/template'
import { useModelStore } from '@/stores/model'
import { generateApi } from '@/api/generate'
const route = useRoute()
const store = useTemplateStore()
const modelStore = useModelStore()
const paragraphs = ref<any[]>([])
const models = ref<any[]>([])
const selectedId = ref(0)
const selectedPara = computed(() => paragraphs.value.find((item) => item.id === selectedId.value))
const previewHtml = computed(() =>
paragraphs.value
.map(
(item) =>
`<div style="margin:8px 0;padding:8px;border:2px solid transparent;border-radius:4px${
selectedId.value === item.id ? ';border-color:#5b5bd6;background:#f0f0ff' : ''
}"><h3>${item.title || ''}</h3><p>${item.content || '点击左侧配置此段落'}</p></div>`,
)
.join(''),
)
function selectPara(id: number) {
selectedId.value = id
}
async function saveTemplate() {
const templateId = Number(route.params.id)
await store.save(templateId)
message.success('模板配置已保存')
}
async function testPara() {
if (!selectedPara.value) {
return
}
const templateId = Number(route.params.id)
const response: any = await generateApi.test({
paragraph_id: selectedPara.value.id,
template_id: templateId,
prompt_text: selectedPara.value.prompt_text || '',
model_id: selectedPara.value.model_id || 0,
file_paths: [],
})
const blocks = response.data?.content?.content || []
const text = blocks
.map((block: any) => {
if (block.type === 'table') {
return `${block.title || '表格'}${(block.rows || []).length}`
}
return block.text || ''
})
.filter(Boolean)
.join('\n\n')
Modal.info({
title: '段落测试结果',
width: 640,
content: text || '当前未返回可展示内容。',
})
}
onMounted(async () => {
const id = Number(route.params.id)
await store.fetchOne(id)
paragraphs.value = store.paragraphs as any
await modelStore.fetchList()
models.value = modelStore.models as any
if (paragraphs.value.length) {
selectedId.value = paragraphs.value[0].id
}
})
</script>
<style scoped>
.para-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px;
border-radius: 6px;
cursor: pointer;
margin-bottom: 2px;
font-size: 13px;
}
.para-item:hover {
background: #f5f5f5;
}
.para-item.active {
background: #f0f0ff;
color: #5b5bd6;
font-weight: 500;
}
.para-item .idx {
width: 20px;
height: 20px;
border-radius: 50%;
background: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: 700;
flex-shrink: 0;
}
.para-item.active .idx {
background: #5b5bd6;
color: #fff;
}
.para-item .title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>