完全移除模块级权限系统,迁移至接口级权限

后端:
- 删除 StudyRolePermission 模型和 project_permissions API 文件
- 重写 project_permissions.py core,移除所有模块级权限函数
- 重写 permission_cache.py,移除模块级权限缓存逻辑
- 新增权限模板功能(PermissionTemplate 模型、API、服务层)
- 新增 permission_templates 数据库迁移
- 迁移 8 个模块(attachments、audit_logs、dashboard、faqs、
  faq_categories、fees_attachments、knowledge_notes、
  material_equipments、overview、subject_histories、subject_pds)
  至接口级权限检查
- 删除所有模块级权限相关测试文件,新增权限模板测试

前端:
- 删除 ProjectPermissions.vue 和 ProjectPermissionsModule.vue
- 重写 projectRoutePermissions.ts,改为基于接口级权限格式
- 更新 store/study.ts、router/index.ts、AuditLogs.vue、Projects.vue
  中的权限 API 调用,从 fetchProjectRolePermissions 改为
  fetchApiEndpointPermissions
- 清理 types/api.ts 中的旧模块级权限类型定义
- 新增 PermissionTemplateSelector.vue 组件
- 更新权限管理页面,移除模块级权限 tab

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Cheng Zhou
2026-05-15 09:07:43 +08:00
parent 3b1bdc2070
commit 20ce6bccef
55 changed files with 1971 additions and 3030 deletions
+32 -38
View File
@@ -30,19 +30,16 @@
</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">
<PermissionTemplateSelector
:study-id="studyId"
:current-permissions="currentPermissionsForTemplate"
@applied="onTemplateApplied"
/>
<el-divider />
<ApiEndpointPermissions
:project="project"
:matrix="apiMatrix"
@@ -50,7 +47,7 @@
/>
</el-tab-pane>
<!-- 标签3权限监控新增 -->
<!-- 权限监控 -->
<el-tab-pane label="权限监控" name="monitoring">
<PermissionMonitoring
:metrics="metrics"
@@ -69,9 +66,7 @@ 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,
@@ -79,8 +74,6 @@ import type {
HealthResponse,
} from "@/types/api";
import {
fetchProjectRolePermissions,
updateProjectRolePermissions,
fetchApiEndpointPermissions,
updateApiEndpointPermissions,
fetchPermissionMetrics,
@@ -90,14 +83,14 @@ import {
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";
import PermissionTemplateSelector from "@/components/PermissionTemplateSelector.vue";
const route = useRoute();
const studyStore = useStudyStore();
const activeTab = ref<"module" | "api" | "monitoring">("module");
const activeTab = ref<"api" | "monitoring">("api");
const loading = ref(false);
const saving = ref(false);
const resetting = ref(false);
@@ -109,7 +102,6 @@ const studyId = computed(() => {
});
// 权限数据
const moduleMatrix = ref<ProjectRolePermissionsResponse | null>(null);
const apiMatrix = ref<ApiEndpointPermissionsResponse | null>(null);
// 监控数据
@@ -125,12 +117,7 @@ const loadPermissionData = async () => {
loading.value = true;
try {
const [moduleRes, apiRes] = await Promise.all([
fetchProjectRolePermissions(studyId.value),
fetchApiEndpointPermissions(studyId.value),
]);
moduleMatrix.value = moduleRes.data;
const apiRes = await fetchApiEndpointPermissions(studyId.value);
apiMatrix.value = apiRes.data;
dirty.value = false;
} catch (error) {
@@ -160,28 +147,36 @@ const loadMonitoringData = async () => {
}
};
const onModuleMatrixUpdate = (newMatrix: ProjectRolePermissionsResponse) => {
moduleMatrix.value = newMatrix;
dirty.value = true;
};
const onApiMatrixUpdate = (newMatrix: ApiEndpointPermissionsResponse) => {
apiMatrix.value = newMatrix;
dirty.value = true;
};
// 将当前 apiMatrix 转换为模板所需的 {role: {endpoint_key: bool}} 格式
const currentPermissionsForTemplate = computed(() => {
if (!apiMatrix.value) return undefined;
const result: Record<string, Record<string, boolean>> = {};
for (const [role, endpoints] of Object.entries(apiMatrix.value as Record<string, Record<string, { allowed: boolean }>>)) {
result[role] = {};
for (const [key, val] of Object.entries(endpoints)) {
result[role][key] = val.allowed;
}
}
return result;
});
const onTemplateApplied = async (permissions: Record<string, Record<string, { allowed: boolean }>>) => {
// 模板应用后刷新权限矩阵
await loadPermissionData();
ElMessage.success("权限已更新");
};
const save = async () => {
if (!studyId.value || !dirty.value) return;
saving.value = true;
try {
// 保存当前活跃标签的权限
if (activeTab.value === "module" && moduleMatrix.value) {
const res = await updateProjectRolePermissions(studyId.value, {
roles: moduleMatrix.value.roles,
});
moduleMatrix.value = res.data;
} else if (activeTab.value === "api" && apiMatrix.value) {
if (apiMatrix.value) {
const res = await updateApiEndpointPermissions(studyId.value, apiMatrix.value);
apiMatrix.value = res.data;
}
@@ -189,7 +184,6 @@ const save = async () => {
dirty.value = false;
ElMessage.success("权限已保存");
// 重新加载数据以确保同步
await loadPermissionData();
} catch (error) {
ElMessage.error("保存权限失败");