2 Commits 592caf3018 ... 1e0198eea8

Tác giả SHA1 Thông báo Ngày
  fusu 1e0198eea8 feat(log): 优化 WAF 日志导出功能 3 ngày trước cách đây
  fusu 7048815f5f feat(log): 优化 WAF 日志导出功能 3 ngày trước cách đây
1 tập tin đã thay đổi với 106 bổ sung65 xóa
  1. 106 65
      web/src/pages/log/waflog.vue

+ 106 - 65
web/src/pages/log/waflog.vue

@@ -130,22 +130,29 @@
           <div class="api-names-header">
             <h4 class="section-title">API名称选择</h4>
             <div class="api-names-actions">
-              <span class="selected-count">已选择: {{ exportForm.apiNames.length }} 个</span>
-              <a-button size="small" type="link" @click="selectAllApiNames">全选</a-button>
-              <a-button size="small" type="link" @click="clearAllApiNames">清空</a-button>
+              <span class="selected-count">已选择: {{ exportForm.apiNames.length }} / {{ totalApiCount }}</span>
             </div>
           </div>
+          <div class="api-quick-filters">
+            <a-button size="small" @click="selectApiNamesByCategory('tcp_udp_web')">TCP/UDP/Web相关</a-button>
+            <a-button size="small" @click="selectApiNamesByCategory('blacklist_whitelist')">黑白名单</a-button>
+            <a-button size="small" type="dashed" @click="selectAllApiNames">全选</a-button>
+            <a-button size="small" type="dashed" danger @click="clearAllApiNames">清空</a-button>
+          </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>
@@ -230,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';
@@ -278,7 +285,6 @@ const columns = [
 const fetchData = () => {
   loading.value = true;
   
-  // 构造请求参数,并处理特殊类型
   const params = {
     requestIp: queryParam.value.requestIp || '',
     uid: queryParam.value.uid ? parseInt(queryParam.value.uid) : '',
@@ -294,10 +300,7 @@ const fetchData = () => {
     order: 'desc'
   };
   
-  console.log('发送WAF日志查询参数:', params);
-  
   getWafLogList(params).then(response => {
-    // 根据实际返回格式处理数据
     if (response && response.code === 0 && response.data) {
       const apiData = response.data;
       if (apiData.records && Array.isArray(apiData.records)) {
@@ -308,12 +311,10 @@ const fetchData = () => {
       } else {
         dataSource.value = [];
         pagination.value.total = 0;
-        console.warn('响应中无WAF日志数据记录');
       }
     } else {
       dataSource.value = [];
       pagination.value.total = 0;
-      console.error('无效的WAF日志响应格式或响应错误', response);
     }
     loading.value = false;
   }).catch((error) => {
@@ -348,10 +349,13 @@ const resetSearch = () => {
 
 const detailModalRef = ref(null);
 
+const goToInfo = (id) => {
+  detailModalRef.value?.open(id);
+};
+
 // 导出相关
 const exportModalVisible = ref(false);
 const exportLoading = ref(false);
-const apiNameOptions = ref([]);
 const advancedFilterKey = ref([]);
 const exportForm = ref({
   id: '',
@@ -368,32 +372,71 @@ const exportForm = ref({
   endTime: null
 });
 
-// 判断是否为TCP、UDP、Web相关的API
-const isTcpUdpWebRelated = (apiName) => {
-  const keywords = ['tcp', 'udp', 'web', '游戏盾', '转发', '网关', '限流', '防护', '规则', '策略'];
-  const lowerApiName = apiName.toLowerCase();
-  return keywords.some(keyword => lowerApiName.includes(keyword.toLowerCase()));
+const categorizedApiOptions = ref([]);
+const totalApiCount = computed(() => {
+  return categorizedApiOptions.value.reduce((acc, category) => acc + category.options.length, 0);
+});
+
+// API 名称关键字分类
+const apiNameCategories = {
+  tcp_udp_web: { name: 'TCP/UDP/Web 相关', keywords: ['tcp', 'udp', '转发', 'web', '网站', 'cdn', 'host', '域名', '游戏盾', '网关', 'gateway'] },
+  blacklist_whitelist: { name: '黑白名单', keywords: ['ip', '黑白名单', 'waf', '防护', '规则', '策略', 'cc'] },
 };
 
-const goToInfo = (id) => {
-  // 打开模态框而不是跳转页面
-  detailModalRef.value?.open(id);
+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 = [];
+  }
+};
+
+// 默认选中TCP, UDP, Web相关
+const setDefaultApiSelection = () => {
+  selectApiNamesByCategory('tcp_udp_web');
 };
 
 // 显示导出弹窗
 const showExportModal = async () => {
   try {
-    // 获取API描述映射
     const response = await getApiDescriptions();
     if (response && response.code === 0 && response.data) {
-      // 将API描述映射转换为checkbox选项格式,只显示中文名称
-      apiNameOptions.value = Object.entries(response.data).map(([key, value]) => ({
-        label: value, // 只显示中文名称
-        value: value  // 传给后端的值也是中文名称
+      const allOptions = Object.entries(response.data).map(([key, value]) => ({
+        label: value,
+        value: value
       }));
+      categorizedApiOptions.value = categorizeApiOptions(allOptions);
     }
     
-    // 重置表单
     exportForm.value = {
       id: '',
       uid: '',
@@ -409,17 +452,11 @@ const showExportModal = async () => {
       endTime: null
     };
     
-    // 默认选中TCP、UDP、Web相关的API
-    if (apiNameOptions.value.length > 0) {
-      const defaultSelected = apiNameOptions.value
-        .filter(option => isTcpUdpWebRelated(option.label))
-        .map(option => option.value);
-      exportForm.value.apiNames = defaultSelected;
+    if (totalApiCount.value > 0) {
+      setDefaultApiSelection();
     }
     
-    // 重置高级筛选折叠状态
     advancedFilterKey.value = [];
-    
     exportModalVisible.value = true;
   } catch (error) {
     console.error('获取API描述失败:', error);
@@ -430,7 +467,6 @@ const showExportModal = async () => {
 // 时间范围快捷选择
 const setTimeRange = (range) => {
   const now = dayjs();
-  
   switch (range) {
     case 'today':
       exportForm.value.startTime = now.startOf('day');
@@ -452,30 +488,28 @@ const setTimeRange = (range) => {
   }
 };
 
-// 清空时间范围
 const clearTimeRange = () => {
   exportForm.value.startTime = null;
   exportForm.value.endTime = null;
 };
 
-// 全选API名称
 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;
 };
 
-// 清空API名称选择
 const clearAllApiNames = () => {
   exportForm.value.apiNames = [];
 };
 
-// 处理导出
 const handleExport = async () => {
   try {
     exportLoading.value = true;
     
-    // 构造导出参数,只传递有值的参数
     const params = {};
-    
     if (exportForm.value.id) params.id = parseInt(exportForm.value.id);
     if (exportForm.value.uid) params.uid = parseInt(exportForm.value.uid);
     if (exportForm.value.name) params.name = exportForm.value.name;
@@ -499,12 +533,8 @@ const handleExport = async () => {
       params.endTime = dayjs(exportForm.value.endTime).format('YYYY-MM-DD HH:mm:ss');
     }
     
-    console.log('导出参数:', params);
-    
-    // 调用导出接口
     const response = await exportWafLog(params);
     
-    // 处理文件下载
     const blob = new Blob([response], { 
       type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' 
     });
@@ -527,7 +557,6 @@ const handleExport = async () => {
   }
 };
 
-// 取消导出
 const handleExportCancel = () => {
   exportModalVisible.value = false;
 };
@@ -542,7 +571,6 @@ onMounted(() => {
   margin-bottom: 24px;
 }
 
-/* 表格样式优化 */
 :deep(.ant-table-body) {
   overflow-x: auto !important;
 }
@@ -572,7 +600,6 @@ onMounted(() => {
   background: #a8a8a8;
 }
 
-/* 导出弹窗样式 */
 .export-form-container {
   max-height: 70vh;
   overflow-y: auto;
@@ -604,7 +631,6 @@ onMounted(() => {
   color: #262626;
 }
 
-/* 时间范围选择区域 */
 .time-range-section {
   background: #fafafa;
   border: 1px solid #f0f0f0;
@@ -630,7 +656,6 @@ onMounted(() => {
   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
 }
 
-/* API名称选择区域 */
 .api-names-section {
   background: #fafafa;
   border: 1px solid #f0f0f0;
@@ -646,6 +671,13 @@ onMounted(() => {
   margin-bottom: 12px;
 }
 
+.api-quick-filters {
+  display: flex;
+  gap: 8px;
+  flex-wrap: wrap;
+  margin-bottom: 12px;
+}
+
 .api-names-actions {
   display: flex;
   align-items: center;
@@ -655,11 +687,10 @@ onMounted(() => {
 .selected-count {
   font-size: 12px;
   color: #666;
-  margin-right: 8px;
 }
 
 .api-names-grid {
-  max-height: 200px;
+  max-height: 250px;
   overflow-y: auto;
   border: 1px solid #e8e8e8;
   border-radius: 6px;
@@ -689,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: grid;
-  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
   gap: 8px 16px;
 }
 
@@ -709,7 +752,6 @@ onMounted(() => {
   background-color: #f5f5f5;
 }
 
-/* 高级筛选折叠面板样式 */
 :deep(.ant-collapse-ghost .ant-collapse-item) {
   border: 1px solid #f0f0f0;
   border-radius: 8px;
@@ -732,7 +774,6 @@ onMounted(() => {
   padding: 16px;
 }
 
-/* 导出弹窗整体样式 */
 :deep(.export-modal .ant-modal-body) {
   padding: 20px 24px;
 }
@@ -746,4 +787,4 @@ onMounted(() => {
   font-size: 16px;
   font-weight: 600;
 }
-</style>
+</style>