Pārlūkot izejas kodu

Merge branch 'dev' of http://192.168.0.3:3000/CRBC-MaaS-Platform-Project/LQAdminFront into dev

chenkun 4 nedēļas atpakaļ
vecāks
revīzija
1aa6274139

+ 4 - 0
src/api/knowledge-base.ts

@@ -8,6 +8,9 @@ export interface KnowledgeBase {
   description?: string
   status: string
   document_count: number
+  is_synced?: boolean
+  created_by?: string
+  updated_by?: string
   created_at: string
   updated_at: string
 }
@@ -47,6 +50,7 @@ export interface UpdateKnowledgeBaseData {
   name?: string
   description?: string
   status?: string
+  metadata_fields?: MetadataField[]
 }
 
 export const getKnowledgeBases = (params: KnowledgeBaseParams) => {

+ 2 - 0
src/api/snippet.ts

@@ -9,6 +9,8 @@ export interface Snippet {
   content: string
   char_count: number
   meta_info: string
+  metadata: any // 新增 metadata 字段,通常是 JSON 对象
+  parent_id?: string // 可能有 parent_id
   status: string
   created_at: string
   updated_at: string

+ 1 - 19
src/layouts/MainLayout.vue

@@ -249,15 +249,6 @@ const getDefaultMenus = (): MenuItem[] => {
       ]
     })
 
-    defaultMenus.push({
-      id: 'images',
-      name: 'images',
-      title: '图片管理中心',
-      path: '/admin/images',
-      icon: 'Picture',
-      children: []
-    })
-
     defaultMenus.push({
       id: 'admin',
       name: 'admin',
@@ -296,18 +287,9 @@ const getDefaultMenus = (): MenuItem[] => {
           path: '/admin/menus',
           icon: 'Menu',
           is_hidden: false
-        },
+        }
       ]
     })
-
-    defaultMenus.push({
-      id: 'admin-tasks',
-      name: 'admin-tasks',
-      title: '任务管理中心',
-      path: '/admin/tasks',
-      icon: 'List',
-      children: []
-    })
   }
   
   return defaultMenus

+ 249 - 132
src/views/documents/KnowledgeBase.vue

@@ -9,7 +9,6 @@
       <div class="action-buttons">
         <el-select v-model="queryParams.status" placeholder="所有状态" clearable style="width: 120px; margin-right: 12px" @change="handleSearch">
           <el-option label="正常" value="normal" />
-          <el-option label="测试" value="test" />
           <el-option label="已禁用" value="disabled" />
         </el-select>
         <el-input
@@ -19,11 +18,10 @@
           clearable
           @keyup.enter="handleSearch"
           @clear="handleSearch"
-        >
-          <template #prefix>
-            <el-icon><Search /></el-icon>
-          </template>
-        </el-input>
+        />
+        <el-button type="primary" @click="handleSearch">
+          <el-icon><Search /></el-icon> 查询
+        </el-button>
         <el-button type="primary" @click="handleAdd">
           <el-icon><Plus /></el-icon> 新建知识库
         </el-button>
@@ -47,6 +45,8 @@
         </el-table-column>
 
         <el-table-column prop="collection_name" label="知识库表名" min-width="150" />
+        
+        <el-table-column prop="document_count" label="文档数量" width="100" />
 
         <el-table-column prop="status" label="状态" width="100">
           <template #default="{ row }">
@@ -54,20 +54,22 @@
           </template>
         </el-table-column>
 
+        <el-table-column prop="created_by" label="创建人" width="100" />
+        
         <el-table-column prop="created_at" label="创建时间" width="160">
             <template #default="{ row }">
                 {{ formatTime(row.created_at) }}
             </template>
         </el-table-column>
 
+        <el-table-column prop="updated_by" label="修改人" width="100" />
+
         <el-table-column prop="updated_at" label="修改时间" width="160">
             <template #default="{ row }">
                 {{ formatTime(row.updated_at) }}
             </template>
         </el-table-column>
 
-        <el-table-column prop="document_count" label="文档数量" width="100" />
-
         <el-table-column label="操作" width="180" fixed="right">
           <template #default="{ row }">
             <el-tooltip content="查看" placement="top">
@@ -80,8 +82,8 @@
                     <el-icon><Edit /></el-icon>
                 </el-button>
             </el-tooltip>
-            <el-tooltip content="同步到Milvus" placement="top">
-                <el-button link type="warning" @click="handleSync(row)">
+            <el-tooltip :content="row.is_synced ? '已同步' : '同步到Milvus'" placement="top">
+                <el-button link type="warning" @click="handleSync(row)" :disabled="row.is_synced">
                     <el-icon><Refresh /></el-icon>
                 </el-button>
             </el-tooltip>
@@ -128,111 +130,106 @@
     <el-dialog
       v-model="dialogVisible"
       :title="dialogType === 'create' ? '新建知识库' : '编辑知识库'"
-      width="500px"
+      width="700px"
       @close="resetForm"
+      class="kb-dialog"
     >
-      <el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
-        <el-form-item label="名称" prop="name">
-          <el-input v-model="formData.name" placeholder="请输入知识库名称" />
-        </el-form-item>
-        <el-form-item label="集合名称" prop="collection_name" v-if="dialogType === 'create'">
-          <el-input v-model="formData.collection_name" placeholder="请输入Milvus集合名称(英文)" />
-        </el-form-item>
-        <el-form-item label="描述" prop="description">
-          <el-input v-model="formData.description" type="textarea" rows="3" placeholder="请输入描述信息" />
-        </el-form-item>
-        <el-form-item label="维度" prop="dimension" v-if="dialogType === 'create'">
-          <el-input-number v-model="formData.dimension" :min="1" :step="1" />
-        </el-form-item>
-        <el-form-item label="自定义Schema" v-if="dialogType === 'create'">
-            <div class="metadata-fields-container">
-                <div v-for="(schema, index) in formData.custom_schemas" :key="index" class="metadata-field-row">
-                    <div class="field-inputs">
-                        <el-row :gutter="10">
-                            <el-col :span="6">
-                                <el-form-item :prop="'custom_schemas.' + index + '.field_name'" :rules="[
-                                    { required: true, message: '请输入字段名', trigger: 'blur' },
-                                    { pattern: /^[a-zA-Z_][a-zA-Z0-9_]*$/, message: '以字母/下划线开头', trigger: 'blur' }
-                                ]" style="margin-bottom: 0;">
-                                    <el-input v-model="schema.field_name" placeholder="Schema字段名(英文)" />
-                                </el-form-item>
-                            </el-col>
-                            <el-col :span="6">
-                                <el-select v-model="schema.field_type" placeholder="Schema类型">
-                                    <el-option label="INT64" value="INT64" />
-                                    <el-option label="INT32" value="INT32" />
-                                    <el-option label="FLOAT" value="FLOAT" />
-                                    <el-option label="VARCHAR" value="VARCHAR" />
-                                    <el-option label="BOOL" value="BOOL" />
-                                    <el-option label="JSON" value="JSON" />
-                                </el-select>
-                            </el-col>
-                            <el-col :span="6">
-                                <el-input v-model="schema.description" placeholder="Schema描述" />
-                            </el-col>
-                            <el-col :span="6" v-if="schema.field_type === 'VARCHAR'">
-                                <el-input-number v-model="schema.max_length" :min="1" placeholder="Max Len" style="width: 100%" />
-                            </el-col>
-                        </el-row>
-                    </div>
-                    <el-button 
-                        type="danger" 
-                        link 
-                        :icon="Delete"
-                        @click="removeCustomSchema(index)"
-                        class="delete-btn"
-                    />
-                </div>
-                <div class="add-field-btn">
-                    <el-button type="success" link :icon="Plus" @click="addCustomSchema">添加Schema字段</el-button>
-                </div>
-            </div>
-        </el-form-item>
+      <el-form ref="formRef" :model="formData" :rules="rules" label-width="110px" class="kb-form">
+        <div class="form-section">
+            <div class="section-title">基本信息</div>
+            <el-row :gutter="20">
+                <el-col :span="12">
+                    <el-form-item label="知识库名称" prop="name">
+                        <el-input v-model="formData.name" placeholder="请输入知识库名称" />
+                    </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                    <el-form-item label="集合名称" prop="collection_name" v-if="dialogType === 'create'">
+                        <el-input v-model="formData.collection_name" placeholder="Milvus集合名(英文)" />
+                    </el-form-item>
+                    <el-form-item label="集合名称" v-else>
+                        <el-input v-model="formData.collection_name" disabled />
+                    </el-form-item>
+                </el-col>
+            </el-row>
+            
+            <el-form-item label="描述" prop="description">
+                <el-input 
+                    v-model="formData.description" 
+                    type="textarea" 
+                    rows="3" 
+                    placeholder="请输入关于此知识库的详细描述..." 
+                />
+            </el-form-item>
+            
+            <el-row :gutter="20">
+                <el-col :span="12" v-if="dialogType === 'create'">
+                    <el-form-item label="向量维度" prop="dimension">
+                        <el-input-number v-model="formData.dimension" :min="1" :step="1" style="width: 100%" />
+                    </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                    <el-form-item label="状态" prop="status">
+                        <el-radio-group v-model="formData.status">
+                            <el-radio-button label="normal">正常</el-radio-button>
+                            <el-radio-button label="disabled">禁用</el-radio-button>
+                        </el-radio-group>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+        </div>
 
-        <el-form-item label="元数据字段" v-if="dialogType === 'create'">
+        <div class="form-section">
+            <div class="section-header">
+                <div class="section-title">元数据字段定义</div>
+                <div class="section-desc">定义文档的额外属性,用于精确检索过滤</div>
+            </div>
+            
             <div class="metadata-fields-container">
-                <div v-for="(field, index) in formData.metadata_fields" :key="index" class="metadata-field-row">
-                    <div class="field-inputs">
-                        <el-row :gutter="10">
-                            <el-col :span="6">
-                                <el-input v-model="field.field_zh_name" placeholder="中文名称" />
-                            </el-col>
-                            <el-col :span="6">
-                                <el-input v-model="field.field_en_name" placeholder="英文名称" />
-                            </el-col>
-                            <el-col :span="5">
+                <div v-for="(field, index) in formData.metadata_fields" :key="index" class="metadata-field-card">
+                    <div class="field-header">
+                        <span class="field-index">字段 {{ index + 1 }}</span>
+                        <el-button 
+                            v-if="formData.metadata_fields.length > 1"
+                            type="danger" 
+                            link 
+                            :icon="Delete"
+                            @click="removeMetadataField(index)"
+                            size="small"
+                        >删除</el-button>
+                    </div>
+                    <el-row :gutter="15">
+                        <el-col :span="6">
+                            <el-form-item label-width="0" style="margin-bottom: 12px;">
+                                <el-input v-model="field.field_zh_name" placeholder="中文名称 (如: 年份)" />
+                            </el-form-item>
+                        </el-col>
+                        <el-col :span="6">
+                            <el-form-item label-width="0" style="margin-bottom: 12px;">
+                                <el-input v-model="field.field_en_name" placeholder="英文标识 (如: year)" />
+                            </el-form-item>
+                        </el-col>
+                        <el-col :span="5">
+                            <el-form-item label-width="0" style="margin-bottom: 12px;">
                                 <el-select v-model="field.field_type" placeholder="类型">
                                     <el-option label="文本" value="text" />
                                     <el-option label="数字" value="num" />
                                 </el-select>
-                            </el-col>
-                            <el-col :span="7">
-                                <el-input v-model="field.remark" placeholder="备注" />
-                            </el-col>
-                        </el-row>
-                    </div>
-                    <!-- 元数据字段现在默认必须存在至少一个,但也允许用户删除 -->
-                    <el-button 
-                        v-if="formData.metadata_fields.length > 1"
-                        type="danger" 
-                        link 
-                        :icon="Delete"
-                        @click="removeMetadataField(index)"
-                        class="delete-btn"
-                    />
+                            </el-form-item>
+                        </el-col>
+                        <el-col :span="7">
+                            <el-form-item label-width="0" style="margin-bottom: 12px;">
+                                <el-input v-model="field.remark" placeholder="备注说明" />
+                            </el-form-item>
+                        </el-col>
+                    </el-row>
                 </div>
+                
                 <div class="add-field-btn">
-                    <el-button type="primary" link :icon="Plus" @click="addMetadataField">添加元数据字段</el-button>
+                    <el-button type="primary" plain :icon="Plus" @click="addMetadataField" style="width: 100%">添加元数据字段</el-button>
                 </div>
             </div>
-        </el-form-item>
-        <el-form-item label="状态" prop="status">
-            <el-radio-group v-model="formData.status">
-                <el-radio label="normal">正常</el-radio>
-                <el-radio label="test">测试</el-radio>
-                <el-radio label="disabled">禁用</el-radio>
-            </el-radio-group>
-        </el-form-item>
+        </div>
       </el-form>
       <template #footer>
         <span class="dialog-footer">
@@ -251,6 +248,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
 import type { FormInstance, FormRules } from 'element-plus'
 import { 
     getKnowledgeBases, 
+    getKnowledgeBaseMetadata,
     createKnowledgeBase, 
     updateKnowledgeBase, 
     updateKnowledgeBaseStatus, 
@@ -289,8 +287,7 @@ const formData = reactive({
   description: '',
   dimension: 768,
   status: 'normal',
-  metadata_fields: [{ field_zh_name: '', field_en_name: '', field_type: 'text', remark: '' }],
-  custom_schemas: [] as any[]
+  metadata_fields: [{ field_zh_name: '', field_en_name: '', field_type: 'text', remark: '' }]
 })
 
 const rules: FormRules = {
@@ -346,21 +343,35 @@ const handleAdd = () => {
   formData.name = ''
   formData.collection_name = ''
   formData.description = ''
-  formData.dimension = 768
+ formData.dimension = 768
   formData.status = 'normal'
   formData.metadata_fields = [{ field_zh_name: '', field_en_name: '', field_type: 'text', remark: '' }]
-  formData.custom_schemas = []
   dialogVisible.value = true
 }
-
-const handleEdit = (row: KnowledgeBase) => {
+const handleEdit = async (row: KnowledgeBase) => {
   dialogType.value = 'edit'
   formData.id = row.id
   formData.name = row.name
   formData.collection_name = row.collection_name
   formData.description = row.description || ''
   formData.status = row.status
-  // dimension cannot be changed
+  
+  // 加载元数据字段,允许编辑
+  try {
+      const res = await getKnowledgeBaseMetadata(row.id)
+      if (res.code === 0 && res.data) {
+          // res.data.metadata_fields 是从 t_samp_metadata 获取的
+          if (res.data.metadata_fields && res.data.metadata_fields.length > 0) {
+              formData.metadata_fields = res.data.metadata_fields
+          } else {
+              formData.metadata_fields = [{ field_zh_name: '', field_en_name: '', field_type: 'text', remark: '' }]
+          }
+      }
+  } catch (error) {
+      console.error("加载元数据失败", error)
+      formData.metadata_fields = [{ field_zh_name: '', field_en_name: '', field_type: 'text', remark: '' }]
+  }
+  
   dialogVisible.value = true
 }
 
@@ -374,11 +385,78 @@ const handleSync = async (row: KnowledgeBase) => {
     }
 }
 
-const handleView = (row: KnowledgeBase) => {
-    ElMessage.info(`查看知识库: ${row.name}`)
+const handleView = async (row: KnowledgeBase) => {
+    // 复用编辑时的逻辑加载元数据,但不打开编辑弹窗
+    try {
+        const res = await getKnowledgeBaseMetadata(row.id)
+        let metadataInfo = ''
+        let schemaInfo = ''
+        
+        if (res.code === 0 && res.data) {
+            if (res.data.metadata_fields && res.data.metadata_fields.length > 0) {
+                metadataInfo = res.data.metadata_fields.map((f: any) => 
+                    `<li>${f.field_zh_name} (${f.field_en_name}): ${f.field_type === 'text' ? '文本' : '数字'} - ${f.remark || '无备注'}</li>`
+                ).join('')
+            }
+            
+            if (res.data.custom_schemas && res.data.custom_schemas.length > 0) {
+                schemaInfo = res.data.custom_schemas.map((s: any) => 
+                    `<li>${s.field_name}: ${s.field_type} ${s.max_length ? `(Max: ${s.max_length})` : ''} - ${s.description || '无描述'}</li>`
+                ).join('')
+            }
+        }
+        
+        const content = `
+            <div style="text-align: left;">
+                <p><strong>名称:</strong> ${row.name}</p>
+                <p><strong>集合:</strong> ${row.collection_name}</p>
+                <p><strong>描述:</strong> ${row.description || '暂无'}</p>
+                <p><strong>状态:</strong> ${getStatusText(row.status)}</p>
+                <p><strong>文档数:</strong> ${row.document_count}</p>
+                <p><strong>创建时间:</strong> ${formatTime(row.created_at)}</p>
+                <br>
+                <p><strong>自定义 Schema (Milvus):</strong></p>
+                <ul>
+                    <li>pk: INT64 (主键)</li>
+                    <li>text: VARCHAR (65535) - 内容</li>
+                    <li>vector: FLOAT_VECTOR (768) - 向量列</li>
+                    <li>sparse: BM25 - 关键字检索</li>
+                    <li>document_id: VARCHAR (128) - 文档ID</li>
+                    <li>parent_id: VARCHAR (128) - 父段ID</li>
+                    <li>index: INT64 - 索引序号</li>
+                    <li>tag_list: VARCHAR (2048) - 标签</li>
+                    <li>permission: JSON - 权限</li>
+                    <li>metadata: JSON - 元数据</li>
+                    <li>is_deleted: BOOL - 删除标志</li>
+                    <li>created_by: VARCHAR (128) - 创建人</li>
+                    <li>created_time: INT64 - 创建时间</li>
+                    <li>updated_by: VARCHAR (128) - 修改人</li>
+                    <li>updated_time: INT64 - 修改时间</li>
+                </ul>
+                <br>
+                <p><strong>元数据字段 (Metadata):</strong></p>
+                <ul>${metadataInfo || '<li>无元数据字段</li>'}</ul>
+            </div>
+        `
+        
+        ElMessageBox.alert(content, '知识库详情', {
+            dangerouslyUseHTMLString: true,
+            confirmButtonText: '关闭',
+            customStyle: { maxWidth: '600px' }
+        })
+    } catch (error) {
+        console.error("加载详情失败", error)
+        ElMessage.error("获取详情失败")
+    }
 }
 
 const handleDelete = (row: KnowledgeBase) => {
+  // 预先检查,提升用户体验
+  if (row.document_count > 0) {
+      ElMessage.warning(`知识库 "${row.name}" 中仍有 ${row.document_count} 条文档,请先清空文档后再删除`)
+      return
+  }
+
   ElMessageBox.confirm(
     `确定要删除知识库 "${row.name}" 吗?此操作不可恢复,且会删除对应的Milvus集合。`,
     '警告',
@@ -413,14 +491,6 @@ const handleCommand = async (command: string, row: KnowledgeBase) => {
     }
 }
 
-const addCustomSchema = () => {
-    formData.custom_schemas.push({ field_name: '', field_type: 'VARCHAR', max_length: 256, is_primary: false, description: '' })
-}
-
-const removeCustomSchema = (index: number) => {
-    formData.custom_schemas.splice(index, 1)
-}
-
 const addMetadataField = () => {
     formData.metadata_fields.push({ field_zh_name: '', field_en_name: '', field_type: 'text', remark: '' })
 }
@@ -441,15 +511,15 @@ const handleSubmit = async () => {
             collection_name: formData.collection_name,
             description: formData.description,
             dimension: formData.dimension,
-            metadata_fields: formData.metadata_fields.filter(f => f.field_zh_name && f.field_en_name),
-            custom_schemas: formData.custom_schemas.filter(s => s.field_name)
+            metadata_fields: formData.metadata_fields.filter(f => f.field_zh_name && f.field_en_name)
           })
           ElMessage.success('创建成功')
         } else {
           await updateKnowledgeBase(formData.id, {
             name: formData.name,
             description: formData.description,
-            status: formData.status
+            status: formData.status,
+            metadata_fields: formData.metadata_fields.filter(f => f.field_zh_name && f.field_en_name)
           })
           ElMessage.success('更新成功')
         }
@@ -583,25 +653,72 @@ onMounted(() => {
     font-size: 13px;
 }
 
-.metadata-fields-container {
-    width: 100%;
+.kb-dialog :deep(.el-dialog__body) {
+    padding: 20px 30px;
+}
+
+.kb-form {
+    padding-right: 10px;
+}
+
+.form-section {
+    margin-bottom: 24px;
+}
+
+.section-title {
+    font-size: 15px;
+    font-weight: 600;
+    color: #303133;
+    margin-bottom: 16px;
+    padding-left: 10px;
+    border-left: 3px solid #409eff;
+}
+
+.section-header {
+    display: flex;
+    align-items: baseline;
+    margin-bottom: 16px;
+}
+
+.section-desc {
+    font-size: 12px;
+    color: #909399;
+    margin-left: 10px;
+}
+
+.metadata-field-card {
+    background-color: #f8f9fa;
+    border-radius: 4px;
+    border: 1px solid #e4e7ed;
+    padding: 12px 16px;
+    margin-bottom: 12px;
 }
 
-.metadata-field-row {
+.field-header {
     display: flex;
+    justify-content: space-between;
     align-items: center;
     margin-bottom: 10px;
 }
 
-.field-inputs {
-    flex: 1;
+.field-index {
+    font-size: 12px;
+    font-weight: 600;
+    color: #606266;
+    background-color: #e6e8eb;
+    padding: 2px 6px;
+    border-radius: 2px;
 }
 
-.delete-btn {
-    margin-left: 10px;
+.add-field-btn {
+    margin-top: 10px;
 }
 
-.add-field-btn {
-    margin-top: 5px;
+.metadata-fields-container {
+    padding: 0 5px;
+}
+
+.delete-btn {
+    margin-left: 10px;
 }
 </style>

+ 261 - 106
src/views/documents/KnowledgeSnippet.vue

@@ -16,7 +16,9 @@
             :key="item.value" 
             :label="item.label" 
             :value="item.value" 
-          />
+          >
+            <span>{{ item.label }}</span>
+          </el-option>
         </el-select>
         <el-input
           v-model="queryParams.keyword"
@@ -25,11 +27,10 @@
           clearable
           @keyup.enter="handleSearch"
           @clear="handleSearch"
-        >
-          <template #prefix>
-            <el-icon><Search /></el-icon>
-          </template>
-        </el-input>
+        />
+        <el-button type="primary" @click="handleSearch">
+          <el-icon><Search /></el-icon> 查询
+        </el-button>
         <el-button type="primary" @click="handleAdd">
           <el-icon><Plus /></el-icon> 新建片段
         </el-button>
@@ -38,25 +39,17 @@
 
     <!-- 搜索栏下方工具条 -->
     <div class="toolbar-section">
-        <el-input
-          v-model="queryParams.contentSearch"
-          placeholder="搜索知识片段"
-          style="width: 300px; margin-right: auto"
-          clearable
-        />
         <el-button type="primary" @click="handleAdd">
             <el-icon><Plus /></el-icon> 新建片段
         </el-button>
-        <el-button>
-            <el-icon><Download /></el-icon> 导出片段
-        </el-button>
-        <el-button>
-            <el-icon><Filter /></el-icon> 筛选
-        </el-button>
     </div>
 
-    <el-card class="table-card" shadow="never">
-      <el-table :data="tableData" v-loading="loading" style="width: 100%" @selection-change="handleSelectionChange">
+        <div v-if="!queryParams.kb" class="empty-state">
+            <el-empty description="请先选择一个知识库以查看内容" />
+        </div>
+        
+        <el-card v-else class="table-card" shadow="never">
+          <el-table :data="tableData" v-loading="loading" style="width: 100%" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" />
         
         <el-table-column label="文档名称" min-width="150">
@@ -72,7 +65,17 @@
 
         <el-table-column label="片段内容" min-width="300">
             <template #default="{ row }">
-                <div class="content-cell">{{ row.content }}</div>
+                <el-tooltip
+                    effect="dark"
+                    :content="row.content"
+                    placement="top"
+                    :show-after="500"
+                    :disabled="!row.content || row.content.length <= 200"
+                >
+                    <div class="content-cell">
+                        {{ row.content && row.content.length > 200 ? row.content.substring(0, 200) + '...' : row.content }}
+                    </div>
+                </el-tooltip>
             </template>
         </el-table-column>
 
@@ -80,7 +83,7 @@
 
         <el-table-column label="元数据信息" min-width="200">
             <template #default="{ row }">
-                <span class="meta-info">{{ row.meta_info }}</span>
+                <span class="meta-info">{{ formatMetaInfo(row) }}</span>
             </template>
         </el-table-column>
 
@@ -126,7 +129,7 @@
         </el-table-column>
       </el-table>
 
-      <div class="pagination-container">
+      <div v-if="queryParams.kb" class="pagination-container">
         <div class="pagination-info">
             显示 {{ (pagination.page - 1) * pagination.pageSize + 1 }} 到 
             {{ Math.min(pagination.page * pagination.pageSize, pagination.total) }} 条,
@@ -146,74 +149,106 @@
     <el-dialog
       v-model="dialogVisible"
       :title="dialogType === 'add' ? '新建片段' : '编辑片段'"
-      width="600px"
+      width="800px"
       @close="resetForm"
+      class="snippet-dialog"
     >
-      <el-form :model="formData" label-width="100px">
-        <el-form-item label="所属知识库" required>
-          <el-select 
-            v-model="formData.collection_name" 
-            placeholder="请选择知识库" 
-            :disabled="dialogType === 'edit'"
-            @change="handleKbChange"
-          >
-            <el-option
-              v-for="item in kbOptions"
-              :key="item.value"
-              :label="item.label"
-              :value="item.value"
-            />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="文档名称">
-          <el-input v-model="formData.doc_name" placeholder="请输入文档来源名称" />
-        </el-form-item>
-        
-        <!-- 动态渲染自定义Schema字段 -->
-        <template v-if="currentKbSchema.length > 0">
-            <el-divider content-position="left">自定义字段</el-divider>
-            <el-form-item 
-                v-for="schema in currentKbSchema" 
-                :key="schema.field_name"
-                :label="schema.field_name"
-            >
-                <!-- 根据类型渲染不同输入框 -->
-                <el-input-number 
-                    v-if="['INT64', 'INT32', 'INT16', 'INT8'].includes(schema.field_type)"
-                    v-model="formData.custom_fields[schema.field_name]"
+      <el-form :model="formData" label-width="100px" class="snippet-form">
+        <el-row :gutter="20">
+            <el-col :span="12">
+                <el-form-item label="所属知识库" required>
+                  <el-select 
+                    v-model="formData.collection_name" 
+                    placeholder="请选择知识库" 
+                    :disabled="dialogType === 'edit'"
+                    @change="handleKbChange"
                     style="width: 100%"
-                />
-                <el-switch
-                    v-else-if="schema.field_type === 'BOOL'"
-                    v-model="formData.custom_fields[schema.field_name]"
-                />
-                <el-input 
-                    v-else 
-                    v-model="formData.custom_fields[schema.field_name]"
-                    :placeholder="schema.description || `请输入${schema.field_name}`"
-                />
-            </el-form-item>
-            <el-divider />
-        </template>
+                  >
+                    <el-option
+                      v-for="item in kbOptions"
+                      :key="item.value"
+                      :label="item.label"
+                      :value="item.value"
+                    />
+                  </el-select>
+                </el-form-item>
+            </el-col>
+            <el-col :span="12">
+                <el-form-item label="文档名称" required>
+                  <el-select 
+                    v-model="formData.doc_name" 
+                    placeholder="请选择或输入文档名称" 
+                    filterable 
+                    allow-create 
+                    default-first-option
+                    :loading="docLoading"
+                    style="width: 100%"
+                    @focus="loadDocOptions"
+                  >
+                    <el-option
+                      v-for="item in docOptions"
+                      :key="item"
+                      :label="item"
+                      :value="item"
+                    />
+                  </el-select>
+                </el-form-item>
+            </el-col>
+        </el-row>
+        
+        <el-form-item label="父段ID">
+          <el-input v-model="formData.parent_id" placeholder="可选: 输入父段ID" />
+        </el-form-item>
 
         <el-form-item label="片段内容" required>
           <el-input
             v-model="formData.content"
             type="textarea"
-            :rows="6"
-            placeholder="请输入片段内容"
+            :rows="15"
+            placeholder="请输入知识片段的具体内容..."
+            resize="vertical"
           />
         </el-form-item>
       </el-form>
       <template #footer>
         <span class="dialog-footer">
-          <el-button @click="dialogVisible = false">取消</el-button>
-          <el-button type="primary" @click="handleSubmit" :loading="submitLoading">
+          <el-button @click="dialogVisible = false" size="large">取消</el-button>
+          <el-button type="primary" @click="handleSubmit" :loading="submitLoading" size="large">
             确定
           </el-button>
         </span>
       </template>
     </el-dialog>
+
+    <!-- View Dialog -->
+    <el-dialog
+      v-model="viewDialogVisible"
+      title="知识片段详情"
+      width="700px"
+      class="view-dialog"
+    >
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="片段编号">{{ viewData.code }}</el-descriptions-item>
+        <el-descriptions-item label="文档名称">{{ viewData.doc_name }}</el-descriptions-item>
+        <el-descriptions-item label="所属知识库">{{ viewData.collection_name }}</el-descriptions-item>
+        <el-descriptions-item label="字符数量">{{ viewData.char_count }}</el-descriptions-item>
+        <el-descriptions-item label="状态">
+            <el-tag :type="viewData.status === 'normal' ? 'success' : 'info'" effect="plain">
+                {{ viewData.status === 'normal' ? '启用' : '禁用' }}
+            </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="元数据信息">{{ formatMetaInfo(viewData) }}</el-descriptions-item>
+        <el-descriptions-item label="创建时间">{{ viewData.created_at || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="修改时间">{{ viewData.updated_at || '-' }}</el-descriptions-item>
+      </el-descriptions>
+      
+      <div class="view-content-section">
+        <div class="section-title">片段内容</div>
+        <div class="content-box">
+            {{ viewData.content }}
+        </div>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
@@ -238,8 +273,7 @@ const queryParams = reactive({
   pageSize: 10,
   keyword: '',
   status: '',
-  kb: '',
-  contentSearch: ''
+  kb: ''
 })
 
 const loading = ref(false)
@@ -257,14 +291,55 @@ const formData = reactive({
     id: '',
     collection_name: '',
     doc_name: '',
+    parent_id: '',
     content: '',
     custom_fields: {} as Record<string, any>
 })
 
+const docOptions = ref<string[]>([])
+const docLoading = ref(false)
+
+const loadDocOptions = async () => {
+    if (!formData.collection_name) return
+    docLoading.value = true
+    try {
+        // 使用一个特殊的查询来获取该知识库下的所有文档名(去重)
+        // 这里暂时使用 getSnippets 模拟,实际上应该有一个 getDocList 接口
+        // 为了简单,我们查询最近的 100 条记录并提取文档名
+        const res = await getSnippets({
+            page: 1,
+            page_size: 100,
+            kb: formData.collection_name
+        })
+        if (res.code === 0) {
+            const names = new Set<string>()
+            res.data.forEach(item => {
+                if (item.doc_name) names.add(item.doc_name)
+            })
+            docOptions.value = Array.from(names)
+        }
+    } catch (error) {
+        console.error(error)
+    } finally {
+        docLoading.value = false
+    }
+}
+
+// View Dialog
+const viewDialogVisible = ref(false)
+const viewData = ref<Snippet>({} as Snippet)
+
 const currentKbSchema = ref<any[]>([]) // 当前选中知识库的自定义Schema
 
 // 当选择知识库变化时,加载对应的 Schema 字段
 const handleKbChange = async (collection_name: string) => {
+    // 清空文档选择
+    formData.doc_name = ''
+    docOptions.value = []
+    
+    // 自动加载文档列表
+    loadDocOptions()
+
     // 找到对应的 KB ID
     const kb = kbOptions.value.find(k => k.value === collection_name)
     if (!kb) {
@@ -272,28 +347,22 @@ const handleKbChange = async (collection_name: string) => {
         return
     }
     
-    try {
-        const res = await getKnowledgeBaseMetadata(kb.id)
-        if (res.code === 0 && res.data) {
-            // 后端返回结构 { metadata_fields: [], custom_schemas: [] }
-            currentKbSchema.value = res.data.custom_schemas || []
-            
-            // 初始化 custom_fields
-            const newFields: Record<string, any> = {}
-            currentKbSchema.value.forEach(schema => {
-                // 如果是编辑模式且已有值,保留;否则初始化为空
-                newFields[schema.field_name] = formData.custom_fields[schema.field_name] || ''
-            })
-            formData.custom_fields = newFields
-        }
-    } catch (error) {
-        console.error("加载Schema失败", error)
-        currentKbSchema.value = []
-    }
+    // 由于后端 Schema 已固定,不再动态加载自定义字段
+    currentKbSchema.value = []
+    
+    // 初始化 custom_fields (如果需要的话,但现在没有动态字段了)
+    formData.custom_fields = {}
 }
 
 // Methods
 const loadData = async () => {
+    // 强制检查是否选择了知识库
+    if (!queryParams.kb) {
+        tableData.value = []
+        pagination.total = 0
+        return
+    }
+
     loading.value = true
     try {
         const res = await getSnippets({
@@ -301,6 +370,7 @@ const loadData = async () => {
             page_size: pagination.pageSize,
             keyword: queryParams.keyword,
             kb: queryParams.kb, // 知识库集合名称
+            status: queryParams.status // 传递状态参数
         })
         if (res.code === 0) {
             tableData.value = res.data
@@ -333,7 +403,13 @@ const loadKbOptions = async () => {
                 id: item.id
             }))
             // 修改为默认不选中(查询所有),直接加载数据
-            if (!queryParams.kb) {
+            // if (!queryParams.kb) {
+            //     loadData()
+            // }
+            
+            // 默认选中第一个知识库(如果存在),并加载数据
+            if (kbOptions.value.length > 0 && !queryParams.kb) {
+                queryParams.kb = kbOptions.value[0].value
                 loadData()
             }
         }
@@ -368,13 +444,14 @@ const handleEdit = (row: Snippet) => {
     formData.collection_name = row.collection_name
     formData.doc_name = row.doc_name
     formData.content = row.content
-    formData.custom_fields = {} // 编辑时也需要回填,但目前列表数据中可能没有这些字段
+    formData.parent_id = '' // 列表中没有返回 parent_id,暂时留空或需要额外请求获取
+    formData.custom_fields = {} 
     
-    // 触发加载 Schema,这里需要确保加载完成后再回填数据(如果列表里有数据的话)
-    handleKbChange(row.collection_name)
+    // 触发加载文档列表
+    loadDocOptions()
     
-    // TODO: 如果 row 中包含 custom_fields 数据,需要回填到 formData.custom_fields
-    // 目前列表接口 _format_snippet 可能没有返回所有自定义字段
+    // 触发加载 Schema
+    handleKbChange(row.collection_name)
     
     dialogVisible.value = true
 }
@@ -393,7 +470,10 @@ const handleSubmit = async () => {
                 doc_name: formData.doc_name || '手动添加',
                 content: formData.content,
                 meta_info: '',
-                custom_fields: formData.custom_fields
+                custom_fields: {
+                    ...formData.custom_fields,
+                    parent_id: formData.parent_id // 传递父段ID
+                }
             })
             ElMessage.success('创建成功')
         } else {
@@ -401,7 +481,10 @@ const handleSubmit = async () => {
                 collection_name: formData.collection_name,
                 doc_name: formData.doc_name,
                 content: formData.content,
-                custom_fields: formData.custom_fields
+                custom_fields: {
+                    ...formData.custom_fields,
+                    parent_id: formData.parent_id
+                }
             })
             ElMessage.success('更新成功')
         }
@@ -418,8 +501,10 @@ const resetForm = () => {
     formData.id = ''
     formData.collection_name = ''
     formData.doc_name = ''
+    formData.parent_id = ''
     formData.content = ''
     formData.custom_fields = {}
+    docOptions.value = []
     currentKbSchema.value = []
 }
 
@@ -444,13 +529,45 @@ const handleDelete = (row: Snippet) => {
 }
 
 const handleView = (row: Snippet) => {
-    ElMessage.info(`查看片段: ${row.code}`)
+    viewData.value = { ...row }
+    viewDialogVisible.value = true
 }
 
 const handleSelectionChange = (val: any) => {
     console.log(val)
 }
 
+const formatMetaInfo = (row: Snippet) => {
+    // 优先展示 metadata 字段中的信息
+    if (row.metadata) {
+        // 如果是 JSON 对象,尝试格式化展示
+        if (typeof row.metadata === 'object') {
+             // 排除一些不需要展示的字段,如 doc_name, file_name, title (因为已有单独列)
+             const displayParts = []
+             for (const [key, value] of Object.entries(row.metadata)) {
+                 if (!['doc_name', 'file_name', 'title'].includes(key)) {
+                     displayParts.push(`${key}: ${value}`)
+                 }
+             }
+             if (displayParts.length > 0) return displayParts.join(' | ')
+        } else {
+            return String(row.metadata)
+        }
+    }
+
+    // 其次尝试 parent_id
+    if (row.parent_id && row.parent_id !== '0' && row.parent_id !== '') {
+        return `ParentID: ${row.parent_id}`
+    }
+
+    // 最后尝试 meta_info (旧字段)
+    if (row.meta_info && row.meta_info !== "ParentID: -" && row.meta_info !== "ParentID: ") {
+        return row.meta_info
+    }
+    
+    return '-'
+}
+
 const handleSizeChange = (val: number) => {
     pagination.pageSize = val
     loadData()
@@ -467,6 +584,10 @@ const handleCurrentChange = (val: number) => {
   padding: 20px;
 }
 
+.snippet-dialog :deep(.el-dialog__body) {
+    padding: 20px 30px;
+}
+
 .header-section {
   display: flex;
   justify-content: space-between;
@@ -512,11 +633,12 @@ const handleCurrentChange = (val: number) => {
 .content-cell {
     overflow: hidden;
     text-overflow: ellipsis;
-    display: -webkit-box;
-    -webkit-line-clamp: 2;
-    -webkit-box-orient: vertical;
+    /* display: -webkit-box;  移除多行省略,改为单行截断逻辑由JS控制,或者保留样式但内容已截断 */
+    /* -webkit-line-clamp: 2; */
+    /* -webkit-box-orient: vertical; */
     color: #303133;
     line-height: 1.5;
+    white-space: normal; /* 允许换行,但内容已被截断 */
 }
 
 .meta-info {
@@ -531,7 +653,12 @@ const handleCurrentChange = (val: number) => {
     line-height: 22px;
 }
 
-.pagination-container {
+.empty-state {
+    padding: 40px;
+    background: #fff;
+    border-radius: 8px;
+}
+  .pagination-container {
   display: flex;
   justify-content: space-between;
   align-items: center;
@@ -542,4 +669,32 @@ const handleCurrentChange = (val: number) => {
     color: #909399;
     font-size: 13px;
 }
+
+.view-content-section {
+    margin-top: 20px;
+}
+
+.view-content-section .section-title {
+    font-weight: 600;
+    margin-bottom: 10px;
+    font-size: 15px;
+    color: #303133;
+}
+
+.content-box {
+    background-color: #f5f7fa;
+    padding: 15px;
+    border-radius: 4px;
+    border: 1px solid #e4e7ed;
+    color: #606266;
+    line-height: 1.6;
+    max-height: 400px;
+    overflow-y: auto;
+    white-space: pre-wrap;
+    word-break: break-all;
+}
+
+.snippet-form {
+    padding-right: 20px;
+}
 </style>

+ 11 - 2
src/views/documents/SearchEngine.vue

@@ -30,7 +30,9 @@
                         :key="kb.id" 
                         :label="kb.name" 
                         :value="kb.collection_name" 
-                    />
+                    >
+                        <span>{{ kb.name }}</span>
+                    </el-option>
                 </el-select>
             </div>
             
@@ -259,9 +261,16 @@ const handleKbChange = async () => {
         if (selectedKb) {
             try {
                 const res = await getKnowledgeBaseMetadata(selectedKb.id)
-                metadataFields.value = res.data
+                // 确保后端返回的数据格式正确
+                if (res.code === 0 && Array.isArray(res.data)) {
+                    metadataFields.value = res.data
+                } else {
+                    console.warn("Invalid metadata response:", res)
+                    metadataFields.value = []
+                }
             } catch (error) {
                 console.error("Failed to load metadata fields", error)
+                metadataFields.value = []
             }
         }
     }