|
@@ -1,340 +1,981 @@
|
|
|
<template>
|
|
|
- <el-dialog
|
|
|
- class="api-edit"
|
|
|
- v-model="showDialog"
|
|
|
- :title="`${formData.id ? '编辑API' : '新增API'}`"
|
|
|
- width="800px"
|
|
|
- :close-on-click-modal="false"
|
|
|
- :close-on-press-escape="false"
|
|
|
- >
|
|
|
- <el-form ref="formRef" :model="formData" :rules="ruleForm" label-width="100px" @keyup.enter="onSubmit">
|
|
|
- <el-form-item label="API名称" prop="name">
|
|
|
- <el-input v-model="formData.name" placeholder="请输入API名称" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="API路径" prop="path">
|
|
|
- <el-input v-model="formData.path" placeholder="请输入API路径,如/api/v1/users" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="请求方法" prop="method">
|
|
|
- <el-select v-model="formData.method" placeholder="请选择请求方法">
|
|
|
- <el-option label="GET" value="GET"></el-option>
|
|
|
- <el-option label="POST" value="POST"></el-option>
|
|
|
- <el-option label="PUT" value="PUT"></el-option>
|
|
|
- <el-option label="DELETE" value="DELETE"></el-option>
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="数据源" prop="dataSourceId">
|
|
|
- <el-select v-model="formData.dataSourceId" placeholder="请选择数据源" filterable>
|
|
|
- <el-option v-for="item in dataSources" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="SQL类型" prop="sqlType">
|
|
|
- <el-radio-group v-model="formData.sqlType">
|
|
|
- <el-radio label="query">查询</el-radio>
|
|
|
- <el-radio label="procedure">存储过程</el-radio>
|
|
|
- </el-radio-group>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="所属分组" prop="groupKey">
|
|
|
- <el-cascader
|
|
|
- v-model="formData.groupKey"
|
|
|
- :options="groupOptions"
|
|
|
- :props="{ checkStrictly: true, emitPath: false, value: 'groupKey', label: 'name' }"
|
|
|
- placeholder="请选择所属分组"
|
|
|
- clearable
|
|
|
- style="width: 100%"
|
|
|
- />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="SQL内容" prop="sqlContent">
|
|
|
- <el-input v-model="formData.sqlContent" type="textarea" :rows="4" placeholder="请输入SQL语句或存储过程名称" />
|
|
|
- </el-form-item>
|
|
|
-
|
|
|
- <el-form-item label="参数定义">
|
|
|
- <el-button type="primary" size="small" @click="addParameter">
|
|
|
- <el-icon><ele-Plus /></el-icon>添加参数
|
|
|
- </el-button>
|
|
|
- <el-table :data="formData.parameters" style="width: 100%; margin-top: 10px;" border>
|
|
|
- <el-table-column label="参数名" width="150">
|
|
|
- <template #default="scope">
|
|
|
- <el-input v-model="scope.row.name" placeholder="参数名"></el-input>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="类型" width="120">
|
|
|
- <template #default="scope">
|
|
|
- <el-select v-model="scope.row.type" placeholder="类型">
|
|
|
- <el-option label="string" value="string"></el-option>
|
|
|
- <el-option label="int" value="int"></el-option>
|
|
|
- <el-option label="float" value="float"></el-option>
|
|
|
- <el-option label="bool" value="bool"></el-option>
|
|
|
- </el-select>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="必填" width="80">
|
|
|
- <template #default="scope">
|
|
|
- <el-checkbox v-model="scope.row.required"></el-checkbox>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="默认值" width="120">
|
|
|
- <template #default="scope">
|
|
|
- <el-input v-model="scope.row.defaultValue" placeholder="默认值"></el-input>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="描述">
|
|
|
- <template #default="scope">
|
|
|
- <el-input v-model="scope.row.description" placeholder="参数描述"></el-input>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="操作" width="80">
|
|
|
- <template #default="scope">
|
|
|
- <el-button type="danger" size="small" @click="removeParameter(scope.$index)" text>
|
|
|
- <el-icon><ele-Delete /></el-icon>
|
|
|
- </el-button>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
- </el-form-item>
|
|
|
-
|
|
|
- <el-form-item label="返回格式" prop="returnFormat">
|
|
|
- <el-select v-model="formData.returnFormat" placeholder="请选择返回格式">
|
|
|
- <el-option label="JSON" value="JSON"></el-option>
|
|
|
- <el-option label="XML" value="XML"></el-option>
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="版本" prop="version">
|
|
|
- <el-input v-model="formData.version" placeholder="请输入版本号,如1.0" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="状态" prop="status">
|
|
|
- <el-select v-model="formData.status" placeholder="请选择状态">
|
|
|
- <el-option label="草稿" value="Draft"></el-option>
|
|
|
- <el-option label="已发布" value="Published"></el-option>
|
|
|
- <el-option label="已废弃" value="Deprecated"></el-option>
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="描述" prop="description">
|
|
|
- <el-input v-model="formData.description" type="textarea" :rows="3" placeholder="请输入API描述" />
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
- <template #footer>
|
|
|
- <div class="dialog-footer">
|
|
|
- <el-button @click="cancel">取消</el-button>
|
|
|
- <el-button type="primary" @click="onSubmit">确定</el-button>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-dialog>
|
|
|
+ <el-dialog
|
|
|
+ class="api-edit"
|
|
|
+ v-model="showDialog"
|
|
|
+ :title="`${formData.id ? '编辑API' : '新增API'}`"
|
|
|
+ :width="isFullscreen ? '100%' : '1000px'"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ :close-on-press-escape="false"
|
|
|
+ :fullscreen="isFullscreen"
|
|
|
+ :before-close="cancel"
|
|
|
+ >
|
|
|
+ <template #header>
|
|
|
+ <div class="dialog-header">
|
|
|
+ <span class="dialog-title">{{ formData.id ? '编辑API' : '新增API' }}</span>
|
|
|
+ <div class="dialog-tools">
|
|
|
+ <el-button
|
|
|
+ type="text"
|
|
|
+ @click="toggleFullscreen"
|
|
|
+ >
|
|
|
+ <el-icon :size="20">
|
|
|
+ <ele-FullScreen v-if="!isFullscreen" />
|
|
|
+ <ele-ScaleToOriginal v-else />
|
|
|
+ </el-icon>
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div class="dialog-body-with-tabs">
|
|
|
+ <div class="tab-navigation">
|
|
|
+ <ul>
|
|
|
+ <li @click="activeTab = 'basic'" :class="{ active: activeTab === 'basic' }">基本信息</li>
|
|
|
+ <li @click="activeTab = 'executor'" :class="{ active: activeTab === 'executor' }">执行器</li>
|
|
|
+ <li @click="activeTab = 'plugins'" :class="{ active: activeTab === 'plugins' }">全局插件</li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ <div class="tab-content">
|
|
|
+ <el-form ref="formRef" :model="formData" :rules="ruleForm" label-width="100px">
|
|
|
+ <!-- 基本信息 Tab -->
|
|
|
+ <div v-show="activeTab === 'basic'" class="basic-info-tab">
|
|
|
+ <div class="form-section-title">基础配置</div>
|
|
|
+ <el-form-item label="API信息" required>
|
|
|
+ <div class="method-name-container">
|
|
|
+ <div class="method-select">
|
|
|
+ <el-select v-model="formData.method" placeholder="请求方法">
|
|
|
+ <el-option label="GET" value="GET"></el-option>
|
|
|
+ <el-option label="POST" value="POST"></el-option>
|
|
|
+ <el-option label="PUT" value="PUT"></el-option>
|
|
|
+ <el-option label="DELETE" value="DELETE"></el-option>
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ <div class="name-input">
|
|
|
+ <el-input v-model="formData.name" placeholder="请输入API名称" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="API路径" prop="path">
|
|
|
+ <div class="path-input-container">
|
|
|
+ <div class="path-prefix">
|
|
|
+ http://127.0.0.1:8199/api/v1/apihub/
|
|
|
+ </div>
|
|
|
+ <el-input v-model="formData.path" placeholder="请输入API路径" class="path-input" />
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="所属分组" prop="groupKey">
|
|
|
+ <el-cascader v-model="formData.groupKey" :options="groupOptions" :props="{ checkStrictly: true, emitPath: false, value: 'GroupKey', label: 'Name', children: 'Children' }" placeholder="请选择所属分组" clearable style="width: 100%" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="版本" prop="version">
|
|
|
+ <el-input v-model="formData.version" placeholder="请输入版本号,如1.0" />
|
|
|
+ </el-form-item>
|
|
|
+ <!-- <el-form-item label="状态" prop="status">
|
|
|
+ <el-select v-model="formData.status" placeholder="请选择状态">
|
|
|
+ <el-option label="草稿" value="Draft"></el-option>
|
|
|
+ <el-option label="已发布" value="Published"></el-option>
|
|
|
+ <el-option label="已废弃" value="Deprecated"></el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item> -->
|
|
|
+ <el-form-item label="描述" prop="description">
|
|
|
+ <el-input v-model="formData.description" type="textarea" :rows="3" placeholder="请输入API描述" />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <div class="form-section-title">参数定义
|
|
|
+ <el-button type="primary" link @click="addParameter" style="margin-left: 10px;">
|
|
|
+ <el-icon><ele-Plus /></el-icon>添加参数
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <el-form-item label-width="0">
|
|
|
+ <el-table :data="formData.parameters" style="width: 100%" border>
|
|
|
+ <el-table-column label="参数名" width="150">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-input v-model="scope.row.name" placeholder="参数名"></el-input>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="类型" width="120">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-select v-model="scope.row.type" placeholder="类型">
|
|
|
+ <el-option label="string" value="string"></el-option>
|
|
|
+ <el-option label="int" value="int"></el-option>
|
|
|
+ <el-option label="float" value="float"></el-option>
|
|
|
+ <el-option label="bool" value="bool"></el-option>
|
|
|
+ </el-select>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="必填" width="80">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-checkbox v-model="scope.row.required"></el-checkbox>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="默认值" width="120">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-input v-model="scope.row.defaultValue" placeholder="默认值"></el-input>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="描述">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-input v-model="scope.row.description" placeholder="参数描述"></el-input>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="80">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-button type="danger" link @click="removeParameter(scope.$index)">
|
|
|
+ <el-icon><ele-Delete /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 执行器 Tab -->
|
|
|
+ <div v-show="activeTab === 'executor'" class="executor-tab">
|
|
|
+ <div class="form-section-title">SQL配置</div>
|
|
|
+ <el-form-item label="数据源" prop="dataSourceKey">
|
|
|
+ <el-select v-model="formData.dataSourceKey" placeholder="请选择数据源" filterable>
|
|
|
+ <el-option v-for="item in dataSources" :key="item.id" :label="item.name" :value="item.dsKey"></el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="SQL类型" prop="sqlType">
|
|
|
+ <el-radio-group v-model="formData.sqlType">
|
|
|
+ <el-radio label="query">查询</el-radio>
|
|
|
+ <el-radio label="procedure">存储过程</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="SQL内容" prop="sqlContent" class="sql-content-item">
|
|
|
+ <div class="monaco-editor-wrapper">
|
|
|
+ <div class="line-number-container">
|
|
|
+ <div v-for="n in lineCount" :key="n" class="line-number">{{ n }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="monaco-editor-container">
|
|
|
+ <MonacoEditor
|
|
|
+ v-model:value="formData.sqlContent"
|
|
|
+ :options="monacoOptions"
|
|
|
+ theme="vs-dark"
|
|
|
+ language="sql"
|
|
|
+ @change="onSqlContentChange"
|
|
|
+ @editorDidMount="editorDidMount"
|
|
|
+ @editorWillMount="editorWillMount"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="返回格式" prop="returnFormat">
|
|
|
+ <el-select v-model="formData.returnFormat" placeholder="请选择返回格式">
|
|
|
+ <el-option label="JSON" value="JSON"></el-option>
|
|
|
+ <el-option label="XML" value="XML"></el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 全局插件 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;">
|
|
|
+ <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>
|
|
|
+ </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"
|
|
|
+ >
|
|
|
+ <el-input
|
|
|
+ v-model="plugin.config"
|
|
|
+ type="textarea"
|
|
|
+ :rows="2"
|
|
|
+ :placeholder="pluginParamsPlaceholder"
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ <div class="plugin-remove-action">
|
|
|
+ <el-button
|
|
|
+ type="danger"
|
|
|
+ link
|
|
|
+ icon="ele-Delete"
|
|
|
+ @click="removePlugin(index)"
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <template #footer>
|
|
|
+ <div class="dialog-footer">
|
|
|
+ <el-button @click="cancel">取消</el-button>
|
|
|
+ <el-button type="primary" @click="onSubmit">确定</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
</template>
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
-import { ref, reactive, nextTick } from 'vue'
|
|
|
-import { ElMessage } from 'element-plus'
|
|
|
-import { ruleRequired } from '/@/utils/validator'
|
|
|
-import request from '/@/utils/request'
|
|
|
+import { ref, reactive, nextTick } from "vue";
|
|
|
+import { ElMessage, ElMessageBox } from "element-plus";
|
|
|
+import { ruleRequired } from "/@/utils/validator";
|
|
|
+import api from "/@/api/modules/apiHub";
|
|
|
+import {
|
|
|
+ Plus as ElePlus,
|
|
|
+ Delete as EleDelete,
|
|
|
+ FullScreen as EleFullScreen,
|
|
|
+ ScaleToOriginal as EleScaleToOriginal
|
|
|
+} from '@element-plus/icons-vue';
|
|
|
+import MonacoEditor from "@guolao/vue-monaco-editor";
|
|
|
+
|
|
|
+const emit = defineEmits(["getList"]);
|
|
|
+
|
|
|
+const showDialog = ref(false);
|
|
|
+const formRef = ref();
|
|
|
+const dataSources = ref<any[]>([]);
|
|
|
+const activeTab = ref('basic'); // 当前激活的Tab
|
|
|
+const isFullscreen = ref(false); // 是否全屏显示
|
|
|
+const lineCount = ref(1); // SQL编辑器行数
|
|
|
+const pluginParamsPlaceholder = '插件参数 (JSON格式,例如:{"key": "value"})';
|
|
|
+
|
|
|
+// Monaco Editor 配置项
|
|
|
+const monacoOptions = {
|
|
|
+ automaticLayout: true,
|
|
|
+ scrollBeyondLastLine: false,
|
|
|
+ minimap: { enabled: false },
|
|
|
+ fontSize: 14,
|
|
|
+ tabSize: 2,
|
|
|
+ lineNumbers: 'off', // 关闭内置行号,因为我们自定义了行号显示
|
|
|
+ scrollbar: {
|
|
|
+ useShadows: false,
|
|
|
+ verticalScrollbarSize: 10,
|
|
|
+ horizontalScrollbarSize: 10,
|
|
|
+ alwaysConsumeMouseWheel: false
|
|
|
+ },
|
|
|
+ lineHeight: 20,
|
|
|
+ renderLineHighlight: 'line',
|
|
|
+ glyphMargin: false,
|
|
|
+ folding: true,
|
|
|
+ contextmenu: true,
|
|
|
+ cursorStyle: 'line',
|
|
|
+ fontFamily: 'Menlo, Monaco, Consolas, "Courier New", monospace',
|
|
|
+ // 自动提示相关配置 - 使用Monaco编辑器自带的SQL支持
|
|
|
+ quickSuggestions: {
|
|
|
+ other: true,
|
|
|
+ comments: true,
|
|
|
+ strings: true
|
|
|
+ },
|
|
|
+ suggestOnTriggerCharacters: true,
|
|
|
+ acceptSuggestionOnEnter: 'on',
|
|
|
+ tabCompletion: 'on',
|
|
|
+ wordBasedSuggestions: true,
|
|
|
+ // 设置触发建议的字符数
|
|
|
+ quickSuggestionsDelay: 0, // 立即显示提示
|
|
|
+ // 自动提示相关配置
|
|
|
+ suggest: {
|
|
|
+ showKeywords: true,
|
|
|
+ showSnippets: true,
|
|
|
+ showWords: true,
|
|
|
+ showFunctions: true,
|
|
|
+ showIcons: true,
|
|
|
+ maxVisibleSuggestions: 15,
|
|
|
+ filterGraceful: false, // 不过滤建议,显示所有可能的选项
|
|
|
+ snippetSuggestions: 'top' // 将代码片段放在提示列表顶部
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// SQL内容变化回调
|
|
|
+const onSqlContentChange = (value) => {
|
|
|
+ formData.sqlContent = value;
|
|
|
+ // 计算行数
|
|
|
+ const lines = value ? value.split('\n').length : 1;
|
|
|
+ lineCount.value = Math.max(1, lines);
|
|
|
+};
|
|
|
+
|
|
|
+// 编辑器将要加载时的回调
|
|
|
+const editorWillMount = (monaco) => {
|
|
|
+ // 自定义SQL关键字高亮
|
|
|
+ monaco.editor.defineTheme('sqlTheme', {
|
|
|
+ base: 'vs-dark',
|
|
|
+ inherit: true,
|
|
|
+ rules: [
|
|
|
+ { token: 'keyword', foreground: '569cd6', fontStyle: 'bold' },
|
|
|
+ { token: 'operator', foreground: 'd4d4d4' },
|
|
|
+ { token: 'string', foreground: 'ce9178' },
|
|
|
+ { token: 'number', foreground: 'b5cea8' },
|
|
|
+ { token: 'comment', foreground: '6a9955', fontStyle: 'italic' }
|
|
|
+ ],
|
|
|
+ colors: {}
|
|
|
+ });
|
|
|
+};
|
|
|
|
|
|
-const emit = defineEmits(['getList'])
|
|
|
+// 编辑器挂载完成回调
|
|
|
+const editorDidMount = (editor) => {
|
|
|
+ // 初始化行数
|
|
|
+ const lines = formData.sqlContent ? formData.sqlContent.split('\n').length : 1;
|
|
|
+ lineCount.value = Math.max(1, lines);
|
|
|
|
|
|
-const showDialog = ref(false)
|
|
|
-const formRef = ref()
|
|
|
-const dataSources = ref([
|
|
|
- { id: 1, name: '主数据库' },
|
|
|
- { id: 2, name: '业务数据库' },
|
|
|
- { id: 3, name: '日志数据库' }
|
|
|
-])
|
|
|
+ // 使用editor实例以避免lint警告
|
|
|
+ if (editor) {
|
|
|
+ // 设置焦点
|
|
|
+ editor.focus();
|
|
|
+
|
|
|
+ // 添加输入监听,在用户开始输入时触发提示
|
|
|
+ editor.onDidChangeModelContent(() => {
|
|
|
+ // 触发自动提示
|
|
|
+ editor.trigger('keyboard', 'editor.action.triggerSuggest', {});
|
|
|
+ });
|
|
|
+
|
|
|
+ // 初始化时就触发一次提示
|
|
|
+ setTimeout(() => {
|
|
|
+ editor.trigger('keyboard', 'editor.action.triggerSuggest', {});
|
|
|
+ }, 100);
|
|
|
+
|
|
|
+ // 添加键盘事件处理,阻止回车键触发表单提交
|
|
|
+ editor.onKeyDown((e) => {
|
|
|
+ // 当用户在编辑器中按下回车键时
|
|
|
+ if (e.keyCode === 13 || e.code === 'Enter') {
|
|
|
+ // 阻止事件冒泡到表单
|
|
|
+ e.stopPropagation();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 检查哪个标签页有验证错误并切换到该标签页
|
|
|
+const checkTabWithErrors = () => {
|
|
|
+ // 获取所有验证失败的字段
|
|
|
+ const fields = formRef.value.fields;
|
|
|
+ const errorFields = [];
|
|
|
+
|
|
|
+ // 收集所有错误字段
|
|
|
+ for (const field in fields) {
|
|
|
+ if (fields[field].validateState === 'error') {
|
|
|
+ errorFields.push(field);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (errorFields.length === 0) return;
|
|
|
+
|
|
|
+ // 基本信息标签页的字段
|
|
|
+ const basicTabFields = ['name', 'path', 'version', 'description', 'groupKey'];
|
|
|
+ // 执行器标签页的字段
|
|
|
+ const executorTabFields = ['dataSourceKey', 'sqlType', 'sqlContent', 'returnFormat', 'status'];
|
|
|
+ // 全局插件标签页的字段
|
|
|
+ const pluginsTabFields = [];
|
|
|
+ // 检查插件相关字段
|
|
|
+ errorFields.forEach(field => {
|
|
|
+ if (field.startsWith('plugins[')) {
|
|
|
+ pluginsTabFields.push(field);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 检查每个标签页并切换到有错误的标签页
|
|
|
+ const errorTabs = [];
|
|
|
+
|
|
|
+ if (errorFields.some(field => basicTabFields.includes(field))) {
|
|
|
+ errorTabs.push({ tab: 'basic', name: '基本信息' });
|
|
|
+ }
|
|
|
+ if (errorFields.some(field => executorTabFields.includes(field))) {
|
|
|
+ errorTabs.push({ tab: 'executor', name: '执行器' });
|
|
|
+ }
|
|
|
+ if (pluginsTabFields.length > 0) {
|
|
|
+ errorTabs.push({ tab: 'plugins', name: '全局插件' });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (errorTabs.length > 0) {
|
|
|
+ // 如果当前标签页不在错误标签页列表中,切换到第一个错误标签页
|
|
|
+ const currentTabHasError = errorTabs.some(tab => tab.tab === activeTab.value);
|
|
|
+ if (!currentTabHasError) {
|
|
|
+ activeTab.value = errorTabs[0].tab;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 显示错误提示
|
|
|
+ showErrorTooltip(errorTabs);
|
|
|
+
|
|
|
+ // 等待DOM更新后滚动到错误字段
|
|
|
+ nextTick(() => {
|
|
|
+ const errorElement = document.querySelector('.is-error');
|
|
|
+ if (errorElement) {
|
|
|
+ errorElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 显示错误提示
|
|
|
+const showErrorTooltip = (errorTabs) => {
|
|
|
+ // 创建错误标签页列表的HTML
|
|
|
+ let errorMessage = '<div class="error-tabs-list">';
|
|
|
+ errorMessage += '<div class="error-title">以下标签页有必填项未填写:</div>';
|
|
|
+ errorMessage += '<ul>';
|
|
|
+ errorTabs.forEach(tab => {
|
|
|
+ errorMessage += `<li class="error-tab-item" data-tab="${tab.tab}">${tab.name}</li>`;
|
|
|
+ });
|
|
|
+ errorMessage += '</ul>';
|
|
|
+ errorMessage += '</div>';
|
|
|
+
|
|
|
+ // 显示消息提示
|
|
|
+ ElMessage({
|
|
|
+ dangerouslyUseHTMLString: true,
|
|
|
+ message: errorMessage,
|
|
|
+ type: 'error',
|
|
|
+ duration: 5000,
|
|
|
+ showClose: true,
|
|
|
+ onClose: () => {
|
|
|
+ // 消息关闭后移除事件监听
|
|
|
+ removeErrorTabClickListeners();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 等待DOM更新后添加点击事件
|
|
|
+ nextTick(() => {
|
|
|
+ addErrorTabClickListeners();
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 添加错误标签页点击事件
|
|
|
+const addErrorTabClickListeners = () => {
|
|
|
+ const tabItems = document.querySelectorAll('.error-tab-item');
|
|
|
+ tabItems.forEach(item => {
|
|
|
+ item.addEventListener('click', handleErrorTabClick);
|
|
|
+ // 添加手型光标和下划线样式
|
|
|
+ item.style.cursor = 'pointer';
|
|
|
+ item.style.textDecoration = 'underline';
|
|
|
+ item.style.color = '#409EFF';
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 移除错误标签页点击事件
|
|
|
+const removeErrorTabClickListeners = () => {
|
|
|
+ const tabItems = document.querySelectorAll('.error-tab-item');
|
|
|
+ tabItems.forEach(item => {
|
|
|
+ item.removeEventListener('click', handleErrorTabClick);
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 错误标签页点击处理函数
|
|
|
+const handleErrorTabClick = (event) => {
|
|
|
+ const tabName = event.target.getAttribute('data-tab');
|
|
|
+ if (tabName) {
|
|
|
+ activeTab.value = tabName;
|
|
|
+ // 等待DOM更新后滚动到错误字段
|
|
|
+ nextTick(() => {
|
|
|
+ const errorElement = document.querySelector('.is-error');
|
|
|
+ if (errorElement) {
|
|
|
+ errorElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+api.dataSource.list().then((res: any) => {
|
|
|
+ dataSources.value = res.list;
|
|
|
+});
|
|
|
|
|
|
// 分组选项
|
|
|
-const groupOptions = ref([])
|
|
|
+const groupOptions = ref([]);
|
|
|
|
|
|
// 定义基础表单数据
|
|
|
const baseForm = {
|
|
|
- id: undefined,
|
|
|
- name: '',
|
|
|
- path: '',
|
|
|
- method: 'GET',
|
|
|
- dataSourceId: undefined,
|
|
|
- sqlType: 'query',
|
|
|
- sqlContent: '',
|
|
|
- parameters: [],
|
|
|
- returnFormat: 'JSON',
|
|
|
- version: '1.0',
|
|
|
- status: 'Draft',
|
|
|
- plugins: [],
|
|
|
- groupKey: '',
|
|
|
- description: ''
|
|
|
-}
|
|
|
-
|
|
|
-const formData = reactive(JSON.parse(JSON.stringify(baseForm)))
|
|
|
+ id: null,
|
|
|
+ name: "",
|
|
|
+ path: "",
|
|
|
+ method: "GET",
|
|
|
+ dataSourceKey: null,
|
|
|
+ sqlType: "query",
|
|
|
+ sqlContent: "",
|
|
|
+ parameters: [],
|
|
|
+ returnFormat: "JSON",
|
|
|
+ version: "1.0",
|
|
|
+ status: "Draft",
|
|
|
+ plugins: [],
|
|
|
+ groupKey: "",
|
|
|
+ description: "",
|
|
|
+};
|
|
|
+
|
|
|
+const formData = reactive(JSON.parse(JSON.stringify(baseForm)));
|
|
|
|
|
|
// 表单验证规则
|
|
|
const ruleForm = {
|
|
|
- name: [ruleRequired('API名称不能为空', 'change')],
|
|
|
- path: [ruleRequired('API路径不能为空', 'change')],
|
|
|
- method: [ruleRequired('请求方法不能为空', 'change')],
|
|
|
- dataSourceId: [ruleRequired('数据源不能为空', 'change')],
|
|
|
- sqlType: [ruleRequired('SQL类型不能为空', 'change')],
|
|
|
- sqlContent: [ruleRequired('SQL内容不能为空', 'change')],
|
|
|
- returnFormat: [ruleRequired('返回格式不能为空', 'change')],
|
|
|
- version: [ruleRequired('版本不能为空', 'change')],
|
|
|
- status: [ruleRequired('状态不能为空', 'change')]
|
|
|
-}
|
|
|
+ name: [ruleRequired("API名称不能为空", "change")],
|
|
|
+ path: [ruleRequired("API路径不能为空", "change")],
|
|
|
+ method: [ruleRequired("请求方法不能为空", "change")],
|
|
|
+ dataSourceKey: [ruleRequired("数据源不能为空", "change")],
|
|
|
+ sqlType: [ruleRequired("SQL类型不能为空", "change")],
|
|
|
+ sqlContent: [ruleRequired("SQL内容不能为空", "change")],
|
|
|
+ returnFormat: [ruleRequired("返回格式不能为空", "change")],
|
|
|
+ version: [ruleRequired("版本不能为空", "change")],
|
|
|
+ status: [ruleRequired("状态不能为空", "change")],
|
|
|
+};
|
|
|
|
|
|
// 添加参数
|
|
|
const addParameter = () => {
|
|
|
- if (!formData.parameters) {
|
|
|
- formData.parameters = []
|
|
|
- }
|
|
|
- formData.parameters.push({
|
|
|
- name: '',
|
|
|
- type: 'string',
|
|
|
- required: false,
|
|
|
- defaultValue: '',
|
|
|
- description: ''
|
|
|
- })
|
|
|
-}
|
|
|
+ if (!formData.parameters) {
|
|
|
+ formData.parameters = [];
|
|
|
+ }
|
|
|
+ formData.parameters.push({
|
|
|
+ name: "",
|
|
|
+ type: "string",
|
|
|
+ required: false,
|
|
|
+ defaultValue: "",
|
|
|
+ description: "",
|
|
|
+ });
|
|
|
+};
|
|
|
|
|
|
// 移除参数
|
|
|
-const removeParameter = (index) => {
|
|
|
- formData.parameters.splice(index, 1)
|
|
|
-}
|
|
|
+const removeParameter = (index: number) => {
|
|
|
+ formData.parameters.splice(index, 1);
|
|
|
+};
|
|
|
|
|
|
-// 添加API
|
|
|
-const addApi = (data) => {
|
|
|
- return request({
|
|
|
- url: '/api/add',
|
|
|
- method: 'post',
|
|
|
- data
|
|
|
- })
|
|
|
-}
|
|
|
+// 添加插件
|
|
|
+const addPlugin = () => {
|
|
|
+ if (!formData.plugins) {
|
|
|
+ formData.plugins = [];
|
|
|
+ }
|
|
|
+ formData.plugins.push({
|
|
|
+ name: "",
|
|
|
+ config: "",
|
|
|
+ });
|
|
|
+};
|
|
|
|
|
|
-// 编辑API
|
|
|
-const editApi = (data) => {
|
|
|
- return request({
|
|
|
- url: '/api/edit',
|
|
|
- method: 'put',
|
|
|
- data
|
|
|
- })
|
|
|
-}
|
|
|
+// 移除插件
|
|
|
+const removePlugin = (index: number) => {
|
|
|
+ formData.plugins.splice(index, 1);
|
|
|
+};
|
|
|
+
|
|
|
+// 切换全屏显示
|
|
|
+const toggleFullscreen = () => {
|
|
|
+ isFullscreen.value = !isFullscreen.value;
|
|
|
+
|
|
|
+ // 全屏模式下调整内容容器高度
|
|
|
+ if (isFullscreen.value) {
|
|
|
+ nextTick(() => {
|
|
|
+ const dialogBodyWithTabs = document.querySelector('.dialog-body-with-tabs');
|
|
|
+ if (dialogBodyWithTabs) {
|
|
|
+ (dialogBodyWithTabs as HTMLElement).style.height = 'calc(100vh - 120px)';
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ nextTick(() => {
|
|
|
+ const dialogBodyWithTabs = document.querySelector('.dialog-body-with-tabs');
|
|
|
+ if (dialogBodyWithTabs) {
|
|
|
+ (dialogBodyWithTabs as HTMLElement).style.height = '600px';
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+};
|
|
|
|
|
|
// 提交表单
|
|
|
-const onSubmit = async () => {
|
|
|
- try {
|
|
|
- // 表单验证
|
|
|
- await formRef.value.validate()
|
|
|
-
|
|
|
- // 准备提交数据
|
|
|
- const submitData = JSON.parse(JSON.stringify(formData))
|
|
|
-
|
|
|
- // 调用API
|
|
|
- if (submitData.id) {
|
|
|
- // 编辑模式
|
|
|
- await editApi(submitData)
|
|
|
- } else {
|
|
|
- // 新增模式
|
|
|
- await addApi(submitData)
|
|
|
- }
|
|
|
-
|
|
|
- ElMessage.success('操作成功')
|
|
|
- resetForm()
|
|
|
- showDialog.value = false
|
|
|
- emit('getList')
|
|
|
- } catch (error) {
|
|
|
- ElMessage.error(error.message || '操作失败')
|
|
|
- }
|
|
|
-}
|
|
|
+const onSubmit = () => {
|
|
|
+ // 使用回调形式的验证,而不是异步验证
|
|
|
+ formRef.value.validate((valid, fields) => {
|
|
|
+ if (valid) {
|
|
|
+ // 验证通过
|
|
|
+ // 准备提交数据
|
|
|
+ const submitData = JSON.parse(JSON.stringify(formData));
|
|
|
+
|
|
|
+ // 调用API
|
|
|
+ if (submitData.id) {
|
|
|
+ // 编辑模式
|
|
|
+ api.edit(submitData).then(() => {
|
|
|
+ ElMessage.success("操作成功");
|
|
|
+ resetForm();
|
|
|
+ showDialog.value = false;
|
|
|
+ emit("getList");
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 新增模式
|
|
|
+ delete submitData.id;
|
|
|
+ api.add(submitData).then(() => {
|
|
|
+ ElMessage.success("操作成功");
|
|
|
+ resetForm();
|
|
|
+ showDialog.value = false;
|
|
|
+ emit("getList");
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 验证失败
|
|
|
+ // 直接显示错误提示
|
|
|
+ showValidationErrorMessage(fields);
|
|
|
+
|
|
|
+ // 验证失败时检查是哪个标签页的必填项出错
|
|
|
+ checkTabWithErrors();
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 显示验证错误消息
|
|
|
+const showValidationErrorMessage = (fields) => {
|
|
|
+ // 获取所有错误字段
|
|
|
+ const errorFields = Object.keys(fields);
|
|
|
+ if (errorFields.length === 0) return;
|
|
|
+
|
|
|
+ // 基本信息标签页的字段
|
|
|
+ const basicTabFields = ['name', 'path', 'version', 'description', 'groupKey'];
|
|
|
+ // 执行器标签页的字段
|
|
|
+ const executorTabFields = ['dataSourceKey', 'sqlType', 'sqlContent', 'returnFormat', 'status'];
|
|
|
+
|
|
|
+ // 检查每个标签页的错误
|
|
|
+ const errorTabs = [];
|
|
|
+
|
|
|
+ if (errorFields.some(field => basicTabFields.includes(field))) {
|
|
|
+ errorTabs.push('基本信息');
|
|
|
+ }
|
|
|
+ if (errorFields.some(field => executorTabFields.includes(field))) {
|
|
|
+ errorTabs.push('执行器');
|
|
|
+ }
|
|
|
+ if (errorFields.some(field => field.startsWith('plugins['))) {
|
|
|
+ errorTabs.push('全局插件');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (errorTabs.length > 0) {
|
|
|
+ // 显示错误消息
|
|
|
+ ElMessage({
|
|
|
+ message: `请检查以下标签页的必填项:${errorTabs.join('、')}`,
|
|
|
+ type: 'error',
|
|
|
+ duration: 5000,
|
|
|
+ showClose: true
|
|
|
+ });
|
|
|
+ }
|
|
|
+};
|
|
|
|
|
|
// 重置表单
|
|
|
const resetForm = () => {
|
|
|
- Object.assign(formData, JSON.parse(JSON.stringify(baseForm)))
|
|
|
- formRef.value && formRef.value.resetFields()
|
|
|
-}
|
|
|
+ Object.assign(formData, JSON.parse(JSON.stringify(baseForm)));
|
|
|
+ activeTab.value = 'basic'; // 重置 Tab
|
|
|
+ formRef.value && formRef.value.resetFields();
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
|
|
|
// 取消操作
|
|
|
const cancel = () => {
|
|
|
- resetForm()
|
|
|
- showDialog.value = false
|
|
|
-}
|
|
|
-
|
|
|
-// 获取分组树形结构
|
|
|
-const getGroupTree = async () => {
|
|
|
- try {
|
|
|
- // 调用API获取分组树
|
|
|
- const res = await request({
|
|
|
- url: '/api_group/tree',
|
|
|
- method: 'get'
|
|
|
- })
|
|
|
- return res.data?.list || []
|
|
|
-
|
|
|
- // 模拟数据
|
|
|
- return [
|
|
|
- {
|
|
|
- id: 1,
|
|
|
- groupKey: 'test_group',
|
|
|
- name: '测试分组',
|
|
|
- parentKey: '',
|
|
|
- sort: 0,
|
|
|
- description: '这是一个测试分组',
|
|
|
- children: [
|
|
|
- {
|
|
|
- id: 3,
|
|
|
- groupKey: 'test_subgroup',
|
|
|
- name: '测试子分组',
|
|
|
- parentKey: 'test_group',
|
|
|
- sort: 0,
|
|
|
- description: '这是一个测试子分组',
|
|
|
- children: []
|
|
|
- }
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- id: 2,
|
|
|
- groupKey: 'user_api',
|
|
|
- name: '用户API',
|
|
|
- parentKey: '',
|
|
|
- sort: 1,
|
|
|
- description: '用户相关API',
|
|
|
- children: []
|
|
|
- }
|
|
|
- ]
|
|
|
- } catch (error) {
|
|
|
- ElMessage.error('获取分组树失败')
|
|
|
- return []
|
|
|
- }
|
|
|
-}
|
|
|
+ ElMessageBox.confirm(
|
|
|
+ '确定要关闭吗?未保存的内容将会丢失',
|
|
|
+ '提示',
|
|
|
+ {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ }
|
|
|
+ )
|
|
|
+ .then(() => {
|
|
|
+ showDialog.value = false;
|
|
|
+ resetForm();
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ // 用户取消关闭,不做任何操作
|
|
|
+ });
|
|
|
+};
|
|
|
|
|
|
// 加载分组选项
|
|
|
const loadGroupOptions = async () => {
|
|
|
- const treeData = await getGroupTree()
|
|
|
- groupOptions.value = treeData
|
|
|
-}
|
|
|
+ api.group.tree().then((res: any) => {
|
|
|
+ groupOptions.value = res.list || [];
|
|
|
+ });
|
|
|
+};
|
|
|
|
|
|
// 打开对话框
|
|
|
const open = async (row?: any, defaultGroupKey?: string) => {
|
|
|
- resetForm()
|
|
|
- showDialog.value = true
|
|
|
-
|
|
|
- // 加载分组选项
|
|
|
- await loadGroupOptions()
|
|
|
-
|
|
|
- if (row) {
|
|
|
- // 编辑模式,填充表单数据
|
|
|
- nextTick(() => {
|
|
|
- // 如果是编辑模式,需要先获取详情
|
|
|
- // 实际使用时,可能需要先调用API获取完整数据
|
|
|
- // 这里模拟直接使用传入的行数据
|
|
|
- Object.assign(formData, JSON.parse(JSON.stringify(row)))
|
|
|
-
|
|
|
- // 确保参数数组存在
|
|
|
- if (!formData.parameters) {
|
|
|
- formData.parameters = []
|
|
|
- }
|
|
|
- })
|
|
|
- } else if (defaultGroupKey) {
|
|
|
- // 新增模式,设置默认分组
|
|
|
- formData.groupKey = defaultGroupKey
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-defineExpose({ open })
|
|
|
+ resetForm();
|
|
|
+ showDialog.value = true;
|
|
|
+
|
|
|
+ // 加载分组选项
|
|
|
+ await loadGroupOptions();
|
|
|
+
|
|
|
+ if (row) {
|
|
|
+ // 编辑模式,填充表单数据
|
|
|
+ nextTick(() => {
|
|
|
+ // 如果是编辑模式,需要先获取详情
|
|
|
+ // 实际使用时,可能需要先调用API获取完整数据
|
|
|
+ // 这里模拟直接使用传入的行数据
|
|
|
+
|
|
|
+ Object.assign(formData, JSON.parse(JSON.stringify(row)));
|
|
|
+
|
|
|
+ // 确保参数数组存在
|
|
|
+ if (!formData.parameters) {
|
|
|
+ formData.parameters = [];
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ if (defaultGroupKey) {
|
|
|
+ // 新增模式,设置默认分组
|
|
|
+ formData.groupKey = defaultGroupKey;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+defineExpose({ open });
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
+/* 错误提示样式 */
|
|
|
+:deep(.error-tabs-list) {
|
|
|
+ text-align: left;
|
|
|
+ padding: 5px 0;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.error-title) {
|
|
|
+ font-weight: bold;
|
|
|
+ margin-bottom: 5px;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.error-tabs-list ul) {
|
|
|
+ list-style: disc;
|
|
|
+ padding-left: 20px;
|
|
|
+ margin: 5px 0;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.error-tab-item) {
|
|
|
+ margin: 3px 0;
|
|
|
+ padding: 2px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-title {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-tools {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-dialog__header) {
|
|
|
+ padding: 15px 0;
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-dialog__headerbtn) {
|
|
|
+ top: 15px;
|
|
|
+ right: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-dialog__body) {
|
|
|
+ padding-top: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.api-edit .dialog-body-with-tabs {
|
|
|
+ display: flex;
|
|
|
+ height: 600px; /* 固定高度,使切换tab时不会改变 */
|
|
|
+}
|
|
|
+
|
|
|
+.tab-navigation {
|
|
|
+ width: 150px; /* 稍微加宽一点导航宽度 */
|
|
|
+ border-right: 1px solid #e0e0e0; /* 颜色调整 */
|
|
|
+ padding-right: 0; /* 移除右边padding,让li撑满 */
|
|
|
+ margin-right: 0; /* 移除右边margin */
|
|
|
+}
|
|
|
+
|
|
|
+.tab-navigation ul {
|
|
|
+ list-style: none;
|
|
|
+ padding: 10px 0;
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.tab-navigation li {
|
|
|
+ padding: 12px 20px; /* 调整padding */
|
|
|
+ cursor: pointer;
|
|
|
+ /* border-radius: 4px; */ /* 移除圆角,使其更像设计图的tab */
|
|
|
+ margin-bottom: 2px; /* tab之间的间距 */
|
|
|
+ font-size: 14px;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
+.tab-navigation li:hover {
|
|
|
+ background-color: #f0f2f5; /* hover颜色调整 */
|
|
|
+}
|
|
|
+
|
|
|
+.tab-navigation li.active {
|
|
|
+ background-color: #e6f7ff; /* active背景色调整 */
|
|
|
+ color: #1890ff; /* active文字颜色调整 */
|
|
|
+ border-right: 3px solid #1890ff; /* active时右侧有蓝色竖线 */
|
|
|
+ font-weight: 500; /* active时文字稍微加粗 */
|
|
|
+}
|
|
|
+
|
|
|
+.tab-content {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ height: 100%; /* 使内容区域填充整个高度 */
|
|
|
+ padding: 10px 20px; /* tab内容的padding */
|
|
|
+}
|
|
|
+
|
|
|
+.form-section-title {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ padding: 10px 0;
|
|
|
+ margin-bottom: 15px;
|
|
|
+ border-bottom: 1px solid #e0e0e0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.basic-info-tab,
|
|
|
+.executor-tab,
|
|
|
+.plugins-tab {
|
|
|
+ height: 100%; /* 使每个标签页内容区域填充整个高度 */
|
|
|
+ overflow-y: auto; /* 内容超出时显示滚动条 */
|
|
|
+}
|
|
|
+
|
|
|
+.basic-info-tab .el-form-item,
|
|
|
+.executor-tab .el-form-item,
|
|
|
+.plugins-tab .el-form-item {
|
|
|
+ margin-bottom: 18px; /* 统一表单项间距 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 全局插件样式 */
|
|
|
+.plugins-tab .form-section-title {
|
|
|
+ margin-bottom: 15px; /* Ensure space before the first plugin item or empty state */
|
|
|
+}
|
|
|
+
|
|
|
+.plugin-list-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start; /* Aligns items to the top, good if details section can vary in height */
|
|
|
+ padding: 12px 0;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+}
|
|
|
+.plugin-list-item:last-of-type {
|
|
|
+ border-bottom: none;
|
|
|
+ padding-bottom: 0; /* No padding if it's the last item and no border */
|
|
|
+}
|
|
|
+
|
|
|
+.plugin-item-details {
|
|
|
+ flex-grow: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.plugin-item-details .plugin-field {
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+.plugin-item-details .plugin-field:last-of-type {
|
|
|
+ margin-bottom: 0; /* No margin for the last field in the details section */
|
|
|
+}
|
|
|
+
|
|
|
+.plugin-remove-action {
|
|
|
+ flex-shrink: 0;
|
|
|
+ margin-left: 15px;
|
|
|
+ padding-top: 5px; /* Small top padding to align better with first line of text or input */
|
|
|
+}
|
|
|
+
|
|
|
+.empty-plugins {
|
|
|
+ text-align: center;
|
|
|
+ padding: 20px 0;
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+/* Monaco Editor 样式 */
|
|
|
+.sql-content-item {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.monaco-editor-wrapper {
|
|
|
+ display: flex;
|
|
|
+ align-items: stretch;
|
|
|
+ width: 100%;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ border-radius: 4px;
|
|
|
+ overflow: hidden;
|
|
|
+ min-width: 500px;
|
|
|
+}
|
|
|
+
|
|
|
+.line-number-container {
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ border-right: 1px solid #dcdfe6;
|
|
|
+ min-width: 40px;
|
|
|
+ width: 40px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ padding-top: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.line-number {
|
|
|
+ color: #909399;
|
|
|
+ font-family: monospace;
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 1.5;
|
|
|
+ text-align: center;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.monaco-editor-container {
|
|
|
+ height: 350px; /* 增加编辑器高度,使其更好地利用空间 */
|
|
|
+ flex-grow: 1;
|
|
|
+ overflow: hidden;
|
|
|
+ border: none;
|
|
|
+}
|
|
|
+
|
|
|
+/* API路径输入框样式 */
|
|
|
+.path-input-container {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.path-prefix {
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ padding: 0 10px;
|
|
|
+ height: 32px;
|
|
|
+ line-height: 32px;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ border-right: none;
|
|
|
+ border-radius: 4px 0 0 4px;
|
|
|
+ color: #606266;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.path-input {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.path-input .el-input__inner) {
|
|
|
+ border-top-left-radius: 0;
|
|
|
+ border-bottom-left-radius: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 请求方法和API名称同行样式 */
|
|
|
+.method-name-container {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ width: 100%;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.method-select {
|
|
|
+ width: 120px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.name-input {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
.dialog-footer {
|
|
|
- display: flex;
|
|
|
- justify-content: flex-end;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ padding: 15px 20px;
|
|
|
+ border-top: 1px solid #e0e0e0;
|
|
|
}
|
|
|
</style>
|