实现模板解析与模板管理基础链路
This commit is contained in:
+2
-2
@@ -5,7 +5,7 @@
|
||||
<div class="logo">{{ collapsed ? "A" : "AI 文档模板" }}</div>
|
||||
<a-menu v-model:selectedKeys="selectedKeys" mode="inline" @click="onMenuClick">
|
||||
<a-menu-item key="/templates"><folder-outlined /> 模板管理</a-menu-item>
|
||||
<a-menu-item key="/models"><cpu-outlined /> 模型管理</a-menu-item>
|
||||
<a-menu-item key="/models"><api-outlined /> 模型管理</a-menu-item>
|
||||
<a-menu-item key="/generate"><thunderbolt-outlined /> 执行生成</a-menu-item>
|
||||
<a-menu-item key="/history"><clock-circle-outlined /> 生成记录</a-menu-item>
|
||||
</a-menu>
|
||||
@@ -30,7 +30,7 @@
|
||||
import { ref, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import {
|
||||
FolderOutlined, CpuOutlined, ThunderboltOutlined, ClockCircleOutlined,
|
||||
FolderOutlined, ApiOutlined, ThunderboltOutlined, ClockCircleOutlined,
|
||||
MenuUnfoldOutlined, MenuFoldOutlined, BellOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template><a-upload-dragger :multiple="multiple" :beforeUpload="(f)=>{$emit('upload',f);return false}" :showUploadList="showList"><p class="ant-upload-drag-icon"><upload-outlined /></p><p class="ant-upload-text">{{text}}</p><p class="ant-upload-hint">{{hint}}</p></a-upload-dragger></template>
|
||||
<template><a-upload-dragger :multiple="multiple" :beforeUpload="(f: File)=>{$emit('upload',f);return false}" :showUploadList="showList"><p class="ant-upload-drag-icon"><upload-outlined /></p><p class="ant-upload-text">{{text}}</p><p class="ant-upload-hint">{{hint}}</p></a-upload-dragger></template>
|
||||
<script setup lang="ts">
|
||||
import { UploadOutlined } from "@ant-design/icons-vue";
|
||||
defineProps<{text?:string;hint?:string;multiple?:boolean;showList?:boolean}>()
|
||||
defineEmits<{upload:[file:File]}>()
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<template><a-modal v-model:open="visible" title="段落测试" :footer="null" width="680px"><a-steps :current="step" style="margin-bottom:24px"><a-step title="上传附件" /><a-step title="处理中" /><a-step title="查看结果" /></a-steps><div v-if="step===0"><a-upload-dragger :beforeUpload="(f)=>{uploadFile=f;step=1;return false}"><p class="ant-upload-drag-icon"><file-add-outlined /></p><p class="ant-upload-text">点击上传参考文件</p></a-upload-dragger></div><div v-if="step===1" style="text-align:center;padding:40px"><a-spin size="large" /><p style="margin-top:16px;color:#666">正在解析文件 → 请求 AI 模型...</p></div><div v-if="step===2"><div style="background:#f0f0ff;padding:16px;border-left:3px solid #5b5bd6;border-radius:4px"><p>AI 生成结果预览:</p><p>{{result}}</p></div></div></a-modal></template>
|
||||
<template><a-modal v-model:open="visible" title="段落测试" :footer="null" width="680px"><a-steps :current="step" style="margin-bottom:24px"><a-step title="上传附件" /><a-step title="处理中" /><a-step title="查看结果" /></a-steps><div v-if="step===0"><a-upload-dragger :beforeUpload="(f: File)=>{uploadFile=f;step=1;return false}"><p class="ant-upload-drag-icon"><file-add-outlined /></p><p class="ant-upload-text">点击上传参考文件</p></a-upload-dragger></div><div v-if="step===1" style="text-align:center;padding:40px"><a-spin size="large" /><p style="margin-top:16px;color:#666">正在解析文件 → 请求 AI 模型...</p></div><div v-if="step===2"><div style="background:#f0f0ff;padding:16px;border-left:3px solid #5b5bd6;border-radius:4px"><p>AI 生成结果预览:</p><p>{{result}}</p></div></div></a-modal></template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue";
|
||||
import { FileAddOutlined } from "@ant-design/icons-vue";
|
||||
@@ -10,4 +10,4 @@ const uploadFile = ref<File|null>(null)
|
||||
const result = ref("这是 AI 生成的示例内容...")
|
||||
watch(()=>props.open,v=>{visible.value=v;if(v){step.value=0;uploadFile.value=null}})
|
||||
watch(visible,v=>{if(!v)emit('close')})
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<template><div style="padding:24px;display:flex;gap:24px;height:calc(100vh - 112px)"><div style="width:340px;flex-shrink:0"><a-card title="选择模板"><a-select style="width:100%" v-model:value="selectedTplId" placeholder="请选择已编辑好的模板" @change="onTplChange"><a-select-option v-for="t in templates" :key="t.id" :value="t.id">{{t.name}}</a-select-option></a-select><a-divider /><a-statistic title="总段落" :value="tplInfo.paragraph_count||0" /><a-statistic title="需上传文件" :value="tplInfo.fileCount||0" suffix="/"+String(tplInfo.paragraph_count||0) /></a-card><div v-if="generating" style="margin-top:16px"><a-card title="生成进度"><a-progress :percent="progress" /><p>{{progressText}}</p></a-card></div></div><div style="flex:1;display:flex;flex-direction:column"><a-card title="段落文件配置" style="flex:1"><div v-for="p in paragraphs" :key="p.id" :class="['para-row',{needFile:p.need_file,noFile:!p.need_file}]"><div class="para-info"><span class="idx">{{p.sort_index}}</span><span>{{p.title}}</span><a-tag color="blue">{{p.modelName||"默认"}}</a-tag></div><div v-if="p.need_file" class="file-info"><a-upload :beforeUpload="(f)=>{return handleFileUpload(p.id,f)}" :showUploadList="false"><a-button size="small">{{uploadedFiles[p.id]?"已上传":"上传文件"}}</a-button></a-upload><span v-if="uploadedFiles[p.id]" style="color:green;margin-left:8px">{{uploadedFiles[p.id]}}</span></div><span v-else class="no-file-tag">无需上传</span></div></a-card><div style="margin-top:16px;display:flex;justify-content:space-between;align-items:center"><span>{{fileCount}}/{{needFileCount}} 个文件已上传</span><a-button type="primary" size="large" :loading="generating" @click="startGen">立即生成</a-button></div></div></div></template>
|
||||
<template><div style="padding:24px;display:flex;gap:24px;height:calc(100vh - 112px)"><div style="width:340px;flex-shrink:0"><a-card title="选择模板"><a-select style="width:100%" v-model:value="selectedTplId" placeholder="请选择已编辑好的模板" @change="onTplChange"><a-select-option v-for="t in templates" :key="t.id" :value="t.id">{{t.name}}</a-select-option></a-select><a-divider /><a-statistic title="总段落" :value="tplInfo.paragraph_count||0" /><a-statistic title="需上传文件" :value="tplInfo.fileCount||0" suffix="/"+String(tplInfo.paragraph_count||0) /></a-card><div v-if="generating" style="margin-top:16px"><a-card title="生成进度"><a-progress :percent="progress" /><p>{{progressText}}</p></a-card></div></div><div style="flex:1;display:flex;flex-direction:column"><a-card title="段落文件配置" style="flex:1"><div v-for="p in paragraphs" :key="p.id" :class="['para-row',{needFile:p.need_file,noFile:!p.need_file}]"><div class="para-info"><span class="idx">{{p.sort_index}}</span><span>{{p.title}}</span><a-tag color="blue">{{p.modelName||"默认"}}</a-tag></div><div v-if="p.need_file" class="file-info"><a-upload :beforeUpload="(f: File)=>{return handleFileUpload(p.id,f)}" :showUploadList="false"><a-button size="small">{{uploadedFiles[p.id]?"已上传":"上传文件"}}</a-button></a-upload><span v-if="uploadedFiles[p.id]" style="color:green;margin-left:8px">{{uploadedFiles[p.id]}}</span></div><span v-else class="no-file-tag">无需上传</span></div></a-card><div style="margin-top:16px;display:flex;justify-content:space-between;align-items:center"><span>{{fileCount}}/{{needFileCount}} 个文件已上传</span><a-button type="primary" size="large" :loading="generating" @click="startGen">立即生成</a-button></div></div></div></template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
@@ -23,4 +23,4 @@ async function onTplChange(id:number){const tpl=await tplStore.fetchOne(id);para
|
||||
function handleFileUpload(paraId:number,file:File){uploadedFiles.value[paraId]=file.name;return false}
|
||||
async function startGen(){if(!selectedTplId.value){message.warning("请先选择模板");return}generating.value=true;progress.value=0;progressText.value="正在生成...";const data={template_id:selectedTplId.value,file_map:{}};try{const doc=await docStore.generateFull(data);message.success("生成完成");router.push(`/preview/${doc.id}`)}catch(e:any){message.error(e.message||"生成失败")}finally{generating.value=false}}
|
||||
</script>
|
||||
<style scoped>.para-row{display:flex;align-items:center;justify-content:space-between;padding:12px;border:1px solid #f0f0f0;border-radius:8px;margin-bottom:8px}.para-row.needFile{background:#fff;border-color:#d9d9d9}.para-row.noFile{background:#fafafa;border-style:dashed;opacity:.7}.para-info{display:flex;align-items:center;gap:8px}.para-info .idx{width:22px;height:22px;border-radius:50%;background:#f0f0ff;color:#5b5bd6;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700}.no-file-tag{font-size:12px;color:#999}</style>
|
||||
<style scoped>.para-row{display:flex;align-items:center;justify-content:space-between;padding:12px;border:1px solid #f0f0f0;border-radius:8px;margin-bottom:8px}.para-row.needFile{background:#fff;border-color:#d9d9d9}.para-row.noFile{background:#fafafa;border-style:dashed;opacity:.7}.para-info{display:flex;align-items:center;gap:8px}.para-info .idx{width:22px;height:22px;border-radius:50%;background:#f0f0ff;color:#5b5bd6;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700}.no-file-tag{font-size:12px;color:#999}</style>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<template><div style="padding:24px"><div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px"><h2>模型管理</h2><a-button type="primary" @click="openAdd">添加模型</a-button></div><a-row :gutter="[16,16]"><a-col :span="8" v-for="m in models" :key="m.id"><a-card :title="m.name" :extra="<a-button type='link' size='small' @click='openEdit(m)'>编辑</a-button>"><p>厂商:{{m.provider}}</p><p>格式:{{m.api_format}}</p><p>地址:{{m.api_endpoint}}</p><p>状态:<a-switch :checked="m.status==='enabled'" @change="toggleStatus(m)" /></p></a-card></a-col></a-row><a-modal v-model:open="modalOpen" :title="isEdit?'编辑模型':'添加模型'" @ok="saveModel"><a-form layout="vertical"><a-form-item label="模型名称"><a-input v-model:value="form.name" /></a-form-item><a-form-item label="供应厂商"><a-input v-model:value="form.provider" /></a-form-item><a-form-item label="API 格式"><a-select v-model:value="form.api_format"><a-select-option value="openai">OpenAI 格式</a-select-option><a-select-option value="anthropic">Anthropic 格式</a-select-option></a-select></a-form-item><a-form-item label="API 地址"><a-input v-model:value="form.api_endpoint" placeholder="https://api.xxx.com" /></a-form-item><a-form-item label="API Key"><a-input-password v-model:value="form.api_key" /></a-form-item></a-form></a-modal></div></template>
|
||||
<template><div style="padding:24px"><div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px"><h2>模型管理</h2><a-button type="primary" @click="openAdd">添加模型</a-button></div><a-row :gutter="[16,16]"><a-col :span="8" v-for="m in models" :key="m.id"><a-card :title="m.name"><template #extra><a-button type="link" size="small" @click="openEdit(m)">编辑</a-button></template><p>厂商:{{m.provider}}</p><p>格式:{{m.api_format}}</p><p>地址:{{m.api_endpoint}}</p><p>状态:<a-switch :checked="m.status==='enabled'" @change="toggleStatus(m)" /></p></a-card></a-col></a-row><a-modal v-model:open="modalOpen" :title="isEdit?'编辑模型':'添加模型'" @ok="saveModel"><a-form layout="vertical"><a-form-item label="模型名称"><a-input v-model:value="form.name" /></a-form-item><a-form-item label="供应厂商"><a-input v-model:value="form.provider" /></a-form-item><a-form-item label="API 格式"><a-select v-model:value="form.api_format"><a-select-option value="openai">OpenAI 格式</a-select-option><a-select-option value="anthropic">Anthropic 格式</a-select-option></a-select></a-form-item><a-form-item label="API 地址"><a-input v-model:value="form.api_endpoint" placeholder="https://api.xxx.com" /></a-form-item><a-form-item label="API Key"><a-input-password v-model:value="form.api_key" /></a-form-item></a-form></a-modal></div></template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import { useModelStore } from "@/stores/model";
|
||||
@@ -14,4 +14,4 @@ function openAdd(){isEdit.value=false;form.value={name:"",provider:"",api_format
|
||||
function openEdit(m:any){isEdit.value=true;editId.value=m.id;form.value={name:m.name,provider:m.provider,api_format:m.api_format,api_endpoint:m.api_endpoint,api_key:""};modalOpen.value=true}
|
||||
async function saveModel(){if(isEdit.value){await store.update(editId.value,form.value)}else{await store.create(form.value)}modalOpen.value=false;models.value=store.models as any;message.success("保存成功")}
|
||||
async function toggleStatus(m:any){m.status=m.status==="enabled"?"disabled":"enabled";await store.update(m.id,{status:m.status});message.success("状态已更新")}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<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"><heading-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" 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>
|
||||
<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, HeadingOutlined, OrderedListOutlined, TableOutlined } from "@ant-design/icons-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>`);
|
||||
@@ -16,4 +16,4 @@ 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)))}
|
||||
</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}.struct-item:hover{background:#f5f5f5}.struct-item.active{background:#f0f0ff;color:#5b5bd6}</style>
|
||||
|
||||
Reference in New Issue
Block a user