完善项目级权限与项目管理入口

- 新增项目成员候选用户接口,避免 PM 读取全局用户列表
- 按项目角色和权限矩阵控制项目管理、成员、中心与审计入口
- 合并侧边栏管理后台入口,统一从项目管理进入成员等模块
- 加强成员管理安全约束,禁止 PM 修改自己、系统管理员或更高权限角色
- 修复多项目角色缓存串用问题,并补充前后端回归测试
This commit is contained in:
Cheng Zhou
2026-05-13 14:16:15 +08:00
parent ab1975d3c6
commit a17f4cc522
21 changed files with 452 additions and 134 deletions
+44
View File
@@ -0,0 +1,44 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readProjects = () => readFileSync(resolve(__dirname, "./Projects.vue"), "utf8");
const readRouter = () => readFileSync(resolve(__dirname, "../../router/index.ts"), "utf8");
const readLayout = () => readFileSync(resolve(__dirname, "../../components/Layout.vue"), "utf8");
describe("project management access", () => {
it("shows project management to all signed-in users while keeping system operations admin-only", () => {
const projects = readProjects();
const layout = readLayout();
const router = readRouter();
expect(layout).toContain('v-if="auth.user"');
expect(layout).toContain('index="/admin/projects"');
expect(router).toContain('name: "AdminProjects"');
expect(router).toContain("meta: { title: TEXT.menu.projectManagement }");
expect(projects).toContain('v-if="isAdmin" type="primary"');
expect(projects).toContain('v-if="isAdmin && !scope.row.is_locked"');
expect(projects).toContain('v-if="isAdmin" :content="TEXT.common.actions.delete"');
});
it("keeps management backend modules under the project management page instead of sidebar shortcuts", () => {
const layout = readLayout();
expect(layout).not.toContain("canAccessPmAdminBackend");
expect(layout).not.toContain("canAccessPmAdminModule");
expect(layout).not.toContain("TEXT.modules.adminProjectMembers.memberLabel");
expect(layout).toContain('index="/admin/projects"');
});
it("uses each project permission matrix to show project management actions", () => {
const source = readProjects();
expect(source).toContain("fetchProjectRolePermissions(item.id)");
expect(source).toContain("permissionsByProject");
expect(source).toContain("const canProject = (row: Study, module: string, action: \"read\" | \"write\")");
expect(source).toContain("canProject(scope.row, 'project_members', 'read')");
expect(source).toContain("canProject(scope.row, 'project_members', 'write')");
expect(source).toContain("canProject(scope.row, 'sites', 'read')");
expect(source).toContain("canProject(scope.row, 'audit_export', 'read')");
});
});