|
@@ -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;
|