699 lines
26 KiB
HTML
699 lines
26 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;
|
||
}
|
||
.top-nav {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 1100;
|
||
background: #fff;
|
||
border-bottom: 1px solid #ebeef5;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||
}
|
||
.top-nav-inner {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 10px 16px;
|
||
}
|
||
.actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
@media (max-width: 1200px) {
|
||
.actions {
|
||
flex-wrap: wrap;
|
||
}
|
||
}
|
||
.small-text {
|
||
color: #909399;
|
||
font-size: 12px;
|
||
}
|
||
.table-pagination {
|
||
margin-top: 12px;
|
||
text-align: right;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="app">
|
||
<div class="top-nav">
|
||
<div class="top-nav-inner">
|
||
<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>
|
||
</div>
|
||
</div>
|
||
<div class="container">
|
||
<el-tabs v-model="activeMainTab" class="section-card">
|
||
<el-tab-pane label="路由配置" name="routes">
|
||
<el-card class="section-card">
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
||
<strong>路由配置(routes)</strong>
|
||
<el-button size="mini" type="primary" @click="openRouteDialogForCreate">新增路由</el-button>
|
||
</div>
|
||
<el-table :data="pagedRoutes" border>
|
||
<el-table-column label="接口名称" min-width="220">
|
||
<template slot-scope="scope">
|
||
<span>{{ scope.row.apiName || "-" }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="请求路径" min-width="260">
|
||
<template slot-scope="scope">
|
||
<span>{{ scope.row.route }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="Mock 文件路径" min-width="300">
|
||
<template slot-scope="scope">
|
||
<span>{{ scope.row.filePath }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="返回状态码" width="120" align="center">
|
||
<template slot-scope="scope">
|
||
<span>{{ scope.row.statusCode || 200 }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="启用" width="90" align="center">
|
||
<template slot-scope="scope">
|
||
<el-switch v-model="scope.row.enabled"></el-switch>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="180">
|
||
<template slot-scope="scope">
|
||
<el-button
|
||
size="mini"
|
||
type="primary"
|
||
plain
|
||
@click="openRouteDialogForEdit(scope.$index)"
|
||
>
|
||
修改
|
||
</el-button>
|
||
<el-button size="mini" type="danger" @click="removeRoute(scope.$index)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-pagination
|
||
class="table-pagination"
|
||
background
|
||
layout="total, prev, pager, next"
|
||
:current-page="routePagination.currentPage"
|
||
:page-size="routePagination.pageSize"
|
||
:total="form.routes.length"
|
||
@current-change="handleRoutePageChange"
|
||
></el-pagination>
|
||
<div class="small-text" style="margin-top:8px;">
|
||
说明:关闭“启用”后会以 # 注释路由,不参与 mock 命中;切换后请点击“保存配置到 config.json”生效到文件。
|
||
</div>
|
||
</el-card>
|
||
</el-tab-pane>
|
||
|
||
<el-tab-pane label="Mock 数据配置" name="mocks">
|
||
<el-card class="section-card">
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
||
<strong>Mock 数据配置</strong>
|
||
<el-button size="mini" type="primary" @click="openMockFileDialogForCreate">新增 Mock 文件</el-button>
|
||
</div>
|
||
<el-table :data="pagedMockFiles" border>
|
||
<el-table-column label="Mock 文件路径" min-width="320">
|
||
<template slot-scope="scope">
|
||
<span>{{ scope.row.filePath }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="内容预览" min-width="420">
|
||
<template slot-scope="scope">
|
||
<span>{{ getPreviewText(scope.row.content) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="180">
|
||
<template slot-scope="scope">
|
||
<el-button size="mini" type="primary" plain @click="openMockFileDialogForEdit(scope.row)">修改</el-button>
|
||
<el-button size="mini" type="danger" @click="removeMockFile(scope.row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-pagination
|
||
class="table-pagination"
|
||
background
|
||
layout="total, prev, pager, next"
|
||
:current-page="mockFilePagination.currentPage"
|
||
:page-size="mockFilePagination.pageSize"
|
||
:total="mockFiles.length"
|
||
@current-change="handleMockFilePageChange"
|
||
></el-pagination>
|
||
</el-card>
|
||
</el-tab-pane>
|
||
|
||
<el-tab-pane label="基础配置" name="basic">
|
||
<el-card class="section-card">
|
||
<el-form :model="form.config" label-width="180px">
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="Mock 开关">
|
||
<el-switch v-model="form.config.mockEnabled"></el-switch>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="自动重载配置文件">
|
||
<el-switch v-model="form.config.reloadOnChange"></el-switch>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="默认响应 Content-Type">
|
||
<el-input v-model="form.config.defaultContentType"></el-input>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="本地代理端口">
|
||
<el-input-number
|
||
v-model="form.config.proxyPort"
|
||
:min="1"
|
||
:max="65535"
|
||
></el-input-number>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="目标主机">
|
||
<el-input v-model="form.config.targetHost"></el-input>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="目标端口">
|
||
<el-input-number
|
||
v-model="form.config.targetPort"
|
||
:min="1"
|
||
:max="65535"
|
||
></el-input-number>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="目标 HTTPS">
|
||
<el-switch v-model="form.config.targetHttps"></el-switch>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
</el-card>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
|
||
<el-dialog
|
||
:title="routeDialog.mode === 'edit' ? '修改路由' : '新增路由'"
|
||
:visible.sync="routeDialog.visible"
|
||
width="760px"
|
||
>
|
||
<el-form :model="routeDialog.form" label-width="180px">
|
||
<el-form-item label="预置接口">
|
||
<el-select
|
||
v-model="routeDialog.form.selectedApiRoute"
|
||
filterable
|
||
clearable
|
||
placeholder="可选:从 mock/api-list.json 选择"
|
||
@change="onSelectApiRoute"
|
||
style="width: 100%;"
|
||
>
|
||
<el-option
|
||
v-for="item in apiList"
|
||
:key="item.route"
|
||
:label="item.name + ' (' + item.route + ')'"
|
||
:value="item.route"
|
||
></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="接口名称">
|
||
<el-input
|
||
v-model="routeDialog.form.apiName"
|
||
placeholder="例如:获取用户信息"
|
||
:disabled="isApiListLocked()"
|
||
></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="请求路径">
|
||
<el-input
|
||
v-model="routeDialog.form.route"
|
||
placeholder="/api/new/mock"
|
||
:disabled="isApiListLocked()"
|
||
></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="Mock 文件">
|
||
<el-select
|
||
v-model="routeDialog.form.filePath"
|
||
filterable
|
||
placeholder="请选择 mock 文件"
|
||
style="width: 100%;"
|
||
>
|
||
<el-option
|
||
v-for="item in mockFiles"
|
||
:key="item.filePath"
|
||
:label="item.filePath"
|
||
:value="item.filePath"
|
||
></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="返回状态码">
|
||
<el-input-number
|
||
v-model="routeDialog.form.statusCode"
|
||
:min="100"
|
||
:max="599"
|
||
style="width: 100%;"
|
||
></el-input-number>
|
||
</el-form-item>
|
||
<el-form-item label="是否启用 Mock">
|
||
<el-switch v-model="routeDialog.form.enabled"></el-switch>
|
||
</el-form-item>
|
||
</el-form>
|
||
<span slot="footer" class="dialog-footer">
|
||
<el-button @click="routeDialog.visible = false">取消</el-button>
|
||
<el-button type="primary" @click="submitRouteDialog">
|
||
{{ routeDialog.mode === "edit" ? "保存修改" : "创建路由+文件" }}
|
||
</el-button>
|
||
</span>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
:title="mockFileDialog.mode === 'edit' ? '修改 Mock 文件' : '新增 Mock 文件'"
|
||
:visible.sync="mockFileDialog.visible"
|
||
width="760px"
|
||
>
|
||
<el-form :model="mockFileDialog.form" label-width="180px">
|
||
<el-form-item label="Mock 文件路径">
|
||
<el-input
|
||
v-model="mockFileDialog.form.filePath"
|
||
placeholder="mock/new-api.json"
|
||
:disabled="mockFileDialog.mode === 'edit'"
|
||
></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="文件内容">
|
||
<el-input
|
||
type="textarea"
|
||
:rows="12"
|
||
v-model="mockFileDialog.form.content"
|
||
placeholder='{"code":0,"message":"ok"}'
|
||
></el-input>
|
||
</el-form-item>
|
||
</el-form>
|
||
<span slot="footer" class="dialog-footer">
|
||
<el-button @click="mockFileDialog.visible = false">取消</el-button>
|
||
<el-button type="primary" @click="submitMockFileDialog">保存 Mock 文件</el-button>
|
||
</span>
|
||
</el-dialog>
|
||
</div>
|
||
</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 {
|
||
activeMainTab: "routes",
|
||
apiList: [],
|
||
mockFiles: [],
|
||
routePagination: {
|
||
currentPage: 1,
|
||
pageSize: 10,
|
||
},
|
||
mockFilePagination: {
|
||
currentPage: 1,
|
||
pageSize: 10,
|
||
},
|
||
form: {
|
||
config: {
|
||
mockEnabled: true,
|
||
cacheConfig: true,
|
||
reloadOnChange: true,
|
||
defaultContentType: "application/json",
|
||
proxyPort: 8877,
|
||
targetHost: "",
|
||
targetPort: 443,
|
||
targetHttps: true,
|
||
},
|
||
routes: [],
|
||
},
|
||
routeDialog: {
|
||
visible: false,
|
||
mode: "create",
|
||
editingIndex: -1,
|
||
form: {
|
||
apiName: "",
|
||
selectedApiRoute: "",
|
||
originalRoute: "",
|
||
originalRawRoute: "",
|
||
route: "",
|
||
filePath: "",
|
||
statusCode: 200,
|
||
enabled: true,
|
||
},
|
||
},
|
||
mockFileDialog: {
|
||
visible: false,
|
||
mode: "create",
|
||
form: {
|
||
filePath: "mock/",
|
||
content: "",
|
||
},
|
||
},
|
||
};
|
||
},
|
||
created: async function () {
|
||
await this.loadApiList();
|
||
await this.loadMockFiles();
|
||
await this.loadConfig();
|
||
},
|
||
methods: {
|
||
getPreviewText: function (content) {
|
||
var text = String(content || "").replace(/\s+/g, " ").trim();
|
||
if (!text) return "(空文件)";
|
||
return text.length > 120 ? text.slice(0, 120) + "..." : text;
|
||
},
|
||
getPagedData: function (list, pagination) {
|
||
var source = Array.isArray(list) ? list : [];
|
||
var pageSize = pagination.pageSize || 10;
|
||
var currentPage = pagination.currentPage || 1;
|
||
var maxPage = Math.max(1, Math.ceil(source.length / pageSize));
|
||
if (currentPage > maxPage) {
|
||
currentPage = maxPage;
|
||
pagination.currentPage = currentPage;
|
||
}
|
||
var start = (currentPage - 1) * pageSize;
|
||
return source.slice(start, start + pageSize);
|
||
},
|
||
handleRoutePageChange: function (page) {
|
||
this.routePagination.currentPage = page;
|
||
},
|
||
handleMockFilePageChange: function (page) {
|
||
this.mockFilePagination.currentPage = page;
|
||
},
|
||
findApiByRoute: function (route) {
|
||
return (this.apiList || []).find(function (item) {
|
||
return item.route === route;
|
||
});
|
||
},
|
||
toRouteArray: function (routesObj, routeStatusesObj) {
|
||
var self = this;
|
||
return Object.keys(routesObj || {}).map(function (rawRoute) {
|
||
var enabled = !rawRoute.startsWith("#");
|
||
var route = enabled ? rawRoute : rawRoute.replace(/^#+/, "");
|
||
var matched = self.findApiByRoute(route);
|
||
return {
|
||
rawRoute: rawRoute,
|
||
route: route,
|
||
filePath: routesObj[rawRoute],
|
||
statusCode: Number(routeStatusesObj && routeStatusesObj[rawRoute]) || 200,
|
||
apiName: matched ? matched.name : "",
|
||
selectedApiRoute: matched ? matched.route : "",
|
||
enabled: enabled,
|
||
};
|
||
});
|
||
},
|
||
toRouteObject: function (routeArray) {
|
||
var obj = {};
|
||
(routeArray || []).forEach(function (item) {
|
||
var route = (item.route || "").trim();
|
||
var filePath = (item.filePath || "").trim();
|
||
if (route && filePath) {
|
||
var routeKey = item.enabled === false ? "#" + route : route;
|
||
obj[routeKey] = filePath;
|
||
}
|
||
});
|
||
return obj;
|
||
},
|
||
toRouteStatusObject: function (routeArray) {
|
||
var obj = {};
|
||
(routeArray || []).forEach(function (item) {
|
||
var route = (item.route || "").trim();
|
||
if (!route) return;
|
||
var routeKey = item.enabled === false ? "#" + route : route;
|
||
var statusCode = Number(item.statusCode) || 200;
|
||
obj[routeKey] = statusCode;
|
||
});
|
||
return obj;
|
||
},
|
||
removeRoute: function (index) {
|
||
var actualIndex =
|
||
(this.routePagination.currentPage - 1) * this.routePagination.pageSize +
|
||
index;
|
||
this.form.routes.splice(actualIndex, 1);
|
||
},
|
||
getDefaultRouteForm: function () {
|
||
return {
|
||
apiName: "",
|
||
selectedApiRoute: "",
|
||
originalRoute: "",
|
||
originalRawRoute: "",
|
||
route: "",
|
||
filePath: "",
|
||
statusCode: 200,
|
||
enabled: true,
|
||
};
|
||
},
|
||
isApiListLocked: function () {
|
||
return !!this.routeDialog.form.selectedApiRoute;
|
||
},
|
||
onSelectApiRoute: function (route) {
|
||
var selected = route ? this.findApiByRoute(route) : null;
|
||
if (!selected) {
|
||
this.routeDialog.form.selectedApiRoute = "";
|
||
return;
|
||
}
|
||
this.routeDialog.form.selectedApiRoute = selected.route;
|
||
this.routeDialog.form.route = selected.route;
|
||
this.routeDialog.form.apiName = selected.name;
|
||
},
|
||
openRouteDialogForCreate: function () {
|
||
this.routeDialog.mode = "create";
|
||
this.routeDialog.editingIndex = -1;
|
||
this.routeDialog.form = this.getDefaultRouteForm();
|
||
this.routeDialog.visible = true;
|
||
},
|
||
openRouteDialogForEdit: function (index) {
|
||
var actualIndex =
|
||
(this.routePagination.currentPage - 1) * this.routePagination.pageSize +
|
||
index;
|
||
var item =
|
||
this.form.routes[actualIndex] || { route: "", filePath: "mock/" };
|
||
this.routeDialog.mode = "edit";
|
||
this.routeDialog.editingIndex = actualIndex;
|
||
var matched = this.findApiByRoute(item.route || "");
|
||
this.routeDialog.form = {
|
||
apiName: matched ? matched.name : item.apiName || "",
|
||
selectedApiRoute: matched ? matched.route : "",
|
||
originalRoute: item.route || "",
|
||
originalRawRoute: item.rawRoute || item.route || "",
|
||
route: item.route || "",
|
||
filePath: item.filePath || "mock/",
|
||
statusCode: Number(item.statusCode) || 200,
|
||
enabled: item.enabled !== false,
|
||
};
|
||
this.routeDialog.visible = true;
|
||
},
|
||
getDefaultMockFileForm: function () {
|
||
return {
|
||
filePath: "mock/",
|
||
content: "",
|
||
};
|
||
},
|
||
openMockFileDialogForCreate: function () {
|
||
this.mockFileDialog.mode = "create";
|
||
this.mockFileDialog.form = this.getDefaultMockFileForm();
|
||
this.mockFileDialog.visible = true;
|
||
},
|
||
openMockFileDialogForEdit: function (item) {
|
||
this.mockFileDialog.mode = "edit";
|
||
this.mockFileDialog.form = {
|
||
filePath: item.filePath || "mock/",
|
||
content: String(item.content || ""),
|
||
};
|
||
this.mockFileDialog.visible = true;
|
||
},
|
||
loadMockFiles: async function () {
|
||
try {
|
||
var resp = await fetch("/__mock-files");
|
||
var data = await resp.json();
|
||
if (!resp.ok || data.success === false) {
|
||
throw new Error(data.error || "加载 mock 文件失败");
|
||
}
|
||
this.mockFiles = Array.isArray(data.list) ? data.list : [];
|
||
this.mockFilePagination.currentPage = 1;
|
||
} catch (err) {
|
||
this.mockFiles = [];
|
||
this.$message.error("加载 mock 文件失败: " + err.message);
|
||
}
|
||
},
|
||
submitMockFileDialog: async function () {
|
||
if (!this.mockFileDialog.form.filePath) {
|
||
this.$message.error("Mock 文件路径不能为空");
|
||
return;
|
||
}
|
||
try {
|
||
var resp = await fetch("/__mock-files", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({
|
||
filePath: this.mockFileDialog.form.filePath,
|
||
content: this.mockFileDialog.form.content,
|
||
}),
|
||
});
|
||
var data = await resp.json();
|
||
if (!resp.ok || data.success === false) {
|
||
throw new Error(data.error || "保存 mock 文件失败");
|
||
}
|
||
this.$message.success("Mock 文件已保存");
|
||
this.mockFileDialog.visible = false;
|
||
await this.loadMockFiles();
|
||
} catch (err) {
|
||
this.$message.error("保存 mock 文件失败: " + err.message);
|
||
}
|
||
},
|
||
removeMockFile: async function (item) {
|
||
try {
|
||
await this.$confirm(
|
||
"确认删除 " + item.filePath + " 吗?删除后引用该文件的路由需要重新选择。",
|
||
"提示",
|
||
{ type: "warning" },
|
||
);
|
||
var resp = await fetch("/__mock-files", {
|
||
method: "DELETE",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ filePath: item.filePath }),
|
||
});
|
||
var data = await resp.json();
|
||
if (!resp.ok || data.success === false) {
|
||
throw new Error(data.error || "删除 mock 文件失败");
|
||
}
|
||
this.$message.success("Mock 文件已删除");
|
||
await this.loadMockFiles();
|
||
} catch (err) {
|
||
if (err !== "cancel") {
|
||
this.$message.error("删除 mock 文件失败: " + err.message);
|
||
}
|
||
}
|
||
},
|
||
loadApiList: async function () {
|
||
try {
|
||
var resp = await fetch("/__api-list");
|
||
var data = await resp.json();
|
||
if (!resp.ok || data.success === false) {
|
||
throw new Error(data.error || "加载接口列表失败");
|
||
}
|
||
this.apiList = Array.isArray(data.list) ? data.list : [];
|
||
} catch (err) {
|
||
this.apiList = [];
|
||
this.$message.error("加载接口列表失败: " + err.message);
|
||
}
|
||
},
|
||
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 || {},
|
||
data.routeStatuses || {},
|
||
);
|
||
this.routePagination.currentPage = 1;
|
||
} catch (err) {
|
||
this.$message.error("加载配置失败: " + err.message);
|
||
}
|
||
},
|
||
saveConfig: async function () {
|
||
var payload = {
|
||
config: this.form.config,
|
||
routes: this.toRouteObject(this.form.routes),
|
||
routeStatuses: this.toRouteStatusObject(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);
|
||
}
|
||
},
|
||
submitRouteDialog: async function () {
|
||
if (!this.routeDialog.form.route) {
|
||
this.$message.error("请求路径不能为空");
|
||
return;
|
||
}
|
||
if (!this.routeDialog.form.filePath) {
|
||
this.$message.error("请选择 Mock 文件");
|
||
return;
|
||
}
|
||
var payload = Object.assign({}, this.routeDialog.form, {
|
||
useExistingFile: true,
|
||
});
|
||
try {
|
||
var resp = await fetch("/__routes", {
|
||
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(
|
||
this.routeDialog.mode === "edit"
|
||
? "路由修改成功"
|
||
: "接口配置文件创建成功",
|
||
);
|
||
this.routeDialog.visible = false;
|
||
await this.loadMockFiles();
|
||
await this.loadConfig();
|
||
} catch (err) {
|
||
this.$message.error("创建失败: " + err.message);
|
||
}
|
||
},
|
||
},
|
||
computed: {
|
||
pagedRoutes: function () {
|
||
return this.getPagedData(this.form.routes, this.routePagination);
|
||
},
|
||
pagedMockFiles: function () {
|
||
return this.getPagedData(this.mockFiles, this.mockFilePagination);
|
||
},
|
||
},
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|