瀏覽代碼

feat:API定义页SQL脚本输入框改为代码编辑器支持。

microrain 5 月之前
父節點
當前提交
83f99ef9dd
共有 1 個文件被更改,包括 273 次插入25 次删除
  1. 273 25
      src/views/apihub/component/edit.vue

+ 273 - 25
src/views/apihub/component/edit.vue

@@ -1,5 +1,30 @@
 <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-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>
@@ -13,19 +38,28 @@
           <!-- 基本信息 Tab -->
           <div v-show="activeTab === 'basic'" class="basic-info-tab">
             <div class="form-section-title">基础配置</div>
-            <el-form-item label="API名称" prop="name">
-              <el-input v-model="formData.name" placeholder="请输入API名称" />
+            <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">
-              <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>
+              <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%" />
@@ -33,13 +67,13 @@
             <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-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> -->
             <el-form-item label="描述" prop="description">
               <el-input v-model="formData.description" type="textarea" :rows="3" placeholder="请输入API描述" />
             </el-form-item>
@@ -106,8 +140,22 @@
                 <el-radio label="procedure">存储过程</el-radio>
               </el-radio-group>
             </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 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"
+                  />
+                </div>
+              </div>
             </el-form-item>
             <el-form-item label="返回格式" prop="returnFormat">
               <el-select v-model="formData.returnFormat" placeholder="请选择返回格式">
@@ -148,18 +196,18 @@
                     <el-input
                       v-model="plugin.config"
                       type="textarea"
-                      :rows="2" 
+                      :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)" 
+                  <el-button
+                    type="danger"
+                    link
+                    icon="ele-Delete"
+                    @click="removePlugin(index)"
                     size="small"
                   />
                 </div>
@@ -183,7 +231,13 @@ 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 } from '@element-plus/icons-vue';
+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"]);
 
@@ -191,8 +245,54 @@ 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',
+};
+
+// SQL内容变化回调
+const onSqlContentChange = (value) => {
+  formData.sqlContent = value;
+  // 计算行数
+  const lines = value ? value.split('\n').length : 1;
+  lineCount.value = Math.max(1, lines);
+};
+
+// 编辑器挂载完成回调
+const editorDidMount = (editor) => {
+  // 初始化行数
+  const lines = formData.sqlContent ? formData.sqlContent.split('\n').length : 1;
+  lineCount.value = Math.max(1, lines);
+
+  // 使用editor实例以避免lint警告
+  if (editor) {
+    // 可以在这里添加编辑器的其他配置,如键盘快捷键等
+    editor.focus();
+  }
+};
+
 api.dataSource.list().then((res: any) => {
   dataSources.value = res.list;
 });
@@ -268,6 +368,28 @@ 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 () => {
   // 表单验证
@@ -359,9 +481,41 @@ defineExpose({ open });
 </script>
 
 <style scoped>
+.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;
-  min-height: 450px; /* 调整最小高度以适应内容 */
+  height: 600px; /* 固定高度,使切换tab时不会改变 */
 }
 
 .tab-navigation {
@@ -400,7 +554,7 @@ defineExpose({ open });
 .tab-content {
   flex: 1;
   overflow-y: auto;
-  max-height: 550px; /* Dialog内部内容区域的最大高度 */
+  height: 100%; /* 使内容区域填充整个高度 */
   padding: 10px 20px; /* tab内容的padding */
 }
 
@@ -415,6 +569,13 @@ defineExpose({ open });
   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 {
@@ -460,6 +621,93 @@ defineExpose({ open });
   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;