【功能新增】AI:知识库文档上传:10% 搭建整体页面结构
							parent
							
								
									94091598a3
								
							
						
					
					
						commit
						b7d7b11d31
					
				|  | @ -630,6 +630,18 @@ const remainingRouter: AppRouteRecordRaw[] = [ | ||||||
|           icon: 'ep:document', |           icon: 'ep:document', | ||||||
|           noCache: false |           noCache: false | ||||||
|         } |         } | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         path: 'console/knowledge/document/create', | ||||||
|  |         component: () => import('@/views/ai/knowledge/document/create/index.vue'), | ||||||
|  |         name: 'AiKnowledgeDocumentCreate', | ||||||
|  |         meta: { | ||||||
|  |           title: '创建文档', | ||||||
|  |           icon: 'ep:plus', | ||||||
|  |           noCache: true, | ||||||
|  |           hidden: true, | ||||||
|  |           activeMenu: '/ai/console/knowledge/document' | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     ] |     ] | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|  | @ -0,0 +1,235 @@ | ||||||
|  | <template> | ||||||
|  |   <div class="process-complete"> | ||||||
|  |     <div class="mb-20px"> | ||||||
|  |       <el-alert | ||||||
|  |         title="处理说明" | ||||||
|  |         type="info" | ||||||
|  |         description="系统将对文档进行处理,包括文本提取、向量化等操作,处理完成后文档将被添加到知识库中。" | ||||||
|  |         show-icon | ||||||
|  |         :closable="false" | ||||||
|  |       /> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <div class="mb-20px"> | ||||||
|  |       <el-card class="box-card"> | ||||||
|  |         <template #header> | ||||||
|  |           <div class="card-header"> | ||||||
|  |             <span class="text-16px font-bold">文档信息</span> | ||||||
|  |           </div> | ||||||
|  |         </template> | ||||||
|  |         <div class="document-info"> | ||||||
|  |           <div class="info-item"> | ||||||
|  |             <span class="label">文档名称:</span> | ||||||
|  |             <span class="value">{{ modelData.name }}</span> | ||||||
|  |           </div> | ||||||
|  |           <div class="info-item"> | ||||||
|  |             <span class="label">知识库:</span> | ||||||
|  |             <span class="value">{{ getKnowledgeBaseName(modelData.knowledgeBaseId) }}</span> | ||||||
|  |           </div> | ||||||
|  |           <div class="info-item"> | ||||||
|  |             <span class="label">文档类型:</span> | ||||||
|  |             <span class="value">{{ getDocumentTypeName(modelData.documentType) }}</span> | ||||||
|  |           </div> | ||||||
|  |           <div class="info-item"> | ||||||
|  |             <span class="label">段落数量:</span> | ||||||
|  |             <span class="value">{{ modelData.segments.length }}</span> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </el-card> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <div class="mb-20px"> | ||||||
|  |       <el-card class="box-card"> | ||||||
|  |         <template #header> | ||||||
|  |           <div class="card-header"> | ||||||
|  |             <span class="text-16px font-bold">处理选项</span> | ||||||
|  |           </div> | ||||||
|  |         </template> | ||||||
|  |         <div class="process-options"> | ||||||
|  |           <el-form :model="processOptions" label-width="120px"> | ||||||
|  |             <el-form-item label="处理模式"> | ||||||
|  |               <el-radio-group v-model="processOptions.mode"> | ||||||
|  |                 <el-radio :label="1">标准处理</el-radio> | ||||||
|  |                 <el-radio :label="2">高级处理</el-radio> | ||||||
|  |               </el-radio-group> | ||||||
|  |             </el-form-item> | ||||||
|  |             <el-form-item label="向量模型" v-if="processOptions.mode === 2"> | ||||||
|  |               <el-select v-model="processOptions.vectorModel" placeholder="请选择向量模型"> | ||||||
|  |                 <el-option label="文本嵌入模型-基础版" value="text-embedding-basic" /> | ||||||
|  |                 <el-option label="文本嵌入模型-高级版" value="text-embedding-advanced" /> | ||||||
|  |                 <el-option label="多模态嵌入模型" value="multimodal-embedding" /> | ||||||
|  |               </el-select> | ||||||
|  |             </el-form-item> | ||||||
|  |             <el-form-item label="处理优先级" v-if="processOptions.mode === 2"> | ||||||
|  |               <el-select v-model="processOptions.priority" placeholder="请选择处理优先级"> | ||||||
|  |                 <el-option label="低" value="low" /> | ||||||
|  |                 <el-option label="中" value="medium" /> | ||||||
|  |                 <el-option label="高" value="high" /> | ||||||
|  |               </el-select> | ||||||
|  |             </el-form-item> | ||||||
|  |           </el-form> | ||||||
|  |         </div> | ||||||
|  |       </el-card> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <div class="mb-20px"> | ||||||
|  |       <el-card class="box-card"> | ||||||
|  |         <template #header> | ||||||
|  |           <div class="card-header"> | ||||||
|  |             <span class="text-16px font-bold">处理状态</span> | ||||||
|  |           </div> | ||||||
|  |         </template> | ||||||
|  |         <div class="process-status"> | ||||||
|  |           <div v-if="!isProcessing && !isProcessed"> | ||||||
|  |             <el-empty description="尚未开始处理" /> | ||||||
|  |             <div class="flex justify-center mt-20px"> | ||||||
|  |               <el-button type="primary" @click="handleStartProcess">开始处理</el-button> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |           <div v-else-if="isProcessing"> | ||||||
|  |             <div class="flex flex-col items-center"> | ||||||
|  |               <el-progress type="circle" :percentage="processPercentage" /> | ||||||
|  |               <div class="mt-10px">{{ processStatus }}</div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |           <div v-else> | ||||||
|  |             <div class="flex items-center justify-center"> | ||||||
|  |               <el-result icon="success" title="处理完成" sub-title="文档已成功处理并添加到知识库中"> | ||||||
|  |                 <template #extra> | ||||||
|  |                   <el-button type="primary" @click="handleViewDocument">查看文档</el-button> | ||||||
|  |                 </template> | ||||||
|  |               </el-result> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </el-card> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { PropType } from 'vue' | ||||||
|  | 
 | ||||||
|  | const props = defineProps({ | ||||||
|  |   modelValue: { | ||||||
|  |     type: Object as PropType<any>, | ||||||
|  |     required: true | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits(['update:modelValue']) | ||||||
|  | 
 | ||||||
|  | // 表单数据 | ||||||
|  | const modelData = computed({ | ||||||
|  |   get: () => props.modelValue, | ||||||
|  |   set: (val) => emit('update:modelValue', val) | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 处理选项 | ||||||
|  | const processOptions = ref({ | ||||||
|  |   mode: 1, // 1: 标准处理, 2: 高级处理 | ||||||
|  |   vectorModel: 'text-embedding-basic', | ||||||
|  |   priority: 'medium' | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 处理状态 | ||||||
|  | const isProcessing = ref(false) | ||||||
|  | const isProcessed = ref(false) | ||||||
|  | const processPercentage = ref(0) | ||||||
|  | const processStatus = ref('正在准备处理...') | ||||||
|  | 
 | ||||||
|  | // 知识库列表(模拟数据) | ||||||
|  | const knowledgeBaseList = [ | ||||||
|  |   { id: 1, name: '产品知识库' }, | ||||||
|  |   { id: 2, name: '技术文档库' }, | ||||||
|  |   { id: 3, name: '客户服务知识库' } | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | // 获取知识库名称 | ||||||
|  | const getKnowledgeBaseName = (id) => { | ||||||
|  |   const base = knowledgeBaseList.find((item) => item.id === id) | ||||||
|  |   return base ? base.name : '未知知识库' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取文档类型名称 | ||||||
|  | const getDocumentTypeName = (type) => { | ||||||
|  |   const typeMap = { | ||||||
|  |     pdf: 'PDF文档', | ||||||
|  |     word: 'Word文档', | ||||||
|  |     text: '文本文件', | ||||||
|  |     url: '网页链接' | ||||||
|  |   } | ||||||
|  |   return typeMap[type] || '未知类型' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 开始处理 | ||||||
|  | const handleStartProcess = () => { | ||||||
|  |   isProcessing.value = true | ||||||
|  |   processPercentage.value = 0 | ||||||
|  |   processStatus.value = '正在准备处理...' | ||||||
|  | 
 | ||||||
|  |   // 模拟处理过程 | ||||||
|  |   const timer = setInterval(() => { | ||||||
|  |     processPercentage.value += 10 | ||||||
|  | 
 | ||||||
|  |     if (processPercentage.value < 30) { | ||||||
|  |       processStatus.value = '正在提取文本内容...' | ||||||
|  |     } else if (processPercentage.value < 60) { | ||||||
|  |       processStatus.value = '正在进行向量化处理...' | ||||||
|  |     } else if (processPercentage.value < 90) { | ||||||
|  |       processStatus.value = '正在写入知识库...' | ||||||
|  |     } else { | ||||||
|  |       processStatus.value = '处理完成,正在整理结果...' | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (processPercentage.value >= 100) { | ||||||
|  |       clearInterval(timer) | ||||||
|  |       isProcessing.value = false | ||||||
|  |       isProcessed.value = true | ||||||
|  |       modelData.value.status = 2 // 已完成 | ||||||
|  |     } | ||||||
|  |   }, 500) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 查看文档 | ||||||
|  | const handleViewDocument = () => { | ||||||
|  |   // 跳转到文档详情页 | ||||||
|  |   console.log('查看文档:', modelData.value.id) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 表单校验 | ||||||
|  | const validate = () => { | ||||||
|  |   return new Promise((resolve, reject) => { | ||||||
|  |     if (modelData.value.status === 2) { | ||||||
|  |       resolve(true) | ||||||
|  |     } else { | ||||||
|  |       reject(new Error('请先完成文档处理')) | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 对外暴露方法 | ||||||
|  | defineExpose({ | ||||||
|  |   validate | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | .process-complete { | ||||||
|  |   .document-info { | ||||||
|  |     .info-item { | ||||||
|  |       margin-bottom: 10px; | ||||||
|  |       display: flex; | ||||||
|  | 
 | ||||||
|  |       .label { | ||||||
|  |         width: 100px; | ||||||
|  |         color: #606266; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       .value { | ||||||
|  |         font-weight: bold; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | @ -0,0 +1,234 @@ | ||||||
|  | <template> | ||||||
|  |   <div class="document-segment"> | ||||||
|  |     <div class="mb-20px"> | ||||||
|  |       <el-alert | ||||||
|  |         title="文档分段说明" | ||||||
|  |         type="info" | ||||||
|  |         description="系统会自动将文档内容分割成多个段落,您可以根据需要调整分段方式和内容。" | ||||||
|  |         show-icon | ||||||
|  |         :closable="false" | ||||||
|  |       /> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <div class="mb-20px flex justify-between items-center"> | ||||||
|  |       <div class="text-16px font-bold">分段设置</div> | ||||||
|  |       <div> | ||||||
|  |         <el-button type="primary" @click="handleAutoSegment">自动分段</el-button> | ||||||
|  |         <el-button @click="handleAddSegment">添加段落</el-button> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <div class="segment-settings mb-20px"> | ||||||
|  |       <el-form :model="segmentSettings" label-width="120px"> | ||||||
|  |         <el-form-item label="分段方式"> | ||||||
|  |           <el-radio-group v-model="segmentSettings.type"> | ||||||
|  |             <el-radio :label="1">按段落分割</el-radio> | ||||||
|  |             <el-radio :label="2">按字数分割</el-radio> | ||||||
|  |             <el-radio :label="3">按标题分割</el-radio> | ||||||
|  |           </el-radio-group> | ||||||
|  |         </el-form-item> | ||||||
|  |         <el-form-item label="最大字数" v-if="segmentSettings.type === 2"> | ||||||
|  |           <el-input-number v-model="segmentSettings.maxChars" :min="100" :max="5000" /> | ||||||
|  |         </el-form-item> | ||||||
|  |       </el-form> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <div class="segment-list"> | ||||||
|  |       <div class="text-16px font-bold mb-10px">段落列表 ({{ modelData.segments.length }})</div> | ||||||
|  | 
 | ||||||
|  |       <el-empty v-if="modelData.segments.length === 0" description="暂无段落数据" /> | ||||||
|  | 
 | ||||||
|  |       <div v-else> | ||||||
|  |         <el-collapse v-model="activeSegments"> | ||||||
|  |           <el-collapse-item | ||||||
|  |             v-for="(segment, index) in modelData.segments" | ||||||
|  |             :key="index" | ||||||
|  |             :title="`段落 ${index + 1}`" | ||||||
|  |             :name="index" | ||||||
|  |           > | ||||||
|  |             <div class="segment-content"> | ||||||
|  |               <el-input | ||||||
|  |                 v-model="segment.content" | ||||||
|  |                 type="textarea" | ||||||
|  |                 :rows="5" | ||||||
|  |                 placeholder="段落内容" | ||||||
|  |               /> | ||||||
|  |               <div class="mt-10px flex justify-end"> | ||||||
|  |                 <el-button type="danger" size="small" @click="handleDeleteSegment(index)"> | ||||||
|  |                   删除段落 | ||||||
|  |                 </el-button> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </el-collapse-item> | ||||||
|  |         </el-collapse> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <!-- 添加底部按钮 --> | ||||||
|  |     <div class="mt-20px flex justify-between"> | ||||||
|  |       <el-button @click="handlePrevStep">上一步</el-button> | ||||||
|  |       <el-button type="primary" @click="handleNextStep">保存并处理</el-button> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { PropType } from 'vue' | ||||||
|  | 
 | ||||||
|  | const props = defineProps({ | ||||||
|  |   modelValue: { | ||||||
|  |     type: Object as PropType<any>, | ||||||
|  |     required: true | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits(['update:modelValue']) | ||||||
|  | 
 | ||||||
|  | // 获取父组件实例 | ||||||
|  | const parent = inject('parent', null) | ||||||
|  | 
 | ||||||
|  | // 表单数据 | ||||||
|  | const modelData = computed({ | ||||||
|  |   get: () => props.modelValue, | ||||||
|  |   set: (val) => emit('update:modelValue', val) | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 分段设置 | ||||||
|  | const segmentSettings = ref({ | ||||||
|  |   type: 1, // 1: 按段落, 2: 按字数, 3: 按标题 | ||||||
|  |   maxChars: 1000 | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 当前展开的段落 | ||||||
|  | const activeSegments = ref([0]) | ||||||
|  | 
 | ||||||
|  | // 自动分段 | ||||||
|  | const handleAutoSegment = () => { | ||||||
|  |   // 根据文档类型和分段设置进行自动分段 | ||||||
|  |   // 这里只是模拟实现,实际需要根据文档内容进行分析 | ||||||
|  | 
 | ||||||
|  |   // 清空现有段落 | ||||||
|  |   modelData.value.segments = [] | ||||||
|  | 
 | ||||||
|  |   // 模拟生成段落 | ||||||
|  |   if (modelData.value.documentType === 'text' && modelData.value.content) { | ||||||
|  |     // 文本类型,直接按段落或字数分割 | ||||||
|  |     const content = modelData.value.content | ||||||
|  | 
 | ||||||
|  |     if (segmentSettings.value.type === 1) { | ||||||
|  |       // 按段落分割 | ||||||
|  |       const paragraphs = content.split(/\n\s*\n/) | ||||||
|  |       paragraphs.forEach((paragraph) => { | ||||||
|  |         if (paragraph.trim()) { | ||||||
|  |           modelData.value.segments.push({ | ||||||
|  |             content: paragraph.trim(), | ||||||
|  |             order: modelData.value.segments.length + 1 | ||||||
|  |           }) | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |     } else if (segmentSettings.value.type === 2) { | ||||||
|  |       // 按字数分割 | ||||||
|  |       const maxChars = segmentSettings.value.maxChars | ||||||
|  |       let remaining = content | ||||||
|  | 
 | ||||||
|  |       while (remaining.length > 0) { | ||||||
|  |         const segment = remaining.substring(0, maxChars) | ||||||
|  |         remaining = remaining.substring(maxChars) | ||||||
|  | 
 | ||||||
|  |         modelData.value.segments.push({ | ||||||
|  |           content: segment, | ||||||
|  |           order: modelData.value.segments.length + 1 | ||||||
|  |         }) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } else { | ||||||
|  |     // 其他类型文档,模拟生成5个段落 | ||||||
|  |     for (let i = 0; i < 5; i++) { | ||||||
|  |       modelData.value.segments.push({ | ||||||
|  |         content: `这是第 ${i + 1} 个自动生成的段落,实际内容将根据文档解析结果填充。`, | ||||||
|  |         order: i + 1 | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // 默认展开第一个段落 | ||||||
|  |   activeSegments.value = [0] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 添加段落 | ||||||
|  | const handleAddSegment = () => { | ||||||
|  |   modelData.value.segments.push({ | ||||||
|  |     content: '', | ||||||
|  |     order: modelData.value.segments.length + 1 | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|  |   // 展开新添加的段落 | ||||||
|  |   activeSegments.value = [modelData.value.segments.length - 1] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 删除段落 | ||||||
|  | const handleDeleteSegment = (index) => { | ||||||
|  |   modelData.value.segments.splice(index, 1) | ||||||
|  | 
 | ||||||
|  |   // 更新段落顺序 | ||||||
|  |   modelData.value.segments.forEach((segment, idx) => { | ||||||
|  |     segment.order = idx + 1 | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 上一步按钮处理 | ||||||
|  | const handlePrevStep = () => { | ||||||
|  |   // 获取父组件的goToPrevStep方法 | ||||||
|  |   const parentEl = parent || getCurrentInstance()?.parent | ||||||
|  |   if (parentEl && typeof parentEl.exposed?.goToPrevStep === 'function') { | ||||||
|  |     parentEl.exposed.goToPrevStep() | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 下一步按钮处理 | ||||||
|  | const handleNextStep = () => { | ||||||
|  |   // 获取父组件的goToNextStep方法 | ||||||
|  |   const parentEl = parent || getCurrentInstance()?.parent | ||||||
|  |   if (parentEl && typeof parentEl.exposed?.goToNextStep === 'function') { | ||||||
|  |     parentEl.exposed.goToNextStep() | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 表单校验 | ||||||
|  | const validate = () => { | ||||||
|  |   return new Promise((resolve, reject) => { | ||||||
|  |     if (modelData.value.segments.length === 0) { | ||||||
|  |       reject(new Error('请至少添加一个段落')) | ||||||
|  |     } else { | ||||||
|  |       // 检查是否有空段落 | ||||||
|  |       const emptySegment = modelData.value.segments.find((segment) => !segment.content.trim()) | ||||||
|  |       if (emptySegment) { | ||||||
|  |         reject(new Error('存在空段落,请填写内容或删除')) | ||||||
|  |       } else { | ||||||
|  |         resolve(true) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 对外暴露方法 | ||||||
|  | defineExpose({ | ||||||
|  |   validate | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 初始化 | ||||||
|  | onMounted(() => { | ||||||
|  |   // 如果已有段落数据,默认展开第一个 | ||||||
|  |   if (modelData.value.segments && modelData.value.segments.length > 0) { | ||||||
|  |     activeSegments.value = [0] | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | .document-segment { | ||||||
|  |   .segment-content { | ||||||
|  |     padding: 10px; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | @ -0,0 +1,225 @@ | ||||||
|  | <template> | ||||||
|  |   <el-form ref="formRef" :model="modelData" :rules="rules" label-width="120px" class="mt-20px"> | ||||||
|  |     <el-form-item label="文档名称" prop="name" class="mb-20px"> | ||||||
|  |       <el-input v-model="modelData.name" clearable placeholder="请输入文档名称" /> | ||||||
|  |     </el-form-item> | ||||||
|  |     <el-form-item label="知识库" prop="knowledgeBaseId" class="mb-20px"> | ||||||
|  |       <el-select | ||||||
|  |         class="!w-full" | ||||||
|  |         v-model="modelData.knowledgeBaseId" | ||||||
|  |         clearable | ||||||
|  |         placeholder="请选择知识库" | ||||||
|  |       > | ||||||
|  |         <el-option | ||||||
|  |           v-for="base in knowledgeBaseList" | ||||||
|  |           :key="base.id" | ||||||
|  |           :label="base.name" | ||||||
|  |           :value="base.id" | ||||||
|  |         /> | ||||||
|  |       </el-select> | ||||||
|  |     </el-form-item> | ||||||
|  |     <el-form-item label="文档类型" prop="documentType" class="mb-20px"> | ||||||
|  |       <el-select | ||||||
|  |         class="!w-full" | ||||||
|  |         v-model="modelData.documentType" | ||||||
|  |         clearable | ||||||
|  |         placeholder="请选择文档类型" | ||||||
|  |       > | ||||||
|  |         <el-option label="PDF文档" value="pdf" /> | ||||||
|  |         <el-option label="Word文档" value="word" /> | ||||||
|  |         <el-option label="文本文件" value="text" /> | ||||||
|  |         <el-option label="网页链接" value="url" /> | ||||||
|  |       </el-select> | ||||||
|  |     </el-form-item> | ||||||
|  |     <el-form-item | ||||||
|  |       label="文档内容" | ||||||
|  |       prop="content" | ||||||
|  |       class="mb-20px" | ||||||
|  |       v-if="modelData.documentType === 'text'" | ||||||
|  |     > | ||||||
|  |       <el-input | ||||||
|  |         v-model="modelData.content" | ||||||
|  |         type="textarea" | ||||||
|  |         :rows="6" | ||||||
|  |         placeholder="请输入文档内容" | ||||||
|  |       /> | ||||||
|  |     </el-form-item> | ||||||
|  |     <el-form-item | ||||||
|  |       label="网页链接" | ||||||
|  |       prop="url" | ||||||
|  |       class="mb-20px" | ||||||
|  |       v-if="modelData.documentType === 'url'" | ||||||
|  |     > | ||||||
|  |       <el-input v-model="modelData.url" clearable placeholder="请输入网页链接" /> | ||||||
|  |     </el-form-item> | ||||||
|  |     <el-form-item | ||||||
|  |       label="上传文件" | ||||||
|  |       prop="file" | ||||||
|  |       class="mb-20px" | ||||||
|  |       v-if="['pdf', 'word'].includes(modelData.documentType)" | ||||||
|  |     > | ||||||
|  |       <el-upload | ||||||
|  |         class="upload-demo" | ||||||
|  |         drag | ||||||
|  |         action="#" | ||||||
|  |         :auto-upload="false" | ||||||
|  |         :on-change="handleFileChange" | ||||||
|  |         :limit="1" | ||||||
|  |       > | ||||||
|  |         <el-icon class="el-icon--upload"><upload-filled /></el-icon> | ||||||
|  |         <div class="el-upload__text"> 拖拽文件到此处,或 <em>点击上传</em> </div> | ||||||
|  |         <template #tip> | ||||||
|  |           <div class="el-upload__tip"> | ||||||
|  |             {{ modelData.documentType === 'pdf' ? 'PDF文件' : 'Word文件(.docx, .doc)' }} | ||||||
|  |           </div> | ||||||
|  |         </template> | ||||||
|  |       </el-upload> | ||||||
|  |     </el-form-item> | ||||||
|  | 
 | ||||||
|  |     <!-- 添加下一步按钮 --> | ||||||
|  |     <el-form-item> | ||||||
|  |       <div class="flex justify-end"> | ||||||
|  |         <el-button type="primary" @click="handleNextStep">下一步</el-button> | ||||||
|  |       </div> | ||||||
|  |     </el-form-item> | ||||||
|  |   </el-form> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { PropType } from 'vue' | ||||||
|  | import { UploadFilled } from '@element-plus/icons-vue' | ||||||
|  | 
 | ||||||
|  | const props = defineProps({ | ||||||
|  |   modelValue: { | ||||||
|  |     type: Object as PropType<any>, | ||||||
|  |     required: true | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits(['update:modelValue']) | ||||||
|  | 
 | ||||||
|  | // 表单引用 | ||||||
|  | const formRef = ref() | ||||||
|  | 
 | ||||||
|  | // 获取父组件实例 | ||||||
|  | const parent = inject('parent', null) | ||||||
|  | 
 | ||||||
|  | // 表单数据 | ||||||
|  | const modelData = computed({ | ||||||
|  |   get: () => props.modelValue, | ||||||
|  |   set: (val) => emit('update:modelValue', val) | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 知识库列表 | ||||||
|  | interface KnowledgeBase { | ||||||
|  |   id: number | ||||||
|  |   name: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const knowledgeBaseList = ref<KnowledgeBase[]>([]) | ||||||
|  | 
 | ||||||
|  | // 表单校验规则 | ||||||
|  | const rules = { | ||||||
|  |   name: [{ required: true, message: '请输入文档名称', trigger: 'blur' }], | ||||||
|  |   knowledgeBaseId: [{ required: true, message: '请选择知识库', trigger: 'change' }], | ||||||
|  |   documentType: [{ required: true, message: '请选择文档类型', trigger: 'change' }], | ||||||
|  |   content: [ | ||||||
|  |     { | ||||||
|  |       required: true, | ||||||
|  |       message: '请输入文档内容', | ||||||
|  |       trigger: 'blur', | ||||||
|  |       validator: (rule, value, callback) => { | ||||||
|  |         if (modelData.value.documentType === 'text' && !value) { | ||||||
|  |           callback(new Error('请输入文档内容')) | ||||||
|  |         } else { | ||||||
|  |           callback() | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   url: [ | ||||||
|  |     { | ||||||
|  |       required: true, | ||||||
|  |       message: '请输入网页链接', | ||||||
|  |       trigger: 'blur', | ||||||
|  |       validator: (rule, value, callback) => { | ||||||
|  |         if (modelData.value.documentType === 'url' && !value) { | ||||||
|  |           callback(new Error('请输入网页链接')) | ||||||
|  |         } else { | ||||||
|  |           callback() | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   file: [ | ||||||
|  |     { | ||||||
|  |       required: true, | ||||||
|  |       message: '请上传文件', | ||||||
|  |       trigger: 'change', | ||||||
|  |       validator: (rule, value, callback) => { | ||||||
|  |         if (['pdf', 'word'].includes(modelData.value.documentType) && !modelData.value.file) { | ||||||
|  |           callback(new Error('请上传文件')) | ||||||
|  |         } else { | ||||||
|  |           callback() | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 文件上传处理 | ||||||
|  | const handleFileChange = (file) => { | ||||||
|  |   modelData.value.file = file.raw | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 下一步按钮处理 | ||||||
|  | const handleNextStep = () => { | ||||||
|  |   // 获取父组件的goToNextStep方法 | ||||||
|  |   const parentEl = parent || getCurrentInstance()?.parent | ||||||
|  |   if (parentEl && typeof parentEl.exposed?.goToNextStep === 'function') { | ||||||
|  |     parentEl.exposed.goToNextStep() | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 初始化数据 | ||||||
|  | const initData = async () => { | ||||||
|  |   // 获取知识库列表 | ||||||
|  |   // knowledgeBaseList.value = await KnowledgeBaseApi.getKnowledgeBaseList() | ||||||
|  | 
 | ||||||
|  |   // 模拟数据 | ||||||
|  |   knowledgeBaseList.value = [ | ||||||
|  |     { id: 1, name: '产品知识库' }, | ||||||
|  |     { id: 2, name: '技术文档库' }, | ||||||
|  |     { id: 3, name: '客户服务知识库' } | ||||||
|  |   ] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 表单校验 | ||||||
|  | const validate = () => { | ||||||
|  |   return new Promise((resolve, reject) => { | ||||||
|  |     formRef.value?.validate((valid) => { | ||||||
|  |       if (valid) { | ||||||
|  |         resolve(true) | ||||||
|  |       } else { | ||||||
|  |         reject(new Error('请完善表单信息')) | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 对外暴露方法 | ||||||
|  | defineExpose({ | ||||||
|  |   validate | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 初始化 | ||||||
|  | onMounted(() => { | ||||||
|  |   initData() | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | .upload-demo { | ||||||
|  |   width: 100%; | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | @ -0,0 +1,214 @@ | ||||||
|  | <template> | ||||||
|  |   <ContentWrap> | ||||||
|  |     <div class="mx-auto"> | ||||||
|  |       <!-- 头部导航栏 --> | ||||||
|  |       <div | ||||||
|  |         class="absolute top-0 left-0 right-0 h-50px bg-white border-bottom z-10 flex items-center px-20px" | ||||||
|  |       > | ||||||
|  |         <!-- 左侧标题 --> | ||||||
|  |         <div class="w-200px flex items-center overflow-hidden"> | ||||||
|  |           <Icon icon="ep:arrow-left" class="cursor-pointer flex-shrink-0" @click="handleBack" /> | ||||||
|  |           <span class="ml-10px text-16px truncate" :title="formData.name || '创建知识库文档'"> | ||||||
|  |             {{ formData.name || '创建知识库文档' }} | ||||||
|  |           </span> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <!-- 步骤条 --> | ||||||
|  |         <div class="flex-1 flex items-center justify-center h-full"> | ||||||
|  |           <div class="w-400px flex items-center justify-between h-full"> | ||||||
|  |             <div | ||||||
|  |               v-for="(step, index) in steps" | ||||||
|  |               :key="index" | ||||||
|  |               class="flex items-center mx-15px relative h-full" | ||||||
|  |               :class="[ | ||||||
|  |                 currentStep === index | ||||||
|  |                   ? 'text-[#3473ff] border-[#3473ff] border-b-2 border-b-solid' | ||||||
|  |                   : 'text-gray-500' | ||||||
|  |               ]" | ||||||
|  |             > | ||||||
|  |               <div | ||||||
|  |                 class="w-28px h-28px rounded-full flex items-center justify-center mr-8px border-2 border-solid text-15px" | ||||||
|  |                 :class="[ | ||||||
|  |                   currentStep === index | ||||||
|  |                     ? 'bg-[#3473ff] text-white border-[#3473ff]' | ||||||
|  |                     : 'border-gray-300 bg-white text-gray-500' | ||||||
|  |                 ]" | ||||||
|  |               > | ||||||
|  |                 {{ index + 1 }} | ||||||
|  |               </div> | ||||||
|  |               <span class="text-16px font-bold whitespace-nowrap">{{ step.title }}</span> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <!-- 右侧按钮 - 已移除 --> | ||||||
|  |         <div class="w-200px flex items-center justify-end gap-2"> </div> | ||||||
|  |       </div> | ||||||
|  | 
 | ||||||
|  |       <!-- 主体内容 --> | ||||||
|  |       <div class="mt-50px"> | ||||||
|  |         <!-- 第一步:上传文档 --> | ||||||
|  |         <div v-if="currentStep === 0" class="mx-auto w-560px"> | ||||||
|  |           <UploadStep v-model="formData" ref="uploadDocumentRef" /> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <!-- 第二步:文档分段 --> | ||||||
|  |         <div v-if="currentStep === 1" class="mx-auto w-560px"> | ||||||
|  |           <SplitStep v-model="formData" ref="documentSegmentRef" /> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <!-- 第三步:处理并完成 --> | ||||||
|  |         <div v-if="currentStep === 2" class="mx-auto w-560px"> | ||||||
|  |           <ProcessStep v-model="formData" ref="processCompleteRef" /> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </ContentWrap> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { useRoute, useRouter } from 'vue-router' | ||||||
|  | import { useMessage } from '@/hooks/web/useMessage' | ||||||
|  | import { useTagsViewStore } from '@/store/modules/tagsView' | ||||||
|  | import UploadStep from './UploadStep.vue' | ||||||
|  | import SplitStep from './SplitStep.vue' | ||||||
|  | import ProcessStep from './ProcessStep.vue' | ||||||
|  | 
 | ||||||
|  | const router = useRouter() | ||||||
|  | const { delView } = useTagsViewStore() // 视图操作 | ||||||
|  | const route = useRoute() | ||||||
|  | const message = useMessage() | ||||||
|  | 
 | ||||||
|  | // 组件引用 | ||||||
|  | const uploadDocumentRef = ref() | ||||||
|  | const documentSegmentRef = ref() | ||||||
|  | const processCompleteRef = ref() | ||||||
|  | 
 | ||||||
|  | const currentStep = ref(0) // 步骤控制 | ||||||
|  | const steps = [{ title: '上传文档' }, { title: '文档分段' }, { title: '处理并完成' }] | ||||||
|  | 
 | ||||||
|  | // 表单数据 | ||||||
|  | const formData = ref({ | ||||||
|  |   id: undefined, | ||||||
|  |   name: '', | ||||||
|  |   knowledgeBaseId: undefined, | ||||||
|  |   documentType: undefined, | ||||||
|  |   content: '', | ||||||
|  |   file: null, | ||||||
|  |   segments: [], | ||||||
|  |   status: 0 // 0: 草稿, 1: 处理中, 2: 已完成 | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | /** 初始化数据 */ | ||||||
|  | const initData = async () => { | ||||||
|  |   const documentId = route.params.id as string | ||||||
|  |   if (documentId) { | ||||||
|  |     // 修改场景 | ||||||
|  |     // 这里需要调用API获取文档数据 | ||||||
|  |     // formData.value = await DocumentApi.getDocument(documentId) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 切换到下一步 */ | ||||||
|  | const goToNextStep = () => { | ||||||
|  |   if (currentStep.value < steps.length - 1) { | ||||||
|  |     currentStep.value++ | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 切换到上一步 */ | ||||||
|  | const goToPrevStep = () => { | ||||||
|  |   if (currentStep.value > 0) { | ||||||
|  |     currentStep.value-- | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 保存操作 */ | ||||||
|  | const handleSave = async () => { | ||||||
|  |   try { | ||||||
|  |     // 更新表单数据 | ||||||
|  |     const documentData = { | ||||||
|  |       ...formData.value | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (formData.value.id) { | ||||||
|  |       // 修改场景 | ||||||
|  |       // await DocumentApi.updateDocument(documentData) | ||||||
|  |       message.success('修改成功') | ||||||
|  |     } else { | ||||||
|  |       // 新增场景 | ||||||
|  |       // formData.value.id = await DocumentApi.createDocument(documentData) | ||||||
|  |       message.success('新增成功') | ||||||
|  |       try { | ||||||
|  |         await message.confirm('创建文档成功,是否继续编辑?') | ||||||
|  |         // 用户点击继续编辑,跳转到编辑页面 | ||||||
|  |         await nextTick() | ||||||
|  |         // 先删除当前页签 | ||||||
|  |         delView(unref(router.currentRoute)) | ||||||
|  |         // 跳转到编辑页面 | ||||||
|  |         await router.push({ | ||||||
|  |           name: 'AiKnowledgeDocumentUpdate', | ||||||
|  |           params: { id: formData.value.id } | ||||||
|  |         }) | ||||||
|  |       } catch { | ||||||
|  |         // 先删除当前页签 | ||||||
|  |         delView(unref(router.currentRoute)) | ||||||
|  |         // 用户点击返回列表 | ||||||
|  |         await router.push({ name: 'AiKnowledgeDocument' }) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } catch (error: any) { | ||||||
|  |     console.error('保存失败:', error) | ||||||
|  |     message.warning(error.message || '请完善所有步骤的必填信息') | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 返回列表页 */ | ||||||
|  | const handleBack = () => { | ||||||
|  |   // 先删除当前页签 | ||||||
|  |   delView(unref(router.currentRoute)) | ||||||
|  |   // 跳转到列表页 | ||||||
|  |   router.push({ name: 'AiKnowledgeDocument' }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 初始化 */ | ||||||
|  | onMounted(async () => { | ||||||
|  |   await initData() | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 提供parent给子组件使用 | ||||||
|  | provide('parent', getCurrentInstance()) | ||||||
|  | 
 | ||||||
|  | // 添加组件卸载前的清理代码 | ||||||
|  | onBeforeUnmount(() => { | ||||||
|  |   // 清理所有的引用 | ||||||
|  |   uploadDocumentRef.value = null | ||||||
|  |   documentSegmentRef.value = null | ||||||
|  |   processCompleteRef.value = null | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 暴露方法给子组件使用 | ||||||
|  | defineExpose({ | ||||||
|  |   goToNextStep, | ||||||
|  |   goToPrevStep, | ||||||
|  |   handleSave | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | .border-bottom { | ||||||
|  |   border-bottom: 1px solid #dcdfe6; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .text-primary { | ||||||
|  |   color: #3473ff; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .bg-primary { | ||||||
|  |   background-color: #3473ff; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .border-primary { | ||||||
|  |   border-color: #3473ff; | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | @ -35,12 +35,7 @@ | ||||||
|       <el-form-item> |       <el-form-item> | ||||||
|         <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> |         <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> | ||||||
|         <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> |         <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> | ||||||
|         <el-button |         <el-button type="primary" plain @click="handleCreate" v-hasPermi="['ai:knowledge:create']"> | ||||||
|           type="primary" |  | ||||||
|           plain |  | ||||||
|           @click="openForm('create')" |  | ||||||
|           v-hasPermi="['ai:knowledge:create']" |  | ||||||
|         > |  | ||||||
|           <Icon icon="ep:plus" class="mr-5px" /> 新增 |           <Icon icon="ep:plus" class="mr-5px" /> 新增 | ||||||
|         </el-button> |         </el-button> | ||||||
|       </el-form-item> |       </el-form-item> | ||||||
|  | @ -106,7 +101,7 @@ | ||||||
| import { getIntDictOptions, DICT_TYPE } from '@/utils/dict' | import { getIntDictOptions, DICT_TYPE } from '@/utils/dict' | ||||||
| import { dateFormatter } from '@/utils/formatTime' | import { dateFormatter } from '@/utils/formatTime' | ||||||
| import { KnowledgeDocumentApi, KnowledgeDocumentVO } from '@/api/ai/knowledge/document' | import { KnowledgeDocumentApi, KnowledgeDocumentVO } from '@/api/ai/knowledge/document' | ||||||
| import { useRoute } from 'vue-router' | import { useRoute, useRouter } from 'vue-router' | ||||||
| // import KnowledgeDocumentForm from './KnowledgeDocumentForm.vue' | // import KnowledgeDocumentForm from './KnowledgeDocumentForm.vue' | ||||||
| 
 | 
 | ||||||
| /** AI 知识库文档 列表 */ | /** AI 知识库文档 列表 */ | ||||||
|  | @ -115,6 +110,7 @@ defineOptions({ name: 'KnowledgeDocument' }) | ||||||
| const message = useMessage() // 消息弹窗 | const message = useMessage() // 消息弹窗 | ||||||
| const { t } = useI18n() // 国际化 | const { t } = useI18n() // 国际化 | ||||||
| const route = useRoute() // 路由 | const route = useRoute() // 路由 | ||||||
|  | const router = useRouter() // 路由 | ||||||
| 
 | 
 | ||||||
| const loading = ref(true) // 列表的加载中 | const loading = ref(true) // 列表的加载中 | ||||||
| const list = ref<KnowledgeDocumentVO[]>([]) // 列表的数据 | const list = ref<KnowledgeDocumentVO[]>([]) // 列表的数据 | ||||||
|  | @ -158,6 +154,11 @@ const openForm = (type: string, id?: number) => { | ||||||
|   formRef.value.open(type, id) |   formRef.value.open(type, id) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /** 跳转到创建文档页面 */ | ||||||
|  | const handleCreate = () => { | ||||||
|  |   router.push({ name: 'AiKnowledgeDocumentCreate' }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /** 删除按钮操作 */ | /** 删除按钮操作 */ | ||||||
| const handleDelete = async (id: number) => { | const handleDelete = async (id: number) => { | ||||||
|   try { |   try { | ||||||
|  |  | ||||||
|  | @ -380,7 +380,7 @@ const handleStepClick = async (index: number) => { | ||||||
|     if (index === 2) { |     if (index === 2) { | ||||||
|       await nextTick() |       await nextTick() | ||||||
|       // 等待更长时间确保组件完全初始化 |       // 等待更长时间确保组件完全初始化 | ||||||
|       await new Promise(resolve => setTimeout(resolve, 200)) |       await new Promise((resolve) => setTimeout(resolve, 200)) | ||||||
|       if (processDesignRef.value?.refresh) { |       if (processDesignRef.value?.refresh) { | ||||||
|         await processDesignRef.value.refresh() |         await processDesignRef.value.refresh() | ||||||
|       } |       } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 YunaiV
						YunaiV