feat(perm/frontend): PM 视角的权限管理界面与个人有效权限接入
- store/study 与 router 改为读取 /api-permissions/me 拉取当前用户在 当前项目的有效权限,菜单与路由守卫据此判定,并在 PM 项目缺省时 主动 ensureDefaultPmStudy。 - api/projectPermissions 暴露 fetchMyApiEndpointPermissions,并允许 调用方关闭统一错误提示,便于在批量项目权限拉取场景下静默失败。 - 项目管理:Projects 页根据每个项目的有效权限决定项目入口的可点击 状态,缺权限时禁用项目名跳转;ProjectDetail 据 setup_config 写权限 渲染保存/回填/清空/发布按钮,并在无读取权限时提示并退出页面。 - 权限管理:PermissionManagement 切换为按角色提交,Admin 视角才能 编辑 PM 行;按系统模块美化系统级权限展示并标注 PM 可访问项;项目 成员表的角色下拉受 PM 等级约束,自定义角色入口下线。 - 监控:PermissionMonitoring/PermissionAccessLogs 增加 isAdmin 与 showSecurityLog 入参,仅 ADMIN 才能看到底层安全日志与统计。 - ApiEndpointPermissions 表格根据当前用户角色禁用不可写行,避免 误操作 PM 行;MODULE_LABELS 增加 setup_config 的中文展示。 - 测试同步覆盖以上行为:新版 router/Layout/Projects/PermissionManagement /PermissionAccessLogs/PermissionMonitoring/PermissionIpLocations 等 组件均补足检测,并按需为相关组件提供 pinia + localStorage stub。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -74,7 +74,8 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="project-info">
|
||||
<el-link type="primary" @click="goProject(scope.row)" class="project-name">{{ scope.row.name }}</el-link>
|
||||
<el-link v-if="canProject(scope.row, 'setup_config', 'write')" type="primary" @click="goProject(scope.row)" class="project-name">{{ scope.row.name }}</el-link>
|
||||
<span v-else class="project-name project-name--disabled">{{ scope.row.name }}</span>
|
||||
<span class="project-code">{{ scope.row.code || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -151,7 +152,7 @@ import { useRouter } from "vue-router";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { User, OfficeBuilding, ArrowRight, Delete, Lock, Key, Document, Plus } from "@element-plus/icons-vue";
|
||||
import { fetchStudies, deleteStudy, lockStudy } from "../../api/studies";
|
||||
import { fetchApiEndpointPermissions } from "../../api/projectPermissions";
|
||||
import { fetchMyApiEndpointPermissions } from "../../api/projectPermissions";
|
||||
import type { ApiEndpointPermissionsResponse, Study } from "../../types/api";
|
||||
import ProjectForm from "./ProjectForm.vue";
|
||||
import { useStudyStore } from "../../store/study";
|
||||
@@ -184,7 +185,7 @@ const loadProjects = async () => {
|
||||
const entries = await Promise.all(
|
||||
items.map(async (item: Study) => {
|
||||
try {
|
||||
const { data: permissions } = await fetchApiEndpointPermissions(item.id);
|
||||
const { data: permissions } = await fetchMyApiEndpointPermissions(item.id, { suppressErrorMessage: true });
|
||||
return [item.id, permissions] as const;
|
||||
} catch {
|
||||
return [item.id, null] as const;
|
||||
@@ -210,14 +211,18 @@ const MODULE_TO_OPERATION: Record<string, Record<string, string>> = {
|
||||
project_members: { read: "project_members:list", write: "project_members:update" },
|
||||
sites: { read: "sites:read", write: "sites:update" },
|
||||
audit_export: { read: "audit_logs:read", write: "audit_logs:read" },
|
||||
setup_config: { read: "setup_config:read", write: "setup_config:update" },
|
||||
};
|
||||
|
||||
const canProject = (row: Study, module: string, action: "read" | "write") => {
|
||||
if (isAdmin.value) return true;
|
||||
const role = row.role_in_study || "";
|
||||
const operationKey = MODULE_TO_OPERATION[module]?.[action];
|
||||
if (!operationKey) return false;
|
||||
return isApiPermissionAllowed(permissionsByProject.value[row.id]?.[role]?.[operationKey]);
|
||||
const perms = permissionsByProject.value[row.id];
|
||||
if (!perms) return false;
|
||||
const rolePerms = Object.values(perms)[0];
|
||||
if (!rolePerms) return false;
|
||||
return isApiPermissionAllowed(rolePerms[operationKey]);
|
||||
};
|
||||
|
||||
const goMembers = (row: Study) => {
|
||||
@@ -330,11 +335,11 @@ const goDetail = (row: Study) => {
|
||||
};
|
||||
|
||||
const goProject = (row: Study) => {
|
||||
if (isAdmin.value) {
|
||||
goDetail(row);
|
||||
if (!canProject(row, "setup_config", "write")) {
|
||||
ElMessage.warning("当前角色无权访问该项目的立项配置");
|
||||
return;
|
||||
}
|
||||
enterStudy(row);
|
||||
goDetail(row);
|
||||
};
|
||||
|
||||
const statusLabel = (status: string) =>
|
||||
@@ -449,6 +454,13 @@ onMounted(() => {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.project-name--disabled {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: var(--ctms-text-secondary);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.project-code {
|
||||
font-size: 12px;
|
||||
color: var(--ctms-text-secondary);
|
||||
|
||||
Reference in New Issue
Block a user