294 lines
10 KiB
HTML
294 lines
10 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>API Proxy Mock 配置管理</title>
|
||
<link
|
||
rel="stylesheet"
|
||
href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"
|
||
/>
|
||
<style>
|
||
body {
|
||
margin: 0;
|
||
background: #f5f7fa;
|
||
}
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 20px auto;
|
||
padding: 0 16px 24px;
|
||
}
|
||
.section-card {
|
||
margin-bottom: 16px;
|
||
}
|
||
.actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.small-text {
|
||
color: #909399;
|
||
font-size: 12px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="app" class="container">
|
||
<el-card class="section-card">
|
||
<div slot="header"><strong>基础配置</strong></div>
|
||
<el-form :model="form.config" label-width="180px">
|
||
<el-form-item label="Mock 开关">
|
||
<el-switch v-model="form.config.mockEnabled"></el-switch>
|
||
</el-form-item>
|
||
<el-form-item label="自动重载配置文件">
|
||
<el-switch v-model="form.config.reloadOnChange"></el-switch>
|
||
</el-form-item>
|
||
<el-form-item label="默认响应 Content-Type">
|
||
<el-input v-model="form.config.defaultContentType"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="本地代理端口">
|
||
<el-input-number
|
||
v-model="form.config.proxyPort"
|
||
:min="1"
|
||
:max="65535"
|
||
></el-input-number>
|
||
</el-form-item>
|
||
<el-form-item label="目标主机">
|
||
<el-input v-model="form.config.targetHost"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="目标端口">
|
||
<el-input-number
|
||
v-model="form.config.targetPort"
|
||
:min="1"
|
||
:max="65535"
|
||
></el-input-number>
|
||
</el-form-item>
|
||
<el-form-item label="目标 HTTPS">
|
||
<el-switch v-model="form.config.targetHttps"></el-switch>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<el-card class="section-card">
|
||
<div slot="header" style="display:flex;justify-content:space-between;align-items:center;">
|
||
<strong>路由配置(routes)</strong>
|
||
<el-button size="mini" type="primary" @click="addRouteRow">新增一行</el-button>
|
||
</div>
|
||
<el-table :data="form.routes" border>
|
||
<el-table-column label="请求路径" min-width="260">
|
||
<template slot-scope="scope">
|
||
<el-input v-model="scope.row.route" placeholder="/api/example"></el-input>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="Mock 文件路径" min-width="300">
|
||
<template slot-scope="scope">
|
||
<el-input
|
||
v-model="scope.row.filePath"
|
||
placeholder="mock/example.json"
|
||
></el-input>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="120">
|
||
<template slot-scope="scope">
|
||
<el-button size="mini" type="danger" @click="removeRoute(scope.$index)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="small-text" style="margin-top:8px;">
|
||
说明:如果路由以 # 开头,会作为注释保留,不参与 mock 命中。
|
||
</div>
|
||
</el-card>
|
||
|
||
<el-card class="section-card">
|
||
<div slot="header"><strong>动态新增接口配置文件</strong></div>
|
||
<el-form :model="newRoute" label-width="180px">
|
||
<el-form-item label="内容模板">
|
||
<el-select
|
||
v-model="newRoute.template"
|
||
placeholder="请选择模板"
|
||
@change="applyTemplate"
|
||
style="width: 100%;"
|
||
>
|
||
<el-option label="自定义内容" value="custom"></el-option>
|
||
<el-option label="基础失败模板" value="basicError"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="请求路径">
|
||
<el-input v-model="newRoute.route" placeholder="/api/new/mock"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="Mock 文件路径">
|
||
<el-input
|
||
v-model="newRoute.filePath"
|
||
placeholder="mock/new-api.json"
|
||
:disabled="newRoute.template === 'basicError'"
|
||
></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="文件内容">
|
||
<el-input
|
||
type="textarea"
|
||
:rows="8"
|
||
v-model="newRoute.fileContent"
|
||
placeholder='{"code":0,"message":"ok"}'
|
||
:disabled="newRoute.template === 'basicError'"
|
||
></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="覆盖已存在文件">
|
||
<el-switch
|
||
v-model="newRoute.overwrite"
|
||
:disabled="newRoute.template === 'basicError'"
|
||
></el-switch>
|
||
</el-form-item>
|
||
</el-form>
|
||
<el-button type="success" @click="createRouteAndFile">创建路由+文件</el-button>
|
||
</el-card>
|
||
|
||
<el-card>
|
||
<div class="actions">
|
||
<el-button type="primary" @click="saveConfig">保存配置到 config.json</el-button>
|
||
<el-button @click="reloadConfig">重载服务内存配置</el-button>
|
||
<el-button @click="loadConfig">刷新页面数据</el-button>
|
||
</div>
|
||
</el-card>
|
||
</div>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
|
||
<script>
|
||
new Vue({
|
||
el: "#app",
|
||
data: function () {
|
||
return {
|
||
form: {
|
||
config: {
|
||
mockEnabled: true,
|
||
cacheConfig: true,
|
||
reloadOnChange: true,
|
||
defaultContentType: "application/json",
|
||
proxyPort: 8877,
|
||
targetHost: "",
|
||
targetPort: 443,
|
||
targetHttps: true,
|
||
},
|
||
routes: [],
|
||
},
|
||
newRoute: {
|
||
template: "custom",
|
||
route: "",
|
||
filePath: "mock/",
|
||
fileContent: "",
|
||
overwrite: false,
|
||
},
|
||
};
|
||
},
|
||
created: function () {
|
||
this.loadConfig();
|
||
},
|
||
methods: {
|
||
toRouteArray: function (routesObj) {
|
||
return Object.keys(routesObj || {}).map(function (route) {
|
||
return { route: route, filePath: routesObj[route] };
|
||
});
|
||
},
|
||
toRouteObject: function (routeArray) {
|
||
var obj = {};
|
||
(routeArray || []).forEach(function (item) {
|
||
var route = (item.route || "").trim();
|
||
var filePath = (item.filePath || "").trim();
|
||
if (route && filePath) {
|
||
obj[route] = filePath;
|
||
}
|
||
});
|
||
return obj;
|
||
},
|
||
addRouteRow: function () {
|
||
this.form.routes.push({ route: "", filePath: "" });
|
||
},
|
||
removeRoute: function (index) {
|
||
this.form.routes.splice(index, 1);
|
||
},
|
||
loadConfig: async function () {
|
||
try {
|
||
var resp = await fetch("/__config");
|
||
var data = await resp.json();
|
||
this.form.config = Object.assign({}, this.form.config, data.config || {});
|
||
this.form.routes = this.toRouteArray(data.routes || {});
|
||
} catch (err) {
|
||
this.$message.error("加载配置失败: " + err.message);
|
||
}
|
||
},
|
||
saveConfig: async function () {
|
||
var payload = {
|
||
config: this.form.config,
|
||
routes: this.toRouteObject(this.form.routes),
|
||
};
|
||
try {
|
||
var resp = await fetch("/__config", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(payload),
|
||
});
|
||
var data = await resp.json();
|
||
if (!resp.ok || data.success === false) {
|
||
throw new Error(data.error || "保存失败");
|
||
}
|
||
this.$message.success("配置已保存");
|
||
} catch (err) {
|
||
this.$message.error("保存失败: " + err.message);
|
||
}
|
||
},
|
||
reloadConfig: async function () {
|
||
try {
|
||
var resp = await fetch("/__reload-config", { method: "POST" });
|
||
var data = await resp.json();
|
||
if (!resp.ok || data.success === false) {
|
||
throw new Error(data.error || "重载失败");
|
||
}
|
||
this.$message.success("配置已重载");
|
||
this.loadConfig();
|
||
} catch (err) {
|
||
this.$message.error("重载失败: " + err.message);
|
||
}
|
||
},
|
||
createRouteAndFile: async function () {
|
||
try {
|
||
var resp = await fetch("/__routes", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(
|
||
Object.assign({}, this.newRoute, {
|
||
useExistingFile: this.newRoute.template === "basicError",
|
||
}),
|
||
),
|
||
});
|
||
var data = await resp.json();
|
||
if (!resp.ok || data.success === false) {
|
||
throw new Error(data.error || "创建失败");
|
||
}
|
||
this.$message.success("接口配置文件创建成功");
|
||
this.newRoute.template = "custom";
|
||
this.newRoute.route = "";
|
||
this.newRoute.filePath = "mock/";
|
||
this.newRoute.fileContent = "";
|
||
this.newRoute.overwrite = false;
|
||
this.loadConfig();
|
||
} catch (err) {
|
||
this.$message.error("创建失败: " + err.message);
|
||
}
|
||
},
|
||
applyTemplate: function (template) {
|
||
if (template === "basicError") {
|
||
this.newRoute.filePath = "mock/basic-error.json";
|
||
this.newRoute.fileContent =
|
||
'{"code": -1, "success": false, "msg":"失败"}';
|
||
this.newRoute.overwrite = false;
|
||
return;
|
||
}
|
||
this.newRoute.filePath = "mock/";
|
||
this.newRoute.fileContent = "";
|
||
},
|
||
},
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|