api-proxy-mock/admin.html

294 lines
10 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>