Browse Source

feat:优化APIHUB模块API定义中的插件配置

microrain 5 months ago
parent
commit
f7e91fb9c3
1 changed files with 448 additions and 46 deletions
  1. 448 46
      src/views/apihub/component/edit.vue

+ 448 - 46
src/views/apihub/component/edit.vue

@@ -175,53 +175,166 @@
 
           <!-- 全局插件 Tab -->
           <div v-show="activeTab === 'plugins'" class="plugins-tab">
-            <div class="form-section-title">插件列表
-              <el-button type="primary" link @click="addPlugin" style="margin-left: 10px;">
+            <div class="form-section-title">全局插件配置
+              <el-button type="primary" @click="showAddPluginDialog" style="margin-left: 10px;" size="small">
                 <el-icon><ele-Plus /></el-icon>添加插件
               </el-button>
             </div>
+
+            <!-- 插件列表 -->
             <div v-if="!formData.plugins || formData.plugins.length === 0" class="empty-plugins">
-              <el-empty description="暂无插件配置" :image-size="80"></el-empty>
+              <el-empty description="暂无插件配置" :image-size="80">
+                <el-button type="primary" @click="showAddPluginDialog" size="small">添加插件</el-button>
+              </el-empty>
             </div>
-            <div v-else>
-              <div v-for="(plugin, index) in formData.plugins" :key="index" class="plugin-list-item">
-                <div class="plugin-item-details">
-                  <el-form-item
-                    :label="`名称`"
-                    :prop="`plugins[${index}].name`"
-                    :rules="[{ required: true, message: '插件名称不能为空', trigger: 'blur' }]"
-                    label-width="60px"
-                    class="plugin-field"
-                  >
-                    <el-input v-model="plugin.name" placeholder="插件Bean名称或类完整路径" size="small" />
-                  </el-form-item>
-                  <el-form-item
-                    :label="`参数`"
-                    :prop="`plugins[${index}].config`"
-                    label-width="60px"
-                    class="plugin-field"
+            <div v-else class="plugins-list">
+              <el-card v-for="(plugin, index) in formData.plugins" :key="index" class="plugin-card" shadow="hover">
+                <template #header>
+                  <div class="plugin-card-header">
+                    <div class="plugin-title">
+                      <el-tag :type="plugin.category === 'Before' ? 'success' : 'warning'" size="small" effect="dark" style="margin-right: 10px;">
+                        {{ plugin.category === 'Before' ? '前置插件' : '后置插件' }}
+                      </el-tag>
+                      <span class="plugin-name">{{ plugin.name }}</span>
+                    </div>
+                    <div class="plugin-actions">
+                      <el-button type="primary" link @click="editPlugin(index)" size="small">
+                        <el-icon><ele-Edit /></el-icon>
+                      </el-button>
+                      <el-button type="danger" link @click="confirmRemovePlugin(index)" size="small">
+                        <el-icon><ele-Delete /></el-icon>
+                      </el-button>
+                    </div>
+                  </div>
+                </template>
+
+                <div class="plugin-info">
+                  <div class="plugin-type">
+                    <span class="label">插件类型:</span>
+                    <el-tag size="small" type="info">{{ plugin.type || 'JavaScript' }}</el-tag>
+                  </div>
+                  <div class="plugin-description" v-if="plugin.description">
+                    <span class="label">插件描述:</span>
+                    <span>{{ plugin.description }}</span>
+                  </div>
+                  <div class="plugin-config" v-if="plugin.config">
+                    <span class="label">配置参数:</span>
+                    <pre class="config-code">{{ formatPluginConfig(plugin.config) }}</pre>
+                  </div>
+                </div>
+              </el-card>
+            </div>
+          </div>
+
+          <!-- 添加插件对话框 -->
+          <el-dialog
+            v-model="addPluginDialogVisible"
+            title="添加全局插件"
+            width="650px"
+            append-to-body
+            destroy-on-close
+          >
+            <el-form ref="pluginFormRef" :model="currentPlugin" label-width="100px" :rules="pluginRules">
+              <el-form-item label="插件类型" required>
+                <el-radio-group v-model="filterPluginType" @change="filterPlugins" style="margin-bottom: 10px;">
+                  <el-radio-button label="">全部</el-radio-button>
+                  <el-radio-button label="JavaScript">JavaScript</el-radio-button>
+<!--                  <el-radio-button label="Go">Go</el-radio-button>-->
+                  <el-radio-button label="LUA">LUA</el-radio-button>
+                </el-radio-group>
+              </el-form-item>
+
+              <el-form-item label="插件分类">
+                <el-radio-group v-model="filterPluginCategory" @change="filterPlugins" style="margin-bottom: 10px;">
+                  <el-radio-button label="">全部</el-radio-button>
+                  <el-radio-button label="Before">前置插件</el-radio-button>
+                  <el-radio-button label="After">后置插件</el-radio-button>
+                </el-radio-group>
+              </el-form-item>
+
+              <el-form-item label="选择插件" required>
+                <div style="display: flex; gap: 10px; align-items: center;">
+                  <el-select
+                    v-model="selectedPluginId"
+                    placeholder="选择现有插件"
+                    style="width: 350px"
+                    filterable
+                    clearable
+                    @change="handlePluginSelected"
                   >
-                    <el-input
-                      v-model="plugin.config"
-                      type="textarea"
-                      :rows="2"
-                      :placeholder="pluginParamsPlaceholder"
-                      size="small"
-                    />
-                  </el-form-item>
+                    <el-option
+                      v-for="item in filteredPlugins"
+                      :key="item.id"
+                      :label="item.name"
+                      :value="item.id"
+                    >
+                      <div style="display: flex; align-items: center; justify-content: space-between;">
+                        <span>{{ item.name }}</span>
+                        <div>
+                          <el-tag size="small" type="info" style="margin-left: 5px;">{{ item.type }}</el-tag>
+                          <el-tag
+                            size="small"
+                            :type="item.category === 'Before' ? 'success' : 'warning'"
+                            style="margin-left: 5px;"
+                          >
+                            {{ item.category === 'Before' ? '前置' : '后置' }}
+                          </el-tag>
+                        </div>
+                      </div>
+                    </el-option>
+                  </el-select>
+                  <el-button type="success" @click="createNewPlugin" size="default">新建插件</el-button>
                 </div>
-                <div class="plugin-remove-action">
-                  <el-button
-                    type="danger"
-                    link
-                    icon="ele-Delete"
-                    @click="removePlugin(index)"
-                    size="small"
+              </el-form-item>
+
+              <template v-if="selectedPluginId || isCreatingNewPlugin">
+                <el-form-item label="插件名称" prop="name" required>
+                  <el-input v-model="currentPlugin.name" placeholder="请输入插件名称" />
+                </el-form-item>
+
+                <el-form-item label="插件类型" prop="type" required>
+                  <el-select v-model="currentPlugin.type" placeholder="请选择插件类型" style="width: 100%;">
+                    <el-option label="JavaScript" value="JavaScript" />
+                    <el-option label="Go" value="Go" />
+                    <el-option label="LUA" value="LUA" />
+                  </el-select>
+                </el-form-item>
+
+                <el-form-item label="插件分类" prop="category" required>
+                  <el-select v-model="currentPlugin.category" placeholder="请选择插件分类" style="width: 100%;">
+                    <el-option label="前置插件" value="Before" />
+                    <el-option label="后置插件" value="After" />
+                  </el-select>
+                </el-form-item>
+
+                <el-form-item label="配置参数" prop="config">
+                  <el-input
+                    v-model="currentPlugin.config"
+                    type="textarea"
+                    :rows="4"
+                    placeholder="请输入JSON格式的配置参数,例如:{ &quot;key&quot;: &quot;value&quot; }"
                   />
-                </div>
+                  <div class="tip-text">配置参数应为JSON格式,会在运行时传递给插件</div>
+                </el-form-item>
+
+                <el-form-item label="描述" prop="description">
+                  <el-input
+                    v-model="currentPlugin.description"
+                    type="textarea"
+                    :rows="2"
+                    placeholder="请输入插件描述"
+                  />
+                </el-form-item>
+              </template>
+            </el-form>
+
+            <template #footer>
+              <div class="dialog-footer">
+                <el-button @click="addPluginDialogVisible = false">取消</el-button>
+                <el-button type="primary" @click="confirmAddPlugin" :loading="savePluginLoading">确定</el-button>
               </div>
-            </div>
-          </div>
+            </template>
+          </el-dialog>
         </el-form>
       </div>
     </div>
@@ -236,10 +349,10 @@
 
 <script lang="ts" setup>
 import { ref, reactive, nextTick } from "vue";
-import SqlEditor from "./SqlEditor.vue";
 import { ElMessage, ElMessageBox } from "element-plus";
-import { ruleRequired } from "/@/utils/validator";
+import SqlEditor from "./SqlEditor.vue";
 import api from "/@/api/modules/apiHub";
+import { ruleRequired } from "/@/utils/validator";
 import getOrigin from '/@/utils/origin';
 import {
   Plus as ElePlus,
@@ -255,7 +368,34 @@ const showDialog = ref(false);
 const formRef = ref();
 const dataSources = ref<any[]>([]);
 const activeTab = ref('basic'); // 当前激活的Tab
-const pluginParamsPlaceholder = '插件参数 (JSON格式,例如:{"key": "value"})';
+const availablePlugins = ref<any[]>([]);
+const addPluginDialogVisible = ref(false);
+const selectedPluginId = ref<number | null>(null);
+const isCreatingNewPlugin = ref(false);
+const savePluginLoading = ref(false);
+const pluginFormRef = ref();
+
+// 插件过滤相关
+const filterPluginType = ref(''); // 过滤的插件类型
+const filterPluginCategory = ref(''); // 过滤的插件分类
+const filteredPlugins = ref<any[]>([]); // 过滤后的插件列表
+
+const currentPlugin = reactive({
+  id: null as number | null,
+  name: '',
+  type: 'JavaScript',
+  category: 'Before',
+  content: '',
+  config: '',
+  description: ''
+});
+
+const pluginRules = {
+  name: [{ required: true, message: '请输入插件名称', trigger: 'blur' }],
+  type: [{ required: true, message: '请选择插件类型', trigger: 'change' }],
+  category: [{ required: true, message: '请选择插件分类', trigger: 'change' }]
+};
+
 const originUrl: string = getOrigin("");
 
 // SQL编辑器已经在组件内部处理了关键字和自动完成功能
@@ -458,15 +598,277 @@ const removeParameter = (index: number) => {
   formData.parameters.splice(index, 1);
 };
 
-// 添加插件
+// 显示添加插件对话框
+const showAddPluginDialog = () => {
+  // 重置对话框状态
+  selectedPluginId.value = null;
+  isCreatingNewPlugin.value = false;
+  filterPluginType.value = '';
+  filterPluginCategory.value = '';
+  Object.assign(currentPlugin, {
+    id: null,
+    name: '',
+    type: 'JavaScript',
+    category: 'Before',
+    content: '',
+    config: '',
+    description: ''
+  });
+
+  // 加载可用插件列表
+  loadAvailablePlugins();
+
+  // 显示对话框
+  addPluginDialogVisible.value = true;
+};
+
+// 过滤插件列表
+const filterPlugins = () => {
+  if (!availablePlugins.value || availablePlugins.value.length === 0) {
+    filteredPlugins.value = [];
+    return;
+  }
+
+  // 同时根据类型和分类过滤
+  filteredPlugins.value = availablePlugins.value.filter(plugin => {
+    // 如果没有设置过滤条件,则返回所有插件
+    const typeMatch = !filterPluginType.value || plugin.type === filterPluginType.value;
+    const categoryMatch = !filterPluginCategory.value || plugin.category === filterPluginCategory.value;
+    return typeMatch && categoryMatch;
+  });
+};
+
+// 加载可用插件列表
+const loadAvailablePlugins = async () => {
+  try {
+    const res = await api.plugin.list({});
+    // 处理可能的不同数据结构
+    if (res.data) {
+      if (Array.isArray(res.data)) {
+        availablePlugins.value = res.data;
+      } else if (res.data.data && Array.isArray(res.data.data)) {
+        availablePlugins.value = res.data.data;
+      } else if (res.data.Data && Array.isArray(res.data.Data)) {
+        availablePlugins.value = res.data.Data;
+      }
+    } else if (res.Data && Array.isArray(res.Data)) {
+      availablePlugins.value = res.Data;
+    }
+
+    // 处理首字母大写的属性名
+    if (availablePlugins.value.length > 0) {
+      availablePlugins.value = availablePlugins.value.map(plugin => {
+        const newPlugin = {...plugin};
+        if (newPlugin.Id !== undefined) newPlugin.id = newPlugin.Id;
+        if (newPlugin.Name !== undefined) newPlugin.name = newPlugin.Name;
+        if (newPlugin.Type !== undefined) newPlugin.type = newPlugin.Type;
+        if (newPlugin.Category !== undefined) newPlugin.category = newPlugin.Category;
+        if (newPlugin.Content !== undefined) newPlugin.content = newPlugin.Content;
+        if (newPlugin.Description !== undefined) newPlugin.description = newPlugin.Description;
+        return newPlugin;
+      });
+    }
+
+    // 应用过滤器
+    filterPlugins();
+  } catch (error) {
+    // 加载插件列表失败
+    ElMessage.error('加载可用插件列表失败');
+  }
+};
+
+// 选择现有插件
+const handlePluginSelected = (pluginId: number) => {
+  if (!pluginId) {
+    // 清除选择
+    Object.assign(currentPlugin, {
+      id: null,
+      name: '',
+      type: 'JavaScript',
+      category: 'Before',
+      content: '',
+      config: '',
+      description: ''
+    });
+    isCreatingNewPlugin.value = false;
+    return;
+  }
+
+  // 查找选中的插件
+  const selectedPlugin = availablePlugins.value.find(p => p.id === pluginId);
+  if (selectedPlugin) {
+    Object.assign(currentPlugin, {
+      id: selectedPlugin.id,
+      name: selectedPlugin.name,
+      type: selectedPlugin.type || 'JavaScript',
+      category: selectedPlugin.category || 'Before',
+      content: selectedPlugin.content || '',
+      config: '',  // 默认为空,让用户自己配置
+      description: selectedPlugin.description || ''
+    });
+    isCreatingNewPlugin.value = false;
+  }
+};
+
+// 创建新插件
+const createNewPlugin = () => {
+  selectedPluginId.value = null;
+  Object.assign(currentPlugin, {
+    id: null,
+    name: '',
+    type: 'JavaScript',
+    category: 'Before',
+    content: '',
+    config: '',
+    description: ''
+  });
+  isCreatingNewPlugin.value = true;
+};
+
+// 编辑插件
+const editPlugin = (index: number) => {
+  if (!formData.plugins || !formData.plugins[index]) return;
+
+  const plugin = formData.plugins[index];
+  selectedPluginId.value = plugin.id || null;
+  Object.assign(currentPlugin, {
+    id: plugin.id || null,
+    name: plugin.name || '',
+    type: plugin.type || 'JavaScript',
+    category: plugin.category || 'Before',
+    content: plugin.content || '',
+    config: plugin.config || '',
+    description: plugin.description || ''
+  });
+
+  // 编辑现有插件
+  isCreatingNewPlugin.value = false;
+  addPluginDialogVisible.value = true;
+};
+
+// 确认添加/编辑插件
+const confirmAddPlugin = async () => {
+  if (!pluginFormRef.value) return;
+
+  await pluginFormRef.value.validate(async (valid: boolean) => {
+    if (!valid) return;
+
+    try {
+      // 检查配置参数是否是有效的JSON
+      if (currentPlugin.config) {
+        try {
+          JSON.parse(currentPlugin.config);
+        } catch (e) {
+          ElMessage.error('配置参数不是有效的JSON格式');
+          return;
+        }
+      }
+
+      // 如果是创建新插件
+      if (isCreatingNewPlugin.value && !selectedPluginId.value) {
+        savePluginLoading.value = true;
+
+        try {
+          // 创建新插件
+          const newPluginData = {
+            name: currentPlugin.name,
+            type: currentPlugin.type,
+            category: currentPlugin.category,
+            content: currentPlugin.content,
+            description: currentPlugin.description
+          };
+
+          const res = await api.plugin.add(newPluginData);
+          const newPluginId = res.data?.id || res.data?.Id;
+
+          if (newPluginId) {
+            ElMessage.success('创建新插件成功');
+            selectedPluginId.value = newPluginId;
+            currentPlugin.id = newPluginId;
+
+            // 重新加载插件列表
+            await loadAvailablePlugins();
+          } else {
+            throw new Error('创建插件失败,无法获取插件ID');
+          }
+        } catch (error) {
+          // 创建插件失败
+          ElMessage.error('创建新插件失败,请重试');
+          return;
+        } finally {
+          savePluginLoading.value = false;
+        }
+      }
+
+      // 无论是否是新建的插件,都添加到API的插件列表中
+      if (!formData.plugins) {
+        formData.plugins = [];
+      }
+
+      // 检查是否已存在相同的插件
+      const existingIndex = formData.plugins.findIndex(p => p.name === currentPlugin.name);
+
+      // 创建插件对象
+      const pluginToAdd = {
+        id: currentPlugin.id,
+        name: currentPlugin.name,
+        type: currentPlugin.type,
+        category: currentPlugin.category,
+        config: currentPlugin.config,
+        description: currentPlugin.description
+      };
+
+      if (existingIndex >= 0) {
+        // 更新现有插件
+        formData.plugins[existingIndex] = pluginToAdd;
+        ElMessage.success('插件配置已更新');
+      } else {
+        // 添加新插件
+        formData.plugins.push(pluginToAdd);
+        ElMessage.success('插件已添加到API配置');
+      }
+
+      // 关闭对话框
+      addPluginDialogVisible.value = false;
+    } catch (error) {
+      // 保存失败
+      ElMessage.error('保存插件失败,请重试');
+    }
+  });
+};
+
+// 格式化插件配置显示
+const formatPluginConfig = (config: string) => {
+  if (!config) return '';
+
+  try {
+    const configObj = JSON.parse(config);
+    return JSON.stringify(configObj, null, 2);
+  } catch (e) {
+    return config; // 如果不是有效的JSON,则原样返回
+  }
+};
+
+// 确认删除插件
+const confirmRemovePlugin = (index: number) => {
+  ElMessageBox.confirm(`确定要移除插件 "${formData.plugins[index]?.name || '未命名插件'}" 吗?`, '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(() => {
+    removePlugin(index);
+    ElMessage.success('插件已移除');
+  }).catch(() => {});
+};
+
+// 此函数保留为兼容性考虑,实际调用showAddPluginDialog
+/* eslint-disable-next-line no-unused-vars */
 const addPlugin = () => {
   if (!formData.plugins) {
     formData.plugins = [];
   }
-  formData.plugins.push({
-    name: "",
-    config: "",
-  });
+  // 显示添加插件对话框
+  showAddPluginDialog();
 };
 
 // 移除插件