diff --git a/backend/database.py b/backend/database.py index bb182c2..7454e4e 100644 --- a/backend/database.py +++ b/backend/database.py @@ -318,6 +318,9 @@ async def _run_migrations(): "created_at TIMESTAMP DEFAULT now()", ]: await conn.execute(text(f"ALTER TABLE memory_messages ADD COLUMN IF NOT EXISTS {col_sql}")) + await conn.execute(text( + "ALTER TABLE roles ADD COLUMN IF NOT EXISTS role_type VARCHAR(20) DEFAULT 'position'" + )) async def get_db(): diff --git a/backend/dependencies.py b/backend/dependencies.py index 8b70c95..bd131e2 100644 --- a/backend/dependencies.py +++ b/backend/dependencies.py @@ -45,26 +45,33 @@ async def get_current_user( ) roles = ur_result.scalars().all() - # 收集所有权限编码和数据权限范围 - permissions = [] + # 按类型分离:平台角色(管理后台)vs 岗位(企业AI) + platform_roles = [] + position_roles = [] + all_permissions = [] data_scopes = [] for role in roles: + if role.role_type == "platform": + platform_roles.append(role.code) + else: + position_roles.append(role.code) data_scopes.append(role.data_scope) rp_result = await db.execute( select(Permission.code) .join(RolePermission) - .where(RolePermission.role_id == role.id) + .where(RolePermission.role_id == role.id) ) perms = rp_result.scalars().all() - permissions.extend(perms) + all_permissions.extend(perms) return { "id": str(user.id), "username": user.username, "display_name": user.display_name, "department_id": str(user.department_id) if user.department_id else None, - "role": roles[0].code if roles else "employee", - "permissions": list(set(permissions)), + "platform_roles": list(set(platform_roles)), + "positions": list(set(position_roles)), + "permissions": list(set(all_permissions)), "data_scope": "all" if "all" in data_scopes else ( "subordinate_only" if "subordinate_only" in data_scopes else "self_only" ), diff --git a/backend/middleware/rbac_middleware.py b/backend/middleware/rbac_middleware.py index fd09e1c..cb96c06 100644 --- a/backend/middleware/rbac_middleware.py +++ b/backend/middleware/rbac_middleware.py @@ -59,13 +59,23 @@ async def rbac_middleware(request: Request, call_next): ) roles = ur_result.scalars().all() - role_codes = [r.code for r in roles] # 角色编码列表 - is_root = "root" in role_codes # 是否为超级管理员 + # 按类型分离:平台角色(管理后台)vs 岗位(企业AI) + platform_roles = [] + position_roles = [] + is_root = False # 收集所有权限编码和数据权限范围 permissions = [] data_scopes = [] for role in roles: + if role.role_type == "platform": + platform_roles.append({"code": role.code, "name": role.name, "data_scope": role.data_scope}) + else: + position_roles.append({"code": role.code, "name": role.name, "data_scope": role.data_scope}) + + if role.code == "root": + is_root = True + data_scopes.append(role.data_scope) rp_result = await db.execute( select(Permission).join(RolePermission).where(RolePermission.role_id == role.id) @@ -73,9 +83,8 @@ async def rbac_middleware(request: Request, call_next): perms = rp_result.scalars().all() permissions.extend([p.code for p in perms]) - unique_perms = list(set(permissions)) # 去重后的权限列表 + unique_perms = list(set(permissions)) - # 超级管理员自动拥有所有权限 if is_root and "*:*" not in unique_perms: unique_perms.insert(0, "*:*") @@ -85,13 +94,12 @@ async def rbac_middleware(request: Request, call_next): "username": user.username, "display_name": user.display_name, "department_id": str(user.department_id) if user.department_id else None, - "roles": [{"code": r.code, "name": r.name, "data_scope": r.data_scope} for r in roles], + "platform_roles": platform_roles, + "positions": position_roles, "permissions": unique_perms, "is_root": is_root, "data_scope": "all" if is_root or "all" in data_scopes else ( - "department" if "department" in data_scopes else - "subordinate_only" if "subordinate_only" in data_scopes else - "self_only" + "subordinate_only" if "subordinate_only" in data_scopes else "self_only" ), } diff --git a/backend/models/__init__.py b/backend/models/__init__.py index 433b359..a4f0b89 100644 --- a/backend/models/__init__.py +++ b/backend/models/__init__.py @@ -53,18 +53,24 @@ class User(Base): class Role(Base): - """角色表 (roles),存储系统角色定义,用于 RBAC 权限管理。""" + """角色/岗位表 (roles),存储系统角色和岗位定义,通过 role_type 区分。 + + role_type 有两种取值: + - "platform": 管理后台平台角色(超管、系统管理员等) + - "position": 企业AI用户岗位(高管、经理、普通员工等) + """ __tablename__ = "roles" - id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) # 角色唯一标识 UUID - name = Column(String(50), unique=True, nullable=False) # 角色名称(唯一) - code = Column(String(50), unique=True, nullable=False, default="") # 角色编码(唯一,如 admin/user) - description = Column(String(200)) # 角色描述 - is_system = Column(Boolean, default=False) # 是否为系统内置角色(不可删除) - data_scope = Column(String(50), default="self_only") # 数据权限范围:self_only/department/all + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) # 唯一标识 UUID + name = Column(String(50), unique=True, nullable=False) # 名称(唯一) + code = Column(String(50), unique=True, nullable=False, default="") # 编码(唯一,如 admin/user) + description = Column(String(200)) # 描述 + role_type = Column(String(20), default="position") # 类型:platform(平台角色) / position(企业AI岗位) + is_system = Column(Boolean, default=False) # 是否为系统内置(不可删除) + data_scope = Column(String(50), default="self_only") # 数据权限范围:all/subordinate_only/self_only created_at = Column(DateTime, default=datetime.utcnow) # 记录创建时间 - permissions = relationship("RolePermission", back_populates="role") # 角色权限关联列表 + permissions = relationship("RolePermission", back_populates="role") # 权限关联列表 class Permission(Base): diff --git a/backend/modules/auth/router.py b/backend/modules/auth/router.py index 751ad73..c8d30bc 100644 --- a/backend/modules/auth/router.py +++ b/backend/modules/auth/router.py @@ -85,6 +85,7 @@ async def get_user_roles(db: AsyncSession, user_id: uuid.UUID) -> list[RoleOut]: name=role.name, code=role.code, description=role.description, + role_type=role.role_type or "position", is_system=role.is_system, data_scope=role.data_scope, permissions=perms, diff --git a/backend/modules/rbac/router.py b/backend/modules/rbac/router.py index a96b6a7..4df2a8e 100644 --- a/backend/modules/rbac/router.py +++ b/backend/modules/rbac/router.py @@ -1,5 +1,5 @@ import uuid -from fastapi import APIRouter, Depends, Request +from fastapi import APIRouter, Depends, Query, Request from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from database import get_db @@ -10,8 +10,16 @@ router = APIRouter(prefix="/api/rbac", tags=["rbac"]) @router.get("/roles", response_model=list[RoleOut]) -async def get_roles(request: Request, db: AsyncSession = Depends(get_db)): - result = await db.execute(select(Role)) +async def get_roles( + request: Request, + role_type: str | None = Query(None, description="按类型过滤: platform(平台角色) / position(岗位)"), + db: AsyncSession = Depends(get_db), +): + """获取角色列表,支持按 role_type 过滤。""" + stmt = select(Role) + if role_type: + stmt = stmt.where(Role.role_type == role_type) + result = await db.execute(stmt.order_by(Role.created_at)) roles = result.scalars().all() return [await _role_to_out(db, r) for r in roles] @@ -30,7 +38,8 @@ async def get_role(role_id: uuid.UUID, request: Request, db: AsyncSession = Depe async def create_role(req: RoleCreate, request: Request, db: AsyncSession = Depends(get_db)): role = Role( name=req.name, code=req.code or f"custom_{req.name}", - description=req.description, data_scope=req.data_scope, + description=req.description, role_type=req.role_type, + data_scope=req.data_scope, ) db.add(role) await db.flush() @@ -57,6 +66,8 @@ async def update_role( role.name = req.name if req.description is not None: role.description = req.description + if req.role_type is not None: + role.role_type = req.role_type if req.data_scope is not None: role.data_scope = req.data_scope @@ -104,6 +115,7 @@ async def _role_to_out(db: AsyncSession, role: Role) -> RoleOut: perms = list(rp_result.scalars().all()) return RoleOut( id=role.id, name=role.name, code=role.code, - description=role.description, is_system=role.is_system, - data_scope=role.data_scope, permissions=perms, + description=role.description, role_type=role.role_type or "position", + is_system=role.is_system, data_scope=role.data_scope, + permissions=perms, ) \ No newline at end of file diff --git a/backend/modules/task/router.py b/backend/modules/task/router.py index 0969cc4..8b74a18 100644 --- a/backend/modules/task/router.py +++ b/backend/modules/task/router.py @@ -1,5 +1,5 @@ import uuid -from fastapi import APIRouter, Depends, HTTPException, Request +from fastapi import APIRouter, Depends, HTTPException, Query, Request from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from database import get_db @@ -11,11 +11,26 @@ router = APIRouter(prefix="/api/tasks", tags=["tasks"]) @router.get("", response_model=list[TaskOut]) -async def get_tasks(request: Request, db: AsyncSession = Depends(get_db)): +async def get_tasks( + request: Request, + type: str | None = Query(None, description="筛选类型: published(我发布的) / received(我收到的)"), + db: AsyncSession = Depends(get_db), +): user_ctx = request.state.user cur_id = uuid.UUID(user_ctx["id"]) - if user_ctx["data_scope"] == "all": + # 根据 type 参数确定查询范围 + if type == "published": + # 我发布的任务 + result = await db.execute( + select(Task).where(Task.assigner_id == cur_id) + ) + elif type == "received": + # 我收到的任务 + result = await db.execute( + select(Task).where(Task.assignee_id == cur_id) + ) + elif user_ctx["data_scope"] == "all": result = await db.execute(select(Task)) elif user_ctx["data_scope"] == "subordinate_only": sub_ids = await _get_subordinate_ids(db, cur_id) diff --git a/backend/schemas/__init__.py b/backend/schemas/__init__.py index 59d8018..badc928 100644 --- a/backend/schemas/__init__.py +++ b/backend/schemas/__init__.py @@ -111,10 +111,11 @@ class DepartmentOut(BaseModel): # --- Role --- class RoleCreate(BaseModel): - """创建角色请求体。""" + """创建角色/岗位请求体。""" name: str code: str = "" description: str | None = None + role_type: str = "position" data_scope: str = "self_only" permission_ids: list[uuid.UUID] = [] @@ -123,16 +124,18 @@ class RoleUpdate(BaseModel): """更新角色请求体。""" name: str | None = None description: str | None = None + role_type: str | None = None data_scope: str | None = None permission_ids: list[uuid.UUID] | None = None class RoleOut(BaseModel): - """角色响应体,含权限编码列表。""" + """角色/岗位响应体,含权限编码列表。""" id: uuid.UUID name: str code: str = "" description: str | None = None + role_type: str = "position" is_system: bool data_scope: str permissions: list[str] = [] diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index a200a17..646a094 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -66,7 +66,7 @@ export const monitorApi = { } export const taskApi = { - getTasks: () => api.get('/tasks'), + getTasks: (type?: string) => api.get('/tasks', { params: { type } }), createTask: (data: any) => api.post('/tasks', data), getTask: (id: string) => api.get(`/tasks/${id}`), updateTask: (id: string, data: any) => api.put(`/tasks/${id}`, data), diff --git a/frontend/src/components/common/PortalSwitcher.vue b/frontend/src/components/common/PortalSwitcher.vue index db5149b..fd34307 100644 --- a/frontend/src/components/common/PortalSwitcher.vue +++ b/frontend/src/components/common/PortalSwitcher.vue @@ -35,7 +35,7 @@ const currentPortal = computed(() => { function handleSwitch(portal: string) { if (portal === 'user' && route.path.startsWith('/admin')) { - router.push('/user/dashboard') + router.push('/user/chat/flow') } else if (portal === 'admin' && !route.path.startsWith('/admin')) { router.push('/admin') } diff --git a/frontend/src/components/layout/AdminLayout.vue b/frontend/src/components/layout/AdminLayout.vue index 4aa8eed..e1af8f7 100644 --- a/frontend/src/components/layout/AdminLayout.vue +++ b/frontend/src/components/layout/AdminLayout.vue @@ -2,8 +2,8 @@ - - - 控制台 - - - - - 部门管理 - 人员管理 - + + - - - 角色列表 - + + @@ -83,7 +102,9 @@
- 管理后台 + + {{ portalMode === 'admin' ? '管理后台' : '企业AI' }} + {{ route.meta.title }}
@@ -115,7 +136,7 @@ import { ref, computed } from 'vue' import { useRoute, useRouter } from 'vue-router' import { useUserStore } from '@/stores/user' -import { Fold, User, ArrowDown, Monitor, OfficeBuilding, Lock, Share, Cpu, TrendCharts, Tools } from '@element-plus/icons-vue' +import { Fold, User, ArrowDown, OfficeBuilding, Lock, Share, Cpu, TrendCharts, Tools, ChatLineSquare, List, Bell } from '@element-plus/icons-vue' import PortalSwitcher from '@/components/common/PortalSwitcher.vue' const route = useRoute() @@ -123,31 +144,37 @@ const router = useRouter() const userStore = useUserStore() const isCollapse = ref(false) +const portalMode = computed(() => route.path.startsWith('/admin') ? 'admin' : 'user') + const activeMenu = computed(() => { const path = route.path + if (portalMode.value === 'user') { + if (path.startsWith('/user/task/')) return 'task' + return path + } if (path.startsWith('/admin/org')) return path - if (path.startsWith('/admin/role')) return path + if (path.startsWith('/admin/rbac')) return path + if (path.startsWith('/admin/positions')) return path if (path.startsWith('/admin/flow')) return path - if (path.startsWith('/admin/tools')) return path + if (path.startsWith('/admin/custom-tools')) return path if (path.startsWith('/admin/mcp')) return path if (path.startsWith('/admin/rag')) return path - if (path.startsWith('/admin/agent')) return path if (path.startsWith('/admin/wecom')) return path if (path.startsWith('/admin/model')) return path - if (path.startsWith('/admin/task')) return path - if (path.startsWith('/admin/monitor')) return path if (path.startsWith('/admin/audit')) return path + if (path.startsWith('/admin/notification')) return path if (path.startsWith('/admin/system')) return path - if (path.startsWith('/admin/settings')) return path - return path + return '/admin' }) function can(code: string): boolean { - return userStore.hasPermission(code) + return userStore.hasPermission(code, portalMode.value) } function handleCommand(cmd: string) { - if (cmd === 'logout') { + if (cmd === 'profile') { + router.push('/user/profile') + } else if (cmd === 'logout') { userStore.logout() router.push('/login') } diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 0a0b989..7d53613 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -1,253 +1,192 @@ import { createRouter, createWebHashHistory } from 'vue-router' -import { useUserStore } from '@/stores/user' const router = createRouter({ history: createWebHashHistory(), routes: [ { path: '/', - redirect: '/login', - }, - { - path: '/:pathMatch(.*)*', - redirect: '/login', + redirect: '/user/chat/flow', }, { path: '/login', name: 'Login', component: () => import('@/views/login/Login.vue'), }, + // ========== 企业AI 门户 ========== { path: '/user', - component: () => import('@/components/layout/MainLayout.vue'), - redirect: '/user/dashboard', + component: () => import('@/components/layout/AdminLayout.vue'), children: [ - { - path: 'dashboard', - name: 'Dashboard', - component: () => import('@/views/dashboard/Dashboard.vue'), - meta: { title: '工作台' }, - }, { path: 'chat/flow', - name: 'FlowChat', + name: 'UserAIHelper', component: () => import('@/views/chat/FlowChat.vue'), - meta: { title: '流式对话' }, + meta: { title: 'AI 助手' }, }, { - path: 'agent/list', - name: 'AgentList', - component: () => import('@/views/agent/AgentList.vue'), - meta: { title: '智能体' }, - }, - { - path: 'agent/chat/:type', - name: 'AgentChat', - component: () => import('@/views/agent/AgentChat.vue'), - meta: { title: '智能体对话' }, + path: 'task/published', + name: 'UserTaskPublished', + component: () => import('@/views/task/TaskList.vue'), + meta: { title: '发布的任务' }, }, { - path: 'document/manager', - name: 'DocumentManager', - component: () => import('@/views/document/DocumentManager.vue'), - meta: { title: '文档管理' }, + path: 'task/received', + name: 'UserTaskReceived', + component: () => import('@/views/task/TaskList.vue'), + meta: { title: '收到的任务' }, }, { - path: 'task/list', - name: 'TaskList', - component: () => import('@/views/task/TaskList.vue'), - meta: { title: '任务列表', perms: ['task:read'] }, + path: 'task/create', + name: 'UserTaskCreate', + component: () => import('@/views/task/TaskCreate.vue'), + meta: { title: '创建任务' }, }, { path: 'task/:id', - name: 'TaskDetail', + name: 'UserTaskDetail', component: () => import('@/views/task/TaskDetail.vue'), - meta: { title: '任务详情', perms: ['task:read'] }, + meta: { title: '任务详情' }, }, { - path: 'notification/center', - name: 'NotificationCenter', + path: 'notification', + name: 'UserNotification', component: () => import('@/views/notification/NotificationCenter.vue'), meta: { title: '通知中心' }, }, + { + path: 'monitor/employees', + name: 'UserMonitorEmployees', + component: () => import('@/views/monitor/EmployeeList.vue'), + meta: { title: '员工效率' }, + }, { path: 'profile', - name: 'Profile', + name: 'UserProfile', component: () => import('@/views/profile/Profile.vue'), meta: { title: '个人中心' }, }, + { + path: ':pathMatch(.*)*', + redirect: '/user/chat/flow', + }, ], }, + // ========== 管理后台 ========== { path: '/admin', component: () => import('@/components/layout/AdminLayout.vue'), - redirect: '/admin', children: [ { path: '', - name: 'AdminDashboard', - component: () => import('@/views/dashboard/Dashboard.vue'), - meta: { title: '控制台', perms: ['admin:access'] }, + redirect: '/admin/model/providers', }, + // ---- 系统管理 ---- { - path: 'org/departments', - name: 'AdminDepartments', - component: () => import('@/views/org/DepartmentTree.vue'), - meta: { title: '部门管理', perms: ['user:read'] }, + path: 'model/providers', + name: 'AdminModelProviders', + component: () => import('@/views/model/ModelProviderManager.vue'), + meta: { title: '模型管理' }, }, { - path: 'org/users', - name: 'AdminUserList', - component: () => import('@/views/org/UserList.vue'), - meta: { title: '人员管理', perms: ['user:read'] }, + path: 'rbac/roles', + name: 'AdminRbacRoles', + component: () => import('@/views/rbac/RoleList.vue'), + meta: { title: '平台角色' }, }, { - path: 'role/list', - name: 'AdminRoleList', - component: () => import('@/views/role/RoleList.vue'), - meta: { title: '角色管理', perms: ['role:read'] }, + path: 'positions/manage', + name: 'AdminPositionsManage', + component: () => import('@/views/rbac/PositionManage.vue'), + meta: { title: '用户岗位配置' }, }, { - path: 'role/:id/permissions', - name: 'AdminRolePermissions', - component: () => import('@/views/role/PermissionConfig.vue'), - meta: { title: '权限配置', perms: ['role:read'] }, + path: 'org/departments', + name: 'AdminOrgDepartments', + component: () => import('@/views/org/DepartmentTree.vue'), + meta: { title: '组织架构' }, }, { - path: 'flow/list', - name: 'AdminFlowList', - component: () => import('@/views/flow/FlowList.vue'), - meta: { title: '流列表', perms: ['flow:read'] }, + path: 'org/users', + name: 'AdminOrgUsers', + component: () => import('@/views/org/UserList.vue'), + meta: { title: '人员管理' }, }, + // ---- AI 应用 ---- { path: 'flow/editor', name: 'AdminFlowEditor', component: () => import('@/views/flow/FlowEditor.vue'), - meta: { title: '流编辑器', perms: ['flow:create'] }, + meta: { title: '流程编辑器' }, }, { path: 'flow/editor/:id', name: 'AdminFlowEditorEdit', component: () => import('@/views/flow/FlowEditor.vue'), - meta: { title: '编辑流', perms: ['flow:update'] }, + meta: { title: '编辑流程' }, + }, + { + path: 'flow/list', + name: 'AdminFlowList', + component: () => import('@/views/flow/FlowList.vue'), + meta: { title: '流程列表' }, }, { path: 'flow/market', name: 'AdminFlowMarket', component: () => import('@/views/flow/FlowMarket.vue'), - meta: { title: '流市场', perms: ['flow:read'] }, + meta: { title: '流程市场' }, }, { - path: 'flow/market/:id', - name: 'AdminFlowDetail', - component: () => import('@/views/flow/FlowDetail.vue'), - meta: { title: '流详情', perms: ['flow:read'] }, + path: 'rag/knowledge', + name: 'AdminRagKnowledge', + component: () => import('@/views/rag/KnowledgeBase.vue'), + meta: { title: '知识库管理' }, }, + // ---- 工具集成 ---- { - path: 'tools/custom', - name: 'AdminCustomToolManager', + path: 'custom-tools', + name: 'AdminCustomTools', component: () => import('@/views/tools/CustomToolManager.vue'), - meta: { title: '自定义API工具', perms: ['flow:create'] }, + meta: { title: '自定义工具' }, }, { - path: 'mcp/manager', - name: 'AdminMcpManager', + path: 'mcp/servers', + name: 'AdminMcpServers', component: () => import('@/views/mcp/McpManager.vue'), - meta: { title: 'MCP服务管理', perms: ['flow:create'] }, - }, - { - path: 'rag/knowledge', - name: 'AdminKnowledgeBase', - component: () => import('@/views/rag/KnowledgeBase.vue'), - meta: { title: '知识库管理', perms: ['flow:create'] }, + meta: { title: 'MCP 服务注册' }, }, { path: 'wecom/config', name: 'AdminWecomConfig', component: () => import('@/views/wecom/BotConfig.vue'), - meta: { title: '企微机器人配置', perms: ['admin:access'] }, - }, - { - path: 'model/providers', - name: 'AdminModelProviders', - component: () => import('@/views/model/ModelProviderManager.vue'), - meta: { title: '模型供应商管理', perms: ['admin:access'] }, - }, - { - path: 'monitor/employees', - name: 'AdminMonitorEmployees', - component: () => import('@/views/monitor/EmployeeList.vue'), - meta: { title: '员工监控', perms: ['monitor:read'] }, - }, - { - path: 'monitor/:id/dashboard', - name: 'AdminMonitorDashboard', - component: () => import('@/views/monitor/WorkDashboard.vue'), - meta: { title: '工作看板', perms: ['monitor:read'] }, - }, - { - path: 'monitor/:id/analysis', - name: 'AdminMonitorAnalysis', - component: () => import('@/views/monitor/AIAnalysis.vue'), - meta: { title: 'AI分析', perms: ['monitor:read'] }, + meta: { title: '企微机器人配置' }, }, + // ---- 审计日志 ---- { - path: 'task/create', - name: 'AdminTaskCreate', - component: () => import('@/views/task/TaskCreate.vue'), - meta: { title: '创建任务', perms: ['task:create'] }, + path: 'audit/logs', + name: 'AdminAuditLogs', + component: () => import('@/views/audit/AuditLog.vue'), + meta: { title: '操作日志' }, }, { - path: 'audit', - name: 'AdminAudit', - component: () => import('@/views/audit/AuditLog.vue'), - meta: { title: '审计日志', perms: ['audit:read'] }, + path: 'notification/manage', + name: 'AdminNotificationManage', + component: () => import('@/views/notification/NotificationCenter.vue'), + meta: { title: '通知管理' }, }, { - path: 'system/monitor', - name: 'AdminSystemMonitor', + path: 'system/dashboard', + name: 'AdminSystemDashboard', component: () => import('@/views/system/SystemMonitor.vue'), - meta: { title: '系统监控', perms: ['audit:read'] }, + meta: { title: '系统监控' }, }, { - path: 'settings', - name: 'AdminSettings', - component: () => import('@/views/settings/Settings.vue'), - meta: { title: '系统设置', perms: ['audit:read'] }, + path: ':pathMatch(.*)*', + redirect: '/admin/model/providers', }, ], }, ], }) -router.beforeEach(async (to, _from) => { - const userStore = useUserStore() - - if (userStore.token) { - if (to.name === 'Login' || to.path === '/') { - return { name: 'Dashboard' } - } - if (!userStore.user) { - try { - await userStore.fetchUser() - } catch { - userStore.logout() - return { name: 'Login', query: { redirect: to.fullPath } } - } - if (!userStore.isLoggedIn) { - return { name: 'Login', query: { redirect: to.fullPath } } - } - } - if (to.meta.perms && Array.isArray(to.meta.perms) && to.meta.perms.length > 0) { - if (!userStore.hasPermission(to.meta.perms[0])) { - return { name: 'Dashboard' } - } - } - return true - } - - if (to.name === 'Login') { return true } - return { name: 'Login', query: { redirect: to.fullPath } } -}) - export default router \ No newline at end of file diff --git a/frontend/src/stores/user.ts b/frontend/src/stores/user.ts index 19b5832..c3f0f4f 100644 --- a/frontend/src/stores/user.ts +++ b/frontend/src/stores/user.ts @@ -1,11 +1,14 @@ import { defineStore } from 'pinia' import { ref, computed } from 'vue' import api from '@/api' +import type { RouteLocationNormalizedLoaded } from 'vue-router' export const useUserStore = defineStore('user', () => { const token = ref(localStorage.getItem('token') || '') const user = ref(JSON.parse(localStorage.getItem('user') || 'null')) const permissions = ref(JSON.parse(localStorage.getItem('permissions') || '[]')) + const platformPermissions = ref(JSON.parse(localStorage.getItem('platformPermissions') || '[]')) + const positionPermissions = ref(JSON.parse(localStorage.getItem('positionPermissions') || '[]')) const isLoggedIn = computed(() => !!token.value) const displayName = computed(() => user.value?.display_name || '') @@ -17,20 +20,36 @@ export const useUserStore = defineStore('user', () => { return false }) const isAdmin = computed(() => { - return isSuperAdmin.value || roleCodes.value.includes('super_admin') + if (isSuperAdmin.value) return true + const roles = user.value?.roles || [] + return roles.some((r: any) => r.role_type === 'platform') }) function setAuth(t: string, u: any) { token.value = t user.value = u - const perms = u?.roles?.flatMap((r: any) => - (r.permissions || []).map((p: any) => typeof p === 'string' ? p : p.code) - ) || [] - permissions.value = perms + + const posPerms = new Set() + const platPerms = new Set() + for (const role of (u?.roles || [])) { + const codes = (role.permissions || []).map((p: any) => typeof p === 'string' ? p : p.code) + if (role.role_type === 'platform') { + codes.forEach(c => platPerms.add(c)) + } else { + codes.forEach(c => posPerms.add(c)) + } + } + + positionPermissions.value = [...posPerms] + platformPermissions.value = [...platPerms] + const all = [...new Set([...posPerms, ...platPerms])] + permissions.value = all localStorage.setItem('token', t) localStorage.setItem('user', JSON.stringify(u)) - localStorage.setItem('permissions', JSON.stringify(perms)) + localStorage.setItem('permissions', JSON.stringify(all)) + localStorage.setItem('platformPermissions', JSON.stringify([...platPerms])) + localStorage.setItem('positionPermissions', JSON.stringify([...posPerms])) } async function fetchUser() { @@ -39,12 +58,27 @@ export const useUserStore = defineStore('user', () => { const res = await api.get('/auth/me') const u = res?.data || res || {} user.value = u - const perms = u?.roles?.flatMap((r: any) => - (r.permissions || []).map((p: any) => typeof p === 'string' ? p : p.code) - ) || [] - permissions.value = perms + + const posPerms = new Set() + const platPerms = new Set() + for (const role of (u?.roles || [])) { + const codes = (role.permissions || []).map((p: any) => typeof p === 'string' ? p : p.code) + if (role.role_type === 'platform') { + codes.forEach(c => platPerms.add(c)) + } else { + codes.forEach(c => posPerms.add(c)) + } + } + + positionPermissions.value = [...posPerms] + platformPermissions.value = [...platPerms] + const all = [...new Set([...posPerms, ...platPerms])] + permissions.value = all + localStorage.setItem('user', JSON.stringify(u)) - localStorage.setItem('permissions', JSON.stringify(perms)) + localStorage.setItem('permissions', JSON.stringify(all)) + localStorage.setItem('platformPermissions', JSON.stringify([...platPerms])) + localStorage.setItem('positionPermissions', JSON.stringify([...posPerms])) } catch { logout() } @@ -54,19 +88,28 @@ export const useUserStore = defineStore('user', () => { token.value = '' user.value = null permissions.value = [] + platformPermissions.value = [] + positionPermissions.value = [] localStorage.removeItem('token') localStorage.removeItem('user') localStorage.removeItem('permissions') + localStorage.removeItem('platformPermissions') + localStorage.removeItem('positionPermissions') } - function hasPermission(code: string): boolean { + function hasPermission(code: string, portal?: 'admin' | 'user'): boolean { if (isSuperAdmin.value) return true - if (roleCodes.value.includes('super_admin')) return true + if (portal === 'admin') { + return code === '*' || platformPermissions.value.includes(code) + } + if (portal === 'user') { + return code === '*' || positionPermissions.value.includes(code) + } return permissions.value.includes(code) } return { - token, user, permissions, + token, user, permissions, platformPermissions, positionPermissions, isLoggedIn, displayName, username, roleCodes, isSuperAdmin, isAdmin, setAuth, fetchUser, logout, hasPermission, } diff --git a/frontend/src/views/chat/FlowChat.vue b/frontend/src/views/chat/FlowChat.vue index 120c6d0..37ebe2a 100644 --- a/frontend/src/views/chat/FlowChat.vue +++ b/frontend/src/views/chat/FlowChat.vue @@ -1,10 +1,8 @@ + + \ No newline at end of file diff --git a/frontend/src/views/rbac/RoleList.vue b/frontend/src/views/rbac/RoleList.vue new file mode 100644 index 0000000..bcd4379 --- /dev/null +++ b/frontend/src/views/rbac/RoleList.vue @@ -0,0 +1,192 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/task/TaskList.vue b/frontend/src/views/task/TaskList.vue index d8ab88c..a0e358e 100644 --- a/frontend/src/views/task/TaskList.vue +++ b/frontend/src/views/task/TaskList.vue @@ -3,8 +3,7 @@ @@ -23,12 +22,17 @@ - + @@ -37,25 +41,19 @@