Selaa lähdekoodia

feat: 要素管理面板添加搜索和类型筛选功能

何文松 4 viikkoa sitten
vanhempi
commit
cc30af640c
1 muutettua tiedostoa jossa 148 lisäystä ja 5 poistoa
  1. 148 5
      frontend/vue-demo/src/views/Editor.vue

+ 148 - 5
frontend/vue-demo/src/views/Editor.vue

@@ -121,16 +121,46 @@
           <div class="element-header">
             <span class="element-title">
               🏷️ 要素管理
-              <span class="element-count">({{ entities.length }})</span>
+              <span class="element-count">({{ filteredEntities.length }}/{{ entities.length }})</span>
             </span>
             <el-button size="small" :icon="Plus" @click="showAddVariableDialog = true">
               添加
             </el-button>
           </div>
+          <!-- 搜索和筛选 -->
+          <div class="element-filter" v-if="entities.length > 0">
+            <el-input
+              v-model="entitySearchKeyword"
+              placeholder="搜索要素..."
+              size="small"
+              :prefix-icon="Search"
+              clearable
+              class="entity-search"
+            />
+            <div class="entity-type-filter">
+              <el-tag
+                v-for="(count, type) in entityTypeCounts"
+                :key="type"
+                :class="['filter-tag', { active: entityTypeFilter === type }]"
+                size="small"
+                @click="toggleEntityTypeFilter(type)"
+              >
+                {{ getEntityTypeIcon(type) }} {{ getEntityTypeName(type) }} ({{ count }})
+              </el-tag>
+              <el-tag
+                v-if="entityTypeFilter"
+                class="filter-tag clear"
+                size="small"
+                @click="entityTypeFilter = ''"
+              >
+                清除筛选
+              </el-tag>
+            </div>
+          </div>
           <div class="element-body">
-            <div class="element-tags-wrap">
+            <div class="element-tags-wrap" v-if="filteredEntities.length > 0">
               <div
-                v-for="entity in entities"
+                v-for="entity in filteredEntities"
                 :key="entity.id"
                 class="var-tag"
                 :class="getEntityTypeClass(entity.type)"
@@ -144,6 +174,9 @@
             <div class="element-hint" v-if="entities.length === 0">
               选中文本后右键标记为变量
             </div>
+            <div class="element-hint" v-else-if="filteredEntities.length === 0">
+              没有匹配的要素
+            </div>
             <div class="element-hint" v-else>
               点击标签定位到文档位置
             </div>
@@ -319,7 +352,7 @@
 import { ref, reactive, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
 import { useRouter, useRoute } from 'vue-router'
 import {
-  ArrowLeft, Clock, Share, Check, Plus, Delete, Connection, Refresh
+  ArrowLeft, Clock, Share, Check, Plus, Delete, Connection, Refresh, Search
 } from '@element-plus/icons-vue'
 import { ElMessage } from 'element-plus'
 import { useTemplateStore } from '@/stores/template'
@@ -365,6 +398,69 @@ const variables = ref([])
 // 文档中的实体(从 blocks 的 elements 中提取)
 const entities = ref([])
 
+// 要素搜索和筛选
+const entitySearchKeyword = ref('')
+const entityTypeFilter = ref('')
+
+// 计算属性:按类型统计要素数量
+const entityTypeCounts = computed(() => {
+  const counts = {}
+  entities.value.forEach(entity => {
+    const type = entity.type || 'default'
+    counts[type] = (counts[type] || 0) + 1
+  })
+  return counts
+})
+
+// 计算属性:筛选后的要素列表
+const filteredEntities = computed(() => {
+  let result = entities.value
+  
+  // 按类型筛选
+  if (entityTypeFilter.value) {
+    result = result.filter(e => e.type === entityTypeFilter.value)
+  }
+  
+  // 按关键词搜索
+  if (entitySearchKeyword.value) {
+    const keyword = entitySearchKeyword.value.toLowerCase()
+    result = result.filter(e => 
+      e.text?.toLowerCase().includes(keyword) ||
+      e.type?.toLowerCase().includes(keyword)
+    )
+  }
+  
+  return result
+})
+
+// 切换类型筛选
+function toggleEntityTypeFilter(type) {
+  if (entityTypeFilter.value === type) {
+    entityTypeFilter.value = ''
+  } else {
+    entityTypeFilter.value = type
+  }
+}
+
+// 获取实体类型名称
+function getEntityTypeName(type) {
+  const typeNames = {
+    'entity': '实体',
+    'concept': '概念',
+    'data': '数据',
+    'location': '地点',
+    'asset': '资产',
+    'person': '人物',
+    'org': '组织',
+    'date': '日期',
+    'product': '产品',
+    'event': '事件',
+    'law': '法规',
+    'default': '其他'
+  }
+  return typeNames[type] || type || '其他'
+}
+
 /**
  * 从结构化文档的 blocks 中提取所有实体
  */
@@ -1872,6 +1968,53 @@ onUnmounted(() => {
     }
   }
 
+  .element-filter {
+    padding: 0 16px 12px;
+    
+    .entity-search {
+      margin-bottom: 10px;
+      
+      :deep(.el-input__wrapper) {
+        border-radius: 18px;
+      }
+    }
+    
+    .entity-type-filter {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 6px;
+      
+      .filter-tag {
+        cursor: pointer;
+        transition: all 0.2s;
+        border-radius: 12px;
+        font-size: 11px;
+        
+        &:hover {
+          border-color: var(--primary);
+          color: var(--primary);
+        }
+        
+        &.active {
+          background: var(--primary);
+          color: white;
+          border-color: var(--primary);
+        }
+        
+        &.clear {
+          background: transparent;
+          border-style: dashed;
+          color: var(--text-3);
+          
+          &:hover {
+            border-color: var(--danger);
+            color: var(--danger);
+          }
+        }
+      }
+    }
+  }
+
   .element-body {
     padding: 0 16px 16px;
   }
@@ -1880,7 +2023,7 @@ onUnmounted(() => {
     display: flex;
     flex-wrap: wrap;
     gap: 8px;
-    max-height: 300px;
+    max-height: 280px;
     overflow-y: auto;
   }