217 lines
6.8 KiB
Vue
217 lines
6.8 KiB
Vue
<template>
|
|
<div class="http-request-designer">
|
|
<div class="request-url-bar">
|
|
<el-select v-model="designer.method" class="request-method" placeholder="请求方法">
|
|
<el-option v-for="item in methodOptions" :key="item" :label="item" :value="item" />
|
|
</el-select>
|
|
<el-input
|
|
v-model="designer.pathTemplate"
|
|
class="request-path"
|
|
placeholder="请输入接口路径,例如 /api/v1/data"
|
|
/>
|
|
</div>
|
|
<el-tabs v-model="activeTab" class="request-tabs">
|
|
<el-tab-pane label="Params" name="params">
|
|
<key-value-editor ref="paramsEditor" v-model="designer.params" add-text="添加 Query 参数" />
|
|
</el-tab-pane>
|
|
<el-tab-pane label="Headers" name="headers">
|
|
<key-value-editor ref="headersEditor" v-model="designer.headers" add-text="添加 Header" />
|
|
</el-tab-pane>
|
|
<el-tab-pane label="Body" name="body">
|
|
<el-radio-group v-model="designer.body.mode" size="small" class="body-mode">
|
|
<el-radio-button label="none">none</el-radio-button>
|
|
<el-radio-button label="form-data">form-data</el-radio-button>
|
|
<el-radio-button label="x-www-form-urlencoded">x-www-form-urlencoded</el-radio-button>
|
|
<el-radio-button label="raw-json">raw JSON</el-radio-button>
|
|
</el-radio-group>
|
|
<key-value-editor
|
|
v-if="designer.body.mode === 'form-data'"
|
|
ref="formDataEditor"
|
|
v-model="designer.body.formData"
|
|
add-text="添加 Form 字段"
|
|
/>
|
|
<key-value-editor
|
|
v-if="designer.body.mode === 'x-www-form-urlencoded'"
|
|
ref="urlencodedEditor"
|
|
v-model="designer.body.urlencoded"
|
|
add-text="添加表单字段"
|
|
/>
|
|
<div v-if="designer.body.mode === 'raw-json'">
|
|
<div style="margin-bottom:6px;text-align:right">
|
|
<el-button size="mini" icon="el-icon-upload2" @click="importJsonDialogVisible = true">导入 JSON</el-button>
|
|
</div>
|
|
<el-input v-model="designer.body.raw" type="textarea" :rows="8" placeholder="请输入 JSON 请求体,可使用 ${变量名} 作为模板变量" />
|
|
</div>
|
|
<el-alert v-if="designer.body.mode === 'none'" title="该接口不配置请求体" type="info" :closable="false" />
|
|
</el-tab-pane>
|
|
<el-tab-pane label="高级配置" name="config">
|
|
<div class="config-panel">
|
|
<div class="config-field">
|
|
<label class="config-label">Content-Type</label>
|
|
<el-select
|
|
v-model="designer.contentType"
|
|
clearable
|
|
placeholder="请选择 Content-Type"
|
|
class="config-control"
|
|
>
|
|
<el-option label="application/json" value="application/json" />
|
|
<el-option label="application/x-www-form-urlencoded" value="application/x-www-form-urlencoded" />
|
|
<el-option label="multipart/form-data" value="multipart/form-data" />
|
|
<el-option label="text/plain" value="text/plain" />
|
|
</el-select>
|
|
</div>
|
|
<div class="config-field config-field--stack">
|
|
<label class="config-label">请求配置</label>
|
|
<div class="config-control">
|
|
<key-value-editor ref="configEditor" v-model="designer.config" add-text="添加配置" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</el-tab-pane>
|
|
<el-tab-pane label="保存预览" name="preview">
|
|
<pre class="preview-json">{{ previewText }}</pre>
|
|
</el-tab-pane>
|
|
</el-tabs>
|
|
|
|
<el-dialog title="导入 JSON" :visible.sync="importJsonDialogVisible" width="600px" append-to-body>
|
|
<el-input v-model="importJsonText" type="textarea" :rows="12" placeholder="粘贴 JSON 内容,支持 ${变量名} 模板变量" />
|
|
<div v-if="importJsonError" style="color:#f56c6c;margin-top:6px;font-size:12px">{{ importJsonError }}</div>
|
|
<div slot="footer">
|
|
<el-button @click="importJsonDialogVisible = false">取 消</el-button>
|
|
<el-button type="primary" @click="confirmImportJson">确 定</el-button>
|
|
</div>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import KeyValueEditor from './KeyValueEditor';
|
|
import { serializeDesigner } from '../utils/requestTemplate';
|
|
|
|
export default {
|
|
name: 'PlatformHttpRequestDesigner',
|
|
components: { KeyValueEditor },
|
|
props: {
|
|
value: { type: Object, required: true },
|
|
},
|
|
data() {
|
|
return {
|
|
activeTab: 'params',
|
|
methodOptions: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
|
|
importJsonDialogVisible: false,
|
|
importJsonText: '',
|
|
importJsonError: '',
|
|
};
|
|
},
|
|
methods: {
|
|
flushEditors() {
|
|
['paramsEditor', 'headersEditor', 'configEditor', 'formDataEditor', 'urlencodedEditor'].forEach(refName => {
|
|
const editor = this.$refs[refName];
|
|
if (editor && typeof editor.flushBulk === 'function') {
|
|
editor.flushBulk();
|
|
}
|
|
});
|
|
},
|
|
confirmImportJson() {
|
|
const text = this.importJsonText.trim();
|
|
if (!text) {
|
|
this.importJsonError = '内容不能为空';
|
|
return;
|
|
}
|
|
// 将模板变量临时替换为合法值再做 JSON 校验
|
|
const sanitized = text.replace(/\$\{[^}]+\}/g, '"__var__"');
|
|
try {
|
|
JSON.parse(sanitized);
|
|
} catch (e) {
|
|
this.importJsonError = 'JSON 格式不正确:' + e.message;
|
|
return;
|
|
}
|
|
this.designer.body.raw = text;
|
|
this.importJsonDialogVisible = false;
|
|
this.importJsonText = '';
|
|
this.importJsonError = '';
|
|
},
|
|
},
|
|
computed: {
|
|
designer: {
|
|
get() {
|
|
return this.value;
|
|
},
|
|
set(value) {
|
|
this.$emit('input', value);
|
|
},
|
|
},
|
|
previewText() {
|
|
return JSON.stringify(serializeDesigner(this.designer), null, 2);
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.http-request-designer {
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
}
|
|
.request-url-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
width: 100%;
|
|
}
|
|
.request-url-bar .request-method {
|
|
width: 108px;
|
|
flex-shrink: 0;
|
|
}
|
|
.request-url-bar .request-path {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
.request-tabs {
|
|
margin-top: 12px;
|
|
}
|
|
.request-tabs ::v-deep .el-tabs__header {
|
|
margin-bottom: 12px;
|
|
}
|
|
.request-tabs ::v-deep .el-tabs__content {
|
|
padding-top: 4px;
|
|
}
|
|
.config-panel {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
}
|
|
.config-field {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
.config-field--stack {
|
|
align-items: flex-start;
|
|
}
|
|
.config-field--stack .config-label {
|
|
padding-top: 8px;
|
|
}
|
|
.config-label {
|
|
flex: 0 0 100px;
|
|
font-size: 14px;
|
|
color: #606266;
|
|
text-align: right;
|
|
line-height: 32px;
|
|
}
|
|
.config-control {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
.body-mode {
|
|
margin-bottom: 12px;
|
|
}
|
|
.preview-json {
|
|
padding: 12px;
|
|
background: #f5f7fa;
|
|
border: 1px solid #e4e7ed;
|
|
border-radius: 4px;
|
|
max-height: 320px;
|
|
overflow: auto;
|
|
}
|
|
</style> |