diff --git a/.trae/specs/refactor-system-auth-pg/checklist.md b/.trae/specs/refactor-system-auth-pg/checklist.md new file mode 100644 index 0000000..33f99e4 --- /dev/null +++ b/.trae/specs/refactor-system-auth-pg/checklist.md @@ -0,0 +1,47 @@ +# Checklist - 系统管理与认证授权模块对齐新设计 + +## 数据库与配置检查 +- [x] server 已安装 pg / pg-hstore 依赖 +- [x] config.default.ts 使用 PostgreSQL(dialect=postgres、port 5432、database=xintong) +- [x] database/db_init.sql 在 PostgreSQL 中执行成功 +- [x] 初始数据齐全:4 角色、11 条菜单(6 顶级 + 5 系统子菜单)、20 条字典、admin 账户、角色菜单绑定 +- [x] admin 账户密码为 admin123456(bcrypt hash 正确) + +## 后端实体与接口检查 +- [x] 7 个系统域实体(sysUser/sysRole/sysUserRole/sysMenu/sysRoleMenu/sysDict/sysOperationLog)创建成功且表名正确 +- [x] 旧实体(user/role/userRole/permission/rolePermission/operationLog)已删除 +- [x] 登录接口基于 sys_user 校验,返回 userInfo/roles/menus +- [x] 刷新 Token 与当前用户接口适配新模型 +- [x] RbacMiddleware 基于菜单 permission 标识鉴权,ADMIN 放行 +- [x] 用户管理接口字段为 realName/phone/email,返回不包含密码哈希 +- [x] 角色管理接口字段为 roleCode/roleName/sortOrder +- [x] 角色菜单配置接口(/roles/:id/menus)读写 sys_role_menu 正确 +- [x] 菜单管理接口:菜单树查询与 CRUD 正常 +- [x] 数据字典接口:列表/类型筛选/CRUD 正常(修复 types() 的 PostgreSQL GROUP BY/ORDER BY SQL 问题) +- [x] 操作日志接口与中间件使用新字段(user_id/module/action/method/params/ip/created_at) +- [x] system.controller.ts / system.service.ts 已删除,无残留引用 + +## 前端检查 +- [x] api/auth.ts 返回类型含 realName/roles/menus +- [x] api/user.ts、api/role.ts 字段更新,权限接口改为菜单接口 +- [x] api/menu.ts、api/dict.ts 新增,api/system.ts 已删除 +- [x] stores/permission.ts 管理用户信息与菜单树(login 完整写入 roles/menus) +- [x] 侧边栏菜单由用户菜单树动态渲染(含图标、两级结构) +- [x] 路由改为静态全量注册,守卫按菜单权限标识控制;未开发模块显示占位页(替代原动态路由注册方案,修复深层刷新跳登录问题) +- [x] 设置页路由与菜单已移除 +- [x] 用户管理页字段为姓名/手机/邮箱 +- [x] 角色管理页权限配置为菜单树勾选 +- [x] 菜单管理页(树形表格 CRUD)可用 +- [x] 数据字典页(列表+类型筛选 CRUD)可用 +- [x] 操作日志页字段与筛选更新 +- [x] v-permission 指令及引用已移除 +- [x] views/settings 目录已删除 + +## 联调与边界检查 +- [x] admin 登录 → 仪表盘 → 系统管理各子页面全流程走通(含深层链接整页刷新) +- [x] 业务员角色仅显示授权菜单,访问未授权接口返回 403(SALESMAN/WORKER 实测 403) +- [x] 写操作自动记录操作日志且可查询 +- [x] 无菜单角色登录不报错,侧边栏仅显示仪表盘(WORKER 实测 menus=0) +- [x] 删除被占用角色被拦截 +- [x] 重复用户名/角色编码校验生效 +- [x] yarn build(server/client)无 TypeScript 编译错误 diff --git a/.trae/specs/refactor-system-auth-pg/spec.md b/.trae/specs/refactor-system-auth-pg/spec.md new file mode 100644 index 0000000..d895e4e --- /dev/null +++ b/.trae/specs/refactor-system-auth-pg/spec.md @@ -0,0 +1,125 @@ +# 系统管理与认证授权模块对齐新设计 Spec + +## Why +系统设计已完成调整(鑫潼智电系统):数据库从 MySQL 6 表升级为 PostgreSQL 37 表;系统管理域改为 7 张 `sys_` 前缀表;RBAC 由"权限码"模型改为"菜单级"模型(`sys_menu`/`sys_role_menu`);新增数据字典(`sys_dict`);原"系统设置"模块在新设计中无对应表。现有系统管理与认证授权代码仍基于旧模型,需要整体对齐新设计。 + +## What Changes +- **数据库引擎切换**:MySQL → PostgreSQL 15+(连接配置、安装 `pg`/`pg-hstore` 驱动;`database/db_init.sql` 已更新为 PostgreSQL 脚本) +- **系统域数据模型重构**(7 张表,替代原 6 张表): + - `sys_user`:username / password_hash / real_name / phone / email / status / last_login_at(移除 nickname、avatar、last_login_ip) + - `sys_role`:role_code / role_name / description / sort_order(移除 status) + - `sys_user_role`:user_id / role_id(不变) + - `sys_menu`:菜单级 RBAC,menu_type 1目录/2菜单(本次不启用 3按钮),含 menu_path / component / icon / sort_order / permission / visible(替代 permissions 表) + - `sys_role_menu`:role_id / menu_id(替代 role_permissions 表) + - `sys_dict`:**新增**数据字典表 + - `sys_operation_log`:字段精简为 user_id / module / action / method / params / ip / created_at(移除 username、url、user_agent、request_params、response_data、status_code、duration、error_message) +- **认证逻辑适配**:登录基于 `sys_user` + `password_hash` 比对,更新 `last_login_at`;登录返回 userInfo(含 realName)、roles(role_code)、menus(角色菜单树) +- **RBAC 重构**:菜单级权限。前端由菜单树驱动侧边栏与路由;后端接口鉴权按"接口路径 → 菜单 permission 标识"校验,ADMIN 角色全量放行;**按钮级权限暂不保留**(v-permission 指令移除) +- **系统设置模块移除**:删除前后端系统设置代码(settings 页面、system.controller/system.service、api/system.ts) +- **新增菜单管理模块**:sys_menu 树形 CRUD(目录/菜单两级) +- **新增数据字典模块**:sys_dict CRUD 与类型分组查询 +- **操作日志对齐**:实体、中间件、接口、前端页面字段更新 +- **初始数据补齐**:4 个角色(ADMIN/SALESMAN/WORKER/CUSTOMER)、6 个顶级业务菜单 + 5 个系统管理子菜单、19 条字典数据、管理员账户 admin/admin123456(ADMIN 角色)及角色-菜单绑定 + +## Impact +- Affected specs: smart-grid-iot-admin(已完成项目,本次为增量调整) +- Affected code: + - server:config/config.default.ts、package.json、entity 全部系统域实体、controller(auth/user/role/log/menu/dict)、service(auth/user/role/log/menu/dict)、middleware(auth/rbac/log)、dto + - client:api(auth/user/role/log/menu/dict)、stores/permission.ts、router/index.ts、layouts/index.vue、views(system/user、system/role、log/operation、新增 system/menu、system/dict)、删除 views/settings + - database/db_init.sql:补充初始数据(管理员账户、角色菜单绑定、系统管理子菜单) + +## ADDED Requirements + +### Requirement: PostgreSQL 数据库连接 +系统 SHALL 使用 PostgreSQL 15+ 作为数据库: +- server 依赖安装 `pg`、`pg-hstore` +- `config.default.ts` 的 sequelize 数据源 dialect 为 `postgres`,连接 xintong 库 +- 系统域 7 张表通过 `database/db_init.sql` 创建(Sequelize `sync: false`) + +#### Scenario: 服务连接 PostgreSQL 成功 +- **WHEN** 启动后端服务且本机已按 db_init.sql 建好库表 +- **THEN** 服务正常启动,登录、查询等接口可正常读写 sys_ 系列表 + +### Requirement: 菜单级 RBAC(sys_menu) +系统 SHALL 以菜单为权限载体: +- `sys_menu` 支持两级树:目录(menu_type=1)、菜单(menu_type=2);字段含 menu_path、component、icon、sort_order、permission、visible +- 用户登录返回其角色(经 `sys_role_menu`)关联的菜单树;前端据此渲染侧边栏与动态路由 +- 后端接口鉴权:RbacMiddleware 将请求路径映射为权限标识,校验用户角色关联菜单的 permission 集合;ADMIN 角色(role_code=ADMIN)直接放行 +- 本次不做按钮级权限(menu_type=3、v-permission 指令移除) + +#### Scenario: 登录用户仅看到被授权菜单 +- **WHEN** 业务员角色登录 +- **THEN** 侧边栏只渲染其角色已绑定的菜单,访问未授权接口返回 403 + +#### Scenario: ADMIN 全量访问 +- **WHEN** 运营管理员(ADMIN 角色)登录 +- **THEN** 可见全部菜单,所有受保护接口放行 + +### Requirement: 菜单管理模块 +系统 SHALL 提供菜单管理能力: +- 菜单树查询(全部菜单,供角色配置使用) +- 目录/菜单的创建、编辑、删除、排序、可见性切换 +- 权限标识(permission)维护 + +### Requirement: 数据字典模块 +系统 SHALL 提供数据字典管理能力: +- sys_dict 的创建、编辑、删除 +- 按 dict_type 分组查询、状态筛选 + +### Requirement: 初始数据 +系统 SHALL 通过 db_init.sql 初始化: +- 4 个角色:ADMIN(运营管理员)/ SALESMAN(业务员)/ WORKER(共享电工)/ CUSTOMER(客户) +- 6 个顶级业务菜单(客户管理/项目管理/运营派单/设备监控/统计报表/系统管理)与系统管理下 5 个子菜单(用户/角色/菜单/数据字典/操作日志) +- 管理员账户 admin/admin123456(bcrypt hash,角色 ADMIN) +- 角色-菜单绑定:ADMIN 绑定全部菜单;SALESMAN 绑定客户/项目/统计报表等业务菜单;WORKER/CUSTOMER 无后台菜单 +- 19 条字典数据(customer_status/equip_type/device_type/dispatch_status/pay_cycle/dispatch_type) + +## MODIFIED Requirements + +### Requirement: 用户管理模块 +The system SHALL 管理 sys_user: +- 列表支持分页、关键词搜索(username/realName/phone)、状态筛选、角色筛选 +- 新增/编辑字段:username、password、realName、phone、email、status、角色分配(roleIds) +- 删除、批量删除、状态切换(原能力保留) +- 返回数据不包含 password_hash + +#### Scenario: 管理员创建用户 +- **WHEN** 管理员填写 username/realName/phone/email/角色并提交 +- **THEN** 系统校验唯一性与必填项,bcrypt 加密密码写入 sys_user,分配 sys_user_role,列表刷新 + +### Requirement: 角色管理模块 +The system SHALL 管理 sys_role: +- 列表支持分页、关键词搜索(roleCode/roleName)、关联用户数统计 +- 新增/编辑字段:roleCode、roleName、description、sortOrder +- 权限配置由"权限码树"改为"菜单树勾选"(sys_role_menu) +- 删除时若关联用户则禁止删除 + +### Requirement: 操作日志模块 +The system SHALL 记录操作日志: +- 字段:user_id、module、action、method、params、ip、created_at +- 日志中间件按新字段写入,仅记录写操作(POST/PUT/DELETE) +- 查询接口支持按操作人/模块/时间范围筛选 +- 前端日志页字段与接口对齐 + +### Requirement: 认证授权服务 +The system SHALL 适配新用户模型: +- 登录:username + password 校验(sys_user.password_hash)、禁用校验、更新 last_login_at +- 登录返回:accessToken、refreshToken、userInfo(id/username/realName)、roles(role_code 数组)、menus(角色菜单树) +- 刷新 Token、当前用户信息接口同步适配 + +### Requirement: 前端动态菜单与路由 +The system SHALL 由菜单树驱动前端: +- 侧边栏菜单由用户 menus 渲染(目录→子菜单两级,含图标) +- 动态路由按 sys_menu.menu_path/component 注册;component 缺失(未开发业务模块)时渲染"功能开发中"占位页 +- 登录跳转仪表盘(仪表盘保持固定路由,不纳入菜单表) +- 移除设置页相关路由与菜单 + +## REMOVED Requirements + +### Requirement: 系统设置模块(站点名称/Logo/会话超时/密码策略/IP白名单) +**Reason**: 新设计无对应表,模块整体移除。 +**Migration**: 删除 server 端 system.controller.ts/system.service.ts、client 端 views/settings 页面与 api/system.ts,以及相关路由、菜单、日志映射与中间件引用。 + +### Requirement: 权限码 RBAC 与按钮级权限(permissions/role_permissions + v-permission) +**Reason**: 新设计采用菜单级 RBAC,按钮级权限暂不保留。 +**Migration**: 删除 permissions/role_permissions 相关实体、服务与 DTO,由 sys_menu/sys_role_menu 替代;移除前端 v-permission 指令及按钮级权限判断;路由 meta.permission 改为基于菜单 permission 标识。 diff --git a/.trae/specs/refactor-system-auth-pg/tasks.md b/.trae/specs/refactor-system-auth-pg/tasks.md new file mode 100644 index 0000000..3b590c6 --- /dev/null +++ b/.trae/specs/refactor-system-auth-pg/tasks.md @@ -0,0 +1,107 @@ +# Tasks - 系统管理与认证授权模块对齐新设计 + +## 阶段一:数据库与基础配置 +- [x] Task 1: 切换数据库引擎为 PostgreSQL + - [x] 在 server/package.json 安装 `pg`、`pg-hstore` 依赖 + - [x] 修改 server/src/config/config.default.ts:sequelize dialect 改为 `postgres`,host/port(5432)/database(xintong)/username/password 环境变量(默认 postgres) + - [x] 检查 config.local.ts 的 sync 配置合理性(表由 SQL 脚本创建,保持 sync: false 或按需调整) + - [x] 验证:yarn build 通过,服务可连接 PostgreSQL + +- [x] Task 2: 补充 database/db_init.sql 初始数据 + - [x] 补充 sys_menu 系统管理目录下的 5 个子菜单(用户管理/角色管理/菜单管理/数据字典/操作日志,含 menu_path/component/permission/sort_order) + - [x] 补充 sys_user:admin/admin123456(用 Node bcryptjs 生成 hash 填入 password_hash,real_name=运营管理员) + - [x] 补充 sys_user_role:admin → ADMIN 角色 + - [x] 补充 sys_role_menu:ADMIN 绑定全部菜单;SALESMAN 绑定客户/项目/统计报表等业务菜单 + - [x] 验证:在 PostgreSQL 中执行脚本无报错,各表初始数据条数正确 + +## 阶段二:后端重构 +- [x] Task 3: 重构系统域实体层(7 个实体) + - [x] 新建 sysUser.entity.ts(sys_user:username/password_hash/real_name/phone/email/status/last_login_at) + - [x] 新建 sysRole.entity.ts(sys_role:role_code/role_name/description/sort_order) + - [x] 新建 sysUserRole.entity.ts(sys_user_role) + - [x] 新建 sysMenu.entity.ts(sys_menu:parent_id/menu_name/menu_path/component/icon/sort_order/menu_type/permission/visible) + - [x] 新建 sysRoleMenu.entity.ts(sys_role_menu) + - [x] 新建 sysDict.entity.ts(sys_dict:dict_type/dict_label/dict_value/sort_order/status) + - [x] 新建 sysOperationLog.entity.ts(sys_operation_log:user_id/module/action/method/params/ip/created_at) + - [x] 删除旧实体 user/role/userRole/permission/rolePermission/operationLog 的 .entity.ts 文件 + - [x] 验证:yarn build 通过 + +- [x] Task 4: 重构认证与 RBAC + - [x] auth.service.ts:登录校验 sys_user.password_hash,更新 last_login_at;返回 accessToken/refreshToken/userInfo(id/username/realName)/roles(role_code 数组)/menus(菜单树) + - [x] auth.controller.ts:登录/刷新/登出/当前用户接口适配 + - [x] 新增菜单查询能力:permission.service(或新 menu.service)提供 getUserMenus(userId)(角色经 sys_role_menu 关联 sys_menu 的树)与 checkUserPermission(userId, code) + - [x] rbac.middleware.ts:路径→权限标识映射改为基于 sys_menu.permission;ADMIN 角色放行;未授权返回 403 + - [x] 验证:admin 登录返回 menus;无权限角色访问受保护接口返回 403 + +- [x] Task 5: 重构用户管理 + - [x] user.service.ts:字段适配 real_name/phone/email(删除 nickname/avatar/last_login_ip),搜索条件更新,返回数据剔除 password_hash + - [x] user.controller.ts:接口保持不变,入参字段对齐 + - [x] dto/user.dto.ts:字段更新(realName/phone/email/roleIds) + - [x] 验证:用户 CRUD 与角色分配联调通过 + +- [x] Task 6: 重构角色管理 + - [x] role.service.ts:字段适配 role_code/role_name/sort_order(删除 status);权限接口改为菜单(getRoleMenus 返回角色已勾选菜单树、updateRoleMenus 批量写 sys_role_menu) + - [x] role.controller.ts:权限相关接口改为 `/roles/:id/menus` + - [x] dto/role.dto.ts:字段更新 + - [x] 验证:角色列表、菜单勾选保存联调通过 + +- [x] Task 7: 新增菜单管理模块 + - [x] 新建 menu.controller.ts + menu.service.ts:菜单树查询(含全部菜单供角色配置)、创建/编辑/删除目录或菜单、排序与可见性维护 + - [x] 新建 dto/menu.dto.ts + - [x] 验证:菜单树 CRUD 联调通过 + +- [x] Task 8: 新增数据字典模块 + - [x] 新建 dict.controller.ts + dict.service.ts:分页列表、按 dict_type 分组查询、创建/编辑/删除 + - [x] 新建 dto/dict.dto.ts + - [x] 验证:字典 CRUD 与类型筛选联调通过 + +- [x] Task 9: 重构操作日志模块 + - [x] log.service.ts / log.controller.ts:字段对齐(user_id/module/action/method/params/ip),查询支持按 user/module/时间范围筛选 + - [x] log.middleware.ts:按新字段写入(params 存请求体),移除 url/user_agent/status_code/duration/error_message + - [x] 验证:写操作自动记录日志,查询接口正常 + +- [x] Task 10: 移除系统设置模块 + - [x] 删除 system.controller.ts / system.service.ts + - [x] 清理 rbac/log 中间件及配置中 settings 相关映射 + - [x] 验证:yarn build 通过,无残留引用 + +## 阶段三:前端重构 +- [x] Task 11: 前端 API 层适配 + - [x] api/auth.ts:登录/当前用户返回类型更新(userInfo.realName、roles、menus) + - [x] api/user.ts:字段更新(realName/phone/email) + - [x] api/role.ts:角色字段更新;权限接口改为菜单接口(getRoleMenus/updateRoleMenus) + - [x] 新增 api/menu.ts、api/dict.ts + - [x] 删除 api/system.ts + - [x] api/log.ts:字段与筛选参数更新 + +- [x] Task 12: 前端权限 Store 与动态路由 + - [x] stores/permission.ts:UserInfo 更新(id/username/realName/roles/menus),提供 menus 状态 + - [x] router/index.ts:移除 settings 路由;基于用户 menus 注册动态路由(menu_path→component 映射,缺失组件渲染"功能开发中"占位页);登录跳转仪表盘 + - [x] layouts/index.vue:侧边栏菜单由 menus 动态渲染(目录→子菜单两级 + 图标),移除静态菜单与设置分组;用户名显示 realName + - [x] 移除 v-permission 指令(directives/permission.ts)及引用 + - [x] 验证:admin 登录后侧边栏显示全部菜单,业务占位页可访问 + +- [x] Task 13: 前端页面适配 + - [x] views/system/user/index.vue:字段改为 realName/phone/email + - [x] views/system/role/index.vue:权限配置弹窗改为菜单树勾选 + - [x] 新增 views/system/menu/index.vue(树形表格 CRUD) + - [x] 新增 views/system/dict/index.vue(列表 + 类型筛选 CRUD) + - [x] views/log/operation.vue:字段与筛选更新 + - [x] 删除 views/settings/basic.vue、views/settings/security.vue + - [x] 验证:各页面构建通过、交互正常 + +## 阶段四:联调与验证 +- [ ] Task 14: 全流程联调 + - [ ] 前端 yarn dev + 后端服务 + PostgreSQL 环境启动 + - [ ] admin 登录 → 仪表盘 → 系统管理(用户/角色/菜单/字典/日志)全流程走通 + - [ ] 业务员角色仅见授权菜单,未授权接口 403 + - [ ] 日志自动记录写操作并可查询 + - [ ] 边界检查:无菜单角色登录、删除被占用角色、重复用户名/角色编码 + +## Task Dependencies +- [Task 2] depends on [Task 1](PG 环境就绪后可执行 SQL) +- [Task 3] 先行;[Task 4-10] depends on [Task 3](实体) +- [Task 11] depends on [Task 5-9](接口就绪) +- [Task 12] depends on [Task 11](API 类型) +- [Task 13] depends on [Task 11] +- [Task 14] depends on [Task 4-13] diff --git a/client/src/api/auth.ts b/client/src/api/auth.ts index 74bda5a..e9d46c8 100644 --- a/client/src/api/auth.ts +++ b/client/src/api/auth.ts @@ -1,15 +1,50 @@ import request from '@/utils/request'; +// 菜单节点(内联定义,避免与 api/menu.ts 产生依赖) +interface MenuNode { + id: number; + parentId: number | null; + menuName: string; + menuPath?: string | null; + component?: string | null; + icon?: string | null; + sortOrder: number; + menuType: number; // 1目录 2菜单 + permission?: string | null; + visible: number; + children?: MenuNode[]; +} + +export interface LoginResult { + accessToken: string; + refreshToken: string; + userInfo: { + id: number; + username: string; + realName: string; + }; + roles: string[]; + menus: MenuNode[]; +} + +export interface CurrentUserInfo { + id: number; + username: string; + realName: string; + roles: string[]; + menus: MenuNode[]; +} + export const authApi = { - login: (data: { username: string; password: string }) => - request.post('/auth/login', data), + login: (data: { username: string; password: string }) => + request.post('/auth/login', data), - logout: () => - request.post('/auth/logout'), + logout: () => + request.post('/auth/logout'), - refreshToken: (refreshToken: string) => - request.post('/auth/refresh', { refreshToken }), + refreshToken: (refreshToken: string) => + request.post<{ accessToken: string }>('/auth/refresh', { refreshToken }), - getCurrentUser: () => - request.get('/auth/currentUser'), + getCurrentUser: () => + request.get('/auth/currentUser'), }; diff --git a/client/src/api/dict.ts b/client/src/api/dict.ts new file mode 100644 index 0000000..1ccecb8 --- /dev/null +++ b/client/src/api/dict.ts @@ -0,0 +1,51 @@ +import request from '@/utils/request'; + +export interface DictItem { + id: number; + dictType: string; + dictLabel: string; + dictValue: string; + sortOrder: number; + status: number; + created_at: string; +} + +export interface DictQuery { + page?: number; + pageSize?: number; + dictType?: string; + keyword?: string; + status?: number; +} + +export interface DictListResult { + list: DictItem[]; + total: number; + page: number; + pageSize: number; +} + +export interface DictForm { + dictType: string; + dictLabel: string; + dictValue: string; + sortOrder?: number; + status?: number; +} + +export const dictApi = { + getDictList: (params?: DictQuery) => + request.get('/dicts', { params }), + + getDictTypes: () => + request.get('/dicts/types'), + + createDict: (data: DictForm) => + request.post('/dicts', data), + + updateDict: (id: number, data: Partial) => + request.put(`/dicts/${id}`, data), + + deleteDict: (id: number) => + request.delete(`/dicts/${id}`), +}; diff --git a/client/src/api/log.ts b/client/src/api/log.ts index 0703ca6..c95da57 100644 --- a/client/src/api/log.ts +++ b/client/src/api/log.ts @@ -1,36 +1,43 @@ -import request from '@/utils/request' +import request from '@/utils/request'; export interface LogItem { - id: number - userId?: number - username?: string - action: string - module?: string - method: string - url: string - ipAddress: string - statusCode: number - duration: number - createdAt: string + id: number; + userId?: number; + module?: string; + action: string; + method: string; + params?: string; + ip: string; + created_at: string; } export interface LogQuery { - page?: number - pageSize?: number - operator?: string - actionType?: string - moduleName?: string - startDate?: string - endDate?: string + page?: number; + pageSize?: number; + userId?: number; + module?: string; + action?: string; + startDate?: string; + endDate?: string; +} + +export interface LogListResult { + list: LogItem[]; + total: number; + page: number; + pageSize: number; } export const logApi = { - getLogList: (params: LogQuery) => - request.get('/logs', { params }), + getLogList: (params: LogQuery) => + request.get('/logs', { params }), - exportLogs: (params: Omit) => - request.get('/logs/export', { - params, - responseType: 'blob', - }), -} + getLogDetail: (id: number) => + request.get(`/logs/${id}`), + + exportLogs: (params: Omit) => + request.get('/logs/export', { + params, + responseType: 'blob', + }), +}; diff --git a/client/src/api/menu.ts b/client/src/api/menu.ts new file mode 100644 index 0000000..9ad7d1b --- /dev/null +++ b/client/src/api/menu.ts @@ -0,0 +1,42 @@ +import request from '@/utils/request'; + +// 菜单节点 +export interface MenuNode { + id: number; + parentId: number | null; + menuName: string; + menuPath?: string | null; + component?: string | null; + icon?: string | null; + sortOrder: number; + menuType: number; // 1目录 2菜单 + permission?: string | null; + visible: number; + children?: MenuNode[]; +} + +export interface MenuForm { + parentId?: number; + menuName: string; + menuPath?: string; + component?: string; + icon?: string; + sortOrder?: number; + menuType: number; + permission?: string; + visible?: number; +} + +export const menuApi = { + getMenuTree: () => + request.get('/menus/tree'), + + createMenu: (data: MenuForm) => + request.post('/menus', data), + + updateMenu: (id: number, data: Partial) => + request.put(`/menus/${id}`, data), + + deleteMenu: (id: number) => + request.delete(`/menus/${id}`), +}; diff --git a/client/src/api/role.ts b/client/src/api/role.ts index 6889013..0d32c49 100644 --- a/client/src/api/role.ts +++ b/client/src/api/role.ts @@ -1,31 +1,22 @@ import request from '@/utils/request'; +import type { MenuNode } from './menu'; export interface RoleInfo { id: number; - name: string; - code: string; + roleCode: string; + roleName: string; description?: string; - status: number; sortOrder: number; userCount: number; - permissions?: any[]; created_at: string; - updated_at: string; } -export interface PermissionTreeNode { +export interface SysRole { id: number; - name: string; - code: string; - type: string; - parent_id?: number | null; - sortOrder: number; - icon?: string; - route?: string; - component?: string; - status: number; - children?: PermissionTreeNode[]; - checked?: boolean; + roleCode: string; + roleName: string; + description?: string; + sortOrder?: number; } export interface RoleListResult { @@ -35,28 +26,33 @@ export interface RoleListResult { pageSize: number; } +export interface RoleMenusResult { + menuTree: MenuNode[]; + menuIds: number[]; +} + export const roleApi = { getRoleList: (params?: { page?: number; pageSize?: number; keyword?: string }) => request.get('/roles', { params }), getAllRoles: () => - request.get('/roles/all'), + request.get('/roles/all'), getRoleDetail: (id: number) => request.get(`/roles/${id}`), - createRole: (data: { name: string; code: string; description?: string; status?: number; sortOrder?: number }) => + createRole: (data: { roleCode: string; roleName: string; description?: string; sortOrder?: number }) => request.post('/roles', data), - updateRole: (id: number, data: { name?: string; description?: string; status?: number; sortOrder?: number }) => + updateRole: (id: number, data: { roleCode?: string; roleName?: string; description?: string; sortOrder?: number }) => request.put(`/roles/${id}`, data), deleteRole: (id: number) => request.delete(`/roles/${id}`), - getRolePermissions: (id: number) => - request.get(`/roles/${id}/permissions`), + getRoleMenus: (id: number) => + request.get(`/roles/${id}/menus`), - updateRolePermissions: (id: number, permissionIds: number[]) => - request.put(`/roles/${id}/permissions`, { permissionIds }), + updateRoleMenus: (id: number, menuIds: number[]) => + request.put(`/roles/${id}/menus`, { menuIds }), }; diff --git a/client/src/api/system.ts b/client/src/api/system.ts deleted file mode 100644 index 27e2d3a..0000000 --- a/client/src/api/system.ts +++ /dev/null @@ -1,39 +0,0 @@ -import request from '@/utils/request'; - -export interface BasicSettings { - siteName: string; - logo: string; - sessionTimeout: number; -} - -export interface SecuritySettings { - passwordMinLength: number; - maxLoginAttempts: number; - lockoutDuration: number; - ipWhitelist: string[]; -} - -export interface NotificationSettings { - enableEmail: boolean; - emailSmtpConfig: any; -} - -export interface SystemSettings { - basic: BasicSettings; - security: SecuritySettings; - notification: NotificationSettings; -} - -export const systemApi = { - getSettings: () => - request.get('/settings'), - - updateBasicSettings: (data: Partial) => - request.put('/settings/basic', data), - - updateSecuritySettings: (data: Partial) => - request.put('/settings/security', data), - - updateNotificationSettings: (data: Partial) => - request.put('/settings/notification', data), -}; diff --git a/client/src/api/user.ts b/client/src/api/user.ts index 9cb416b..8b98b42 100644 --- a/client/src/api/user.ts +++ b/client/src/api/user.ts @@ -3,30 +3,24 @@ import request from '@/utils/request'; export interface UserInfo { id: number; username: string; - nickname: string; + realName: string; email?: string; phone?: string; - avatar?: string; status: number; + lastLoginAt: string | null; userRoles?: UserRoleInfo[]; created_at: string; - updated_at: string; } export interface UserRoleInfo { - id: number; - userId: number; roleId: number; role?: RoleInfo; } export interface RoleInfo { id: number; - name: string; - code: string; - description?: string; - status: number; - sortOrder: number; + roleCode: string; + roleName: string; } export interface UserQuery { @@ -37,10 +31,17 @@ export interface UserQuery { roleId?: number; } +export interface UserListResult { + list: UserInfo[]; + total: number; + page: number; + pageSize: number; +} + export interface CreateUserParams { username: string; password: string; - nickname: string; + realName: string; email?: string; phone?: string; roleIds: number[]; @@ -48,29 +49,36 @@ export interface CreateUserParams { } export interface UpdateUserParams { - nickname?: string; + realName?: string; email?: string; phone?: string; - avatar?: string; roleIds?: number[]; status?: number; password?: string; } export const userApi = { - getUserList: (params: UserQuery) => request.get('/users', { params }), + getUserList: (params: UserQuery) => + request.get('/users', { params }), - getRoleList: () => request.get('/users/roles'), + getRoleList: () => + request.get('/users/roles'), - getUserDetail: (id: number) => request.get(`/users/${id}`), + getUserDetail: (id: number) => + request.get(`/users/${id}`), - createUser: (data: CreateUserParams) => request.post('/users', data), + createUser: (data: CreateUserParams) => + request.post('/users', data), - updateUser: (id: number, data: UpdateUserParams) => request.put(`/users/${id}`, data), + updateUser: (id: number, data: UpdateUserParams) => + request.put(`/users/${id}`, data), - deleteUser: (id: number) => request.delete(`/users/${id}`), + deleteUser: (id: number) => + request.delete(`/users/${id}`), - batchDeleteUsers: (ids: number[]) => request.post('/users/batch-delete', { ids }), + batchDeleteUsers: (ids: number[]) => + request.post('/users/batch-delete', { ids }), - updateStatus: (id: number, status: number) => request.put(`/users/${id}/status`, { status }), + updateStatus: (id: number, status: number) => + request.put(`/users/${id}/status`, { status }), }; diff --git a/client/src/directives/permission.ts b/client/src/directives/permission.ts deleted file mode 100644 index d5ba0df..0000000 --- a/client/src/directives/permission.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { App, Directive, DirectiveBinding } from 'vue'; - -const permissionDirective: Directive = { - mounted(el: HTMLElement, binding: DirectiveBinding) { - const { value } = binding; - - if (!value) { - console.warn('[v-permission] 需要提供权限码'); - return; - } - - const stores = import('@/stores/permission'); - stores.then(({ usePermissionStore }) => { - const store = usePermissionStore(); - - if (store.userInfo?.roles.includes('super_admin')) { - return; - } - - if (!store.permissions.includes(value)) { - el.parentNode?.removeChild(el); - } - }); - }, -}; - -export default function setupPermissionDirective(app: App) { - app.directive('permission', permissionDirective); -} diff --git a/client/src/layouts/index.vue b/client/src/layouts/index.vue index 90a7187..bbb9510 100644 --- a/client/src/layouts/index.vue +++ b/client/src/layouts/index.vue @@ -91,24 +91,29 @@ + + diff --git a/client/src/views/log/operation.vue b/client/src/views/log/operation/index.vue similarity index 74% rename from client/src/views/log/operation.vue rename to client/src/views/log/operation/index.vue index 8a67636..4dff79e 100644 --- a/client/src/views/log/operation.vue +++ b/client/src/views/log/operation/index.vue @@ -4,19 +4,28 @@ + + + - + 登录 创建 @@ -63,24 +72,14 @@ @change="handleTableChange" >