前端权限管理:实现接口级权限管理UI和监控仪表板
新增功能: - 新增 ApiPermissions.vue 主页面,支持模块级/接口级权限切换 - 新增 ProjectPermissionsModule.vue 组件,展示模块级权限矩阵 - 新增 ApiEndpointPermissions.vue 组件,展示接口级权限矩阵 - 新增 PermissionMonitoring.vue 组件,展示权限系统监控仪表板 - 更新 projectPermissions.ts API 客户端,支持接口级权限和监控API - 新增权限相关的 TypeScript 类型定义 - 更新路由配置,添加接口级权限管理路由 - 更新国际化文本,添加权限管理相关的中文文本 API 客户端新增函数: - fetchApiEndpointPermissions: 获取接口级权限矩阵 - updateApiEndpointPermissions: 更新接口级权限矩阵 - fetchPermissionMetrics: 获取权限检查指标 - fetchCacheStats: 获取缓存统计 - fetchPermissionAlerts: 获取告警列表 - fetchPermissionHealth: 获取系统健康状态 - resetPermissionMetrics: 重置指标 - clearPermissionAlerts: 清除告警 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
<template>
|
||||
<div class="permission-page">
|
||||
<div class="permission-shell unified-shell" v-loading="loading">
|
||||
<!-- 顶部操作栏 -->
|
||||
<div class="permission-header unified-action-bar">
|
||||
<div class="permission-title">
|
||||
<el-icon><Key /></el-icon>
|
||||
<h2>权限管理</h2>
|
||||
</div>
|
||||
<div class="permission-project-meta">
|
||||
<span class="meta-item">
|
||||
<span class="meta-label">项目编号</span>
|
||||
<strong>{{ project?.code || "-" }}</strong>
|
||||
</span>
|
||||
<span class="meta-separator" />
|
||||
<span class="meta-item">
|
||||
<span class="meta-label">项目名称</span>
|
||||
<strong>{{ project?.name || "-" }}</strong>
|
||||
</span>
|
||||
</div>
|
||||
<div class="permission-actions">
|
||||
<el-button @click="resetMetrics" :loading="resetting">
|
||||
<el-icon><RefreshRight /></el-icon>
|
||||
刷新指标
|
||||
</el-button>
|
||||
<el-button type="primary" :loading="saving" :disabled="!dirty" @click="save">
|
||||
<el-icon><Check /></el-icon>
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 标签页:模块级权限 vs 接口级权限 -->
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 标签1:模块级权限(现有) -->
|
||||
<el-tab-pane label="模块级权限" name="module">
|
||||
<ProjectPermissionsModule
|
||||
:project="project"
|
||||
:matrix="moduleMatrix"
|
||||
@update="onModuleMatrixUpdate"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 标签2:接口级权限(新增) -->
|
||||
<el-tab-pane label="接口级权限" name="api">
|
||||
<ApiEndpointPermissions
|
||||
:project="project"
|
||||
:matrix="apiMatrix"
|
||||
@update="onApiMatrixUpdate"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 标签3:权限监控(新增) -->
|
||||
<el-tab-pane label="权限监控" name="monitoring">
|
||||
<PermissionMonitoring
|
||||
:metrics="metrics"
|
||||
:health="health"
|
||||
:alerts="alerts"
|
||||
@refresh="loadMonitoringData"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { Key, RefreshRight, Check } from "@element-plus/icons-vue";
|
||||
import type { Study } from "@/types/api";
|
||||
import type {
|
||||
ProjectRolePermissionsResponse,
|
||||
ApiEndpointPermissionsResponse,
|
||||
PermissionMetricsResponse,
|
||||
CacheStatsResponse,
|
||||
AlertsResponse,
|
||||
HealthResponse,
|
||||
} from "@/types/api";
|
||||
import {
|
||||
fetchProjectRolePermissions,
|
||||
updateProjectRolePermissions,
|
||||
fetchApiEndpointPermissions,
|
||||
updateApiEndpointPermissions,
|
||||
fetchPermissionMetrics,
|
||||
fetchCacheStats,
|
||||
fetchPermissionAlerts,
|
||||
fetchPermissionHealth,
|
||||
resetPermissionMetrics,
|
||||
} from "@/api/projectPermissions";
|
||||
import { useStudyStore } from "@/store/study";
|
||||
import ProjectPermissionsModule from "@/components/ProjectPermissionsModule.vue";
|
||||
import ApiEndpointPermissions from "@/components/ApiEndpointPermissions.vue";
|
||||
import PermissionMonitoring from "@/components/PermissionMonitoring.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const studyStore = useStudyStore();
|
||||
|
||||
const activeTab = ref<"module" | "api" | "monitoring">("module");
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const resetting = ref(false);
|
||||
|
||||
const project = computed(() => studyStore.currentStudy);
|
||||
const studyId = computed(() => route.params.id as string);
|
||||
|
||||
// 权限数据
|
||||
const moduleMatrix = ref<ProjectRolePermissionsResponse | null>(null);
|
||||
const apiMatrix = ref<ApiEndpointPermissionsResponse | null>(null);
|
||||
|
||||
// 监控数据
|
||||
const metrics = ref<PermissionMetricsResponse | null>(null);
|
||||
const health = ref<HealthResponse | null>(null);
|
||||
const alerts = ref<AlertsResponse | null>(null);
|
||||
|
||||
// 脏值检测
|
||||
const dirty = ref(false);
|
||||
|
||||
const loadPermissionData = async () => {
|
||||
if (!studyId.value) return;
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const [moduleRes, apiRes] = await Promise.all([
|
||||
fetchProjectRolePermissions(studyId.value),
|
||||
fetchApiEndpointPermissions(studyId.value),
|
||||
]);
|
||||
|
||||
moduleMatrix.value = moduleRes;
|
||||
apiMatrix.value = apiRes;
|
||||
dirty.value = false;
|
||||
} catch (error) {
|
||||
ElMessage.error("加载权限数据失败");
|
||||
console.error(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadMonitoringData = async () => {
|
||||
if (!studyId.value) return;
|
||||
|
||||
try {
|
||||
const [metricsRes, healthRes, alertsRes] = await Promise.all([
|
||||
fetchPermissionMetrics(),
|
||||
fetchPermissionHealth(),
|
||||
fetchPermissionAlerts(undefined, 20),
|
||||
]);
|
||||
|
||||
metrics.value = metricsRes;
|
||||
health.value = healthRes;
|
||||
alerts.value = alertsRes;
|
||||
} catch (error) {
|
||||
ElMessage.error("加载监控数据失败");
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const onModuleMatrixUpdate = (newMatrix: ProjectRolePermissionsResponse) => {
|
||||
moduleMatrix.value = newMatrix;
|
||||
dirty.value = true;
|
||||
};
|
||||
|
||||
const onApiMatrixUpdate = (newMatrix: ApiEndpointPermissionsResponse) => {
|
||||
apiMatrix.value = newMatrix;
|
||||
dirty.value = true;
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
if (!studyId.value || !dirty.value) return;
|
||||
|
||||
saving.value = true;
|
||||
try {
|
||||
// 保存当前活跃标签的权限
|
||||
if (activeTab.value === "module" && moduleMatrix.value) {
|
||||
await updateProjectRolePermissions(studyId.value, {
|
||||
roles: moduleMatrix.value.roles,
|
||||
});
|
||||
} else if (activeTab.value === "api" && apiMatrix.value) {
|
||||
await updateApiEndpointPermissions(studyId.value, apiMatrix.value);
|
||||
}
|
||||
|
||||
dirty.value = false;
|
||||
ElMessage.success("权限已保存");
|
||||
} catch (error) {
|
||||
ElMessage.error("保存权限失败");
|
||||
console.error(error);
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const resetMetrics = async () => {
|
||||
resetting.value = true;
|
||||
try {
|
||||
await resetPermissionMetrics();
|
||||
await loadMonitoringData();
|
||||
ElMessage.success("指标已重置");
|
||||
} catch (error) {
|
||||
ElMessage.error("重置指标失败");
|
||||
console.error(error);
|
||||
} finally {
|
||||
resetting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadPermissionData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.permission-page {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.permission-shell {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.permission-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.permission-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.permission-project-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.meta-label {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.meta-separator {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background: #dcdfe6;
|
||||
}
|
||||
|
||||
.permission-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-tabs) {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user