Browse Source

refactor(editor): 分离"我的要素"和"AI建议"为独立区块

- "AI识别"改名为"AI建议"
- 将两个区域从同一个section分离为独立的section
- "我的要素"区块:显示已确认的要素,支持搜索和类型筛选
- "AI建议"区块:独立显示,带淡灰色背景以区分
- 添加 myEntityTypeCounts 计算属性用于筛选已确认要素
- 优化样式和布局
何文松 3 weeks ago
parent
commit
c3bf2a811c
1 changed files with 87 additions and 104 deletions
  1. 87 104
      frontend/vue-demo/src/views/Editor.vue

+ 87 - 104
frontend/vue-demo/src/views/Editor.vue

@@ -259,19 +259,19 @@
 
       <!-- 右侧要素面板:仅在选中文档时显示 -->
       <div v-if="hasActiveDocument" class="right-panel" :style="{ width: rightPanelWidth + 'px' }">
-        <!-- 要素管理(展示文档中识别的实体) -->
+        <!-- 我的要素(已确认的实体) -->
         <div class="element-section">
           <div class="element-header">
             <span class="element-title">
-              🏷️ 要素管理
-              <span class="element-count">({{ allFilteredEntities?.length || 0 }}/{{ entities?.length || 0 }})</span>
+              🏷️ 我的要素
+              <span class="element-count">({{ myEntities?.length || 0 }})</span>
             </span>
             <el-button size="small" :icon="Plus" @click="showAddVariableDialog = true">
               添加
             </el-button>
           </div>
           <!-- 搜索和筛选 -->
-          <div class="element-filter" v-if="entities && entities.length > 0">
+          <div class="element-filter" v-if="myEntities && myEntities.length > 0">
             <el-input
               v-model="entitySearchKeyword"
               placeholder="搜索要素..."
@@ -282,7 +282,7 @@
             />
             <div class="entity-type-filter">
               <el-tag
-                v-for="(count, type) in entityTypeCounts"
+                v-for="(count, type) in myEntityTypeCounts"
                 :key="type"
                 :class="['filter-tag', { active: entityTypeFilter === type }]"
                 size="small"
@@ -301,61 +301,53 @@
             </div>
           </div>
           <div class="element-body">
-            <!-- 我的要素(已确认) -->
-            <div class="entity-group my-entities" v-if="myEntities.length > 0">
-              <div class="entity-group-header">
-                <span class="group-title">✓ 我的要素</span>
-                <span class="group-count">{{ myEntities.length }}</span>
-              </div>
-              <div class="element-tags-wrap">
-                <div
-                  v-for="entity in myEntities"
-                  :key="entity.id"
-                  class="var-tag confirmed"
-                  :class="[getEntityTypeClass(entity.type)]"
-                  :title="`${getEntityTypeName(entity.type)}: ${entity.text}`"
-                  @click="scrollToEntity(entity.id)"
-                  @dblclick="openEntityEditModal(entity)"
-                >
-                  <span class="tag-icon">{{ getEntityTypeIcon(entity.type) }}</span>
-                  <span class="tag-name">{{ entity.text }}</span>
-                  <span class="tag-status">✓</span>
-                </div>
+            <div class="element-tags-wrap" v-if="myEntities.length > 0">
+              <div
+                v-for="entity in myEntities"
+                :key="entity.id"
+                class="var-tag confirmed"
+                :class="[getEntityTypeClass(entity.type)]"
+                :title="`${getEntityTypeName(entity.type)}: ${entity.text}`"
+                @click="scrollToEntity(entity.id)"
+                @dblclick="openEntityEditModal(entity)"
+              >
+                <span class="tag-icon">{{ getEntityTypeIcon(entity.type) }}</span>
+                <span class="tag-name">{{ entity.text }}</span>
+                <span class="tag-status">✓</span>
               </div>
             </div>
-            
-            <!-- AI 识别(待采纳) -->
-            <div class="entity-group ai-suggestions" v-if="aiSuggestedEntities.length > 0">
-              <div class="entity-group-header">
-                <span class="group-title">💡 AI 识别</span>
-                <span class="group-count">{{ aiSuggestedEntities.length }}</span>
-                <div class="group-actions">
-                  <el-button size="small" text type="primary" @click="adoptAllAiSuggestions">全部采纳</el-button>
-                  <el-button size="small" text @click="ignoreAllAiSuggestions">全部忽略</el-button>
-                </div>
-              </div>
-              <div class="element-tags-wrap ai-tags">
-                <div
-                  v-for="entity in aiSuggestedEntities"
-                  :key="entity.id"
-                  class="var-tag ai-suggestion"
-                  :class="[getEntityTypeClass(entity.type)]"
-                  :title="`${getEntityTypeName(entity.type)}: ${entity.text} - 点击采纳`"
-                  @click.stop="adoptEntity(entity)"
-                >
-                  <span class="tag-icon">{{ getEntityTypeIcon(entity.type) }}</span>
-                  <span class="tag-name">{{ entity.text }}</span>
-                  <span class="tag-action">+</span>
-                </div>
-              </div>
+            <div class="element-hint" v-else>
+              选中文本后右键标记为实体,或从 AI 建议中采纳
             </div>
-            
-            <!-- 空状态提示 -->
-            <div class="element-hint" v-if="!entities || entities.length === 0">
-              选中文本后右键标记为实体
+          </div>
+        </div>
+        
+        <!-- AI 建议(待采纳的实体) -->
+        <div class="element-section ai-section" v-if="aiSuggestedEntities.length > 0">
+          <div class="element-header">
+            <span class="element-title">
+              💡 AI 建议
+              <span class="element-count">({{ aiSuggestedEntities?.length || 0 }})</span>
+            </span>
+            <div class="header-actions">
+              <el-button size="small" text type="primary" @click="adoptAllAiSuggestions">全部采纳</el-button>
+              <el-button size="small" text @click="ignoreAllAiSuggestions">忽略全部</el-button>
             </div>
-            <div class="element-hint" v-else-if="!filteredEntities || filteredEntities.length === 0">
-              没有匹配的要素
+          </div>
+          <div class="element-body">
+            <div class="element-tags-wrap ai-tags">
+              <div
+                v-for="entity in aiSuggestedEntities"
+                :key="entity.id"
+                class="var-tag ai-suggestion"
+                :class="[getEntityTypeClass(entity.type)]"
+                :title="`${getEntityTypeName(entity.type)}: ${entity.text} - 点击采纳`"
+                @click.stop="adoptEntity(entity)"
+              >
+                <span class="tag-icon">{{ getEntityTypeIcon(entity.type) }}</span>
+                <span class="tag-name">{{ entity.text }}</span>
+                <span class="tag-action">+</span>
+              </div>
             </div>
           </div>
         </div>
@@ -1151,7 +1143,7 @@ const entities = ref([])
 const entitySearchKeyword = ref('')
 const entityTypeFilter = ref('')
 
-// 计算属性:按类型统计要素数量
+// 计算属性:按类型统计要素数量(全部)
 const entityTypeCounts = computed(() => {
   const counts = {}
   if (!entities.value || !Array.isArray(entities.value)) return counts
@@ -1163,6 +1155,17 @@ const entityTypeCounts = computed(() => {
   return counts
 })
 
+// 计算属性:按类型统计已确认的要素数量
+const myEntityTypeCounts = computed(() => {
+  const counts = {}
+  if (!entities.value || !Array.isArray(entities.value)) return counts
+  entities.value.filter(e => e && e.confirmed).forEach(entity => {
+    const type = entity.type || 'default'
+    counts[type] = (counts[type] || 0) + 1
+  })
+  return counts
+})
+
 // 实体显示数量限制(性能优化)
 
 // 计算属性:筛选后的要素列表(全部)
@@ -3932,6 +3935,33 @@ onUnmounted(() => {
       }
     }
     
+    .header-actions {
+      display: flex;
+      gap: 4px;
+      
+      .el-button {
+        padding: 4px 8px;
+        font-size: 12px;
+      }
+    }
+  }
+  
+  // AI 建议区块特殊样式
+  &.ai-section {
+    background: var(--bg);
+    border-bottom: none;
+    
+    .element-header {
+      .element-title {
+        color: var(--text-2);
+      }
+    }
+    
+    .element-tags-wrap {
+      max-height: 300px;
+    }
+  }
+    
     // 要素 Tab 切换 - V2 风格
     .element-tabs {
       display: flex;
@@ -4023,53 +4053,6 @@ onUnmounted(() => {
     gap: 16px;
   }
 
-  // 实体分组
-  .entity-group {
-    .entity-group-header {
-      display: flex;
-      align-items: center;
-      gap: 8px;
-      margin-bottom: 10px;
-      padding-bottom: 8px;
-      border-bottom: 1px solid var(--border);
-      
-      .group-title {
-        font-size: 13px;
-        font-weight: 600;
-        color: var(--text-1);
-      }
-      
-      .group-count {
-        font-size: 12px;
-        color: var(--text-3);
-        background: var(--bg);
-        padding: 2px 8px;
-        border-radius: 10px;
-      }
-      
-      .group-actions {
-        margin-left: auto;
-        display: flex;
-        gap: 4px;
-        
-        .el-button {
-          padding: 4px 8px;
-          font-size: 12px;
-        }
-      }
-    }
-    
-    &.ai-suggestions {
-      .entity-group-header {
-        border-bottom-style: dashed;
-        
-        .group-title {
-          color: var(--text-2);
-        }
-      }
-    }
-  }
-
   // 要素标签容器 - V2 风格
   .element-tags-wrap {
     display: flex;