Explorar o código

feat(log): 优化 WAF 日志导出功能

- 将 API 名称选项按类别整理,提高可读性和选择效率
- 新增 TCP/UDP/Web 和黑白名单 两个分类,其他不分类的归为 其他 类别
- 优化 API 名称列表的渲染方式,使用 grid 布局
- 重构相关代码,提高可维护性
fusu hai 3 días
pai
achega
1e0198eea8
Modificáronse 1 ficheiros con 80 adicións e 43 borrados
  1. 80 43
      web/src/pages/log/waflog.vue

+ 80 - 43
web/src/pages/log/waflog.vue

@@ -130,7 +130,7 @@
           <div class="api-names-header">
             <h4 class="section-title">API名称选择</h4>
             <div class="api-names-actions">
-              <span class="selected-count">已选择: {{ exportForm.apiNames.length }} / {{ apiNameOptions.length }}</span>
+              <span class="selected-count">已选择: {{ exportForm.apiNames.length }} / {{ totalApiCount }}</span>
             </div>
           </div>
           <div class="api-quick-filters">
@@ -141,15 +141,18 @@
           </div>
           <div class="api-names-grid">
             <a-checkbox-group v-model:value="exportForm.apiNames" class="custom-checkbox-group">
-              <div class="checkbox-grid">
-                <a-checkbox 
-                  v-for="option in apiNameOptions" 
-                  :key="option.value" 
-                  :value="option.value"
-                  class="checkbox-item"
-                >
-                  {{ option.label }}
-                </a-checkbox>
+              <div v-for="category in categorizedApiOptions" :key="category.name" class="api-category">
+                <div class="api-category-title">{{ category.name }}</div>
+                <div class="checkbox-grid">
+                  <a-checkbox 
+                    v-for="option in category.options" 
+                    :key="option.value" 
+                    :value="option.value"
+                    class="checkbox-item"
+                  >
+                    {{ option.label }}
+                  </a-checkbox>
+                </div>
               </div>
             </a-checkbox-group>
           </div>
@@ -234,7 +237,7 @@
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue';
+import { ref, onMounted, computed } from 'vue';
 import { message } from 'ant-design-vue';
 import dayjs from 'dayjs';
 import { getWafLogList, getApiDescriptions, exportWafLog } from '~/api/waf/waflog.js';
@@ -353,7 +356,6 @@ const goToInfo = (id) => {
 // 导出相关
 const exportModalVisible = ref(false);
 const exportLoading = ref(false);
-const apiNameOptions = ref([]);
 const advancedFilterKey = ref([]);
 const exportForm = ref({
   id: '',
@@ -370,39 +372,57 @@ const exportForm = ref({
   endTime: null
 });
 
+const categorizedApiOptions = ref([]);
+const totalApiCount = computed(() => {
+  return categorizedApiOptions.value.reduce((acc, category) => acc + category.options.length, 0);
+});
+
 // API 名称关键字分类
 const apiNameCategories = {
-  tcp_udp_web: ['tcp', 'udp', '转发', 'web', '网站', 'cdn', 'host', '域名', '游戏盾'],
-  blacklist_whitelist: ['ip', '黑白名单', 'waf', '防护', '规则', '策略', 'cc'],
+  tcp_udp_web: { name: 'TCP/UDP/Web 相关', keywords: ['tcp', 'udp', '转发', 'web', '网站', 'cdn', 'host', '域名', '游戏盾', '网关', 'gateway'] },
+  blacklist_whitelist: { name: '黑白名单', keywords: ['ip', '黑白名单', 'waf', '防护', '规则', '策略', 'cc'] },
 };
 
-// 根据分类选择API名称 (此操作会覆盖当前选择)
-const selectApiNamesByCategory = (category) => {
-  const keywords = apiNameCategories[category];
-  if (!keywords) {
+const categorizeApiOptions = (options) => {
+  const categories = {
+    tcp_udp_web: { name: apiNameCategories.tcp_udp_web.name, options: [] },
+    blacklist_whitelist: { name: apiNameCategories.blacklist_whitelist.name, options: [] },
+    other: { name: '其他', options: [] }
+  };
+
+  const allKeywords = new Set([
+    ...apiNameCategories.tcp_udp_web.keywords,
+    ...apiNameCategories.blacklist_whitelist.keywords
+  ]);
+
+  options.forEach(option => {
+    const lowerLabel = option.label.toLowerCase();
+    if (apiNameCategories.tcp_udp_web.keywords.some(kw => lowerLabel.includes(kw))) {
+      categories.tcp_udp_web.options.push(option);
+    } else if (apiNameCategories.blacklist_whitelist.keywords.some(kw => lowerLabel.includes(kw))) {
+      categories.blacklist_whitelist.options.push(option);
+    } else {
+      categories.other.options.push(option);
+    }
+  });
+
+  return Object.values(categories).filter(c => c.options.length > 0);
+};
+
+
+// 根据分类选择API名称
+const selectApiNamesByCategory = (categoryKey) => {
+  const category = categorizedApiOptions.value.find(c => c.name === apiNameCategories[categoryKey].name);
+  if (category) {
+    exportForm.value.apiNames = category.options.map(opt => opt.value);
+  } else {
     exportForm.value.apiNames = [];
-    return;
   }
-  const selected = apiNameOptions.value
-    .filter(option => {
-      const lowerLabel = option.label.toLowerCase();
-      return keywords.some(keyword => lowerLabel.includes(keyword));
-    })
-    .map(option => option.value);
-  exportForm.value.apiNames = selected;
 };
 
 // 默认选中TCP, UDP, Web相关
 const setDefaultApiSelection = () => {
-  const defaultKeywords = apiNameCategories.tcp_udp_web || [];
-  
-  const defaultSelected = apiNameOptions.value
-    .filter(option => {
-      const lowerLabel = option.label.toLowerCase();
-      return defaultKeywords.some(keyword => lowerLabel.includes(keyword));
-    })
-    .map(option => option.value);
-  exportForm.value.apiNames = defaultSelected;
+  selectApiNamesByCategory('tcp_udp_web');
 };
 
 // 显示导出弹窗
@@ -410,10 +430,11 @@ const showExportModal = async () => {
   try {
     const response = await getApiDescriptions();
     if (response && response.code === 0 && response.data) {
-      apiNameOptions.value = Object.entries(response.data).map(([key, value]) => ({
+      const allOptions = Object.entries(response.data).map(([key, value]) => ({
         label: value,
         value: value
       }));
+      categorizedApiOptions.value = categorizeApiOptions(allOptions);
     }
     
     exportForm.value = {
@@ -431,7 +452,7 @@ const showExportModal = async () => {
       endTime: null
     };
     
-    if (apiNameOptions.value.length > 0) {
+    if (totalApiCount.value > 0) {
       setDefaultApiSelection();
     }
     
@@ -473,7 +494,11 @@ const clearTimeRange = () => {
 };
 
 const selectAllApiNames = () => {
-  exportForm.value.apiNames = apiNameOptions.value.map(option => option.value);
+  let allNames = [];
+  categorizedApiOptions.value.forEach(category => {
+    allNames.push(...category.options.map(opt => opt.value));
+  });
+  exportForm.value.apiNames = allNames;
 };
 
 const clearAllApiNames = () => {
@@ -665,7 +690,7 @@ onMounted(() => {
 }
 
 .api-names-grid {
-  max-height: 200px;
+  max-height: 250px;
   overflow-y: auto;
   border: 1px solid #e8e8e8;
   border-radius: 6px;
@@ -695,9 +720,21 @@ onMounted(() => {
   width: 100%;
 }
 
+.api-category + .api-category {
+  margin-top: 16px;
+}
+
+.api-category-title {
+  font-weight: 500;
+  color: #555;
+  margin-bottom: 10px;
+  padding-bottom: 6px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
 .checkbox-grid {
-  display: flex;
-  flex-wrap: wrap;
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
   gap: 8px 16px;
 }
 
@@ -708,7 +745,7 @@ onMounted(() => {
   transition: background-color 0.2s ease;
   border-radius: 4px;
   padding-left: 8px;
-  margin-right: 16px; /* 增加右边距确保换行时有间距 */
+  margin-left: 0 !important;
 }
 
 .checkbox-item:hover {
@@ -750,4 +787,4 @@ onMounted(() => {
   font-size: 16px;
   font-weight: 600;
 }
-</style>
+</style>