fastbee/history/referencePage/HttpRequestDesigner.vue
2026-05-31 08:42:02 +08:00

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>