feat(front): 网关台账前端——台账/路由/目标三页+重载注册表

- 与 notice 同形态:hash 路由 + TablePageAlovaNew 分页 + alova 信封分支
- API 层手写(文档端点仅容器网内可达,api:gen 无通路),键名对齐后端 operationId
- 审计修复:outDir 回 dist 根(CI zip 根须含 html)、main.ts 显式引入 @/api 挂载全局 Apis、
  保存失败重拉回滚、路由页补只读 timeout/bodyLimit 列、详情弹窗 backendCode 恒空说明
This commit is contained in:
chenxing
2026-09-23 14:28:10 +08:00
commit bed87ced52
88 changed files with 12542 additions and 0 deletions
+122
View File
@@ -0,0 +1,122 @@
<template>
<div class="main-wrapper">
<div class="main-container">
<div class="search-wrap fix">
<span class="tip">路由注册表(open_api_route):治理旋钮行内编辑,保存后网关热重载,无须重启。</span>
<Button type="primary" class="fr" @click="refresh">刷新</Button>
</div>
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columns" :tableHeight="tableHeight">
<template #visibility="{ row }">
<Select v-model="row.visibility" size="small" style="width: 110px">
<Option value="INTERNAL">INTERNAL</Option>
<Option value="EXTERNAL">EXTERNAL</Option>
</Select>
</template>
<template #authMode="{ row }">
<Select v-model="row.authMode" size="small" style="width: 120px">
<Option value="SIGNED">SIGNED</Option>
<Option value="ANONYMOUS">ANONYMOUS</Option>
</Select>
</template>
<template #cacheTtlSeconds="{ row }">
<Input v-model="row.cacheTtlSeconds" type="number" size="small" style="width: 80px" />
</template>
<template #cacheScope="{ row }">
<Select v-model="row.cacheScope" size="small" style="width: 100px">
<Option value="CALLER">CALLER</Option>
<Option value="GLOBAL">GLOBAL</Option>
</Select>
</template>
<template #rateLimitQps="{ row }">
<Input v-model="row.rateLimitQps" type="number" size="small" style="width: 80px" />
</template>
<template #enabled="{ row }">
<i-switch v-model="row.enabled" size="small" />
</template>
<template #remark="{ row }">
<Input v-model="row.remark" size="small" placeholder="-" />
</template>
<template #action="{ row }">
<Button type="primary" size="small" :loading="savingId === row.id" @click="save(row)">保存</Button>
</template>
</TablePageAlovaNew>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import useCurrentInstance from '@/utils/hook/useCurrentInstance';
import TablePageAlovaNew from '@/components/TablePageAlovaNew.vue';
defineOptions({ name: 'RouteList' });
const { $utils } = useCurrentInstance();
const tableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
const savingId = ref<number | null>(null);
const tableApi = (params: { pageIndex: number; pageSize: number }) =>
Apis.GatewayAdmin.routes({ params, cacheFor: null });
const tableHeight = ref($utils.useTableHeight({ isOperat: false, isPage: true, isSearch: true }));
const columns = [
{ title: 'id', key: 'id', width: 70, align: 'center' },
{ title: 'target', key: 'targetCode', width: 120, ellipsis: true, tooltip: true },
{ title: 'path', key: 'pathPattern', minWidth: 200, ellipsis: true, tooltip: true },
{ title: 'method', key: 'httpMethod', width: 90, align: 'center' },
{ title: 'visibility', slot: 'visibility', width: 130 },
{ title: 'authMode', slot: 'authMode', width: 140 },
{ title: 'cacheTtl(s)', slot: 'cacheTtlSeconds', width: 105 },
{ title: 'cacheScope', slot: 'cacheScope', width: 120 },
{ title: 'qps', slot: 'rateLimitQps', width: 90 },
{ title: 'timeout(ms)', key: 'timeoutMs', width: 105, align: 'right' },
{ title: 'bodyLimit(mb)', key: 'requestBodyLimitMb', width: 115, align: 'right' },
{ title: '启用', slot: 'enabled', width: 80, align: 'center' },
{ title: '备注', slot: 'remark', minWidth: 160 },
{ title: '操作', slot: 'action', width: 90, align: 'center', fixed: 'right' as const },
];
function refresh() {
tableRef.value?.refreshData();
}
/** 只提交治理字段(target/pattern/method 是路由身份,后端不接受改动) */
async function save(row: any) {
savingId.value = row.id;
try {
const d: any = await Apis.GatewayAdmin.updateRoute({
pathParams: { id: row.id },
data: {
visibility: row.visibility,
authMode: row.authMode,
cacheTtlSeconds: Number(row.cacheTtlSeconds) || 0,
cacheScope: row.cacheScope,
rateLimitQps: Number(row.rateLimitQps) || 0,
enabled: row.enabled,
remark: row.remark,
},
});
Message.success(`已保存,注册表加载 ${d?.loaded ?? '-'} 条路由`);
} catch (e: any) {
Message.error(e?.message || '保存失败');
// 失败不留假状态:重拉列表,行 UI 回滚到服务端真值
tableRef.value?.refreshData();
} finally {
savingId.value = null;
}
}
</script>
<style lang="scss" scoped>
.search-wrap {
line-height: 32px;
.tip {
color: #848c98;
font-size: 13px;
}
}
</style>